【问题标题】:Itemscontrol that arrange Items with Gridsplitter-Functionality使用 Gridsplitter-Functionality 排列 Items 的 Itemscontrol
【发布时间】:2014-05-13 19:39:08
【问题描述】:

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

我的第一个想法是构建一个从 Grid 派生的 CustomControl。他们我使用“OnVisualChildrenChanged”方法在添加新项目时做出反应。然后我想添加新的列/行定义(取决于它应该是水平的还是垂直的)。但这是第一个 Proplem。当 Grid 设置为 IsItemsHost="true" 时,我收到一个错误,即无法访问 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。 在他的模板中,我添加了一个网格而不插入 Itempresenter。现在我重写 OnItemsChange。当一个项目添加到集合中时,我创建一个 FrameworkElement 并将 DataContext 设置为添加的项目,然后我将两个列定义添加到网格并将我的项目和一个 Gridsplitter 添加到网格。这很好用,现在我只需要从我的 Itemscontrol 中获取 ItemTemplate 即可在网格中使用它。

当我解决了最后一个问题时,我将添加一个小例子。

【问题讨论】:

    标签: wpf layout grid itemscontrol gridsplitter


    【解决方案1】:

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

    下面是 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】:

      您可以创建一个 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>
      

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

      【讨论】:

      • 感谢您的快速答复。但我需要调整拆分器两侧的列/行的大小。不过感谢这个解决方案,当然我用它作为一种解决方法。
      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        这是我在尝试实现类似场景几次失败后最终得到的一个面板:

        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(我的用例中不需要这些)。虽然它具有内置的网格拆分器,但您可以使用附加属性指定列大小。我希望它可以帮助某人。 :)

        【讨论】:

        • 唯一的缺点是继承了 Grid 所以不能指定自定义模板。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-12-02
        • 1970-01-01
        • 1970-01-01
        • 2020-09-30
        • 1970-01-01
        • 1970-01-01
        • 2010-12-26
        相关资源
        最近更新 更多