我正在创建一个WPF解决方案,该解决方案使用MVVM模式异步加载搜索控件中的搜索项。作为WPF用户控件的搜索控件是使用文本框创建的,用于输入搜索文本和搜索按钮以及隐藏列表框,该列表框在加载搜索项目列表时可见。此用户控件又嵌入到另一个WPF视图中,该视图具有某些项目的树视图。该视图具有视图模型,其中加载树视图的搜索项的逻辑将被加载到搜索控件中。一直以来,这都是在没有使用任何Dispatcher调用的情况下同步进行的。但是,在更改请求之后,我想使用Dispatcher在另一个线程中异步发生这种情况。
有谁能告诉我如何在视图模型类中处理Search控件的Dispatcher,以便使用MVVM模式在其上调用BeginInvoke,其中我的View模型不知道视图?任何线索都将受到高度赞赏。
public ObservableCollection<Details> CatalogSearchResults { get; private set; }
private void ExecuteSearchCommand(object parameter)
{
CatalogSearchResults.Clear();
if (string.IsNullOrEmpty(parameter.ToString())) return;
searchtext = (string)parameter;
searchtext.Trim();
SetSearchResults();
}
private void SetSearchResults()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += LoadResults;
bw.RunWorkerCompleted += this.LoadResultsCompleted;
bw.RunWorkerAsync();
}
private void LoadResults(object sender, DoWorkEventArgs args)
{
IsSearchInProgress = true;
foreach (var category in _rootCategory.Recurse(FindChildren))
{
if (category.CommentDetails != null)
{
//limitation - there is no direct way to add range to observable collection.
//Using linq query would result in two loops rather than one.
foreach (var node in category.Details)
{
if (node.Name.IndexOf(searchtext, StringComparison.CurrentCultureIgnoreCase) >= 0
|| node.PrecannedText.IndexOf(searchtext, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate { CatalogSearchResults.Add(node); });
Thread.Sleep(100);
}
}
}
}
IsSearchInProgress = false;
}
在xaml中,我将Search控件的Items属性绑定到CatalogSearchResults:
<ctrl:SearchControl x:Name="Ctrl" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" ToolTip="Search" Command="{Binding SearchCommand}" Grid.ColumnSpan="3"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}"
Items ="{Binding CatalogSearchResults}" > </ctrl:SearchControl>
谢谢, Sowmya
答案 0 :(得分:2)
这是一个简单的实现,展示了如何在BackgroundWorker
运行时使用DoWork
更新UI线程上的对象 - 在此示例中,UI中的ListBox
绑定到{ {1}},FilteredItems
是ItemsSource
类型UserControl
的属性:
IEnumerable
请注意,每次找到项目时调用 FilteredItems = new ObservableCollection<object>();
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerAsync();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker) sender;
var result = ItemsSource
.OfType<object>()
.Where(x => x.ToString().Contains(_FilterText));
foreach (object o in result)
{
// Pass each object found to bw_ProgressChanged in the UserState argument.
// This updates the UI as each item is found.
bw.ReportProgress(0, o);
}
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// FilteredItems is bound to the UI, but it's OK to update it here because
// the ProgressChanged event handler runs on the UI thread.
FilteredItems.Add(e.UserState);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
}
都是非常低效的,因为您通过ReportProgress
调用来编组在线程中找到的每个项目。根据过滤实际采用的时间长短,最好累积一堆结果并将Invoke
传递给List<object>
而不是仅传递bw_ReportProgress
。
答案 1 :(得分:1)
这取决于很多因素(你的描述有点令人困惑),但我给出了一个冗长的答案here,可能会对此事有所了解。基本上,单独使用调度程序不会自动使代码多线程;你需要一些真正的多线程机制,如BackgroundWorker或任务并行库。您可能确实需要在调度程序线程上调用某些操作,这取决于您如何设置内容以及您在其他线程中执行的操作 - 但是在大多数情况下,BackgroundWorker会自动执行此操作,因此我会将其用于简单的操作。任务并行库也对调度程序有特殊处理,你应该在MSDN或任何TPL教程上找到更多关于它的信息。
如果你到目前为止没有大量处理多线程,那么我给出的最好的建议是尽可能多地收集信息,因为,直到现在已经无数次说过,多线程是硬盘! :)
答案 2 :(得分:1)
应用程序中的所有视图都有相同的调度程序,您可以使用Application.Current.Dispatcher
访问它。
但无论如何,您不需要调度程序在工作线程上执行操作。您只需要它就可以在UI上执行操作,因为只能从UI线程访问UI元素。但即便如此,您通常也不需要明确地操纵调度程序。您可以从工作线程更新ViewModel的属性,绑定到此属性的控件将更新,因为PropertyChanged
事件会自动编组到UI调度程序。
不工作的是从工作线程修改绑定ObservableCollection<T>
:您需要使用Dispatcher.Invoke
从UI线程执行此操作。您还可以使用specialized ObservableCollection<T>
that raises event on the UI thread。
答案 3 :(得分:1)
根据需要进行修改。 'items'只是从VM公开的字符串的observableCollection
private void SetSearchResults()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += LoadResults;
bw.RunWorkerCompleted += this.LoadResultsCompleted;
bw.RunWorkerAsync();
}
private void LoadResultsCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
private void LoadResults(object sender, DoWorkEventArgs args)
{
List<string> results = GetResults();
foreach (string result in results)
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Normal, (ThreadStart)delegate { Items.Add(result); } //Dont worry about access to modified closure in this case
Thread.Sleep(100);
}
}
在XAML中
<ListBox ItemsSource={Binding Items}/>