How can I override TreeViewItem in WPF to allow asynchronous children loading? - wpf

I am using MVVM as my WPF architecture and I have a WPF TreeView implemented as delay loading (child items are not loaded recursively until you expand).
However, I need to implement asynchronous loading as well upon expansion. Is there a way to do that? I need to keep this in control level and not in code-behind/application level.
Thanks

Why do you need to implement this in the control itself?
Assuming that isn't an absolute requirement, I would handle this in the property that returns the children of the specified node. If the children haven't been populate yet, use a background thread to load the children, and as they are found notify the UI thread and then add the child object to the collection of children. Assuming that you're using an ObservableCollection (or at least a collection that implements INotifyCollectionChanged), as the children are added they will appear in the UI asynchronously.

If you use lazy loading and HierarchicalDataTemplates you will only get the items you've selected loading. Keep in mind, the TreeView will need to load one level below what it is displaying to determine whether or not it should show the Expand/Collapse Toggle button

Here is how to do this:
Create an attached property of type IEnumerable
In the PropertyChanged handler for the property, cast sender to TreeViewItem and check the IsExpanded property.
If IsExpanded is true, do a Dispatcher.BeginInvoke at ApplicationPriority.ApplicationIdle priority to your fill routine
If IsExpanded is false, set an event to detect when it becomes true and in the callback do the Dispatcher.BeginInvoke to your fill routine
In your fill routine, set the ItemsSource of the target control to the attached property value
Use a style to set your attached property on TreeViewItem instead of setting it through HierarchicalDataTemplate (omit ItemsSource there)
This prevents the TreeViewItem from ever having its ItemsSource set until it is expanded. Creating this mechanism is some work, but once it is created you can make any TreeView delay-load by simply removing the ItemsSource from the HierarchicalDataTemplate and setting it in your ItemContainerStyle instead.

For every UI element that needs delayed loading I have used a 'View GUID'.
Explanation by example:
At the beginning, Control.Tag = "0000-0000 ...."
User makes an interaction,
a random GUID is generated, namely, guid1
Control.Tag = guid1
a thread is created which takes (what do to, control, guid1)
in between, the user makes another interaction
a random GUID is generated (guid2 this time)
Control.Tag = guid2
a thread is created which takes (what do to, control, guid2)
The first thread finishes:
it invokes a code into UI thread which takes results and the guid it got before execution
the invoked code checks that Control.Tag != guid1, makes no changes
The second thread finishes:
it invokes a code into UI thread which takes results and the guid it got before execution
the invoked code sees that Control.Tag == guid2, makes changes

Related

Event that gets called when the control finished binding

I have an ItemsControl control. In its items I show a lot of things: images, textblocks, etc.
I have a 'Search' functionality implemented on the itemscontrol - meaning that if the user enters some letters from the keyboard, the items control will be refreshed. My Search method is in code-behind and it takes less than a second. However, the time between I enter the letters and see the results is 3-4 seconds. I have a window closing command and I want to put it in the exact moment before showing the search results. If I put this command in the end of my Search method (in the code-behind), there is still a few seconds delay between closing the window and showing the items. I'm thinking that the binding is slow and that's why i need to catch the event that gets called when the binding finishes. Is there such an event in WPF?
OnPropertyChanged event gets called before the Search methods finishes, so that doesn't help me.
I also tried with the OnDataContextChanged event, but it gets called just once - when the control is initialized. I need it to also get called when the user enters letters and new binding occurs.
When you establish a data binding between a source property and a target dependency property in WPF, this Binding is actually translated into a BindingExpression object which does the heavy lifting of updating the source and the target at the appropriate moments.
Unfortunately, BindingExpression does not provide events when something is updated, as you can see here: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingexpression(v=vs.110).aspx
The only way is to set the UpdateSourceTrigger to Explicit when you define your binding, get the binding expression in code behind and update the source and target manually - then you have full control and can encapsulate your common functionality in this scenario.
You can obtain the BindingExpression by using the BindingOperations.GetBindingExpression static method: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.getbindingexpression(v=vs.110).aspx
If you have any further questions, please feel free to ask.
P.S.: isn't there maybe another way to do this? If you're using a source collection that you bind to your ItemsControl, couldn't you perform the searching / filtering operations on the source collection and just let the collection binding update your ItemsControl?

WPF: How to trigger GUI behaviours in response to view-model events?

