Click here to Skip to main content
11,636,300 members (77,514 online)
Click here to Skip to main content

ListView Layout Manager

, 27 Sep 2012 CPOL 319.7K 5.5K 229
Rate this:
Please Sign up or sign in to vote.
WPF: Customizing ListView/GridView Column-Layout
ListView_layout_manager/ListViewLayoutManager.gif

Introduction

Using a ListViewLayoutManager allows controlling the behavior of the column layout of ListView/GridView controls:

  • Fixed Column: Column with fixed column width
  • Range Column: Column with minimal and/or maximal column width
  • Proportional Column: Column with proportional column width

The Range Column allows to restrict the column width as well as to fill the remaining visible area with the column.

As known from HTML tables or the Grid control, the Proportional Column determines the column widths on a percentage basis. The following factors determine the width of a proportional column:

  • Visibility of the vertical ListView scrollbars
  • Changes of the ListView control width
  • Changes of the width of a non-proportional column

The implementation supports both controlling through XAML or Code Behind. Usage of XAML styles allows a ListViewLayoutManager to be 'attached' to an existing ListView control.

The class ConverterGridColumn offers object specific binding by using the interface IValueConverter. Using the ImageGridViewColumn class allows representing a column as an image/icon using a DataTemplate.

In the article User Settings Applied, I describe how you can persist order and size of the ListView columns.

ListView/GridView Layout in XAML

Fixed Column

The following example shows controlling columns with fixed widths using XAML:

  <ListView
    Name="MyListView"
    ctrl:ListViewLayoutManager.Enabled="true">

    <ListView.View>
      <GridView>
        <GridViewColumn
          DisplayMemberBinding="{Binding Path=Name}"

          ctrl:FixedColumn.Width="100"
          Header="Name" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=City}"
          ctrl:FixedColumn.Width="300"

          Header="City" />
      </GridView>

    </ListView.View>
  </ListView>

Setting the property Enabled binds the ListViewLayoutManager to the ListView control. The property FixedColumn.Width determines the column width and prevents resizing using the mouse.

Proportional Column

The following example shows controlling columns with proportional widths using XAML:

  <ListView
    Name="MyListView"
    ctrl:ListViewLayoutManager.Enabled="true">
    <ListView.View>

      <GridView>

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=Name}"
          ctrl:ProportionalColumn.Width="1"

          Header="Name" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=City}"

          ctrl:ProportionalColumn.Width="3"
          Header="City" />
      </GridView>

    </ListView.View>

  </ListView>

Matching the RowDefinition.Width of the Grid control, the value of ProportionalColumn.Width represents the percentage. The scenario above sets the column Name to 25% and the column City to 75% of the total width. Analogous to the fixed columns, resizing with the mouse is disabled.

Range Column

The following example shows controlling ranged columns with minimal/maximal widths using XAML:

  <ListView
    Name="MyListView"
    ctrl:ListViewLayoutManager.Enabled="true">
    <ListView.View>

      <GridView>

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=Name}"
          ctrl:RangeColumn.MinWidth="100"
          Width="150"

          Header="Name" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=City}"
          ctrl:RangeColumn.MaxWidth="200"
          Width="150"

          Header="City" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=Country}"
          Width="100"
          ctrl:RangeColumn.MinWidth="50"

          ctrl:RangeColumn.MaxWidth="150"
          Header="Country" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=State}"
          Width="100"

          ctrl:RangeColumn.MinWidth="100"
          ctrl:RangeColumn.IsFillColumn="true"
          Header="Country" />

      </GridView>

    </ListView.View>
  </ListView>

The first range column which has the value IsFillColumn set to true, will automatically get resized to the remaining space. The column will not get filled if the ListView contains a proportional column.

Dragging the mouse out of the configured range when resizing, the mouse cursor changes its representation to indicate this.

Combined Usage

