使用MVVM取消WPF中的组合框选择

dro*_*ned 25 c# wpf combobox mvvm

我的WPF应用程序中有一个组合框:

<ComboBox  ItemsSource="{Binding CompetitorBrands}" DisplayMemberPath="Value" 
   SelectedValuePath="Key" SelectedValue="{Binding Path=CompMfgBrandID, Mode=TwoWay,
   UpdateSourceTrigger=PropertyChanged}" Text="{Binding CompMFGText}"/>
Run Code Online (Sandbox Code Playgroud)

绑定到的集合 KeyValuePair<string, string>

这是我的ViewModel中的CompMfgBrandID属性:

public string CompMfgBrandID
{
    get { return _compMFG; }
    set
    {    
        if (StockToExchange != null && StockToExchange.Where(x => !string.IsNullOrEmpty(x.EnteredPartNumber)).Count() > 0)
        {
            var dr = MessageBox.Show("Changing the competitor manufacturer will remove all entered parts from the transaction.  Proceed?",
                "Transaction Type", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            if (dr != DialogResult.Yes)
                return;
        }

        _compMFG = value;
        StockToExchange.Clear();

        ...a bunch of other functions that don't get called when you click 'No'...
        OnPropertyChanged("CompMfgBrandID");
    }
}
Run Code Online (Sandbox Code Playgroud)

如果选择"是",则表现如预期.清除项目并调用其余功能.如果我选择"否",它将返回并且不会清除我的列表或调用任何其他功能,这很好,但组合框仍然显示新选择.当用户选择"否"时,我需要它恢复到原始选择,就像没有任何改变一样.我怎么能做到这一点?我也尝试添加代码e.Handled = true隐藏,但无济于事.

Dvo*_*nik 21

.NET 4.5.1+的非常简单的解决方案:

<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}"  />
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,它对我有用.您可以在组合框中回滚选择,只需在没有赋值的情况下触发NotifyPropertyChanged.

  • 我不敢相信这项工作,非常感谢 (2认同)

spl*_*tor 18

这可以使用Blend的通用行为以通用和紧凑的方式实现.

该行为定义了一个名为的依赖项属性SelectedItem,您应该将绑定放在此属性中,而不是放在ComboBox的SelectedItem属性中.该行为负责将依赖项属性中的更改传递给ComboBox(或更一般地,传递给Selector),并且当Selector SelectedItem更改时,它会尝试将其分配给自己的SelectedItem属性.如果赋值失败(可能是因为绑定的VM proeprty setter拒绝了赋值),则该行为会SelectedItem使用其SelectedItem属性的当前值更新Selector .

出于各种原因,您可能会遇到清除选择器中的项列表并且所选项变为空的情况(请参阅此问题).在这种情况下,您通常不希望VM属性变为null.为此,我添加了IgnoreNullSelection依赖项属性,默认情况下为true.这应该可以解决这个问题.

这是CancellableSelectionBehavior班级:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;

namespace MySampleApp
{
    internal class CancellableSelectionBehavior : Behavior<Selector>
    {
        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));

        /// <summary>
        /// Determines whether null selection (which usually occurs since the combobox is rebuilt or its list is refreshed) should be ignored.
        /// True by default.
        /// </summary>
        public bool IgnoreNullSelection
        {
            get { return (bool)GetValue(IgnoreNullSelectionProperty); }
            set { SetValue(IgnoreNullSelectionProperty, value); }
        }

        /// <summary>
        /// Called when the SelectedItem dependency property is changed.
        /// Updates the associated selector's SelectedItem with the new value.
        /// </summary>
        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;
            }
        }

        /// <summary>
        /// Called when the associated selector's selection is changed.
        /// Tries to assign it to the <see cref="SelectedItem"/> property.
        /// If it fails, updates the selector's with  <see cref="SelectedItem"/> property's current value.
        /// </summary>
        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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是在XAML中使用它的方法:

<Window x:Class="MySampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="My Smaple App" Height="350" Width="525"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:MySampleApp"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainWindowViewModel}">
    <StackPanel>
        <ComboBox ItemsSource="{Binding Options}">
            <i:Interaction.Behaviors>
                <local:CancellableSelectionBehavior SelectedItem="{Binding Selected}" />
            </i:Interaction.Behaviors>
        </ComboBox>
    </StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

这是VM属性的示例:

private string _selected;

public string Selected
{
    get { return _selected; }
    set
    {
        if (IsValidForSelection(value))
        {
            _selected = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


WPF*_*-it 17

在MVVM下实现这一目标....

1]具有处理SelectionChangedComboBox事件的附加行为.这个事件是通过一些带有Handled标志的事件args引发的.但将其设置为true对于SelectedValue绑定无用.无论事件是否被处理,绑定都会更新源.

2]因此我们将ComboBox.SelectedValue绑定配置为TwoWayExplicit.

3]只有当您的支票满意并且消息框说明Yes是我们执行时BindingExpression.UpdateSource().否则,我们只需调用BindingExpression.UpdateTarget()以恢复旧的选择.


