使用Dispatcher在WPF列表框中异步加载项目列表

时间:2010-10-07 22:11:09

标签: wpf mvvm dispatcher

我正在创建一个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

4 个答案:

答案 0 :(得分:2)

这是一个简单的实现,展示了如何在BackgroundWorker运行时使用DoWork更新UI线程上的对象 - 在此示例中,UI中的ListBox绑定到{ {1}},FilteredItemsItemsSource类型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}/>