In real life, it is common to combine these column types. Their order can be varied as required:

  <ListView
    Name="MyListView"
    ctrl:ListViewLayoutManager.Enabled="true">

    <ListView.View>
      <GridView>

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=State}"
          ctrl:FixedColumn.Width="25"
          Header="Name" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=Name}"

          Width="150"
          ctrl:RangeColumn.MinWidth="100"
          ctrl:RangeColumn.MaxWidth="200"

          Header="City" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=City}"
          ctrl:ProportionalColumn.Width="1"
          Header="Zip" />

        <GridViewColumn
          DisplayMemberBinding="{Binding Path=Country}"

          ctrl:ProportionalColumn.Width="2"
          Header="Country" />
      </GridView>

    </ListView.View>
  </ListView>

ListView/GridView Layout using Code Behind

It is also possible to setup the column layout in the source code:

 ListView listView = new ListView();
 new ListViewLayoutManager( listView ); // attach the layout manager

 GridView gridView = new GridView();
 gridView.Columns.Add( FixedColumn.ApplyWidth( new MyGridViewColumn( "State" ), 25 ) );
 gridView.Columns.Add( RangeColumn.ApplyWidth( new MyGridViewColumn( "Name" ), 100,
     150, 200 ) ); // 100...200
 gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn( "City" ),
     1 ) ); // 33%
 gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn(
     "Country" ), 2 ) ); // 66%

 listView.View = gridView;

Columns with Custom Representation

The class ConverterGridColumn serves as a base class for binding table columns to individual objects:

  // ------------------------------------------------------------------------
  public class Customer
  {
    // ----------------------------------------------------------------------
    public Customer()
    {
    } // Customer

    // ----------------------------------------------------------------------
    public string FirstName
    {
      get { return this.firstName; }
      set { this.firstName = value; }
    } // FirstName

    // ----------------------------------------------------------------------
    public string LastName
    {
      get { return this.lastName; }
      set { this.lastName = value; }
    } // LastName

    // ----------------------------------------------------------------------
    // members
    private string firstName;
    private string lastName;

  } // class Customer

  // ------------------------------------------------------------------------
  public class CustomerFullNameColumn : ConverterGridViewColumn
  {
    // ----------------------------------------------------------------------
    public CustomerGridViewColumn() :
      base( typeof( Customer ) )
    {
    } // CustomerGridViewColumn

    // ----------------------------------------------------------------------
    protected override object ConvertValue( object value )
    {
      Customer customer = value as Customer;
      return string.Concat( customer.FirstName, " ", customer.LastName );
    } // ConvertValue

  } // class CustomerFullNameColumn

Columns Represented as Images

The class ImageGridColumn serves as a base class for binding table columns to images/icons:

  // ------------------------------------------------------------------------
  public class Customer
  {
    // ----------------------------------------------------------------------
    public Customer()
    {
    } // Customer

    // ----------------------------------------------------------------------
    public bool IsActive
    {
      get { return this.isActive; }
      set { this.isActive = value; }
    } // IsActive

    // ----------------------------------------------------------------------
    // members
    private bool isActive;

  } // class Customer

  // ------------------------------------------------------------------------
  public class CustomerActiveColumn : ImageGridViewColumn
  {
    // ----------------------------------------------------------------------
    public CustomerActiveColumn()
    {
    } // CustomerActiveColumn

    // ----------------------------------------------------------------------
    protected override ImageSource GetImageSource( object value )
    {
      Customer customer = value as Customer;
      if ( customer != null )
      {
        return new BitmapImage( new Uri( customer.IsActive ? "Active.png" :
            "Inactive.png" ) );
      }
      return null;
    } // GetImageSource

  } // class CustomerActiveColumn

Points of Interest

At the core of layouting the ListView control lies the ListViewLayoutManager with the following responsibilities:

  • Preventing resizing columns of type Fixed and Proportional
  • Enforcing the range of allowed widths for type Range
  • Updating the column layout upon changes to the size of the ListView control
  • Updating the column layout upon changes to the widths of individual columns

To properly receive the required information, it is necessary to analyze the Visual Tree of the ListView control. The object Thumb provides the events for changes to the column width. To ensure correctly representing the mouse cursor, the events PreviewMouseMove and PreviewMouseLeftButtonDown are being handled.

The event ScrollChanged of the class ScrollViewer triggers updates due to control size changes. Only changes to the size of the control Viewport are relevant for resizing (ScrollChangedEventArgs.ViewportWidthChange).

