Как отменить изменение вкладки в WPF TabControl
Я нашел несколько вопросов об этой проблеме на SO, однако я все еще не могу получить реальное решение. Вот к чему я пришел, прочитав ответы.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300" x:Name="this">
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Tabs, ElementName=this}" x:Name="TabControl"/>
</Window>
Код сзади:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var tabs = new ObservableCollection<string> {"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", но если вы выберете" yes", то откроется вторая вкладка, а не третья. Мне трудно понять, что происходит в wtf. :)
Кто-нибудь видит ошибку в моем решении? Или есть более простой способ отображения подтверждающего сообщения, когда пользователь переключает вкладки? Я также готов использовать любой элемент управления opensource tab, который имеет соответствующее событие 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;
}
Все работает нормально. Странный.
6 ответов:
, кажется, работает довольно хорошо с
<TabControl ... yournamespace:SelectorAttachedProperties.IsSynchronizedWithCurrentItemFixEnabled="True" .../> 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-индекс Табитема, отключенного для выхода.
Внутри обработчика событий 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:
<TabControl SelectedItem="{Binding SelectedTab}" ...Тогда в модели вида:
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.модель представления.ConfigurationViewModel с вашим собственным классом модели представления. Наконец, убедитесь, что вы инициализируете _selectedTab в вашем конструкторе, иначе TabControl не будет иметь начального выбора.
Comments