【问题标题】:Create WPF Commands in another Thread在另一个线程中创建 WPF 命令
【发布时间】:2020-11-13 03:37:46
【问题描述】:

我目前正在开发一个登录窗口,它应该负责建立与服务器的连接、验证用户凭据,然后打开一个 MVVM 'MainWindow'。我想稍微加快 MainWindow 的打开过程,这就是为什么我试图将其 ViewModel 的初始化转移到自己的Task 中。这不知何故破坏了我的命令,我不明白为什么,所以我创建了一个最小可重现示例来解释我的问题。

ViewModel 看起来像这样:

public class MainWindowViewModel
  {

    public MainWindowViewModel()
    {
        

    }

    public ICommand ClickCommand { get; set; }

    public void CreateCommands()
    {
      ClickCommand = new RelayCommand(ExecuteClick);
    }


    public void ExecuteClick()
    {

    }

View 只是一个默认窗口,带有一个带有命令绑定的按钮:Command="{Binding ClickCommand}" 我创建视图和视图模型并从任务中调用CreateCommands,这很有效。

  MainWindow mw = new MainWindow();

  MainWindowViewModel vm = new MainWindowViewModel();
  mw.DataContext = vm;
   
  Task.Run(() =>
  {
   vm.CreateCommands();

  });
  mw.ShowDialog();

现在我在我的视图中创建一个新方法Init() 并让它调用CreateCommands()。只要我在我的任务中调用Init() 而不是CreateCommands(),如下所示。按下按钮时不会调用ExecuteClick()

public void Init()
{
   ((MainWindowViewModel)DataContext).CreateCommands();
}

--

  Task.Run(() =>
  {
   mw.Init();

  });

由于在没有任务的情况下调用mw.Init(),我怀疑WPF 调度程序有问题,所以我尝试使用App.Current.Dispatcher.BeginInvoke(new Action(() =>mw.Init()));Invoke,但这并没有改变任何东西。在将 Init() Mehtod 放置在其他 UI 元素中时,我也尝试复制此行为,但从另一个窗口调用它会再次起作用:

public Window1()
{
  InitializeComponent();

  MainWindow mw = new MainWindow();

  MainWindowViewModel vm = new MainWindowViewModel();
  mw.DataContext = vm;
   
  Task.Run(() =>
  {
   Init(vm);

  });
  mw.ShowDialog();


}

public void Init(MainWindowViewModel vm)
{
  vm.CreateCommands();
}

我还尝试了 Core 和 Framework 之间的差异,或者将我的 ViewModel 保存在成员变量中而不是使用 DataContext,没有任何显着差异。

到底发生了什么,只有在通过视图在线程中创建命令时才阻止创建我的命令?

【问题讨论】:

  • ICommand 属性的异步初始化不会使命令异步。你可能想看看这个:stackoverflow.com/q/54232156/1136211
  • Showdialog 被阻止,所以我想知道它是否会阻止您的任务。您也没有实现 inpc 并在您的命令属性设置时通知,以便永远不会拾取该命令。
  • Asynchronous Programming。有关异步命令实现的示例,请查找 AsyncRelayCommand 类示例。这个问题与世界一样古老。

标签: c# wpf multithreading mvvm


【解决方案1】:

优化前的配置文件

问题是主窗口打开速度很慢。您是否分析并得出结论是命令创建是主要瓶颈?如果没有,就从那开始。

Wpf 是单线程的

WPF(和大多数其他 UI 框架)基本上是单线程的。这意味着只能从 UI 线程访问任何 UI 对象。仍然可以在后台进行一些初始化,但是任何导致 UI 更新的事情都必须在 UI 线程上完成。

如果有一些缓慢的后台工作,典型的方法是在后台线程上做缓慢的事情,当这完成后,在 UI 线程上进行 UI 更新。这可以使用 async/await 轻松完成。

viewmodel更新时没有通知

视图模型应实现INotifyPropertyChanged,任何更改都应引发PropertyChanged 事件。您的示例中没有这样做。这可能就是使用任务对您不起作用的原因,因为视图绑定可能不会更新。

【讨论】:

    【解决方案2】:

    我创建了一个可重现的最小示例来解释我的问题

    如果您分享 minimal, reproducible example,将会有所帮助。您发布的代码不符合该定义。

    也就是说,如果您发布的代码确实表明了实际可重现的示例,那么我会说问题在于您没有在 MainWindowViewModel 对象中实现 INotifyPropertyChanged。我的意思是,代码也存在其他问题,但这似乎会直接导致您看到的问题。

    具体来说:您正在异步初始化ClickCommand 属性。但是到设置属性值时,几乎可以肯定视图(希望在 XAML 中声明,但您没有显示它,因此无法确定)已经完全初始化并且绑定已经填充。

    因此,无论您绑定了该属性,绑定的目标在初始化绑定时都会收到未初始化的null 值。并且由于你没有实现INotifyPropertyChanged,当属性的值实际上最终在稍后初始化时,绑定没有任何办法知道更新目标。

    如果这不能回答您的问题,请改进问题,使其实际上确实包含minimal, reproducible example

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多