Introduction
I had a requirement to display data that would be paged, and would handle resizing. It seemed like the UniformGrid
was the right choice. I just had to add paging.
Design
To make the control as easy to use as possible, and not require an XAML file, I create a template for the ItemsPanel
in code, and set it there. This template just contains a UniformGrid
. There are two bindings that I create for the Columns
and Rows
properties of the UniformGrid
that are bound each to a private DependencyProperty
in this control.
There is a public DependencyProperty
for the minimum column and row size (MinColumnWidth
and MinRowHeight
), and there is also a MainItemsSource DependencyProperty
that is to be used instead of the ItemsSource DependencyProperty
. This is because of the paging.
There are also two ICommand
properties that are used for Binding
buttons to implement the paging.
The UniformGrid
is set as the ItemsPanel
with the following code:
FrameworkElementFactory factory =
new FrameworkElementFactory(typeof(UniformGrid), "UniformGrid");
Binding bindingColumns = new Binding("UniformGirdColumns")
{
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, GetType(), 1)
};
Binding bindingRows = new Binding("UniformGirdRows")
{
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, GetType(), 1)
};
factory.SetBinding(UniformGrid.ColumnsProperty, bindingColumns);
factory.SetBinding(UniformGrid.RowsProperty, bindingRows);
ItemsPanel = new ItemsPanelTemplate(factory);
DependencyProperties
There are three custom DependencyProperty types for this control that are used to control the behavior of the UniformGrid control used as the ItemsPanelTemplate:
MinColumnWidth: This is the minimum column width for the UniformGird ItemsPanelTemplate. If the value is 0 (the default) then there will only be a single column. Otherwise there will be a minimum of 1, or the number of columns equal floor of the width of the ItemsControl divided by the MinColumnWidth. The actual width of the columns will be the width of the ItemsControl divided by this number. The number of columns is maintained in a private DependencyProperty called UniformGirdColumns.
MinRowHeight: This is the minimum row height for the UniformGird ItemsPanelTemplate. If the value is 0 (the default) then there will only be a single row. Otherwise there will be a minimum of 1, or the number of rows equal floor of the height of the ItemsControl divided by the MinRowHeight. The actual height of the rows will be the height of the ItemsControl divided by this number. The number of rows is maintained in a private DependencyProperty called UniformGirdRows.
Orientation: This will specify whether the items will priority horizonally or priority vertically. It is of Type System.Windows.Controls.Orientation. The default is Horizontal. If this is changed to Vertical, there will be empty DataTemplate items in the grid if the ItemTemplate is specified. If the DataTemplate with the correct DataType is specified in the Resources, then the empty DataTemplates will not be displayed, but understand that the DataContext will have to match the Type for the DataTemplate.
INotifyCollectionChanged
There is one problem that requires special consideration, and that if if the collection bound to the MainItemsSource is modified, the control has to know about this change. In my requirement I did not require handling adding or removing from the collection, but there are many situations where there is a need to handle this sitution. An ObservableCollection is usually used to resolve this issue. Therefore I have improved the code to check if the IEnumerable parent class implements INotifyPropertyChanged, and attaches an event handler to IEnumerable that will cause the contents of the ItemsSource property to be recalculator:
private static void OnMainItemsSourceChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var itemsControl = (UniformGirdItemsControl)d;
if (e.OldValue is INotifyCollectionChanged)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -=
itemsControl.UniformGirdItemsControl_CollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged +=
itemsControl.UniformGirdItemsControl_CollectionChanged;
}
itemsControl.OnDataChanged();
}
private void UniformGirdItemsControl_CollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
OnDataChanged();
}
Using the Code
Below is an example of using this Control:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Margin="10"
Padding="5"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<local:UniformGirdItemsControl x:Name="ItemsControl"
MainItemsSource="{Binding ItemsSource}"
MinColumnWidth="100"
MinRowHeight="50" />
</Border>
<Button Grid.Row="1"
Width="80"
Margin="10"
HorizontalAlignment="Left"
Command="{Binding ElementName=ItemsControl, Path=PreviousPageCommand}"
Content="Previous" />
<Button Grid.Row="1"
Width="80"
Margin="10"
HorizontalAlignment="Right"
Command="{Binding ElementName=ItemsControl, Path=NextPageCommand}"
Content="Next" />
</Grid>
I have included the UniformGridItems
Control and two Button
controls for navigation to provide a fully functional sample. There is also a TextBox
and a Button
to add a new Button
to the UniformGridItemsControl
.
Important Note
This control was designed for a very static environment. It has been updated to handle adding or removing items in the collection. There may be other issues, and would be happy to take into any reports of issues. I would like to thank Alexander Sharykin for his comment.
History
- 02/07/2016: Initial version
- 02/08/2016: Update with Important Note section.
- 03/03/2017: Added the
Orientation
DependencyProperty