The Windows Home Server user interface includes a number of controls that extend normal Windows Forms functionality. FancyListView is the Windows Home Server extension of ListView, and includes a few nifty features:

  • ImageSubItems to display images as ListView subitems
  • FilledBarSubItems to show a configurable progress bar and accompanying label as a subitem
  • Sorting by column works out of the box

Brendan has a lot more detail on these new features in WHS Developer Tip #11: FancyListView.

Dropping a FancyListView on to your design surface and adding a few columns is easy. You can load up ListViewItems and work with the FancyListView as you would a normal ListView, and you don’t need to do any work to implement sorting. It can’t get any more straight-forward, really.

Because FancyListView is based on ListView, you can also use groups. Groups in a ListView help you visually break down items into related sets, for easier identification.

To use groups, you need to instantiate a new ListViewGroup, add the group to the Groups collection of the ListView and then assign the group to the Group property of the ListViewItem.

FancyListView ListViewDisks = new FancyListView();
ListViewGroup GroupStoragePool = new ListViewGroup("Storage Pool");
ListViewItem item = new ListViewItem();

ListViewDisks.Groups.Add(GroupStoragePool);

item.Group = GroupStoragePool;

ListViewDisks.Items.Add(item);

You’ll end up with something like this:

 image

Groups can significantly improve readability of the data in in a ListView with a lot of ListViewItems. The major downside of using Groups with a FancyListView is that they break the built-in column sorting.

If you want to use groups, and continue to sort your FancyListView by column (who doesn’t?), the solution is to implement a custom FancyListViewColumnSorter that handles the sorting for you.

public class ListViewColumnSorter : Microsoft.HomeServer.Controls.ListViewColumnSorter
{
    public override int Compare(object x, object y)
    {
        ListViewItem item1 = (ListViewItem)x;
        ListViewItem item2 = (ListViewItem)y;

        int result = String.Compare(item1.SubItems[SortColumn].Text, item2.SubItems[SortColumn].Text);

        if (Order == SortOrder.Ascending)
        {
            return result;
        }
        else if (Order == SortOrder.Descending)
        {
            return -result;
        }
        else
        {
            return 0;
        }
    }
}

We override the Compare method so we can make our own decisions about how to compare the two ListViewItems.

In the example above, I’m sorting on the Text value, using String.Compare. What if our two text values are “80.0 GB” and “157.0 GB”? Because I’m comparing the string value, “157.0 GB” comes first – probably not the result we want.

To work around this, you could store the unformatted number of bytes (168577466368, for 157 GB) as the SubItem.Tag, and compare that instead. Then our 80 GB drive will be correctly listed first.

Once you’ve customised your FancyListViewColumnSorter , assign is to the FancyListView.

FancyListView ListViewDisks = new FancyListView();
ListViewDisks.ListViewItemSorter = new ListViewColumnSorter();

Add an event handler for the ColumnClicked event of the FancyListView that calls the Sort() method.

private void ListViewDisks_ColumnClick(object sender, ColumnClickEventArgs e)
{
    ((ListViewColumnSorter)this.ListViewDisks.ListViewItemSorter).SortColumn = e.Column;
    ((ListViewColumnSorter)this.ListViewDisks.ListViewItemSorter).Order = SortOrder.Ascending;

    ListViewDisks.BeginUpdate();
    ListViewDisks.Sort();
    ListViewDisks.EndUpdate();
}

You’ll see that I’m calling BeginUpdate() and EndUpdate() on the ListView; this prevents some very odd redraw issues that basically make your FancyListView unreadable.

You can also get creative with the SortOrder by adding some code to reverse the SortOrder when the same column is clicked twice (changing from Ascending to Descending). The variable e.Column is the zero-based index of the column that was clicked, so you can check to see if you’ve passed the same column number to your FancyListViewColumnSorter.SortColumn.

For bonus points, you could create yourself an enum to keep track of which columns are which.

public enum ListViewColumns
{
    PhysicalDisk = 0,
    Interface = 1,
    DiskController = 2,
    Location = 3,
    DiskName = 4,
    Capacity = 5,
    Used = 6,
    Status = 7,
    Temperature = 8,
    Activity = 9
}

Each of those columns could have its own sorting criteria, and you can handle that in your FancyListViewColumnSorter.

public class ListViewColumnSorter : Microsoft.HomeServer.Controls.ListViewColumnSorter
{
    public override int Compare(object x, object y)
    {
        ListViewItem item1 = (ListViewItem)x;
        ListViewItem item2 = (ListViewItem)y;

        int result = 0;

        switch ((ListViewColumns)SortColumn)
        {
            case ListViewColumns.PhysicalDisk:
                result = ((int)item1.Tag).CompareTo((int)item2.Tag);
                break;
            case ListViewColumns.Used:
                result = ((FancyListView.FilledBarSubItem)item1.SubItems[SortColumn]).PercentageFilled.CompareTo(((FancyListView.FilledBarSubItem)item2.SubItems[SortColumn]).PercentageFilled);
                break;
            case ListViewColumns.Capacity:
                result = ((long)item1.SubItems[SortColumn].Tag).CompareTo((long)item2.SubItems[SortColumn].Tag);
                break;
            case ListViewColumns.Temperature:
                result = ((int)item1.SubItems[SortColumn].Tag).CompareTo((int)item2.SubItems[SortColumn].Tag);
                break;
            case ListViewColumns.Activity:
                result = ((float)item1.SubItems[SortColumn].Tag).CompareTo((float)item2.SubItems[SortColumn].Tag);
                break;
            case ListViewColumns.Interface:
            case ListViewColumns.DiskController:
            case ListViewColumns.Location:
            case ListViewColumns.DiskName:
            case ListViewColumns.Status:
            default:
                result = String.Compare(item1.SubItems[SortColumn].Text, item2.SubItems[SortColumn].Text);
                break;
        }

        if (Order == SortOrder.Ascending)
        {
            return result;
        }
        else if (Order == SortOrder.Descending)
        {
            return -result;
        }
        else
        {
            return 0;
        }
    }
}

Not every FancyListView will need that sort of complexity, of course, and you’re making an assumption that only the FancyListView with those precise columns will every be using this FancyListViewColumnSorter.

On the other hand, having the capability to customise a Sorter to your exact requirements does give you a lot of flexibility.

posted on Tuesday, October 21, 2008 1:33 AM | Filed Under [ Windows Home Server Development ]

Comments

Gravatar
# re: Sorting a FancyListView with Groups (TxDot @ 1/13/2009 7:30 AM)

Since you add a column sort eventhandler,
ListViewDisks.ListViewItemSorter = new ListViewColumnSorter();
why do you also add an event to the ColumnClicked event to call your sort routine? Isn't the sort routine automatically called in a Fancy ListView?
 
Gravatar
# re: Sorting a FancyListView with Groups (Sam Wood @ 1/13/2009 7:37 AM)

I'm not sure if the Sorter is called automatically (will test when I get home, if I remember), but I wanted to pass through a Sort direction (asc or desc) as well as use the BeginUpdate() and EndUpdate() methods explicitly.

Post Comment

Title *
Name *
Email
Url
Comment *  
Remember me
Please add 1 and 6 and type the answer here:

Search

Site Sections

Recent Posts

Archives

Post Categories

WHS Add-In Tutorial

WHS Blogs

WHS Development