Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ListViewFilter Control for .NET

0.00/5 (No votes)
28 Apr 2003 28  
An enhanced ListView with sorting/shading and a filterbar to filter items.

Sample Image - ListViewFilter.gif

Introduction

This control is another extension to the now standard and widely used ListView control. I have included some of the more common features: shaded columns, column sorting (with data type), but the real addition is the FILTERBAR features of the header. This implementation eliminates all of the work of dealing with the filter messages and item filtering by incorporating it into the control. This could have been implemented as delegates, but we create controls to do the work for us, don't we? I would like to thank Carlos H. Perez since a lot of the implementation came from examples he set with his ListViewEx control.

Background

The HeaderControl FILTERBAR feature has been around since IE5.0 was released but it is not at all widely implemented even in controls like Windows Explorer! It can be a very useful addition to detailed ListView controls but the code involved in using one has always limited the availability of it. You have to trap WM_NOTIFY messages from the header control, change the header control style, and manually update the ListView items. This implementation attempts to remove those hard parts and make ListView filtering easier. I have applications currently bound that use this feature and the customer feedback has been great. Many have asked why Microsoft doesn't do this too.

This implementation has been tested only on XP and 2000. It all depends upon the comctl32.dll that is available on the platform. The Up/Down arrows for sort direction in the column header are only supported in version 6.0 and above, and you HAVE to supply a manifest file, or dynamically attach the .DLL and theme, in order for that to be implemented. Others may want to add a CUSTOMDRAW feature to the header control or ImageList for non-XP platforms to see the Up/Down arrows.

ListViewFilter Control Features

