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

时间:2015-06-08 10:28:38

标签: c# 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;
}

基本上我想显示一条确认消息,当用户导航到不同的标签时,如果他点击了#34; no&#34; - 中止过渡。但是这段代码不起作用。如果你在&#34; Tab2&#34;上多次点击,每次选择&#34; no&#34;在消息框中,它在某些时候停止工作:事件停止触发。如果您单击&#34; Tab3&#34;,则会再次触发事件,但如果您选择&#34;是&#34;它打开第二个选项卡,而不是第三个我无法搞清楚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;
}

一切正常。怪异。

6 个答案:

答案 0 :(得分:1)

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

似乎与

效果很好
<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
}

答案 1 :(得分:0)

由于某些原因,添加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();
        }));
    }
}

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

答案 2 :(得分:0)

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

<input type="text" value="Hello, World!" id="myTextBox"/>
<input type="button" value="Lose Focus" id="loseFocusBtn"/>

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

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

在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会导致循环,然后重新输入 此事件处理程序。但是,这一次,我们退出是因为所选页面是禁止页面。

答案 5 :(得分:-1)

有一个更简单的解决方案。将绑定添加到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.ViewModel.ConfigurationViewModel替换为您自己的视图模型类。最后确保在构造函数中初始化_selectedTab,否则TabControl将没有初始选择。