如何在Binding中加载控件而不阻止UI?
我有这种情况:
我想在MainWindow的ViewModel中加载一个集合,我有一个大数据,所以当我进入人物集合的幻灯片时,它应该等待完成加载然后我可以看到完整列表,我有以下内容:
-
在xaml代码我有:
-
在视图模式中我有:
// ... private ObservableCollection _People; public ObservableCollection People { get{return _People;} set{ _People = value; RaisePropertyChange("People");}} // ...
并且我想在加载第一个完全开始加载第二个…之后逐个加载所有人列表而不阻塞主窗口我希望我的窗口看起来像:
我厌倦了这样做,但我失败了。 谢谢你。
存在使用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不会被冻结。 IsBusy
和BusyText
是视图模型变量,它们与视图等待消息和等待元素的可见性绑定。 这是在子视图模型的命令中使用的示例:
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的示例