I have found multiple questions about this problem on SO, however I still can't quite get a realiable solution. Here is what I came up with after reading the answers.
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>
Code behind:
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;
}
Basically I want to display a confirmation message, when user navigates to different tab, and if he clicks "no" - abort the transition. This code does not work though. If you click multiple times on "Tab2", each time choosing "no" in message box, at some point it stops working: events stop triggering. Event will trigger again if you click on "Tab3", but if you choose "yes" it opens second tab and not third. I am having trouble figuring out wtf is going on. :)
Does anyone see a bug in my solution? Or is there an easier way to display a confirmation message, when user switches tabs? I am also willing to use any opensource tab control, which does have a proper SelectionChanging
event. I could not find any though.
I am using .Net 4.0.
Edit: If I comment the message box out:
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;
}
Everything works fine. Weird.
seems to work quite well with
<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
}
For some reason adding TabControl.Focus() fixes things:
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();
}));
}
}
I still have no clue what on Earth is going on here. So I will gladly accept the answer, which sheds some light on this issue.
He who must be obeyed requested that the application ask the user if they wish to leave the page so here is the slightly changed code:
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");
}
}
I think this small change does pretty much what you wanted.
private void MainTabControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ReasonBecauseLeaveTabItemIsForbidden)
{
if (MainTabControl.SelectedIndex == IndexOfTabItem)
{
MessageBox.Show(SomeMessageWhyLeaveTabItemIsForbidden);
}
MainTabControl.SelectedIndex = IndexOfTabItem;
}
}
IndexOfTabItem - index of TabItem that disabled for leaving.
inside the tabControl_SelectionChanged event handler:
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");
}
}
Note that setting forbiddenPage.IsSelected = true causes a loop and you reenter this event handler. This time, however, we exit because the page selected IS the forbidden page.
There is a much easier solution. Add a binding to the selected item in the XAML:
<TabControl SelectedItem="{Binding SelectedTab}" ...
Then in the view model:
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");
}
}
Obviously you replace ADR_Scanner.ViewModel.ConfigurationViewModel with your own view model class. Lastly make sure you initialise _selectedTab in your constructor otherwise the TabControl will have no initial selection.
来源:https://stackoverflow.com/questions/30706758/how-to-cancel-tab-change-in-wpf-tabcontrol