我有一个使用MVVM模式的WPF应用程序。我在窗口中有控件绑定到ViewModel中的属性。我有一个Play按钮,它通过ICommand接口实现绑定到Play()方法。当Play()方法逐步执行时,我首先更改一些属性以更改UI以向用户显示应用程序正在运行:
IsPlaying = true;
IsNotPlaying = false;
DurationTimer.Start();
Status = $"Playing: {_playingUrl}";
FilePreview?.FileNameSet(_playingUrl, "");
FilePreview?.FilePlayStart();
按下“播放”按钮时,应通过IsPlaying
属性禁用“播放”按钮,并通过IsNotPlaying
属性启用“停止”按钮。此外,DurationTimer
应该启动(显示计时器)和Status
属性。如上所述,这些用于向用户显示事情正在发生,因为FilePreview?.FilePlayStart();
是一种阻塞方法,并且UI在处理时锁定。
然而,当按下“播放”按钮时,UI立即锁定,然后,一旦FilePlayStart()方法完成其处理,它就会释放,其他项目将生效。
我错过了什么吗?
答案 0 :(得分:0)
与大多数UI框架一样,WPF仅从一个线程更新UI(任何从另一个线程更新UI的尝试都会引发System.InvalidOperationException。)
现在,由于UI线程正在忙于执行您的方法,因此它无法同时更新UI。
WPF与"批量生产"代码执行批量后,UI会处理所有更新。在执行结束时,而不是在中间。
因此,如果在Execute
方法(或在UI线程上执行的任何其他方法)中设置了40次"可以执行= true","可以执行= false&# 34;,"可以执行= true","可以执行= false",实际上你不会看到Button
无法使用和禁用40次。相反,当方法退出时,然后使用最后一个值更新UI。
那么,如何解决这个问题呢? Execute
方法应该是异步的。
类似的东西:
public class Command : ICommand
{
//ICommad implementation and other stuffs
//...
public async void Execute(object parameter)
{
await DoExecute(parameter);
}
private async Task DoExecute(object parameter)
{
//do something asynchronously...
}
}
在您的特定情况下,FilePreview?.FilePlayStart();
应该是异步的,您应该将此方法传递给Command
。
你可以写一般Command
:
public class Command : ICommand
{
//ICommad implementation and other stuffs
//...
//pass the execution in the constructor
public Command(Func<object, Task> execution)
{
_execution = execution;
}
private Func<object, Task> _execution;
public async void Execute(object parameter)
{
await _execution(parameter);
}
private async Task DoExecute(object parameter)
{
//do something asynchronously... like await Task.Delay(2000);
}
}
然后,您可以在Command
:
MyCommand = new Command(async parameter =>
{
IsPlaying = true;
IsNotPlaying = false;
await FilePreview?.FilePlayStartAsync();
});
一旦输入await
部分,执行就会传递给另一个线程,并且当前线程(即UI线程)可以自由更新UI,您将看到{{1根据需要启用/禁用。
如果该方法的异步版本不可用,您可以写:
Button
答案 1 :(得分:0)
您无法在UI线程上执行长时间运行操作,因为它会阻止调度程序直到完成处理。
在这种情况下,只需使用async / await释放调度程序并允许消息继续进行。
private async void PlayCommand()
{
IsPlaying = true;
IsNotPlaying = false;
DurationTimer.Start();
Status = $"Playing: {_playingUrl}";
await Task.Run(()=>
{
FilePreview?.FileNameSet(_playingUrl, "");
FilePreview?.FilePlayStart();
});
}