Загрузка списка элементов асинхронно в списке WPF с помощью Dispatcher - PullRequest
2 голосов
/ 08 октября 2010

Я работаю над созданием решения WPF, которое использует шаблон MVVM для асинхронной загрузки искомых элементов в элемент управления поиска. Элемент управления поиском, который является пользовательским элементом управления WPF, создается с помощью текстового поля для ввода текста поиска и кнопки поиска, а также скрытого списка, который будет виден при загрузке в него списка найденных элементов. Этот пользовательский элемент управления в свою очередь встроен в другое представление WPF, которое имеет древовидное представление определенных элементов. Это представление имеет модель представления, в которой логика загрузки искомых элементов представления дерева будет загружена в элемент управления поиском. Все это происходило синхронно, без использования какого-либо вызова диспетчера. Но после запроса на изменение я бы хотел, чтобы это происходило асинхронно в другом потоке с помощью Dispatcher.

Может ли кто-нибудь сообщить мне, как получить дескриптор Dispatcher элемента управления Search в классе модели представления, чтобы вызвать для него BeginInvoke с использованием шаблона MVVM, в котором моя модель 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 я привязываю свойство Items элемента управления Search к 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 ]

2 голосов
/ 08 октября 2010

Вот простая реализация, показывающая, как использовать BackgroundWorker для обновления объектов в потоке пользовательского интерфейса во время работы DoWork - в этом примере в пользовательском интерфейсе есть ListBox, привязанный к 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 вместо одного object.

1 голос
/ 08 октября 2010

Все представления в приложении имеют одного диспетчера, доступ к нему можно получить с помощью Application.Current.Dispatcher.

Но в любом случае вам не нужен диспетчер для выполнения операций над рабочим потоком. Он нужен только для выполнения действий с пользовательским интерфейсом, поскольку к элементам пользовательского интерфейса можно получить доступ только из потока пользовательского интерфейса. Но даже тогда вам обычно не нужно явно манипулировать диспетчером. Вы можете обновить свойство вашей ViewModel из рабочего потока, элементы управления, связанные с этим свойством, будут обновлены в порядке, поскольку событие PropertyChanged автоматически передается диспетчеру пользовательского интерфейса.

Что не работает, так это изменение границы ObservableCollection<T> из рабочего потока: вам нужно сделать это из потока пользовательского интерфейса, используя Dispatcher.Invoke. Вы также можете использовать специализированный ObservableCollection<T>, который вызывает событие в потоке пользовательского интерфейса .

1 голос
/ 08 октября 2010

Изменить при необходимости. «Items» - это просто наблюдаемая коллекция строк, представленная из 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);
        }
    }

В XAML

<ListBox ItemsSource={Binding Items}/>
1 голос
/ 08 октября 2010

Это зависит от многих факторов (и ваше описание немного сбивает с толку), но я дал длинный ответ здесь , который может пролить некоторый свет на этот вопрос.По сути, использование только диспетчера не сделает код многопоточным;вам понадобится какой-нибудь реальный многопоточный механизм, например BackgroundWorker или Task Parallel Library.В зависимости от того, как у вас настроены вещи, и от того, что именно вы делаете в другом потоке, вам действительно может понадобиться вызвать некоторые действия в потоке диспетчера - однако BackgroundWorker делает это автоматически в большинстве случаев, поэтому я бы использовал это для простых вещей,Библиотека параллельных задач также имеет специальную обработку для диспетчера, вы должны найти дополнительную информацию об этом в MSDN или любом учебном пособии по TPL.

Лучший совет, который я бы дал, если бы вы не занимались многопоточностьюдо сих пор собирать как можно больше информации о нем, потому что, как говорилось бесчисленное количество раз до сих пор, многопоточность сложна !:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...