Tracking the property Width of class GridViewColumn using DependencyPropertyDescriptor gives notification of changes to column widths.

To support integration in existing systems, the required column data is kept in Attached Properties. Using the method DependencyProperty.ReadLocalValue() allows detecting whether the own property is present in an object.

The class ConverterGridViewColumn uses a simple Binding and at the same time represents the converter (interface IValueConverter).

The class ImageGridViewColumn uses the FrameworkElementFactory in a DataTemplate to embed the image dynamically. By default, the images in the ListView/GridView control will be stretched automatically (property Image.Stretch). Because the image in a DataTemplate gets created dynamically, the property value of the template element must be assigned using a Binding:

 // ----------------------------------------------------------------------
 protected ImageGridViewColumn( Stretch imageStretch )
 {
   FrameworkElementFactory imageElement = new FrameworkElementFactory( typeof( Image ) );

   // image stretching
   Binding imageStretchBinding = new Binding();
   imageStretchBinding.Source = imageStretch;
   imageElement.SetBinding( Image.StretchProperty, imageStretchBinding );

   DataTemplate template = new DataTemplate();
   template.VisualTree = imageElement;
   CellTemplate = template;
 } // ImageGridViewColumn

History

  • 28th September, 2012
    • Added projects and solutions for Visual Studio 2010
    • ReSharped source code
    • ListViewLayoutManager: Using a value range to check if the scroll viewer has been changed
    • ListViewLayoutManager: New public method Refresh
    • ListViewLayoutManager: Hide fixed column thumb - thanks Name taken
  • 3rd August, 2009
    • RangeColumn now supports columns with automatic width (Width="Auto") - thanks inv_inv
  • 27th November, 2008
    • Fixed memory leak by un-registering the ListView events - thanks Dave and John
  • 3rd November, 2008
    • Fixed infinite recursion of column resizing in some rare cases - thanks ascalonx
    • Fixed column resizing in case of nested ListView - thanks mburi
  • 19th September, 2008
    • Fill column can now be at any position - the first occurrence will be used
  • 12th June, 2008
    • Added RangeColumn.IsFillColumn to fill the last column to the available visible area
  • 3rd June, 2008
    • Considering limits of range column during auto-resize (mouse double click) - thanks paul
  • 14th May, 2008
    • Fixed horizontal scrolling in case of absent proportional columns - thanks vernarim
  • 10th May, 2008
  • 23rd April, 2008
    • Added screenshot descriptions
    • Minor bug fixes
  • 6th April, 2008
    • Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jani Giannoudis
Software Developer (Senior)
Switzerland Switzerland
Jani is Co-founder of Meerazo.com, a free service which allows to share resources like locations, things, persons and their services in a cooperating group of people.

You may also be interested in...

Comments and Discussions

 
BugMissing 4 pixels Pin
Name taken1-Oct-12 0:48
memberName taken1-Oct-12 0:48 
AnswerRe: Missing 4 pixels Pin
Jani Giannoudis13-Oct-12 23:05
mvpJani Giannoudis13-Oct-12 23:05 
QuestionRegard other columns' MinWidth when resizing a column Pin
Name taken27-Sep-12 22:27
memberName taken27-Sep-12 22:27 
QuestionRe: Regard other columns' MinWidth when resizing a column Pin
Jani Giannoudis13-Oct-12 23:13
mvpJani Giannoudis13-Oct-12 23:13 
GeneralRe: Regard other columns' MinWidth when resizing a column Pin
Name taken14-Oct-12 21:24
memberName taken14-Oct-12 21:24 
SuggestionSuggestion: Hide fixed column thumb Pin
Name taken27-Sep-12 2:40
memberName taken27-Sep-12 2:40 
AnswerRe: Suggestion: Hide fixed column thumb Pin
Jani Giannoudis27-Sep-12 22:01
mvpJani Giannoudis27-Sep-12 22:01 
GeneralRe: Suggestion: Hide fixed column thumb Pin
Name taken27-Sep-12 22:23
memberName taken27-Sep-12 22:23 
QuestionLast column is ignored by LayoutManager Pin
Name taken27-Sep-12 0:53
memberName taken27-Sep-12 0:53 
AnswerRe: Last column is ignored by LayoutManager Pin
Jani Giannoudis27-Sep-12 1:54
mvpJani Giannoudis27-Sep-12 1:54 
GeneralRe: Last column is ignored by LayoutManager Pin
Name taken27-Sep-12 1:56
memberName taken27-Sep-12 1:56 
AnswerRe: Last column is ignored by LayoutManager Pin
Name taken27-Sep-12 2:00
memberName taken27-Sep-12 2:00 
AnswerRe: Last column is ignored by LayoutManager Pin
Jani Giannoudis27-Sep-12 2:04
mvpJani Giannoudis27-Sep-12 2:04 
AnswerFound a (hacky!) fix for the width problem a lot of people seem to be having Pin
Pyritie24-Sep-12 23:55
memberPyritie24-Sep-12 23:55 
I have two columns in my ListView. The left one only contains text and should stretch to the longest string, but the right contains a variety of different Controls (text boxes, check boxes, etc). The right is also the Fill column.

