Here is a panel I ended up with after a few failed attempts to implement similar scenario:
class SplitPanel : Grid
{
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate", typeof (DataTemplate), typeof (SplitPanel), new PropertyMetadata(default(DataTemplate), OnItemTemplateChanged));
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
foreach (var child in ((SplitPanel)d).Children.OfType<ContentPresenter>())
{
child.ContentTemplate = (DataTemplate)e.NewValue;
}
}
public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register(
"ItemContainerStyle", typeof(Style), typeof(SplitPanel), new PropertyMetadata(default(Style), OnContainerStyleChanged));
public Style ItemContainerStyle
{
get { return (Style)GetValue(ItemContainerStyleProperty); }
set { SetValue(ItemContainerStyleProperty, value); }
}
private static void OnContainerStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
foreach (var child in ((SplitPanel)d).Children.OfType<ContentPresenter>())
{
child.Style = (Style) e.NewValue;
}
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof (IEnumerable), typeof (SplitPanel), new PropertyMetadata(null ,OnItemsSourceChanged));
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = (SplitPanel)d;
grid.Children.Clear();
grid.ColumnDefinitions.Clear();
var items = e.NewValue as IEnumerable;
if (items == null) return;
var children = items.Cast<object>().Select(grid.GenerateContainer).ToArray();
for (int i = 0; ; i++)
{
var child = children[i];
var column = new ColumnDefinition
{
MinWidth = GetMinColumnWidth(child),
Width = GetColumnWidth(child),
MaxWidth = GetMaxColumnWidth(child)
};
grid.ColumnDefinitions.Add(column);
grid.Children.Add(child);
SetColumn(child, i);
if (i == children.Length - 1) break;
var splitter = new GridSplitter
{
Width = 5,
ResizeBehavior = GridResizeBehavior.CurrentAndNext,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Right,
Background = Brushes.Transparent
};
SetColumn(splitter, i);
grid.Children.Add(splitter);
}
}
public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.RegisterAttached(
"ColumnWidth", typeof (GridLength), typeof (SplitPanel), new PropertyMetadata(new GridLength(1, GridUnitType.Star), OnColumnWidthChanged));
public static void SetColumnWidth(DependencyObject element, GridLength value)
{
element.SetValue(ColumnWidthProperty, value);
}
public static GridLength GetColumnWidth(DependencyObject element)
{
return (GridLength) element.GetValue(ColumnWidthProperty);
}
private static void OnColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UpdateColumnDefinition(d, column => column.Width = (GridLength)e.NewValue);
}
public static readonly DependencyProperty MinColumnWidthProperty = DependencyProperty.RegisterAttached(
"MinColumnWidth", typeof (double), typeof (SplitPanel), new PropertyMetadata(100d, OnMinColumnWidthChanged));
public static void SetMinColumnWidth(DependencyObject element, double value)
{
element.SetValue(MinColumnWidthProperty, value);
}
public static double GetMinColumnWidth(DependencyObject element)
{
return (double) element.GetValue(MinColumnWidthProperty);
}
private static void OnMinColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UpdateColumnDefinition(d, column => column.MinWidth = (double)e.NewValue);
}
public static readonly DependencyProperty MaxColumnWidthProperty = DependencyProperty.RegisterAttached(
"MaxColumnWidth", typeof (double), typeof (SplitPanel), new PropertyMetadata(double.MaxValue, OnMaxColumnWidthChanged));
public static void SetMaxColumnWidth(DependencyObject element, double value)
{
element.SetValue(MaxColumnWidthProperty, value);
}
public static double GetMaxColumnWidth(DependencyObject element)
{
return (double) element.GetValue(MaxColumnWidthProperty);
}
private static void OnMaxColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UpdateColumnDefinition(d, column => column.MaxWidth = (double)e.NewValue);
}
private static void UpdateColumnDefinition(DependencyObject child, Action<ColumnDefinition> updateAction)
{
var grid = VisualTreeHelper.GetParent(child) as SplitPanel;
if (grid == null) return;
var column = GetColumn((UIElement)child);
if (column >= grid.ColumnDefinitions.Count) return;
grid.Dispatcher.BeginInvoke(new Action(() => updateAction(grid.ColumnDefinitions[column])));
}
private ContentPresenter GenerateContainer(object item)
{
return new ContentPresenter {Content = item, ContentTemplate = ItemTemplate};
}
}
Usage:
<wpfApplication1:SplitPanel ItemsSource="{Binding Items}">
<wpfApplication1:SplitPanel.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="wpfApplication1:SplitPanel.ColumnWidth" Value="{Binding Width}"/>
<Setter Property="wpfApplication1:SplitPanel.MinColumnWidth" Value="10"/>
<Setter Property="wpfApplication1:SplitPanel.MaxColumnWidth" Value="100"/>
</Style>
</wpfApplication1:SplitPanel.ItemContainerStyle>
<wpfApplication1:SplitPanel.ItemTemplate>
<DataTemplate>
<TextBlock Text="123"/>
</DataTemplate>
</wpfApplication1:SplitPanel.ItemTemplate>
</wpfApplication1:SplitPanel>
It only auto-generates columns and it does not support INotifyCollectionChanged
(I do not need those in my use case). It has built-in grid splitters though, and you can specify column sizes using attached properties. I hope it helps someone. :)