WPF: ListView/GridView minimum and maximum width for a column

A WPF ListView is a possible replacement for a datagrid, when it doesn't have to support advanced features like grouping or filtering of rows. If you use a ListView in your WPF application, and set the "View" property of this ListView to be a GridView.

A ListView/GridView even supports features like reordering and resizing columns. Unfortunately, it is not possible to define a minimum or maximum width for the columns. Fortunately, it is not really to do this in the code behind. Let's see how.

First we need to understand how exactly the resizing of a GridView column works. When a column can be resized, a narrow vertical line appears on the right side of the column. Passing the mouse over this line turns the cursor into a "resize" cursor.

To find out what exactly this line is made of, we can use a tool like Snoop, or Mole. These tools display the inner structure of a WPF UI, also known as the Visual Tree. For example, in Mole, we can navigate down to our ListView and display the header row's inner structure:

Header row of a ListView

The feature that is of particular interest to us is the "Thumb" control, named PART_HeaderGripper. This requires some explanations:

  • A Drag action is the action through which a control is selected (click-and-hold) and then the mouse is moved. The expected action is usually that the clicked control moves together with the mouse, but other actions may be performed too, for example resizing the columns of a grid, etc...
  • A Thumb is a small WPF control that can be dragged. It has a collection of events that are used to program what happens when a drag action is performed on the control. For example, the event Thumb.DragDeltaEvent is fired every time that the Thumb is dragged a little.
    Note: A Thumb doesn't really look like anything (well, it does look like a Rectangle!). With the great ability that WPF has to redefine the look&feel of any control, you can make a Thumb look like anything you need. This is why the Thumb control is in the namespace System.Windows.Controls.Primitives: The Thumb is usually used to create other controls, hence the "Primitives".
  • The makers of WPF controls have for convention to name inner controls of other controls with the "PART_" prefix.

350-030 and 220-602 is very easy for those students who have written 70-640. Many 640-822 and 70-236 professionals also find them tough without the latter credit.

Once we know this, we can try to intercept the events raised by the Thumb control named "PART_HeaderGripper", when the ListView's user resizes the columns. Remember, in WPF, events are "routed", we say that they "bubble" up from the control they originate from to the parent and the parent's parent, etc...

As a first try, we can add an event handler on the ListView and try to handle the "Thumb.DragDelta" event:

<ListView x:Name="MyListView" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Items, Mode=Default, Source={StaticResource DataProvider}}" Thumb.DragDelta="Thumb_DragDelta">

with:

void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { // Do nothing for the moment Console.WriteLine("Thumb_DragDelta"); }

Unfortunately, if we place a breakpoint inside the Thumb_DragDelta event handler, it is never reached, not even if you resize any column of the ListView. Why not? Well, when a routed event is handled by any control, it can be marked as "handled". This is done by setting the RoutedEventArgs.Handled property to true inside the event handler.

Fortunately, there is a way to tell the framework that we are interested in an event even if it has been marked as handled. We cannot do this in XAML, however, so just remove the Thumb.DragDelta from the XAML:

<ListView x:Name="MyListView" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Items, Mode=Default, Source={StaticResource DataProvider}}">

Instead, we will add an event handler in code behind. This is one of the few things that you cannot do in XAML!

public Window1() { InitializeComponent(); MyListView.AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(Thumb_DragDelta), true); }

Notice that the last parameter of the AddHandler override is set to "true": This is what indicates that we want to get notified when the DragDelta event "bubbles" to us, even if it has been marked handled before it reaches us. This time, if you run the application and resize a column, the event handler is executed and the breakpoint is reached.

The last thing we need to do is to forbid the column to be resized under a minimum size, or above a maximum size. To do this, we need to do a few steps:

  • Get the Thumb at the origin of the event. This is not the "sender" parameter, because the event handler is placed on the ListView. So even though the routed event originates on the Thumb, the "sender" is actually the ListView. However, there is another way to get the Thumb: The parameter "e" (of type DragDeltaEventArgs), like all RoutedEventArgs, contains a property named "OriginalSource". This is the Thumb we want!
  • Then, we want to get the GridViewColumnHeader containing the Thumb. Here too, we need to understand how the visual tree is built. All controls in WPF are essentially lookless, and the look&feel is created in a ControlTemplate, which is separate. We can, however, get the parent's template using the TemplatedParent property.
  • We're almost there: Once we have the GridViewColumnHeader, all we need to do is get the Column it belongs to (conveniently exposed in the Column property), and set its Width to the minimum respectively maximum value we want to use. Translated in code, this is:
void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { Thumb senderAsThumb = e.OriginalSource as Thumb; GridViewColumnHeader header = senderAsThumb.TemplatedParent as GridViewColumnHeader; if (header.Column.ActualWidth < MIN_WIDTH) { header.Column.Width = MIN_WIDTH; } if (header.Column.ActualWidth > MAX_WIDTH) { header.Column.Width = MAX_WIDTH; } }

where MIN_WIDTH and MAX_WIDTH are two constants. We could as well use properties to be able to set these values from the outside.

With this code, the column will be resizable up to a certain limit, and then the Thumb will stop moving. While it requires a good understanding of the inner works of a WPF control, this is not very complicated. It requires some code-behind, however, and cannot be done in pure XAML.

http://www.galasoft.ch

Print | posted on Tuesday, May 6, 2008 7:05 PM

Feedback

Comments are closed.
Comments have been closed on this topic.