Introduction
In the beginning of 2008 I developed the Picasa plugin for the blogging tool Windows Live Writer in C# (runtime version 2.0). After several years and many downloads (up to now 45,000 downloads from the Microsoft page http://plugins.live.com/writer/detail/picasa-image-plugin), I decided to implement a new version of the plugin using the new runtime features from .NET 3.5. In the end the new plugin should have the look and feel of a state-of-the-art user interface.
Background
With that in my mind I quickly jumped into the new features of WPF, its databinding possibilities, and the power of LINQ and lambda expressions.
WPF and the new user interface
I always admired the way of Apple products: simplicity, user-friendliness, and the fact that it always worked (I’m aware of the fact that Apple has its bugs too but they are in some way not that present as the ones in Microsoft user interfaces). I was always proud to belong to the Microsoft community and my intention was to implement a UI, that is:
- fast and simple, and
- looks like hell – in a good way of course :)
I started with the development of the new user interface and this was the easy part. The XAML parts were easy to setup and contained no business logic. But when I started to implement the worker thread I soon got stuck in the problem on how to update the UI while loading the data in a different thread. Although I knew that only the UI thread itself is allowed to update the controls on the user interface I didn’t manage it in the first place to get the databinding of the user controls working.
The app crashed while the UI thread wanted to access the data collection which my implementation of the background worker class filled with objects – from a different (asynchronous) thread of course. The reason for it was an overlap: while the background worker was filling the collection the user interface tried to access that object collection because of the databinding mechanism. Thanks to Quantum Bit Designs and the article about WPF cross-thread collection binding, I found the best approach to fix this problem. The following diagram shows the principles of this approach:
At first the background worker executes an asynchronous operation (Step 1).
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(WorkerDoWorkOC);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerRunWorkerCompleted);
worker.RunWorkerAsync();
The observable collection which the UI controls are bound to isn’t accessed by the worker thread. The worker thread fills another object collection (internal list) that needs to inform the observable collection that all objects are available for display (Step 2). Now the databinding mechanisms of WPF come into play (Step 3).
The interface INotifyPropertyChanged
owns the task to inform controls which data is bound to and has to be implemented. All we need to do is raise the OnPropertyChanged(<propertyName>)
method and pass as parameter the name of the attribute the UI is bound to. If we do that the UI will crash. The reason for it is that only the thread
that created a DispatcherObject
may access that object. By passing the dispatcher object from the UI control to the background list, the dispatcher is invoked using the asynchronous method:
this._dispatcher.BeginInvoke(DispatcherPriority.Background,
new AddCallback(AddAlbumFromDispatcherThread), albums);
In the AddAlbumFromDispatcherThread
method the PropertyChanged
event is raised. A great article about the dispatcher
can be found here: http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher - “Working with the WPF dispatcher”. After the UI and its bound data elements have been informed by the right thread everything works as designed. The user interface now gets updated asynchronously and the UI is responsive even if the user scrolls through the listbox items. The new user interface looks like this:
L
LINQ
Another powerful feature of the framework 3.5 is LINQ (language-integrated query). With LINQ there is a standardized and simplified way to query and access data from different sources (database,
XML, web services, …).
In the first implementation of the old plugin (runtime version 2.0) I implemented an
XML parser which returned me the data by traversing the whole document tree with various
if
-statements. I wanted to create one query and it should be much more performant than parsing the whole document line by line. With LINQ it was possible to access data in different namespaces, to filter out specific attributes in the node, and even to get nested data from filtered
XML nodes in one single query. LINQ queries are usually deferred until the data is requested. Immediate execution is forced by calling the
ToList<someType>
method.
List<PicasaAlbum> retVal = (from feed in xmlDoc.Descendants(atom + "entry")
select new PicasaAlbum {
User = feed.Element(photo + "user").Value,
AlbumID = Convert.ToInt64(feed.Element(photo + "id").Value),
HasProxy = hasProxy,
NumPhotos = Convert.ToInt64(feed.Element(photo + "numphotos").Value),
Published = Convert.ToDateTime(feed.Element(atom + "published").Value),
AlbumName = HttpUtility.HtmlDecode(feed.Element(atom + "title").Value),
AlbumLink = ( from links in feed.Elements(atom + "link")
where (string)links.Attribute("rel") == "alternate"
select links.Attribute("href").Value).First()
}).ToList<PicasaAlbum>();
Even sorting is easily done. I replaced the old implementation of an anonymous function:
retVal.Sort(delegate(PicasaImage a1, PicasaImage a2)
{
if (a1.Published < a2.Published)
return 1;
else if(a1.Published == a2.Published)
return 0;
else
return -1;
});
with a lambda expression:
items.Sort((a, b) => a.Published.CompareTo(b. Published));
Summing it up, I reduced the code a lot, created – at least in my eyes – a very comfortable and pretty-good-looking UI and was allowed to play with.
- WPF
- Databinding in WPF
- LINQ and lambda expressions.
If anybody wants to see all these things in action and happen to be a Windows Live Writer blogger this plugin for
Picasa a.k.a. BlogPiccs 2.0 can be downloaded from my website (www.omanno.de) or my webshop (http://shop.omanno.de) . This plugin works with Windows Live Writer, Picasa, and of course .NET 3.5. With this plugin you can manage new albums and photos,
and integrate albums and photos from a Picasa account into personal web blogs. If you have any comments feel free to contact me, any feedback is much appreciated.