使用Gridsplitter-Functionality排列Items的Itemscontrol

时间:2014-04-02 13:20:58

标签: wpf layout grid itemscontrol gridsplitter

我需要一个将项目水平或垂直堆叠的布局。每个项目应由Splitter分隔,因此用户可以更改大小(如VisualStudio中的工具箱容器) Items来自Model,所以我需要一个ItemsControl来设置ItemsSource。

我的第一个想法是构建一个派生自Grid的CustomControl。他们使用了" OnVisualChildrenChanged"添加新项目时的反应方法。然后我想添加新的Column- / Rowdefinitions(依赖于它应该是水平的还是垂直的)。但这是第一个Proplem。当Grid设置为IsItemsHost =" true"我得到一个Error,说明DefinitionsCollection不可访问......

继承自自定义控件的CodeSnipped,我尝试添加一行。

 private void SetRows()
    {
       this.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto };             
    }

    protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
    {
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);
        SetRows();            
    }

编辑:

我想我现在找到了解决方案。

首先,我创建了一个派生自ItemsControl的CustomControl。 在他的模板中,我添加了一个Grid而没有插入Itempresenter。现在我重写OnItemsChange。将Item添加到Collection时,我创建一个FrameworkElement并将DataContext设置为添加的Item,而不是将两个Column定义添加到Grid并将我的Item和Gridsplitter添加到Grid。这很好用,现在我只需要从我的Itemscontrol中获取ItemTemplate以在网格中使用它。

当我解决最后一期时,我会添加一个小例子。

4 个答案:

答案 0 :(得分:2)

您可以创建一个Stackpanel,并为每个元素创建一个Grid,其结构如下:

<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>

请注意,您一次只能调整一列,但我相信您可以稍微改进一下。

答案 1 :(得分:2)

到目前为止,这是我的解决方案。如果有人有更好的想法,欢迎你。

后面是CustomControl代码:

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;
    }
}

通用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>

以及如何使用它:

            <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>

答案 2 :(得分:1)

我无法在waits83的回答中添加评论。 而不是在OnApplyTemplate上创建网格分割器,使用OnItemsSourceChanged可能会更好。 因为使用OnApplyTemplate,需要在视图构造函数中的initializeComponents()之前绑定数据上下文。

答案 3 :(得分:1)

以下是我在尝试实施类似方案失败后最终得出的一个小组:

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};
    }
}

用法:

<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>

它只会自动生成列,但不支持INotifyCollectionChanged(在我的用例中我不需要这些列)。它具有内置网格分割器,您可以使用附加属性指定列大小。我希望它对某人有帮助。 :)