views:

103

answers:

4

Hi All,

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

A: 

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! :)

Alex Paven
A: 

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.

Thomas Levesque
Hi Thomas, thanks for the response. As mentioned in your reply, I was unable to modify the bound ObservableCollection from a worker thread that i created in the view model. I used the approach provided in the link, but yet the collection is not getting reflected on the UI, but I am not getting any exception this time though. Any pointers on what I would be missing? If I have to do it through UI thread itself, how do I get handle of the UI thread in the View model class? Please suggest, Thanks
Sowmya
Are there any binding errors in the output window? If are checking for the item value changed within the collection i.e. modified value rather than the collection changed then they should be notified by PropertyChanged of INotifyPropertyChanged interface.
bjoshi
Nope, I am not checking for the modified value rather for the collection changed itself, but still it does not work:(...any clue? Thanks
Sowmya
A: 

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}/>
NVM
Thanks for sharing the sample snippet, but I am actually wondering if the Application.Current.Dispatcher would let the background worker process to go and update the SearchControl in the UI? As I got No Results Found in the SearchControl by using the above implementation though the Items collection has data to which the Search control is bound in the xaml file.Can you please let me know where I am going wrong?
Sowmya
Well I do exactly this to load a large number of images in my code and it works fine. The only difference I have is that I've set IsAsync=True on my binding but that is for other reasons. I dont think it applies to your case. I think you should show us your code and someone can help.
NVM
Should not Items ="{Binding CatalogSearchResults}" be ItemsSource ="{Binding CatalogSearchResults}" ?
NVM
Items is the dependency property i created in my usercontrol SearchControl, public ICollection Items { get { return (ICollection)GetValue(ItemsProperty); } set { SetValue(ItemsProperty, value); } } public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ICollection), typeof(SearchControl), new UIPropertyMetadata());
Sowmya
Is it because that Items is a ICollection dependency property defined in a user control?
Sowmya
Well your problem is that you are binding an ICollection with the ObservableCollection. In the dispatcher call your are doing Add but that wont notify the UI that your collection has changes. If you do Items = new ObservableCollection<...> in the dispatcher call it will update. That however is not your real solution I am just trying to tell you what the problem is... You should read and understand how ItemsSource works in WPF.
NVM
*edit* it still wont update because your CatalogSearchResults property does not raise 'PropertyChanged'
NVM
Infact, the Items dependency property which is defined in the user control class does exactly same what you just mentioned. It goes and updates the ItemSource property of the listbox control that i have defined in the control template of the search control. This is the xaml: <ListBox Name="PART_DropDownSearch" ItemsSource="{Binding Items, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
Sowmya
So, would you suggest I change the Set property of the CatalogSearchResults to raise a propertychanged notification? Would it work if so? Please let me know.
Sowmya
Items ="{Binding CatalogSearchResults}" does not do what you expect. Items does not/cannot know when your do CatalogSearchResults.Add()
NVM
No, although you should raise propertychanged for GetCatalogResults, you should bind CatalogSearchResults directly to your ListControl and drop the intermediate ICollection alltogether.
NVM
So if possible, could you please suggest me so that I could achieve the intended behavior? Thanks for all your suggestions!
Sowmya
<ListBox Name="PART_DropDownSearch" ItemsSource="{Binding Items, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/> WHat this means is that the parent which is most likely the search control supports ItemsSource. So if you bing directly to ItemsSource on your search control(which should have that property) you should be all set.
NVM
But, the search control is designed as a generic control in my application and using the Items property we try and bind the search results to it using MVVM approach in the view model class. If i have to remove the intermediate collection i would need to create a separate listbox for loading search results in my view and bind that list box to the CatalogSearchResults, isn't it? Then it would beat the very purpose of creating search control as a generic control. Keeping this in view, could we achieve async loading by making any other change? Your inputs are very much appreciated!
Sowmya
Ok firstly raise OnPropertyChanged("CatalogSearchResults") in the setter of the property and see what happens.
NVM
Yep did that already, no change:) as you mentioned earlier...Is there a way to expose the Listbox's ItemSource property in the search control class for me to bind it directly?
Sowmya
Moreover, could you let me know how is it that if i do not use Dispatcher and load items through the same Items intermediate property that I am able to see the search results?
Sowmya
Actually you are right. Now I am confused as well. You should wait for someone more knowledgeable than me to respond or open another question with this very specific problem in the question title and you should get some correct answers.
NVM
I just could figure out that if I change the Items property to IEnumerable it worked very well with the same code you suggested:):)...i tried it looking at the sample posted by Robert herewith!! I am lost on how did it work so, was I missing something somewhr, coz as far as i know ICollection derives from IEnumerable itself...any clue??
Sowmya
Well I actually had a look at the ItemsSource property of an ItemsControl and its indeed IEnumerable. I also noticed that your Items is ICollection. And I also guessed that ICollection derives from IEnumerable and is why I didnt mention it. But are you saying if you change ICollection to IEnumerable it all works??!?
NVM
hmm...I checked it again and I got to know that it is not the change of the datatype which made it happen, I wrote the whole explanation as a comment in the above post...could you please check?
Sowmya
probably, I need to rephrase my query now to how to update the ICollection (in Search control) property count (which is bound to a ObservableCollection in VM) while adding the items to the ObservableCollection in a backgroungworker thread:)..
Sowmya
A: 

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.

Robert Rossney
Thanks Robert for the sample code:)!! It works like charm now, the only change that I made from the existing implementation is that I modified the Items property in my user control from ICollection to IEnumerable and it works soo perfect!! I was wondering what is it that made it async operation work now with the change in the datatype of the Items? Could you please shed some light?
Sowmya
I'm confused. Are you using the sample I posted?
Robert Rossney
Yep, I used the code the you have posted. In your post as you mentioned ItemSource is IEnumerable, I thought to change Items property in the user control to IEnumerable from ICollection. Along with this I had to make one more change which I forgot to mention in the above reply, that I removed a line of code which was setting a boolean to true based on the no. of items in the Items collection. Since IEnumerable does not support Count property I removed that line of code and it worked then. So I assumed it was working as I changed it to IEnumerable.
Sowmya
Whenever there are no items in the collection, there is a boolean variable which is set to false in the code. If this variable is false a No results Found label appears in the place of the list box. Here is the code lock (this) { ExecuteCommand(); HasItemsInResult = false; if (null != Items) { HasItemsInResult = Items.Count != 0; } IsItemsVisible = true; }
Sowmya
While using backgroundworker or dispatcher, always Items.Count is Zero and so though the UI thread was updated with the result set since the boolean variable was set to false, always it was showing No results found, which made me think that the updating to the listbox was not happening:(...when i changed it to IEnumerable, I ignored HasItemsInResult = Items.Count != 0; this line of code and made it true even when Items was not null and so the list was updated in the UI...
Sowmya