How to cancel tab change in WPF TabControl

可紊 提交于 2019-12-06 06:24:30

This solution http://coderelief.net/2011/11/07/fixing-issynchronizedwithcurrentitem-and-icollectionview-cancel-bug-with-an-attached-property/

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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!