The contents of the two columns changes very often in my application and so both columns needed to resize frequently.

While doing
firstColumn.Width = firstColumn.ActualWidth;
firstColumn.Width = Double.NaN;
worked fine for resizing the first column, the second column always had the wrong size -- on closer inspection, it actually had the width that the previous "set" of data should've had!

I fiddled around a bit more and eventually just gave up and stuck a timer in there. It's very hacky, but it works well enough.

(The "Items" property is bound to the "ItemsSource" property of my ListView, and my DataContext object implements INotifyPropertyChanged)
void OnWhateverChanged() {
    OnPropertyChanged("Items");
 
    RecalculateFirstColumnWidth();
 
    var timer = new Timer(10);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
}
void timer_Elapsed(object sender, ElapsedEventArgs e) {
     Timer timer = sender as Timer;
 
     OnPropertyChanged("Items");
     timer.Stop();
     timer.Elapsed -= timer_Elapsed;
}
void RecalculateFirstColumnWidth() {
     GridViewColumn firstColumn = (myListView.View as GridView).Columns[0];
     firstColumn.Width = firstColumn.ActualWidth;
     firstColumn.Width = Double.NaN;
}
Only problem I've found is that this doesn't seem to do anything if the left column only contains checkboxes or combo boxes, but if it has a text box (or one of the UpDown controls from the WpfToolkit) then it works fine.
AnswerRe: Found a (hacky!) fix for the width problem a lot of people seem to be having Pin
Jani Giannoudis27-Sep-12 1:48
mvpJani Giannoudis27-Sep-12 1:48 
Generalhelp? ProportionalColumn.Width and RangeColumn.IsFillColumn causing issues for me Pin
fredowall18-Jul-12 22:17
memberfredowall18-Jul-12 22:17 
GeneralRe: help? ProportionalColumn.Width and RangeColumn.IsFillColumn causing issues for me Pin
Jani Giannoudis19-Jul-12 16:30
mvpJani Giannoudis19-Jul-12 16:30 
GeneralRe: help? ProportionalColumn.Width and RangeColumn.IsFillColumn causing issues for me Pin
fredowall26-Jul-12 6:39
memberfredowall26-Jul-12 6:39 
QuestionGreat project, but have auto width problem Pin
peter.roca13-Jun-12 11:14
memberpeter.roca13-Jun-12 11:14 
AnswerRe: Great project, but have auto width problem Pin
Jani Giannoudis13-Jun-12 21:34
mvpJani Giannoudis13-Jun-12 21:34 
GeneralRe: Great project, but have auto width problem Pin
peter.roca14-Jun-12 6:14
memberpeter.roca14-Jun-12 6:14 
AnswerRe: Great project, but have auto width problem Pin
Jani Giannoudis14-Jun-12 20:37
mvpJani Giannoudis14-Jun-12 20:37 
GeneralRe: Great project, but have auto width problem Pin
peter.roca15-Jun-12 5:24
memberpeter.roca15-Jun-12 5:24 
AnswerRe: Great project, but have auto width problem Pin
Jani Giannoudis15-Jun-12 5:44
mvpJani Giannoudis15-Jun-12 5:44 
GeneralRe: Great project, but have auto width problem Pin
peter.roca15-Jun-12 7:06
memberpeter.roca15-Jun-12 7:06 
AnswerRe: Great project, but have auto width problem Pin
Jani Giannoudis17-Jun-12 22:58
mvpJani Giannoudis17-Jun-12 22:58 
GeneralRe: Great project, but have auto width problem Pin
peter.roca18-Jun-12 11:26
memberpeter.roca18-Jun-12 11:26 
AnswerRe: Great project, but have auto width problem Pin
Jani Giannoudis19-Jun-12 20:27
mvpJani Giannoudis19-Jun-12 20:27 
GeneralMy vote of 5 Pin
AdamDavidHill5-Jun-12 1:49
memberAdamDavidHill5-Jun-12 1:49 
AnswerRe: My vote of 5 Pin
Jani Giannoudis5-Jun-12 2:07
mvpJani Giannoudis5-Jun-12 2:07 
GeneralMy vote of 5 Pin
hichaeretaqua3-Jun-12 22:15
memberhichaeretaqua3-Jun-12 22:15 
AnswerRe: My vote of 5 Pin
Jani Giannoudis4-Jun-12 2:03
mvpJani Giannoudis4-Jun-12 2:03 
QuestionProportional, fixed and range columns are not clickable Pin
Lapag17-Feb-12 8:43
memberLapag17-Feb-12 8:43 
QuestionIssue when ListView.View has changed Pin
Xxbz13-Sep-11 23:55
memberXxbz13-Sep-11 23:55 
AnswerRe: Issue when ListView.View has changed Pin
Jani Giannoudis15-Sep-11 4:38
memberJani Giannoudis15-Sep-11 4:38 
QuestionFixed issue with applying widths to non-visible element from Style/Template [modified] Pin
SilverX6917-Jul-11 17:19
memberSilverX6917-Jul-11 17:19 
GeneralNice work! Pin
youngcat29-Jun-11 5:56
memberyoungcat29-Jun-11 5:56 
AnswerRe: Nice work! Pin
Jani Giannoudis29-Jun-11 10:16
memberJani Giannoudis29-Jun-11 10:16 
GeneralMy vote of 5 Pin
Wes Jones17-Jun-11 6:58
memberWes Jones17-Jun-11 6:58 
AnswerRe: My vote of 5 Pin
Jani Giannoudis17-Jun-11 11:47
memberJani Giannoudis17-Jun-11 11:47 
GeneralUse in VB.NET project Pin
Member 768560418-Feb-11 6:52
memberMember 768560418-Feb-11 6:52 
AnswerRe: Use in VB.NET project Pin
Jani Giannoudis20-Feb-11 21:03
memberJani Giannoudis20-Feb-11 21:03 
GeneralRe: Use in VB.NET project Pin
Member 768560423-Feb-11 11:59
memberMember 768560423-Feb-11 11:59 
AnswerRe: Use in VB.NET project Pin
Jani Giannoudis24-Feb-11 19:58
memberJani Giannoudis24-Feb-11 19:58 
QuestionHide column feature Pin
stivo224-Aug-10 21:46
memberstivo224-Aug-10 21:46 
GeneralError when column Header is DynamicResource Pin
Danil11-Jun-10 2:46
memberDanil11-Jun-10 2:46 
GeneralColumn Resize problem after opening a child window. Pin
Violinist23-Jun-10 3:44
memberViolinist23-Jun-10 3:44 
GeneralRe: Column Resize problem after opening a child window. Pin
Violinist23-Jun-10 6:28
memberViolinist23-Jun-10 6:28 
GeneralRe: Column Resize problem after opening a child window. Pin
Violinist24-Jun-10 0:19
memberViolinist24-Jun-10 0:19 
GeneralColumns don't auto resize when itemsource is changed. Pin
MazakSteve24-Jan-10 22:24
memberMazakSteve24-Jan-10 22:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150728.1 | Last Updated 28 Sep 2012
Article Copyright 2008 by Jani Giannoudis
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid