MVVM通知属性已更改回

时间:2015-06-25 21:16:17

标签: c# wpf multithreading mvvm

我有一个基于MVVM的应用程序(不使用任何特定的mvvm库或框架),我想要启用和禁用一些按钮,具体取决于方法是否正在运行。我在ViewModel上设置了一个bool属性," ProcessRunning":

bool _ProcessRunning = false;
/// <summary>
/// Whether A Process Is Currently Taking Place
/// </summary>
public bool ProcessRunning
{
    get
    {
        return _ProcessRunning;
    }
    set
    {
        if (_ProcessRunning != value)
        {
            _ProcessRunning = value;
            RaisePropertyChanged("ProcessRunning");
        }
    }
}

然后我有一系列按钮,其样式将它们绑定到此属性:

按钮:

<Button Name="btn_AddMore" Content="More" Style="{StaticResource ExecuteButtons}"
        ToolTip="Enumerate Customer Folders To Get More Customer And Job Options"
        Command="{Binding AddFolderOptions}"/>

样式:

<Style TargetType="Button" x:Key="ExecuteButtons">
    <Setter Property="Width" Value="80"/>
    <Setter Property="Height" Value="40"/>
    <Setter Property="FontFamily" Value="Arial"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="Padding" Value="0,0,0,0"/>
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="FontWeight" Value="700"/>
    <Setter Property="BorderBrush" Value="#FFA0A0A0"/>
    <Setter Property="Margin" Value="12,0,0,0"/>
    <Setter Property="Background" Value="#FFEEEEEE"/>
    <Setter Property="IsEnabled" Value="{Binding ProcessRunning, Converter={StaticResource Inverse}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Cursor" Value="Hand"/>
        </Trigger>
    </Style.Triggers>
</Style>

除了在执行其他操作的方法中更改它时,绑定按预期工作:

private void AddOptionsFromFolders(object Parameter)
{
    try
    {
        ProcessRunning = true;
        Transfer_Workers.Program.Processes.AddFolderChoices(ref _LocalSettings);

在这种情况下,在AddOptionsFromFolders退出之后,View才会更新。

我通过其他几个问题发现UI线程在此方法调用期间被阻塞,并且在方法完成之前不会释放更新,所以我尝试了这个:

private void AddOptionsFromFolders(object Parameter)
{
    try
    {
        ProcessRunning = true;

        //Transfer_Workers.Program.Processes.AddFolderChoices(ref _LocalSettings);

        Thread test = new Thread(() => { Thread.Sleep(2000); ProcessRunning = false; });
        test.Start();

在这种情况下,当ProcessRunning设置为true但是当&#34; test&#34;线程完成并应将ProcessRunning设置为false,UI不会更新。

我的ViewModel继承自主视图模型,而且我从基础视图模型继承了我已声明的RaisePropertyChanged:

internal void RaisePropertyChanged(string prop)
{
    PropertyChanged(this, new PropertyChangedEventArgs(prop));
}

我似乎无法弄清楚为什么在ProcessUpdated设置回false时UI不会更新。不会抛出异常,并且输出窗口中没有警告或错误(Visual Studio 2013 Pro)。

我的预期结果是,当方法运行时,按钮将被禁用,而不是按钮被启用。如果有人能够对这里的问题有所了解,或者甚至建议采用不同的方式通过更加异步的方式通知房产变更视图,我将非常感激。

此外,如果有任何我不清楚的信息,或者我遗漏的信息只是问我,我会尽快给你。

2 个答案:

答案 0 :(得分:1)

这是因为您正在从后台线程更新属性。无法从UI线程以外的线程更新UI,因此将忽略属性更改。

您可以使用async / await:

执行此操作
ProcessRunning = true;
await Task.Run(() => 
{ 
    // do work here 
});
ProcessRunning = false;

等待Task时捕获同步上下文,因此它将继续在UI线程上执行。

但是,对于禁用按钮,您应该只确保命令的CanExecute返回false,直到命令执行完毕。

答案 1 :(得分:0)

应该避免将直接读取和写入可能与GUI线程相关联的变量。

对于这种情况,内部并在您的线程结束时,使用调度程序通知gui线程更改,例如

Application.Current.Dispatcher.BeginInvoke(
     DispatcherPriority.Background,
      new Action(() => ProcessRunning = false));
当我尝试从后台线程中读取时,我遇到了类似的问题。在此处了解我的体验C# WPF: Linq Fails in BackgroundWorker DoWork Event

次要思想

另请注意,使用多线程时,您可能会遇到竞争状态。

真的可以消除另一个线程也可能正在改变标志吗?或者后台线程实际上比预期更快完成并在最初设置之前先设置值?

种族条件可能是阴险的,确实会发生。