Cancel combobox selection in WPF with MVVM

后端 未结 12 1487
你的背包
你的背包 2020-12-08 10:23

I\'ve got a combobox in my WPF application:



        
12条回答
  •  半阙折子戏
    2020-12-08 10:57

    This can be achieved in a generic and compact way using Blend's Generic Behavior.

    The behavior defines a dependency property named SelectedItem, and you should put your binding in this property, instead of in the ComboBox's SelectedItem property. The behavior is in charge of passing changes in the dependency property to the ComboBox (or more generally, to the Selector), and when the Selector's SelectedItem changes, it tries to assign it to the its own SelectedItem property. If the assignment fails (probably because the bound VM proeprty setter rejected the assignment), the behavior updates the Selector’s SelectedItem with the current value of its SelectedItem property.

    For all sorts of reasons, you might encounter cases where the list of items in the Selector is cleared, and the selected item becomes null (see this question). You usually don't want your VM property to become null in this case. For this, I added the IgnoreNullSelection dependency property, which is true by default. This should solve such problem.

    This is the CancellableSelectionBehavior class:

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Interactivity;
    
    namespace MySampleApp
    {
        internal class CancellableSelectionBehavior : Behavior
        {
            protected override void OnAttached()
            {
                base.OnAttached();
                AssociatedObject.SelectionChanged += OnSelectionChanged;
            }
    
            protected override void OnDetaching()
            {
                base.OnDetaching();
                AssociatedObject.SelectionChanged -= OnSelectionChanged;
            }
    
            public static readonly DependencyProperty SelectedItemProperty =
                DependencyProperty.Register("SelectedItem", typeof(object), typeof(CancellableSelectionBehavior),
                    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
    
            public object SelectedItem
            {
                get { return GetValue(SelectedItemProperty); }
                set { SetValue(SelectedItemProperty, value); }
            }
    
            public static readonly DependencyProperty IgnoreNullSelectionProperty =
                DependencyProperty.Register("IgnoreNullSelection", typeof(bool), typeof(CancellableSelectionBehavior), new PropertyMetadata(true));
    
            /// 
            /// Determines whether null selection (which usually occurs since the combobox is rebuilt or its list is refreshed) should be ignored.
            /// True by default.
            /// 
            public bool IgnoreNullSelection
            {
                get { return (bool)GetValue(IgnoreNullSelectionProperty); }
                set { SetValue(IgnoreNullSelectionProperty, value); }
            }
    
            /// 
            /// Called when the SelectedItem dependency property is changed.
            /// Updates the associated selector's SelectedItem with the new value.
            /// 
            private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var behavior = (CancellableSelectionBehavior)d;
    
                // OnSelectedItemChanged can be raised before AssociatedObject is assigned
                if (behavior.AssociatedObject == null)
                {
                    System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
                    {
                        var selector = behavior.AssociatedObject;
                        selector.SelectedValue = e.NewValue;
                    }));
                }
                else
                {
                    var selector = behavior.AssociatedObject;
                    selector.SelectedValue = e.NewValue;
                }
            }
    
            /// 
            /// Called when the associated selector's selection is changed.
            /// Tries to assign it to the  property.
            /// If it fails, updates the selector's with   property's current value.
            /// 
            private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                if (IgnoreNullSelection && (e.AddedItems == null || e.AddedItems.Count == 0)) return;
                SelectedItem = AssociatedObject.SelectedItem;
                if (SelectedItem != AssociatedObject.SelectedItem)
                {
                    AssociatedObject.SelectedItem = SelectedItem;
                }
            }
        }
    }
    

    This is the way to use it in XAML:

    
        
            
                
                    
                
            
        
    
    

    and this is a sample of the VM property:

    private string _selected;
    
    public string Selected
    {
        get { return _selected; }
        set
        {
            if (IsValidForSelection(value))
            {
                _selected = value;
            }
        }
    }
    

提交回复
热议问题