My blog about computing
PriorityObject : more fluent than PriorityBinding in list context (as ListBox) (C#)
The files archive is here.
You will find a copy of this article on CodeProject.
2012-05-15 Updates:
- I made the Silverlight version identical to the WPF version, so you can use PriorityObject the same way on both platforms.
- Now there is a Windows Phone 7 example in the archive as well.
Reason
Recently, I tried to use PriorityBinding in a WPF ListBox, but it was extremely slow and erratic.
So I tried to code a more fluent replacement.
At last, I created PriorityObject, and now I can display a fluent ListBox updating smoothly and quickly.
Recall: PriorityBinding is a binding with multiple sources, some quick, others slow. Usually, the slowest binding has the best result, and the fastest has the worst result. For example, imagine the source of an image: the fastest binding will be a blur thumbnail, and the slowest will be the complete high-resolution picture.
Of course, there is a priority in the sources, so when the best source is ready, the other sources are ignored then. In our example, if the complete picture is available before the thumbnail, the thumbnail will be ignored and only the picture will be displayed.
A comparison with PriorityBinding
There are three main differences between Dot.net’s PriorityBinding and my PriorityObject:
- PriorityObject uses Threads and therefore is much more ‘fluent’, especially in list controls (as ListBox).
I presume PriorityBinding uses BackgroundWorker, which is fine with isolated bindings but chaotic with lists. - A PriorityBinding is partly described in the XAML itself, which is a violation of the rule about separation of the visual description and the object implementation.
On the contrary, PriorityObject let the XAML be ordinary while the object implementation manages the content (with threads). - PriorityObject is compatible with Silverlight.
Other advantages:
Your value can be lazy (evaluated on demand). Just choose Lazy=true when you call the builder of PriorityObject.
That is very useful when the ListBox is not always displayed (it can be in a tab or in a secondary window, for example).
Inconvenience:
Your procedures are evaluated in threads.
So you will have to manage the thread headaches with WPF’s objects. I can not do anything against that, this is a big limitation of WPF, but you can use Dispatchers to do some of the work on the UI thread (or your object’s Dispatcher). Anyway, this problem have to do with threads and WPF, so you cannot avoid it in parallel programming whether you use PriorityObject or not.
Example of two ListBox launched in parallel
Both ListBox display two texts, one with priority evaluation functions (on the left, the text #1) and one ordinary (the text #2).
The left text (#1) has three states:
- At the beginning, it has a default value: “Fast text #1”.
- After 3 seconds, it gets a new value: “Slower text #1”.
- After another 2 seconds, it gets its final value: “SloweST text #1”.
The ListBox on the left uses Dot.net’s PriorityBinding, and the ListBox on the right uses PriorityObject and have a number as an additional column (its value change after 2 seconds).
I populate both ListBoxes at the same time.
As you can see on this screen shot, after 6 seconds, the PriorityBindings are not completed, they even mix the three states on different lines. They need 10 seconds to complete the list update.
On the contrary, the PriorityObjects complete their task at the right moment, after 5 seconds exactly (as we expect).
GIF animation:
Code example
Let’s implement the previous example, with ListBox displaying items made of two texts and a number.
The first text and the number are based on PriorityObject, the second text is an ordinary binding, so I do not describe it.
XAML
<ListBox x:Name="AdvancedListBox" Width="280"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="115"/> <ColumnDefinition Width="115"/> <ColumnDefinition Width="40"/> </Grid.ColumnDefinitions> <!-- We have to add '.Value' for PriorityObject: --> <TextBlock Grid.Column="0" Text="{Binding Text1.Value}"/> <!-- An ordinary static text: --> <TextBlock Grid.Column="1" Text="{Binding Text2}" /> <!-- We don't need to add '.Value' here because Double1 encapsulates _Double1.Value: --> <TextBlock Grid.Column="2" Text="{Binding Double1}" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
As you can see, we have three bindings:
- Text1.Value, directly bound to the content of a PriorityObject<string>.
- Text2, bound to an ordinary string with get/set.
- Double1, indirectly bound to a PriorityBinding<string>, via Double1 : get { return _Double1.Value; }
That needs two definitions in the CS but has the advantage of the clarity in the XAML.
CS
The item class
public class AdvancedAsyncListItemWithPriorityObject : INotifyPropertyChanged
The item class has to implement the interface INotifyPropertyChanged.
Text1
Property
public PriorityObject<string> Text1 { get; set; }
We need the get and set, otherwise ListBox will not update the text.
Initialization in the class builder
var valueDelegatesForText1 = new PriorityObjectDelegates<string>(); // First evaluation function: slowest & best valueDelegatesForText1.Add( () => { Thread.Sleep(5000); // This simulates a lengthy time before the data being bound to is actualy available. return "SloweST text #1"; }); // Second evaluation fonction: quicker (but sill slow) and worse valueDelegatesForText1.Add( () => { Thread.Sleep(3000); // This simulates a lengthy time before the data being bound to is actualy available. return "Slower text #1"; }); Text1 = new PriorityObject<string>( "Text1", this.NotifyPropertyChanged, valueDelegatesForText1, true, "Default quickest text"); // a default quick but worst text.
I deployed this example, in order to show its structure. See Double1 for a compact example.
Double 1
Property
private PriorityObject<double> _Double1; // Double1 encapsulates _Double1 in order to avoid the addition of ".Value" in the XAML: public double Double1 { get { return _Double1.Value; } set { _Double1.Value = value; } }
In order to simplify the binding in the XAML, we use Double1 to access to _Double1.Value.
Initialization in the class builder
_Double1 = new PriorityObject<double> ("Double1", this.NotifyPropertyChanged, new PriorityObjectDelegates<double>() { ()=> { // This simulates a lengthy time before the data being bound to is actualy available. Thread.Sleep(2000); return 2000.0; } }, false);
This example is a bit more compact than the one with Text1.
And there is only one thread/evaluation/source function, as even with only one source PriorityObject is easier than the ordinary thread programming.
Please note we give “Double1” as the notification name, and not “_Double1”, because the ListBox is bound to Double1.
The INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged; // This standard procedure is necessary to PriorityObject. private void NotifyPropertyChanged(string info) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(info)); }
A standard implementation of INotifyPropertyChanged.
Please note we absolutely need the NotifyPropertyChanged procedure for the PriorityObjects.
Differences in Silverlight / Windows Phone 7
Currently, there is no difference.
I initially wrote an adaptation for Silverlight because it needed the right Dispatcher to invoke the NotifyPropertyChanged procedure.
It appears Silverlight uses only one dispatcher, so I modified my code.
If one day Silverlight uses several dispatchers, you will be able to adapt the code of PriorityObject.cs by allowing its define “RequestObjectSDispatcher”.
Download the source code and some examples
The archive contains the source code of PriorityObject and three example projects:
one for WPF/Dot.net on desktop, one for Silverlight and one for Windows Phone 7.
FAQ
- Do we really need the NotifyPropertyChanged procedure for the PriorityObjects ?
Why don’t we access directly to PropertyChanged in PriorityObject ?- A: PropertyChanged is an event, and an event is compiled an unusual way in the C#, so we cannot invoke it outside its class.
That is why we need to give the NotifyPropertyChanged reference to the builder of PriorityObject (as NotifyPropertyChanged is not officially part of INotifyPropertyChanged, how strange it may appear to us).
- A: PropertyChanged is an event, and an event is compiled an unusual way in the C#, so we cannot invoke it outside its class.
- In Silverlight, could we extract the Dispatcher from the instance of the PriorityObject we are building ?
- A: Yes, I updated my code to internally use the only one Dispatcher instanciated by an Silverlight application for all its objects.
Open source
The license of this code is the Ms-PL, which basically allows you to use the source code in your programs, open or closed. If you modify the code, you don’t have to publish your modifications.
Something you can appreciate in this license: it warrants its author (I here) will not claim anything about patents (this plague of modern programming). Many licenses do not say anything about patents and expose you (yes, including the most famous licenses). See here for some details.