在下面的示例中,我有一个KeyValuePair<int, int>绑定到窗口数据上下文的列表.它ComboBox.SelectedValue被绑定到一个简单的可写MyKey属性Window.

XAML ......

    <ComboBox ItemsSource="{Binding}"
              DisplayMemberPath="Value"
              SelectedValuePath="Key"
              SelectedValue="{Binding MyKey,
                                      ElementName=MyDGSampleWindow,
                                      Mode=TwoWay,
                                      UpdateSourceTrigger=Explicit}"
              local:MyAttachedBehavior.ConfirmationValueBinding="True">
    </ComboBox>
Run Code Online (Sandbox Code Playgroud)

MyDGSampleWindowx:名称在哪里Window

代码背后......

public partial class Window1 : Window
{
    private List<KeyValuePair<int, int>> list1;

    public int MyKey
    {
        get; set;
    }

    public Window1()
    {
        InitializeComponent();

        list1 = new List<KeyValuePair<int, int>>();
        var random = new Random();
        for (int i = 0; i < 50; i++)
        {
            list1.Add(new KeyValuePair<int, int>(i, random.Next(300)));
        }

        this.DataContext = list1;
    }
 }
Run Code Online (Sandbox Code Playgroud)

附加的行为

public static class MyAttachedBehavior
{
    public static readonly DependencyProperty
        ConfirmationValueBindingProperty
            = DependencyProperty.RegisterAttached(
                "ConfirmationValueBinding",
                typeof(bool),
                typeof(MyAttachedBehavior),
                new PropertyMetadata(
                    false,
                    OnConfirmationValueBindingChanged));

    public static bool GetConfirmationValueBinding
        (DependencyObject depObj)
    {
        return (bool) depObj.GetValue(
                        ConfirmationValueBindingProperty);
    }

    public static void SetConfirmationValueBinding
        (DependencyObject depObj,
        bool value)
    {
        depObj.SetValue(
            ConfirmationValueBindingProperty,
            value);
    }

    private static void OnConfirmationValueBindingChanged
        (DependencyObject depObj,
        DependencyPropertyChangedEventArgs e)
    {
        var comboBox = depObj as ComboBox;
        if (comboBox != null && (bool)e.NewValue)
        {
            comboBox.Tag = false;
            comboBox.SelectionChanged -= ComboBox_SelectionChanged;
            comboBox.SelectionChanged += ComboBox_SelectionChanged;
        }
    }

    private static void ComboBox_SelectionChanged(
        object sender, SelectionChangedEventArgs e)
    {
        var comboBox = sender as ComboBox;
        if (comboBox != null && !(bool)comboBox.Tag)
        {
            var bndExp
                = comboBox.GetBindingExpression(
                    Selector.SelectedValueProperty);

            var currentItem
                = (KeyValuePair<int, int>) comboBox.SelectedItem;

            if (currentItem.Key >= 1 && currentItem.Key <= 4
                && bndExp != null)
            {
                var dr
                    = MessageBox.Show(
                        "Want to select a Key of between 1 and 4?",
                        "Please Confirm.",
                        MessageBoxButton.YesNo,
                        MessageBoxImage.Warning);
                if (dr == MessageBoxResult.Yes)
                {
                    bndExp.UpdateSource();
                }
                else
                {
                    comboBox.Tag = true;
                    bndExp.UpdateTarget();
                    comboBox.Tag = false;
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在行为中,我使用ComboBox.Tag属性临时存储一个标志,当我们恢复到旧的选定值时,该标志会跳过重新检查.

如果这有帮助,请告诉我.

  • 这很好,谢谢.它让我更加遗憾地使用MVVM模式.我讨厌最简单的概念(恢复下拉列表值)如何需要所有这些额外的代码,时间和故障排除. (5认同)
  • @drowned,MVVM和WPF是不可分割的.我曾参与过许多WPF项目,其中大多数都使用过MVMM ......并且相信我在使用MVVM时可以更容易管理它. (2认同)
  • 我无法决定这是辉煌还是疯狂。 (2认同)

CNa*_*Nad 6

用户shaun在另一个帖子上找到了一个更简单的答案:https://stackoverflow.com/a/6445871/2340705

基本问题是属性更改事件被吞噬.有人会称这是一个错误.为了解决这个问题,请使用Dispatcher中的BeginInvoke来强制将属性更改事件放回到UI事件队列的末尾.这不需要更改xaml,也不需要额外的行为类,并且单行代码更改为视图模型.