如何在WPF TabControl中取消选项卡更改

我在SO上发现了关于这个问题的多个问题,但是我仍然无法得到一个可行的解决方案。 这是我在阅读答案后想出的。

XAML:

   

代码背后:

 public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var tabs = new ObservableCollection {"Tab1", "Tab2", "Tab3"}; Tabs = CollectionViewSource.GetDefaultView(tabs); Tabs.CurrentChanging += OnCurrentChanging; Tabs.CurrentChanged += OnCurrentChanged; Tabs.MoveCurrentToFirst(); CurrentTab = tabs.First(); } private void OnCurrentChanging(object sender, CurrentChangingEventArgs e) { //only show message box when tab is changed by user input if (!_cancelTabChange) { if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No) { _cancelTabChange = true; return; } } _cancelTabChange = false; } private void OnCurrentChanged(object sender, EventArgs e) { if (!_cancelTabChange) { //Update current tab property, if user did not cancel transition CurrentTab = (string)Tabs.CurrentItem; } else { //navigate back to current tab otherwise Dispatcher.BeginInvoke(new Action(() => Tabs.MoveCurrentTo(CurrentTab))); } } public string CurrentTab { get; set; } public static readonly DependencyProperty TabsProperty = DependencyProperty.Register("Tabs", typeof(ICollectionView), typeof(MainWindow), new FrameworkPropertyMetadata(default(ICollectionView))); public ICollectionView Tabs { get { return (ICollectionView)GetValue(TabsProperty); } set { SetValue(TabsProperty, value); } } private bool _cancelTabChange; } 

基本上我想显示确认消息,当用户导航到不同的选项卡时,如果他点击“否” – 中止转换。 但是这段代码不起作用。 如果在“Tab2”上多次单击,每次在消息框中选择“否”时,它会在某个时刻停止工作:事件停止触发。 如果单击“Tab3”,事件将再次触发,但如果选择“是”,则会打开第二个选项卡而不是第三个选项卡。 我无法搞清楚wtf是怎么回事。 🙂

有没有人在我的解决方案中看到错误? 或者,当用户切换标签时,是否有更简单的方法来显示确认消息? 我也愿意使用任何开源选项卡控件,它具有正确的SelectionChanging事件。 我找不到任何东西。

我使用的是.Net 4.0。

编辑:如果我评论消息框:

 private void OnCurrentChanging(object sender, CurrentChangingEventArgs e) { //only show message box when tab is changed by user input if (!_cancelTabChange) { //if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No) //{ Debug.WriteLine("Canceled"); _cancelTabChange = true; return; //} } _cancelTabChange = false; } 

一切正常。 奇怪的。

此解决方案http://coderelief.net/2011/11/07/fixing-issynchronizedwithcurrentitem-and-icollectionview-cancel-bug-with-an-attached-property/

似乎工作得很好

  private void OnCurrentChanging(object sender, CurrentChangingEventArgs e) { if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No) { e.Cancel = true; } } public static class SelectorAttachedProperties { private static Type _ownerType = typeof(SelectorAttachedProperties); #region IsSynchronizedWithCurrentItemFixEnabled public static readonly DependencyProperty IsSynchronizedWithCurrentItemFixEnabledProperty = DependencyProperty.RegisterAttached("IsSynchronizedWithCurrentItemFixEnabled", typeof(bool), _ownerType, new PropertyMetadata(false, OnIsSynchronizedWithCurrentItemFixEnabledChanged)); public static bool GetIsSynchronizedWithCurrentItemFixEnabled(DependencyObject obj) { return (bool)obj.GetValue(IsSynchronizedWithCurrentItemFixEnabledProperty); } public static void SetIsSynchronizedWithCurrentItemFixEnabled(DependencyObject obj, bool value) { obj.SetValue(IsSynchronizedWithCurrentItemFixEnabledProperty, value); } private static void OnIsSynchronizedWithCurrentItemFixEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Selector selector = d as Selector; if (selector == null || !(e.OldValue is bool && e.NewValue is bool) || e.OldValue == e.NewValue) return; bool enforceCurrentItemSync = (bool)e.NewValue; ICollectionView collectionView = null; EventHandler itemsSourceChangedHandler = null; itemsSourceChangedHandler = delegate { collectionView = selector.ItemsSource as ICollectionView; if (collectionView == null) collectionView = CollectionViewSource.GetDefaultView(selector); }; SelectionChangedEventHandler selectionChangedHanlder = null; selectionChangedHanlder = delegate { if (collectionView == null) return; if (selector.IsSynchronizedWithCurrentItem == true && selector.SelectedItem != collectionView.CurrentItem) { selector.IsSynchronizedWithCurrentItem = false; selector.SelectedItem = collectionView.CurrentItem; selector.IsSynchronizedWithCurrentItem = true; } }; if (enforceCurrentItemSync) { TypeDescriptor.GetProperties(selector)["ItemsSource"].AddValueChanged(selector, itemsSourceChangedHandler); selector.SelectionChanged += selectionChangedHanlder; } else { TypeDescriptor.GetProperties(selector)["ItemsSource"].RemoveValueChanged(selector, itemsSourceChangedHandler); selector.SelectionChanged -= selectionChangedHanlder; } } #endregion IsSynchronizedWithCurrentItemFixEnabled } 

由于某些原因,添加TabControl.Focus()修复了一些事情:

 private void OnCurrentChanged(object sender, EventArgs e) { if (!_cancelTabChange) { //Update current tab property, if user did not cancel transition CurrentTab = (string)Tabs.CurrentItem; } else { //navigate back to current tab otherwise Dispatcher.BeginInvoke(new Action(() => { Tabs.MoveCurrentTo(CurrentTab); TabControl.Focus(); })); } } 

我仍然不知道地球上到底发生了什么。 所以我很乐意接受这个问题的答案。

必须遵守的人要求应用程序询问用户是否希望离开页面,所以这里是稍微改变的代码:

  private Object _selectedTab; public Object SelectedTab { get { return _selectedTab; } set { if ( !(_selectedTab is ADR_Scanner.ViewModel.ConfigurationViewModel) || !_configurationViewModel.HasChanged || (System.Windows.Forms.MessageBox.Show("Are you sure you want to leave this page without saving the configuration changes", ADR_Scanner.App.Current.MainWindow.Title, System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Error) == System.Windows.Forms.DialogResult.Yes) ) { _selectedTab = value; } OnPropertyChanged("SelectedTab"); } } 

我认为这个小小的变化几乎可以满足您的需求。

 private void MainTabControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (ReasonBecauseLeaveTabItemIsForbidden) { if (MainTabControl.SelectedIndex == IndexOfTabItem) { MessageBox.Show(SomeMessageWhyLeaveTabItemIsForbidden); } MainTabControl.SelectedIndex = IndexOfTabItem; } } 

IndexOfTabItem – 禁用离开的TabItem的索引。

在tabControl_SelectionChanged事件处理程序中:

 if (e.OriginalSource == tabControl) //if this event fired from your tabControl { e.Handled = true; if (!forbiddenPage.IsSelected) //User leaving the tab { if (forbiddenTest()) { forbiddenPage.IsSelected = true; MessageBox.Show("you must not leave this page"); } } 

请注意,设置forbiddenPage.IsSelected = true会导致循环,并重新输入此事件处理程序。 但是,这一次,我们退出是因为所选页面是禁止页面。

有一个更容易的解决方案。 将绑定添加到XAML中的选定项目:

   

然后在视图模型中:

  private Object _selectedTab; public Object SelectedTab { get { return _selectedTab; } set { if (_selectedTab is ADR_Scanner.ViewModel.ConfigurationViewModel && _configurationViewModel.HasChanged) { System.Windows.Forms.MessageBox.Show("Please save the configuration changes", ADR_Scanner.App.ResourceAssembly.GetName().Name, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); } else { _selectedTab = value; } OnPropertyChanged("SelectedTab"); } } 

显然,您将ADR_Scanner.ViewModel.ConfigurationViewModel替换为您自己的视图模型类。 最后确保在构造函数中初始化_selectedTab,否则TabControl将没有初始选择。