如何在Binding中加载控件而不阻止UI?

我有这种情况:

我想在MainWindow的ViewModel中加载一个集合,我有一个大数据,所以当我进入人物集合的幻灯片时,它应该等待完成加载然后我可以看到完整列表,我有以下内容:

  • 在xaml代码我有:

     
  • 在视图模式中我有:

     // ... private ObservableCollection _People; public ObservableCollection People { get{return _People;} set{ _People = value; RaisePropertyChange("People");}} // ... 

并且我想在加载第一个完全开始加载第二个…之后逐个加载所有人列表而不阻塞主窗口我希望我的窗口看起来像:

UI示例

我厌倦了这样做,但我失败了。 谢谢你。

存在使用SynchronizationContext从另一个线程修改视图的方法。

请看这个例子:

  private void Button_Click(object sender, RoutedEventArgs e) { var sync = SynchronizationContext.Current; BackgroundWorker w = new BackgroundWorker(); w.DoWork+=(_, __)=> { //sync.Post(p => { button.Content = "Working"; }, null); int j = 0; for (int i = 0; i < 10; i++) { j++; sync.Post(p => { button.Content = j.ToString(); }, null); Thread.Sleep(1000); } sync.Post(p => { button.Background = Brushes.Aqua; button.Content = "Some Content"; }, null); }; w.RunWorkerAsync(); } 

这就是观点:

      

此代码会多次更新视图(在本例中为按钮)。 我认为这解决了你的初步问题。

– – 编辑 – –

这是使用这个想法的更好方法:我建议在基本视图模型中创建这样的方法:

  public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null) { if (IsBusy) return; var currentSyncContext = SynchronizationContext.Current; ActiveThread = new Thread((_) => { currentSyncContext.Send(t => { IsBusy = true; BusyText = string.IsNullOrEmpty(text) ? "Wait please..." : text; if (beforeVisualAction != null) beforeVisualAction(); }, null); action(); currentSyncContext.Send(t => { IsBusy = false; BusyText = ""; if (afterVisualAction != null) afterVisualAction(); }, null); }); ActiveThread.Start(); } 

通过这种方式,任何子视图模型都可以使用它来进行大量的数据处理,并且UI不会被冻结。 IsBusyBusyText是视图模型变量,它们与视图等待消息和等待元素的可见性绑定。 这是在子视图模型的命令中使用的示例:

  private RelayCommand _SomeCommand; public RelayCommand SomeCommand { get { return _SomeCommand ?? (_SomeCommand = new RelayCommand(ExecuteSomeCommand, CanExecuteSomeCommand)); } } private void ExecuteSomeCommand() { Action t = ()=> { //some action }; LockAndDoInBackground(t, "Generating Information..."); } private bool CanExecuteSomeCommand() { return SelectedItem != null; } 

希望这将成为一个更明确的例子。

我会这样做的方式:

  • IsLoading标志添加到项目
  • 该项的DataTemplate应该检查该标志,并且对于IsLoading=true它应该显示一些选取框进度,否则实际数据
  • 使用IsLoading = TRUE将空项对象添加到ObservableCollection,然后开始在另一个线程上检索项数据
  • 检索项目数据后,在UI线程中设置IsLoading = FALSE
  • 该项应实现’INotifyPropertyChanged’,所有可见属性应发送PropertyChanged事件

您有两种方法可以实现所需的方案:

  • 使用BackgroundWorker并实现它。 如果要将ProgressChanged与进度一起使用,则它将是合适的。 DoWork将是您负载繁重的数据。 CompletedEvent是您完成该任务的完成。

  • 使用TPL ,您可以使用Task.Factory.StartNew()立即开始使用它, Task.Factory.StartNew()操作作为数据加载。 一个是构造函数,你可以传入TaskScheduler.FromCurrentSynchronizationContext()这样它就可以将它编组到UI Dispatcher

  • 任务并行库的示例

  • BackgroundWorker的示例