WHS Developer Tip #11: FancyListView
Q: How can make a ListView that looks more like what I see in the Home Server Console with so many images and progress bars?
A: Another one of the wonderful undocumented features of Windows Home Server is the FancyListView control (Microsoft.HomeServer.Controls, HomeServerControls.dll) that is used by all of the official Home Server Console tabs for displaying of the system's shares, users, computers and hard drives.
The first difference a user will notice is that it is drawn differently than a regular ListView, automatically making it look more Windows Home Server-ish in terms of it's shading, but also allows for the easy adding of images and progress bars in various different locations.
Before digging in, lets go through a few terms used with ListViews so as to better understand the mechanics of what is used under the hood to draw a normal one.
- LargeIcon - Each item appears as a full-sized icon with a label below it.
- Details - Each item appears on a separate line with further information about each item arranged in columns. The left-most column contains a small icon and label, and subsequent columns contain sub items as specified by the application.
- SmallIcon - Each item appears as a small icon with a label to its right.
- List - Each item appears as a small icon with a label to its right. Items are arranged in columns with no column headers.
- Tile - Each item appears as a full-sized icon with the item label and subitem information to the right of it. The subitem information that appears is specified by the application.
The most common View used in the Windows Home Server Console is Details which gives us the familiar row and column look.
In order to automatically display an image inside of a ListViewItem, a programmer would specify with ImageKey or ImageIndex property the value corresponding to an ImageList instance that has been set to the SmallImageList property, in C# this would look like (assuming a ListView named listView1 has been created on the form and it's View property has been set to Details):
Which gives us:
This is how a normal ListView functions and a FancyListView works in virtually the same way, only it adds to new ListViewSubItems to the mix.
A drawback of the stock ListViewSubItem class is that it does not have any built-in support for images and leaves such work to the programmer to implement themselves. This is where ImageSubItem comes into play, providing the ImageIndex and ImageKey properties that use the same ImageList as any other ListViewItems on the list.
To expand the earlier example, lets start with the following nearly identical code (assuming a FancyListView named fancyListView1 has been created on the form and it's View property has been set to Details):
Which in turn displays the following for us:
In order to add a pair of ImageSubItems that use an image from the existing Image List, we need only add the following code:
Which makes our example look something like this:
Another neat feature we've seen on the Computers & Backups tab is a progress bar, something that programmatically behaves not unlike the progress bars and SubItems we've already used or added.
To create and add them, we need only add this code to the end of our running example:
which makes our example now look like:
Aside from our ability to change the style of the bar (Rectangle vs RoundedRectangle) which we see demonstrated by the two FilledBarSubItem instances used above, another very useful option we have is the ability to specify additional colors for ranges.
Ordinarily if you need to specify some visual range difference, the ProgressBar control isn't enough, instead other controls may be employed or worse yet... writing your own which would likely involve manually checking the ranges ourselves, instead FilledBarSubItem gives us the following properties to do that for us:
Together we can get automatic colorization:
All by just by these lines to the above running example:
And that's in addition to being able to specify an arbitrary color for our bar in the first place.
Unlike a control like the TextBox or Button classes, simply updating properties of a ImageSubItem or FilledBarSubItem will not cause them to visually be updated because they aren't controls, instead they are only visually updated when the parent FancyListView is redrawn, something that ordinarily will occur when ever the containing form is hidden and then shown, something is dragged over the control, or you click on it.
Instead of relying on the system or the user, you as the programmer can call the FancyListView's Invalidate() or Refresh() methods to force it to redraw itself and all of the ImageSubItem or FilledBarSubItem instances so that any chances are then visible just as you can with most other controls, however FancyListView which inherits from ListView offers an even better option, namelyRedrawItems() which allows you to redraw only those items you want to in the list which can be as simple as:
Sizing and Stretching
In the previous examples I've focused on using icons with a size of 32x32 which causes each row in the FancyListView to be 32 pixels tall. Want to make it shorter? Simply change the size of the ImageList that the FancyListView uses.
One quirk of using this method to define the size of our rows, is that it is not uncommon to have a large 32x32 image for the first column, but then want a small 16x16 image for a status icon... not unlike how the Home Server Console notes drive or duplication status, you'll find that the smaller image will automatically be stretched to fill the available area.
In order to make such a small image stay small, you should use a larger image that contains the smaller image, not unlike how the CommonImages class exposes GrayIcon, GreenIcon and RedIcon which are each only 16x16, but also available as part of larger 32x32 images as GrayIcon32, GreenIcon32 and RedIcon32 as well.
Another one of the advantages of FancyListView is that does sorting right out of the box without any work from you as a programmer whenever a user clicks on a column header.
With an ordinary ListView, simply creating a class that implements the IComparer interface and setting an instance of it to the ListViews ListViewItemSorter property is all that is needed. FancyListView takes this a step further by defining a custom sorting class ListViewColumnSorter (Microsoft.HomeServer.Controls, HomeServerControlls.dll) which it automatically uses and requires.
Unfortunately an ordinary custom sorting class that simply implements the IComparer interface cannot be used because internally FancyListView.ListViewItemSorter typecasts the value to a ListViewColumnSorter, which forces any other sorter to from ListViewColumnSorter.
I have built a pair of sample add-ins that demonstrate a few of the things discussed above in a (very) mock (and not very accurate) Shared Folders like display which shows off several static ImageSubItems and a FilledBarSubItem that is updated and then moved once it's maximum value has been reached:
FancyListView provides an easy to use mechanism for displaying additional information to a user by allowing images in any columns and progress bars to better inform the user of the current state of the system or whatever else the add-in wants to say.
Next week we'll dive into a few more controls in the HomeServerControls.dll assembly, some of their quirks and how they can be better used to make our add-ins look more at home in the Home Server Console.
Note: The information in this post is based on undocumented and at times deduced information on Windows Home Server and is not officially supported or endorsed by Microsoft and could very easily be wrong or subject to change in future, so please take it and everything else said on this blog with a grain of salt and use with caution.
Labels: WHS Dev Tips