Introduction
Using a ListViewLayoutManager allows controlling the behavior of the column layout of ListView/GridView controls:
- Fixed Column: Column with fixed column width
- Proportional Column: Column with proportional column width
- Range Column: Column with minimal und/or maximal column width
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 class ImageGridViewColumn allows representing a column as an image/icon using a DataTemplate.
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" />
</GridView>
</ListView.View>
</ListView>
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 );
GridView gridView = new GridView();
gridView.Columns.Add( FixedColumn.ApplyWidth( new MyGridViewColumn( "State" ), 25 ) );
gridView.Columns.Add( RangeColumn.ApplyWidth( new MyGridViewColumn( "Name" ), 100,
150, 200 ) );
gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn( "City" ),
1 ) );
gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn(
"Country" ), 2 ) );
listView.View = gridView;
Columns with Custom Representation
The class ConverterGridColumn serves as a base class for binding table columns to individual objects:
Collapse
public class Customer
{
public Customer()
{
}
public string FirstName
{
get { return this.firstName; }
set { this.firstName = value; }
}
public string LastName
{
get { return this.lastName; }
set { this.lastName = value; }
}
private string firstName;
private string lastName;
}
public class CustomerFullNameColumn : ConverterGridViewColumn
{
public CustomerGridViewColumn() :
base( typeof( Customer ) )
{
}
protected override object ConvertValue( object value )
{
Customer customer = value as Customer;
return string.Concat( customer.FirstName, " ", customer.LastName );
}
}
Columns Represented as Images
The class ImageGridColumn serves as a base class for binding table columns to images/icons:
Collapse
public class Customer
{
public Customer()
{
}
public bool IsActive
{
get { return this.isActive; }
set { this.isActive = value; }
}
private bool isActive;
}
public class CustomerActiveColumn : ImageGridViewColumn
{
public 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;
}
}
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 informations, 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 detection 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 ) );
Binding imageStretchBinding = new Binding();
imageStretchBinding.Source = imageStretch;
imageElement.SetBinding( Image.StretchProperty, imageStretchBinding );
DataTemplate template = new DataTemplate();
template.VisualTree = imageElement;
CellTemplate = template;
}