As with any .NET custom control you must make the .DLL available for use in the designer through the toolbox customization feature. Since this is as pure an implementation of a ListView as possible, all existing and new features work in DesignMode (except item filtering, but that's execution only). The Browsable properties are:

  • Filtered - bool, turn on/off the filterbar
  • IgnoreCase - bool, ignore character case when filtering strings
  • Shaded - bool, color the background of the sorted column differently
  • ShadeColor - Color, the color to use for the shaded column
  • SortColumn - int, the column to sort
  • SortOrder - bool, true for ascending
Non-Browsable properties:
  • Header - ListViewFilterHeader, readonly access to access column properties (see description of ListViewFilterHeader for column specific properties)
  • SortType - LVFDataType enum, current sort column data type for comparison (String, Number, Date)
As with all implementations of a FILTERBAR, a lot of functionality is hidden from the casual user. It takes a little explanation and practice before they become comfortable with it. I hid a ContextMenu attached to the filter button that pops up to allow changes to the alignment, data style, and other features. I guess hiding things in the user interface is too easy to ignore doing it!

The most hidden feature is the content of the filter text field. You are not limited to just entering ABC... or numbers or dates, you may preceed the actual string with a comparison type. By default comparison is equal to (=) if nothing is supplied. You may also use less then (<), less than or equal to (<=), greater than (>), greater than or equal to (>=) or not (!) comparison types. I will probably add ContextMenu items to add/change the filter text comparison type at some time when I am more comfortable working with the MenuItem event (there should be a better way to check which MenuItem is the sender instead of if then else logic).

Most of this control isn't difficult code, just implementation of features that have not really been exploited. Once you trap the correct messages (override WndProc) and crack the message headers this is simply a matter of having the right algorithm. Note, I'm not saying all of the implementation is correct, only that it works. I truly believe there are many other and better ways to do this, hopefully others can use this as an example for what not to do!

Since the ListViewItemCollection MUST contain all the visible Items we have to deal with taking Items out and putting them back into that collection to filter the list. Previous implementations in other languages allowed me to use OWNERDATA style ListView controls and private containers for the items. That may still be possible in .NET but would require that all filtering and item additions/removals be done through delegates. The simplest way is to let the ListViewItemCollection think it contains all the Items in the ListView.

The primary function for for filterig items is FilterUpdate() which does all the work when a filter content has changed. FilterBuild() helps by creating LVFFilter entries that describe all active column filters, FilterCheck() is the helper to perform an item/filter comparison for FilterUpdate(). FilterUpdate() has three functions:

  • Loop through all collection Items removing filter failures to a holding array
  • Loop through all previous filtered items returning now viable items to the collection
  • Add the holding array items from step 1 to the updated filtered items array
If no filters are applied all items in the current filtered array are returned to the ListViewItemCollection and the filtered Items array cleared. Before re-enabling update, a Sort is done to finish the process.

The trickiest piece of code was reading and writing the FILTERBAR content. This is done via messages to the header control (WM_GETITEMA/WM_SETITEMA) and the HDITEM structure. What is tricky is that for access to the filters you have to reference a HDTEXTFILTER structure from the HDITEM structure. Doing that involved marshaling the memory for the HDTEXTFILTER prior to passing the reference to the HDITEM structure in the message.

// structures used (see Win32 documentation)

HDTEXTFILTER hdTextfilter = new HDTEXTFILTER();
HDITEM       hdItem       = new HDITEM();

// get the current text content of a <code>column</code> filter.

// this is tricky since it involves marshalling pointers

// to structures that are used as a reference in another

// structure.  first initialize the receiving HDTEXTFILTER

hdTextfilter.pszText    = new string( new char[ 64 ]);
hdTextfilter.cchTextMax = hdTextfilter.pszText.Length;

// set the HDITEM up to request the current filter content

// NOTE: the HDI_FILTER flag in the mask means that the

// pvFilter points to a HDTEXTFILTER structure...

hdItem.mask = W32_HDI.HDI_FILTER;
hdItem.type = (uint)W32_HDFT.HDFT_ISSTRING;

// marshall memory big enough to contain a HDTEXTFILTER

hdITEM.pvFilter = Marshal.AllocCoTaskMem(
  Marshal.SizeOf( hdTextfilter ) );

// now copy the HDTEXTFILTER structure to the marshalled memory

Marshal.StructureToPtr( hdTextfilter, hdItem.pvFilter, false );

// retrieve the header filter string as non-wide string

SendMessage( col_hdrctl.Handle, W32_HDM.HDM_GETITEMA, <code>column</code>,
ref hdItem ); // un-marshall the memory back into the HDTEXTFILTER structure hdTextfilter = (HDTEXTFILTER)Marshal.PtrToStructure( col_hditem.pvFilter, typeof( HDTEXTFILTER )); // remember to free the marshalled IntPtr memory... Marshal.FreeCoTaskMem( hdItem.pvFilter ); // return the string now in the text filter area return hdTextfilter.pszText;
NOTE: None of this works without all the standard Win32 constants (#define) that are freely available to C++ programmers. I know that there are a number of 'libraries' built (re: UtilityLibrary) but since I only needed a subset (and some of the flags weren't in UtilityLibrary) I simplified this by creating all the needed enumerations and structures as W32_xxx values. See the Win32Enum.cs, Win32Msgs.cs, and Win32Struct.cs files included in the source.

ListViewFilterHeader Control Features

Since the Header is available as a execution mode readonly property by ListViewFilter, it publishes a number of properties of it's own for use in manipulating the filters directly from code. Again my implementation may be called into question, but I wanted to access each property as an array. I could have made a Columns[] class with it's own properties to do that, but I didn't. Some of these properties are accesible through the Columns of the ListView itself but I implemented them as Header properties especially for the ContextMenu so that it did not have to know about the ListView.Columns, just the Header. The ContextMenu appears when you click one of the column filter buttons. The available properties are:

  • Alignment - HorizontalAlignment, column data format, Left, Right, Center
  • DataType - LVFDataType enum, String, Number, Date format for comparison
  • Filter - string, get/set the content of the filter
  • Names - string, get/set the column header name
  • SizeInfo - Size, readonly Width and Left in a (misused) Size structure
Properties promoted by ListViewFilter itself
  • Filtered - bool, turn on/off the filterbar
  • SortColumn - int, the column to sort
  • SortOrder - bool, true for ascending
The big thing about the ListViewFilterHeader control is that it is an encapsulation of the System.Windows.Forms.NativeWindow control and deals directly with the Handle of the HeaderControl itself. The ListViewFilter is responsible for the creation and destruction of this object. When the ListViewFilter.Handle changes this object is recreated and the new Handle attached in the constructor.

The primary purpose of this class is to set the header style, set the up/down sorted column arrow (this could be changed to an image index), and to promote access to the filter text, data type, and other properties of a column as needed. Any feature wanted for a header control accessible via SendMessage could be added to this class and publised as properties just like these.

Caveats

Marshalling memory for structures isn't really that hard, it was just difficult to find the right documentation on how and when to do so. Creating the enumerations for all the needed Win32 definitions is time consuming and prone to error (I really want to see .NET implement that somehow). You may notice that some of the messages are the A version not W because string just is easier with non-unicode text.

There will be problems with this control on some machines; you have the source, make it work for you. I have been developing controls for many years now (mostly in Borland C++ Builder) and this is my first submission to any board or group (maybe I have been selfish). So, take it easy on me this time I've only been coding C# for two weeks now.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here