I'm developing a WPF/MVVM application and I have a listbox binding to data in a ViewModel. At various points I need the view model to cause the listbox to scroll to a given element.
How can I do this without creating a custom control and while still maintaining good separation of concerns?
I've currently got it working by creating a custom behavior class in the view layer with a dependency property VisibleIndex which the XAML code then binds to an integer in the view model:
<ListBox x:Name="myListBox"
local:ListBoxVisibilityBehavior.VisibleIndex="{Binding VisibleIndex}">
When the integer is set it triggers the dependency properties update handler which tells the listbox to scroll to the associated index.
This seems a bit hacky though because the dependency property value is never changed by the listbox and the update handler only gets called when the value changes, so the only way to ensure that the relevent item is visible is to do something like this:
// view-model code
this.VisibleIndex = -1;
this.VisibleIndex = 10;
The only reason I'm using a behaviour class at the moment is for binding my custom dependency property, is there a way to do something like this with events instead?
Attached properties are somewhat required in your case - as at some point, 'somewhere' you need to call the following method...
ListBox.ScrollIntoView(item)
or
ListBoxItem.BringIntoView();
And for that you need some sort of code behind - and attached properties/behaviors are a nice way of packaging that, w/o impacting your MVVM.
Having said that - if you just need to have your 'selected item' scrolled into view at all times (which is the case most of the time). Then you could use a different attached-property based solution (that again):
mvvm how to make a list view auto scroll to a new Item in a list view
All you have to do then is to set or bind to SelectedItem.
That's a bit 'nicer' if you wish - but the mechanism is the same.
For anyone else interested in the answer to this one of the MS engineers on the WPF forum cleared it up for me. Instead of binding to an event directly you bind to a wrapper object that encapsulates that event. The behaviour can then grab the reference to the wrapper from its DP and do whatever it wants with it i.e. subscribe to the event, trigger it etc.

Modifying ObservableCollection while it is being displayed

I have a tree view in which the ItemsSource is an ObservableCollection. I am dynamically loading the children contents every time the user expands the subtree by using a background worker and modifying the underlying ObservableCollection that corresponds to the children nodes. However, when I try to modify this ObservableCollection inside that thread, it will fail IF AND ONLY IF the tree is already expanded (meaning that if the number of children is small, then it will correctly populate).
What I am hoping to achieve is once I expand the tree, I can see children being dynamically populated as they get processed. How can I achieve that behavior using ObservableCollection?
How can I achieve that behavior using ObservableCollection?
The problem is that you aren't allowed to update a collection on a background thread in WPF. The binding system will automatically marshal most simple bindings to the UI thread, but not collections. You have two options.
You can marshal the call to add the data back onto the UI thread using Dispatcher.Invoke or Dispatcher.BeginInvoke. This will allow the data to be added, but not cause a cross thread exception when WPF updates the binding.
(If you're using .NET 4.5) You can use the new EnableCollectionSynchronization on the binding to allow cross-thread access to the collection.

WPF MVVM updating the View Model on a Background Worker

Is this senerio valid?
I have a View to maintain an Item.
I have View Model which exposes the Item Object (implements INotifyPropertyChanged) as a Property to which is View is bound.
Is it valid for me to pass the Item object to a Backgroundworker where it is modified, then raise the PropertyChanged event when the BackgroundWorking is complete?
Or should the BackgroundWorker in no way modify the Item object. I would update the existing Item object with the results passed back by the BackgroundWorker. This would be done in the RunWorkerCompleted event. But does this lock the UI thread and defeat the object of having a backgound worker?
Confused?
I'll try to explain.
The User takes an option to create an Item. I create the View and View Model. In the View model an empty Item object is created. He is presented with a View to maintain the Item. On selecting the Item Type property, this instigates a complex process to create a list of proerties to be entered by the User. I could block the UI thread whilst the list is created but this gives a poor user experience. I want to pass the processing to a background thread while keeping the UI alive. At present, I set a flag to indicate the section on the View is loading, pass the Item object to the BackgroundWorker which updates the observable collection of Properties. When the BackgroundWorking is complete I call the PropertyChanged event which updates the section of the View which is bound to the list and turn off the flag to indicate the section is loading. This seems to work with no issues. But I have a gut feeling that I should not be updating bound onjects from the View Model in a background thread.
Thanks Tim
This sounds ok. As long as your item-object is no DependencyObject, you can change the properties of them in the background worker.
DataBinding to properties of your object will work, the binding engine will do the thread-switching automatically for you.
However, dont't fill data-bound collections or manipulate properties of DependencyObjects (such as UI-Controls) in the background worker without dispatching the manipulations. This would lead to an exception.
Edit:
Only for clarification: The real question is not, if the item-object is a DependencyObject but if the property is a CLR-property or a DependencyProperty. Because DependencyProperties are bound to DependencyObjects, I often use the above simplification, but its not the full truth.
This means that if you have a CLR-property, you can set its value from a foreign thread, regardless of your class is a DepenendencyObject or not. This is a slight difference to my first statement.

Silverlight: Is there an event that fires on a FrameworkElement before it is rendered?

In our Silverlight 2 project we have created an attached property to perform on-the-fly translation to text properties of various user controls. To achieve this, we hook the Loaded event of the FrameworkElement when the property is set. When the event fires, we take the existing text property value and perform some simple string substitutions on it, before replacing the property value with the translated text. However, this results in the control being rendered with the untranslated text, then the text is quickly replaced with the translated version.
Is there an alternate event we can hook that would fire before the control is rendered?
I've changed my code so that it now performs the translation as soon as the setter for the attached property is called. There's no need to wait for the FrameworkElement to have finished loading, as I can change the Text property long before the element is rendered.
My initial thoughts on using the Loaded event were to reduce the startup time of the application by only translating the controls that were visible on the screen. As it turns out, I'm duplicating some of the work performed by the runtime, as the runtime won't call the property setter until it needs to anyway.
I'm not totally sure about this, but can you use the LayoutUpdated event. It will fire when the control is resized and such (you could take measures to ensure your code only executes once.)
I know it doesn't seem like the "right" event for this but unfortunately Silverlight kinda leaves you standing there holding it when it comes to events.

Resources