我需要一个将项目水平或垂直堆叠的布局。每个项目应由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以在网格中使用它。
当我解决最后一期时,我会添加一个小例子。
答案 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
(在我的用例中我不需要这些列)。它具有内置网格分割器,您可以使用附加属性指定列大小。我希望它对某人有帮助。 :)