更改PropertyChanged方法中的属性而不更新视图

时间:2017-04-25 18:14:07

标签: c# wpf mvvm combobox propertychanged

在某些情况下,如果用户选择组合框中的项目,则必须自动将其更改为其他项目

视图模型

public class VM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    private string selected;
    public string Selected
    {
        get { return selected; }
        set
        {
            if (selected != value)
            {
                selected = value;
                OnPropertyChanged("Selected");
            }
        }
    }

    private ObservableCollection<string> collection;
    public ObservableCollection<string> Collection
    {
        get { return collection; }
        set
        {
            collection = value;
            OnPropertyChanged("Collection");
        }
    }

    public VM()
    {
        this.Collection = new ObservableCollection<string>(new string[] { "A", "B", "C" });
        this.Selected = "A";
        this.PropertyChanged += VM_PropertyChanged;
    }

    void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.Selected = "C";
    }
}

查看

<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="350" Width="525">
<StackPanel>
    <Grid>
      <ComboBox ItemsSource="{Binding Collection}" SelectedValue="{Binding Selected}"/>
    </Grid>
    <Label Content="{Binding Selected, UpdateSourceTrigger=PropertyChanged}"/>
 </StackPanel>
</Window>

所以,在这个例子中,无论我选择什么,它都应该在组合框和Label上显示“C”,但“C”只显示在Label上,这意味着ViewModel已更新但不是视图

这里的问题似乎是尝试从PropertyChanged方法更改属性。

enter image description here

可能出现什么问题?

2 个答案:

答案 0 :(得分:1)

以下是我最有可能做到的事情,但执行魔术的BeginInvoke()调用可以从PropertyChanged处理程序中轻松调用。

它正在做的事情本质上是在整个房地产集业务完全完成后将行动排队。 DispatcherPriority.ApplicationIdle标志是关键点。

正如您所发现的那样,PropertyChanged处理程序和属性设置器在PropertyChanged仍处于更改其选择的过程中时引发ComboBox是没用的。这段代码让整个事情完成,然后立即将Selected更改为其他内容。此时,ComboBox将会闲暇时注意到您的PropertyChanged事件,并更新自己的选择。

private string selected;
public string Selected
{
    get { return selected; }
    set
    {
        if (selected != value)
        {
            //  Don't let them select "B". 
            if (value == "B")
            {
                Dispatcher.CurrentDispatcher.
                    BeginInvoke(new Action(() => this.Selected = "C"),
                                DispatcherPriority.ApplicationIdle);
                return;
            }
            selected = value;
            OnPropertyChanged("Selected");
        }
    }
}

答案 1 :(得分:1)

  

在某些情况下,如果用户选择组合框中的项目,则必须自动将其更改为其他项目

对于它的价值,我认为重新审视这种设计选择是个好主意。它可能会让用户感到困惑,并且可能有更好的方式向用户呈现这种状态,而不是忽略他们为程序提供的输入。在你的问题中没有足够的上下文来完全理解你是如何进入这种情况的,所以我不能提供任何建议,而不是建议修改设计更好,而不是让代码去做你想做的事情。

那说......

您遇到的问题是WPF忽略了当前已经更新的绑定源的属性更改事件。在您的方案中,绑定正在从其绑定更新Selected值,因此在完成绑定更新之前,将忽略对该属性的更改。

有多种方法可以让代码按照您想要的方式工作。可能最简单的方法是简单地推迟源属性的更新,直到用户输入的处理完成为止。您可以使用Dispatcher.InvokeAsync()方法执行此操作:

void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    Dispatcher.CurrentDispatcher.InvokeAsync(() => this.Selected = "C");
}

我不是上述人的忠实粉丝,因为理想情况下它应该是一个与视图无关的对象,视图模型,并注入特定视图API的知识,即使用{{ 1}}对象。也就是说,您可以使用其他类似的机制,这些机制不依赖Dispatcher对象(例如使用异步计时器)。

在Stack Overflow上有很多其他方法可以解决这个问题。例如,您可以查看this answer的灵感。我认为它不会完全符合您的要求“直接开箱即用”,但附加属性方法可能是您认为更合适的方法,通过将逻辑从视图模型移动到视图代码,因此它是一个地方更适合使用Dispatcher。 (并且可以说,如果你打算做这样的事情,那么逻辑可能属于视图中......它在那里很奇怪,但我认为没有令人信服的理由这应该是视图模型中固有的。)

问题Coerce a WPF TextBox not working anymore in .NET 4.0中可以看到另一种方法。即事后,手动强制视图的状态更新。