属性 由事件处理程序更新时 WPF 绑定不起作用

WPF Binding not working when property is updated by a event handler

我正在使用 WPF 将我以前的一个玩具项目重写为 MVVM 结构。但是绑定不起作用。

我的项目将 .xef 文件转换为 .mat 文件。为了遵循 MVVM 结构,首先我创建了一个 xef2matcore class 来执行业务逻辑,并提供了几个事件(例如 FileLoaded、ConversionProgressUpdated)。然后我创建了一个 ViewModel class,其中存储了将绑定到 UI 的属性(例如 Progress、IsButtionEnabled)。 ViewModel 实现了 INotify属性Changed 接口。最后,属性绑定到 UI 元素(例如 progressbar.Value、buttion.IsEnabled)。

问题来了: 如果通过调用 ViewModel 中定义的方法直接更改绑定 属性,则绑定可以正常工作。但是,如果通过事件处理程序方法更改绑定 属性(并最终调用 ViewModel 中定义的相同方法),则绑定将不起作用(UI 即使绑定 属性 也不会更新值已更改)。

代码说明如下:

public class ViewModel: ObservableObject
{
    private double _progress;
    public double Progress
    {
        get => _progress;
        set => Set(ref _progress, value);
    }

    private string _fileName;
    public string FileName
    {
        get => _fileName;
        set => Set(ref _fileName, value);
    }
    private bool _isButtonEnabled;
    public bool IsButtonEnabled
    {
        get => _isButtonEnabled;
        set => Set(ref _isButtonEnabled, value);
    }
    private bool _isBusy;
    public bool IsBusy
    {
        get => _isBusy;
        set => Set(ref _isBusy, value);
    }

    Xef2MatCore XCore;

    public ViewModel()
    {
        Progress = 0;
        FileName = "";
        IsButtonEnabled = true;            
    }

    private void XCore_FileLoaded()
    {
        IsButtonEnabled = false;
    }
    private void XCore_ProgressUpdated(double progress)
    {
        Progress = progress;
    }
    private void XCore_ExportFinished()
    { 
        IsButtonEnabled = true; 
        Progress = 50; 
    }

    public IAsyncCommand SelectFileCommandAsync { get => new AsyncCommand(UpdateFileSelectionExecuteAsync, CanFileSelectionExecute); }

    private bool CanFileSelectionExecute() => !IsBusy;    

    private async Task UpdateFileSelectionExecuteAsync()
    {

        var folder_path = Environment.CurrentDirectory;
        if (!Directory.Exists(folder_path)) Directory.CreateDirectory(folder_path);

        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.InitialDirectory = Environment.CurrentDirectory;
        openFileDialog.Filter = "Kinect Studio Data File|*.xef";
        openFileDialog.RestoreDirectory = true;
        openFileDialog.FilterIndex = 1;

        var result = openFileDialog.ShowDialog();
        if (result.HasValue && result.Value)
        {
            FileName = openFileDialog.FileName;

            IsBusy = true;

            XCore = new Xef2MatCore();
            XCore.FileLoaded += XCore_FileLoaded;
            XCore.ProgressUpdated += XCore_ProgressUpdated;
            XCore.ExportFinished += XCore_ExportFinished;

            //await Do_work();
            await XCore.LoadAsync(FileName);

            IsBusy = false;
        }
        else
        {
            return;
        }
    }

    private async Task Do_work()
    {
        XCore_FileLoaded();
        await Task.Run(() =>
        {
            int i = 0;
            while (i < 100)
            {
                XCore_ProgressUpdated(i++);
                Thread.Sleep(100);
            }                              
        });
        XCore_ExportFinished();
    }
}

如果我取消注释 await Do_work();,则绑定有效,但如果我调用 await XCore.LoadAsync(FileName);(通过事件处理方法)。

XAML 文件:

<Window x:Class="Xef2MatUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Xef2MatUI"
        mc:Ignorable="d"
        Title="Xef2Mat Converter" >
    <Window.DataContext>
        <local:ViewModel x:Name="_viewmodel"/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>

        <Label Grid.Column="0" Grid.Row="0" 
               x:Name="label1" 
               Content="Select Kinect Studio (.xef) file:" 
               HorizontalAlignment="Left" VerticalAlignment="Center"/>
        <Button Grid.Column="0" Grid.Row="1" 
                x:Name="button" 
                Content="Select" 
                IsEnabled="{Binding IsButtonEnabled, Mode=OneWay}"
                HorizontalAlignment="Left" VerticalAlignment="Center" 
                Command="{Binding SelectFileCommand}"
                />
        <Button Grid.Column="1" Grid.Row="1" 
                Content="Select" 
                />
        <Label Grid.Column="0" Grid.Row="2" 
               x:Name="label2" Content="Progress:" 
               HorizontalAlignment="Left" VerticalAlignment="Center"/>
        <ProgressBar Grid.Column="0" Grid.Row="3" 
                     x:Name="progressBar"
                     Value="{Binding Progress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
        <Label Grid.Column="1" Grid.Row="3" 
               x:Name="label3" Content="{Binding Progress, Mode=OneWay, StringFormat={}{0}%}" 
               HorizontalAlignment="Center"  VerticalAlignment="Center"/>
    </Grid>
</Window>

感谢任何帮助。

正如Selvin评论的那样,当我调用await XCore.LoadAsync(FileName);时,耗时的任务阻塞了OnEventHappened函数。然后把它们分开,把耗时的任务放到新的线程中,问题就解决了。