I'm trying to develop a system maintenance screen for my application in which I have several tabs each representing a different maintenance option i.e. maintain system users et cetera. Once a user clicks on edit/new to change a existing record I want to prevent navigating away from the current tab until the user either clicks save or cancel.
After some googling I've found a link http://joshsmithonwpf.wordpress.com/2009/09/04/how-to-prevent-a-tabitem-from-being-selected/ which seemed to solve my problem, or so I thought.
I've tried implementing this开发者_Go百科, but my event never seems to fire. Below is my XAML.
<TabControl Name="tabControl">
<TabItem Header="Users">
<DockPanel>
<GroupBox Header="Existing Users" Name="groupBox1" DockPanel.Dock="Top" Height="50">
<StackPanel Orientation="Horizontal">
<Label Margin="3,3,0,0">User:</Label>
<ComboBox Width="100" Height="21" Margin="3,3,0,0"></ComboBox>
<Button Width="50" Height="21" Margin="3,3,0,0" Name="btnUsersEdit" Click="btnUsersEdit_Click">Edit</Button>
<Button Width="50" Height="21" Margin="3,3,0,0" Name="btnUsersNew" Click="btnUsersNew_Click">New</Button>
</StackPanel>
</GroupBox>
<GroupBox Header="User Information" Name="groupBox2">
<Button Content="Cancel" Height="21" Name="btnCancel" Width="50" Click="btnCancel_Click" />
</GroupBox>
</DockPanel>
</TabItem>
<TabItem Header="User Groups">
</TabItem>
</TabControl>
And this is my code
public partial class SystemMaintenanceWindow : Window
{
private enum TEditMode { emEdit, emNew, emBrowse }
private TEditMode _EditMode = TEditMode.emBrowse;
private TEditMode EditMode
{
get { return _EditMode; }
set
{
_EditMode = value;
}
}
public SystemMaintenanceWindow()
{
InitializeComponent();
var view = CollectionViewSource.GetDefaultView(tabControl.Items.SourceCollection);
view.CurrentChanging += this.Items_CurrentChanging;
}
void Items_CurrentChanging(object sender, CurrentChangingEventArgs e)
{
if ((e.IsCancelable) && (EditMode != TEditMode.emBrowse))
{
var item = ((ICollectionView)sender).CurrentItem;
e.Cancel = true;
tabControl.SelectedItem = item;
MessageBox.Show("Please Save or Cancel your work first.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void btnUsersNew_Click(object sender, RoutedEventArgs e)
{
EditMode = TEditMode.emNew;
}
private void btnUsersEdit_Click(object sender, RoutedEventArgs e)
{
EditMode = TEditMode.emEdit;
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
EditMode = TEditMode.emBrowse;
}
}
Apologies now if I'm being stupid, but for the life of me I cannot workout see why my event does not fire when the user clicks between tabs.
Thanks for all your help.
Emlyn
I've come up with a solution which suits my needs. Seems slightly backwards but compared to the other options I found, it seems nice and neat.
Basically I keep a private variable of the current tabIndex and on SelectionChanged
event of the TabControl
, I'm doing some checks and set the TabControl.SelectedIndex
back to this value if the user is not in browse mode.
private void tabControl_SelectionChanged(object sender,
System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.OriginalSource == tabControl)
{
if (EditMode == TEditMode.emBrowse)
{
_TabItemIndex = tabControl.SelectedIndex;
}
else if (tabControl.SelectedIndex != _TabItemIndex)
{
e.Handled = true;
tabControl.SelectedIndex = _TabItemIndex;
MessageBox.Show("Please Save or Cancel your work first.", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
I was struggling with this too. Just got it working by simply adding the
IsSynchronizedWithCurrentItem="True"
setting to the TabControl. Worked like a charm after that.
According to this post
https://social.msdn.microsoft.com/Forums/vstudio/en-US/d8ac2677-b760-4388-a797-b39db84a7e0f/how-to-cancel-tabcontrolselectionchanged?forum=wpf
this worked for me:
<TabControl>
<TabControl.Resources>
<Style TargetType="TabItem">
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="OnPreviewMouseLeftButtonDown"/>
</Style>
</TabControl.Resources>
<TabItem Header="Tab1"/>
<TabItem Header="Tab2"/>
</TabControl>
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is TabItem) //do not handle clicks on TabItem content but on TabItem itself
{
var vm = this.DataContext as MyViewModel;
if (vm != null)
{
if (!vm.CanLeave())
{
e.Handled = true;
}
}
}
}
Josh is using tab.ItemsSource
. You are using tab.Items.SourceCollection
. This might be the problem.
Or implement it yourself...
public delegate void PreviewSelectionChangedEventHandler(object p_oSender, PreviewSelectionChangedEventArgs p_eEventArgs);
public class PreviewSelectionChangedEventArgs
{
internal PreviewSelectionChangedEventArgs(IList p_lAddedItems, IList p_lRemovedItems)
{
this.AddedItems = p_lAddedItems;
this.RemovedItems = p_lRemovedItems;
}
public bool Cancel { get; set; }
public IList AddedItems { get; private set; }
public IList RemovedItems { get; private set; }
}
public class TabControl2: TabControl
{
public event PreviewSelectionChangedEventHandler PreviewSelectionChanged;
private int? m_lLastSelectedIndex;
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
// déterminer si on doit annuler la sélection
PreviewSelectionChangedEventArgs eEventArgs = new PreviewSelectionChangedEventArgs(e.AddedItems, e.RemovedItems);
if (m_lLastSelectedIndex.HasValue)
if (PreviewSelectionChanged != null)
PreviewSelectionChanged(this, eEventArgs);
// annuler (ou pas) la sélection
if (eEventArgs.Cancel)
this.SelectedIndex = m_lLastSelectedIndex.Value;
else
m_lLastSelectedIndex = this.SelectedIndex;
}
}
I am starting to grasp XAML so I hope I didn't miss your point. I had the same problem and for me this worked best.
In XAML I define a Style that toggles the ReadOnly State
<!-- prevent the tab from being changed while editing or adding a physician -->
<Style BasedOn="{StaticResource {x:Type TabControl}}"
TargetType="{x:Type TabControl}" x:Key="InactivateTabControl">
<!-- <Setter Property="IsEnabled" Value="True" /> -->
<Style.Triggers>
<DataTrigger Binding="{Binding PhysicianTypeTabAreLocked}" Value="False">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding PhysicianTypeTabAreLocked}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
<TabControl Grid.Row="1" Grid.Column="0" Margin="0, 10, 0, 0"
x:Name="PhysicianTypesTabControl"
SelectedIndex="{Binding PhysicianTypeSelectedTabIndex, Mode=TwoWay}"
Style="{StaticResource InactivateTabControl}">
<!-- rest here ....-->
<TabControl/>
In the viewmodel I have a property
private EditingState _PhysicianEditingState;
public EditingState PhysicianEditingState
{
get { return _PhysicianEditingState; }
set
{
_PhysicianEditingState = value;
PhysicianTypeTabAreLocked = (PhysicianEditingState != EditingState.NotEditing);
NotifyPropertyChanged("PhysicianTypeTabAreLocked");
}
}
Hope this helps.
The thing that finally worked really well for me was to disable the "TabItems" when I didn't want the user to be able to select them, and then to enable them when I did want them too.
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}" SelectedItem="{Binding SelectedPage}" TabStripPlacement="Left">
<TabControl.Resources>
<Style BasedOn="{StaticResource {x:Type TabItem}}" TargetType="TabItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding StepName}" Foreground="{DynamicResource TabActiveSelectedFont}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
This allowed my view model (in this case my pages) to be able to determine when they allowed the user to move to the next page. So When they were done with the current page I would "enable" the next page so they could click on it to continue.
You could simply set the TabItems' IsEnabled
property to false which prevents navigation/activation but is not disabling the tabs' content, until your condition (save/cancel) is met.
精彩评论