我正在尝试使用MVVM模式实现我的第一个应用程序。我已经做了大部分工作,但是现在我遇到了以下问题(恕我直言)很常见的情况:
按Button
(查看)将调用方法(模型)。使用ICommand
(ViewModel)非常简单。但是如果必须执行耗时的操作该怎么办?
我当前的解决方案要求我实施包含WorkQueue
的{{1}}类。 WorkQueueItems
有一个与之关联的线程,它执行WorkQueue
s。每个WorkQueueItem
都有WorkQueueItem
,Name
和Status
,在执行期间会更新。
每个Progress
都有自己的Window
- 可视化为WorkQueue
。
我的问题: ViewModel如何找到合适的StatusBar
?我是否必须将WorkQueue
传递给我创建的每个ViewModel(这真的很烦人)?或者我可以使用其他机制吗?
我对WorkQueue
并不熟悉 - 强硬的基本概念似乎朝着这个方向发展。我很乐意看到一个解决方案,我可以将RoutedCommand
绑定到一个命令/事件,然后命令/事件冒泡到包含WorkQueueItem
的{{1}},并将其添加到Window
的{ {1}}。
我还考虑过让Window
成为单身人士 - 但这只有在我一次只有一个WorkQueue
时才有效。
答案 0 :(得分:4)
使用后来的.Net框架(4.0+)和WPF,您可以利用System.Threading.Tasks
库提供大量此类工作。
如果说你的命令需要更新View Model上的属性,但它必须等待信息,你只需启动一个任务来执行IO:
this.FindDataCommand = new RelayCommand<string>(
/* ICommand.Execute */
value =>
{
Task.Factory
.StartNew<IEnumerable<Foo>>(() => FindData(value))
.ContinueWith(
task =>
{
this.foundData.Clear();
this.foundData.AddRange(task.Result);
},
TaskScheduler.FromCurrentSynchronizationContext());
},
/* ICommand.CanExecute */
value => !String.IsNullOrWhitespace(value));
将其分解为可管理的部分,我们starting a new task调用了一些方法IEnumerable<Foo> FindData(string)
。这是你一直写的普通的无聊的同步代码。可能它已经存在于你的视图模型中了!
接下来,我们使用ContinueWith
告诉框架start a new task when that one finishes,但要在WPF Dispatcher instead上执行此操作。这样可以避免UI元素的跨线程问题。
您可以使用帮助程序类对此进行扩展以进行监视:
public class TaskManager
{
private static ConcurrentDictionary<Dispatcher, TaskManager> _map
= new ConcurrentDictionary<Dispatcher, TaskManager>();
public ObservableCollection<WorkItem> Running
{
get;
private set;
}
public TaskManager()
{
this.Running = new ObservableCollection<WorkItem>();
}
public static TaskManager Get(Dispatcher dispatcher)
{
return _map.GetOrAdd(dispatcher, new TaskManager());
}
// ...
在XAML中使用此类将是将其实例添加到Window的ViewModel
:
public TaskManager CurrentTaskManager
{
get { return TaskManager.Get(Dispatcher.CurrentDispatcher); }
}
// <StatusBarItem Content="{Binding CurrentTaskManager.Running.Count}" />
然后,您将向TaskManager添加一个方法,以处理与Running集合之间的任务添加:
public Task<TResult> StartNew<TResult>(Func<TResult> work)
{
var task = Task.Factory
.StartNew<TResult>(work);
// build our view model
var workItem = new WorkItem(task);
this.Running.Add(workItem);
// Pass the result back using ContinueWith
return task.ContinueWith(
t => { this.Running.Remove(workItem); return t.Result; },
TaskScheduler.FromCurrentSynchronizationContext());
}
现在我们只需更改FindDataCommand
实施:
TaskManager.Get(Dispatcher.CurrentDispatcher)
.StartNew<IEnumerable<Foo>>(() => FindData(value))
.ContinueWith(
task =>
{
this.foundData.Clear();
this.foundData.AddRange(task.Result);
},
TaskScheduler.FromCurrentSynchronizationContext());
WorkItem
类可以将Task
类的属性公开给UI,或者可以扩展为封装CancellationToken
以支持将来取消。
答案 1 :(得分:1)
我不确定我的问题是否正确,但我觉得在Dispatcher中使用buil可以解决您的问题而且您不需要手动实现WorkQueue
,因为Dispatcher会为您实现这样的队列并且能够使用预定义的优先级集将“工作项”分派到UI /任何thred。您可以使用Dispatcher.Invoke()
或Dispatcher.BeginInvoke()
有用的链接: