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

发布于 2024-09-26 17:56:06 字数 2626 浏览 9 评论 0原文

我正在创建一个 WPF 解决方案,该解决方案使用 MVVM 模式在搜索控件中异步加载搜索项。搜索控件是一个 WPF 用户控件,它带有一个用于输入搜索文本和搜索按钮的文本框以及一个隐藏列表框,该列表框在加载搜索项列表时可见。该用户控件又嵌入到另一个 WPF 视图中,该视图具有某些项目的树视图。该视图有一个视图模型,其中加载树视图的搜索项的逻辑将加载到搜索控件中。一直以来,这都是同步发生的,无需使用任何调度程序调用。但是,在更改请求之后,我想使用 Dispatcher 在不同的线程中异步发生此操作。

谁能告诉我如何获取视图模型类中搜索控件的调度程序的句柄,以便使用 MVVM 模式对其调用 BeginInvoke,其中我的视图模型不知道该视图?任何线索将不胜感激。

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 中,我将搜索控件的 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>

谢谢, 索米亚

I am working on creating a WPF solution which uses MVVM pattern to load searched items in a search control asynchronously. The search control which is a WPF usercontrol is created with a textbox to enter search text and search button and a hidden listbox which would be visible when it loads the searched items list in it. This user control is in turn embedded into another WPF view which has a treeview of certain items. This view has a view model in which the logic to load the searched items of the tree view would be loaded in the search control. All the while, this has been happening synchronously without the use of any Dispatcher call. But, after a change request, I would like to make this happen asynchronously in a different thread using Dispatcher.

Could anyone please let me know how to get handle of the Dispatcher of the Search control in the view model class so as to call BeginInvoke on it using MVVM pattern wherein my View model is not aware of the view? Any clue would be highly appreciated.

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;
}

In the xaml, I am biding the Items property of the Search control to the 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>

Thanks,
Sowmya

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

猥︴琐丶欲为 2024-10-03 17:56:06

下面是一个简单的实现,展示了如何在 DoWork 运行时使用 BackgroundWorker 更新 UI 线程上的对象 - 在此示例中,有一个 ListBox绑定到 FilteredItems 的 UI,并且 ItemsSourceIEnumerable 类型的 UserControl 的属性:

    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传递给 bw_ReportProgress 而不是仅传递一个 >对象

Here's a simple implementation showing how to use BackgroundWorker to update objects on the UI thread while DoWork is running - in this example, there's a ListBox in the UI that's bound to FilteredItems, and ItemsSource is a property of the UserControl of type 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);
        }
    }

Note that calling ReportProgress every time you find an item is pretty inefficient, as you're marshalling every item found across threads with an Invoke call. Depending on how long the filtering is actually taking, it may be better to accumulate a bunch of results and pass a List<object> to bw_ReportProgress instead of just a single object.

绝對不後悔。 2024-10-03 17:56:06

这取决于很多因素(你的描述有点令人困惑),但我给出了一个冗长的答案这里可能会对此事有所启发。基本上,单独使用调度程序不会自动使代码成为多线程;您将需要一些真正的多线程机制,例如BackgroundWorker或任务并行库。根据您的设置方式以及您在另一个线程中执行的操作,您可能确实需要在调度程序线程上调用一些操作 - 但是,BackgroundWorker 在大多数情况下会自动执行此操作,因此我会选择它来完成简单的事情。任务并行库还对调度程序进行特殊处理,您应该在 MSDN 或任何 TPL 教程上找到更多相关信息。

如果您到目前为止还没有大量处理多线程,我给出的最好建议是收集尽可能多的信息,因为正如到目前为止已经无数次说过的那样,多线程是很难! :)

It depends on a lot of factors (and your description is a bit confusing), but I've given a lengthy answer here that may shed some light on the matter. Basically, using the dispatcher alone will not automatically make the code multi-threaded; you'll need some real multi-threading mechanism like BackgroundWorker or the Task Parallel Library. Depending on how you have things set up and on exactly what you do in the other thread, you may indeed need to invoke some actions on the dispatcher thread - however BackgroundWorker does this automatically in most cases so I'd go with that for simple things. The Task Parallel Library also has special handling for the dispatcher, you should find more info on that on MSDN or any TPL tutorial.

The best advice I'd give if you didn't deal heavily with multi-threading until now is to gather as much information as possible on it, because, as it has been said countless times until now, multi-threading is hard! :)

冰雪梦之恋 2024-10-03 17:56:06

根据需要进行修改。 “Items”只是

    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 中从 VM 公开的字符串的可观察集合

<ListBox ItemsSource={Binding Items}/>

Modify as necessary. 'Items' is just an observableCollection of strings exposed from the VM

    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);
        }
    }

In XAML

<ListBox ItemsSource={Binding Items}/>
硬不硬你别怂 2024-10-03 17:56:06

应用程序中的所有视图都有相同的调度程序,您可以使用 Application.Current.Dispatcher 访问它。

但无论如何,您不需要调度程序在工作线程上执行操作。您只需要它在 UI 上执行操作,因为只能从 UI 线程访问 UI 元素。但即便如此,您通常也不需要显式操作调度程序。您可以从工作线程更新 ViewModel 的属性,绑定到该属性的控件将正确更新,因为 PropertyChanged 事件会自动编组到 UI 调度程序。

不起作用的是从工作线程修改绑定的 ObservableCollection:您需要使用 Dispatcher.Invoke从 UI 线程执行此操作代码>.您还可以使用专门的ObservableCollection< ;T> 在 UI 线程上引发事件

All views in the application have the same dispatcher, you can access it with Application.Current.Dispatcher.

But anyway, you don't need the dispatcher to perform operations on a worker thread. You only need it to perform actions on the UI, because UI elements can only be accessed from the UI thread. But even then, you usually don't need to explicitly manipulate the dispatcher. You can update a property of your ViewModel from the worker thread, controls bound to this property will be updated alright, because the PropertyChanged event is automatically marshalled to the UI dispatcher.

What doesn't work is modifying an bound ObservableCollection<T> from a worker thread: you need to do it from the UI thread using Dispatcher.Invoke. You can also use a specialized ObservableCollection<T> that raises event on the UI thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文