Itemscontrol that arrange Items with Gridsplitter-Functionality

天大地大妈咪最大 提交于 2019-12-05 18:23:20

Heres my Solution so far. If someone has a better idea, you're welcome.

Heres the CustomControl Code behind:

public class ResizableItemControl : ItemsControl
{
    public ObservableCollection<FrameworkElement> _gridItems = new ObservableCollection<FrameworkElement>();
    private Grid _grid;

    static ResizableItemControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ResizableItemControl), new FrameworkPropertyMetadata(typeof(ResizableItemControl)));
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        if (this.Template != null)
        {
            _grid = this.Template.FindName("PART_Grid", this) as Grid;
        }

        // Add all existing items to grid
        foreach (var item in Items)
        {
            AddItemToGrid(item);
        }
    }

    /// <summary>
    /// Called when Items in ItemsCollection changing
    /// </summary>
    /// <param name="e"></param>
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                if (Items.Count > 0)
                {
                    //Add Items in Grid when new Items where add
                    var myItem = this.Items[Items.Count - 1];
                    AddItemToGrid(myItem);
                }
                break;
        }


    }

    /// <summary>
    ///  Adds Items to grid plus GridSplitter
    /// </summary>
    /// <param name="myItem"></param>
    private void AddItemToGrid(object myItem)
    {
        var visualItem = this.ItemTemplate.LoadContent() as FrameworkElement;
        if (visualItem != null)
        {
            visualItem.DataContext = myItem;

            if (_grid != null)
            {
                if (_grid.ColumnDefinitions.Count != 0)
                {
                    _grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
                    _grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
                    var gridSplitter = CreateSplitter();
                    Grid.SetColumn(gridSplitter, _grid.ColumnDefinitions.Count - 2);
                    Grid.SetColumn(visualItem, _grid.ColumnDefinitions.Count - 1);
                    _grid.Children.Add(gridSplitter);
                    _grid.Children.Add(visualItem);

                    //_grid.Children.Add(myTest);
                }
                else
                {
                    _grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
                    Grid.SetColumn(visualItem, _grid.ColumnDefinitions.Count - 1);
                    _grid.Children.Add(visualItem);
                }
            }
        }

    }

    private static GridSplitter CreateSplitter()
    {
        var gridSplitter = new GridSplitter() {ResizeDirection = GridResizeDirection.Columns};
        gridSplitter.Width = 5;
        gridSplitter.HorizontalAlignment = HorizontalAlignment.Stretch;
        gridSplitter.Background = new SolidColorBrush(Colors.Black);
        return gridSplitter;
    }
}

The generic Xaml:

<Style TargetType="{x:Type controls:ResizableItemControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:ResizableItemControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden">
                        <Grid x:Name="PART_Grid"></Grid>
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

And how to use it:

            <controls:ResizableItemControl
                ItemsSource="{Binding ElementName=this,Path=Items}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Label
                            Content="{Binding Name}"
                            ToolTip="{Binding Description}"
                            Background="Black"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </controls:ResizableItemControl>

You could create a Stackpanel, and for each element, a Grid inside it, with the following structure:

<StackPanel Orientation="Vertical">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Button>Hallo</Button>
        <GridSplitter Height="5" Background="Transparent" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Grid.Row="0" ResizeDirection="Rows" ResizeBehavior="CurrentAndNext"/>
    </Grid>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Button>Hallo</Button>
        <GridSplitter Height="5" Background="Transparent" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Grid.Row="0" ResizeDirection="Rows" ResizeBehavior="CurrentAndNext"/>
    </Grid>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Button>Hallo</Button>
        <GridSplitter Height="5" Background="Transparent" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Grid.Row="0" ResizeDirection="Rows" ResizeBehavior="CurrentAndNext"/>
    </Grid>       
</StackPanel>

Note that you can only resize one column at a time, but i'm sure you can refine this a bit.

I cannot add comments to waits83's answer. Instead of create grid splitter on OnApplyTemplate, use OnItemsSourceChanged may be better. because with OnApplyTemplate, need to bind data context before initializeComponents() in view constructor.

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. :)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!