如何在 XAML / WPF 中以特定方式绘制 WrapPanel
How to draw a WrapPanel in a specific way in XAML / WPF
我有一个元素列表框,我将 ItemPanel 设置为 WrapPanel,因为我希望我的面板每 4 个元素包装一次。
我使用了以下内容:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
现在,我希望我的项目显示如下:
当只有 4 件或更少时:
当超过 4 件时:
我需要将 cornerRadius 应用于四个边缘,无论是否只有一行。
结果这有点痛苦。如果您希望正确参数化角半径和边框厚度,则需要做更多的工作:您需要值转换器来根据需要创建或修改 CornerRadius
和 Thickness
值。
另一种方法是省略触发器并编写两个大型多转换器,用于 Thickness 和 CornerRadius,它们采用与我编写的参数相同的参数,加上 "default" 边界厚度和角半径值,然后分别是 return Thickness
和 CornerRadius
。
<Style TargetType="ListBox" x:Key="GridLineListBox">
<Style.Resources>
<local:CellTypeConverter x:Key="CellTypeConverter" />
</Style.Resources>
<Setter Property="AlternationCount" Value="{x:Static sys:Int32.MaxValue}" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="BorderBrush" Value="SteelBlue" />
<Setter Property="local:GridLineListBox.ColumnCount" Value="6" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="16"
ClipToBounds="True"
>
<ItemsPresenter Margin="-1" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid
Columns="{Binding (local:GridLineListBox.ColumnCount), RelativeSource={RelativeSource AncestorType=ListBox}}"
IsItemsHost="True"
/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<!-- Put this in an attached property so we don't have to copy/paste the whole binding for each trigger -->
<Setter Property="local:GridLineListBox.CellType">
<Setter.Value>
<MultiBinding Converter="{StaticResource CellTypeConverter}">
<Binding Path="Items.Count" RelativeSource="{RelativeSource AncestorType=ListBox}" />
<Binding Path="(ItemsControl.AlternationIndex)" RelativeSource="{RelativeSource Self}" />
<Binding Path="(local:GridLineListBox.ColumnCount)" RelativeSource="{RelativeSource AncestorType=ListBox}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0" />
<Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=ListBox}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<!--
Negative right/bottom margin because I'm getting a gap with
SnapToDevicePixels and I'm too lazy to figure out the real reason.
-->
<Border
x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0,0,2,2"
Background="{TemplateBinding Background}"
ClipToBounds="True"
Margin="-1"
>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="local:GridLineListBox.CellType" Value="TopLeft">
<Setter TargetName="Bd" Property="CornerRadius" Value="16,0,0,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="TopRight">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,2" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,16,0,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="Right">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,2" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="BottomRight">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,0,16,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="Bottom">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,2,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="BottomLeft">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,2,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,0,0,16" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="SingleRowLeft">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,2,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="16,0,0,16" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="SingleRowRight">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,16,16,0" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
C#
public enum CellType {
TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left,
SingleRowLeft, SingleRowRight, Inner
}
public class CellTypeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var itemsCount = System.Convert.ToInt32(values[0]);
var itemIndex = System.Convert.ToInt32(values[1]);
var columnCount = System.Convert.ToInt32(values[2]);
int rowCount = itemsCount / columnCount;
if (itemsCount % columnCount > 0)
++rowCount;
int lowerRightIndex = (rowCount * columnCount) - 1;
int lowerLeftIndex = (rowCount - 1) * columnCount;
if (itemIndex == 0)
{
return (rowCount == 1) ? CellType.SingleRowLeft : CellType.TopLeft;
}
else if (itemIndex == columnCount - 1)
{
return (rowCount == 1) ? CellType.SingleRowRight : CellType.TopRight;
}
else if (itemIndex < columnCount)
return CellType.Top;
else if (itemIndex == lowerRightIndex)
return CellType.BottomRight;
else if ((itemIndex + 1) % columnCount == 0)
return CellType.Right;
else if (itemIndex == lowerLeftIndex)
return CellType.BottomLeft;
else if (itemIndex > lowerLeftIndex)
return CellType.Bottom;
else if (itemIndex % columnCount == 0)
return CellType.Left;
return CellType.Inner;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public static class GridLineListBox
{
public static CellType GetCellType(ListBoxItem obj)
{
return (CellType)obj.GetValue(CellTypeProperty);
}
public static void SetCellType(ListBoxItem obj, CellType value)
{
obj.SetValue(CellTypeProperty, value);
}
public static readonly DependencyProperty CellTypeProperty =
DependencyProperty.RegisterAttached("CellType", typeof(CellType), typeof(GridLineListBox),
new PropertyMetadata((CellType)(-1)));
public static int GetColumnCount(ListBox obj)
{
return (int)obj.GetValue(ColumnCountProperty);
}
public static void SetColumnCount(ListBox obj, int value)
{
obj.SetValue(ColumnCountProperty, value);
}
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached("ColumnCount", typeof(int), typeof(GridLineListBox),
new PropertyMetadata(0, ColumnCount_PropertyChanged));
private static void ColumnCount_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as ListBox;
}
}
示例:
<ListBox
ItemsSource="{Binding CollectionOfStrings}"
Style="{StaticResource GridLineListBox}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Label
Content="{Binding}"
HorizontalAlignment="Center"
/>
<Label
Content="{Binding (local:GridLineListBox.CellType), RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
HorizontalAlignment="Center"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
我有一个元素列表框,我将 ItemPanel 设置为 WrapPanel,因为我希望我的面板每 4 个元素包装一次。
我使用了以下内容:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
现在,我希望我的项目显示如下:
当只有 4 件或更少时:
当超过 4 件时:
我需要将 cornerRadius 应用于四个边缘,无论是否只有一行。
结果这有点痛苦。如果您希望正确参数化角半径和边框厚度,则需要做更多的工作:您需要值转换器来根据需要创建或修改 CornerRadius
和 Thickness
值。
另一种方法是省略触发器并编写两个大型多转换器,用于 Thickness 和 CornerRadius,它们采用与我编写的参数相同的参数,加上 "default" 边界厚度和角半径值,然后分别是 return Thickness
和 CornerRadius
。
<Style TargetType="ListBox" x:Key="GridLineListBox">
<Style.Resources>
<local:CellTypeConverter x:Key="CellTypeConverter" />
</Style.Resources>
<Setter Property="AlternationCount" Value="{x:Static sys:Int32.MaxValue}" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="BorderBrush" Value="SteelBlue" />
<Setter Property="local:GridLineListBox.ColumnCount" Value="6" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="16"
ClipToBounds="True"
>
<ItemsPresenter Margin="-1" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid
Columns="{Binding (local:GridLineListBox.ColumnCount), RelativeSource={RelativeSource AncestorType=ListBox}}"
IsItemsHost="True"
/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<!-- Put this in an attached property so we don't have to copy/paste the whole binding for each trigger -->
<Setter Property="local:GridLineListBox.CellType">
<Setter.Value>
<MultiBinding Converter="{StaticResource CellTypeConverter}">
<Binding Path="Items.Count" RelativeSource="{RelativeSource AncestorType=ListBox}" />
<Binding Path="(ItemsControl.AlternationIndex)" RelativeSource="{RelativeSource Self}" />
<Binding Path="(local:GridLineListBox.ColumnCount)" RelativeSource="{RelativeSource AncestorType=ListBox}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0" />
<Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=ListBox}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<!--
Negative right/bottom margin because I'm getting a gap with
SnapToDevicePixels and I'm too lazy to figure out the real reason.
-->
<Border
x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0,0,2,2"
Background="{TemplateBinding Background}"
ClipToBounds="True"
Margin="-1"
>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="local:GridLineListBox.CellType" Value="TopLeft">
<Setter TargetName="Bd" Property="CornerRadius" Value="16,0,0,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="TopRight">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,2" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,16,0,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="Right">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,2" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="BottomRight">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,0,16,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="Bottom">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,2,0" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="BottomLeft">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,2,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,0,0,16" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="SingleRowLeft">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,2,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="16,0,0,16" />
</Trigger>
<Trigger Property="local:GridLineListBox.CellType" Value="SingleRowRight">
<Setter TargetName="Bd" Property="BorderThickness" Value="0,0,0,0" />
<Setter TargetName="Bd" Property="CornerRadius" Value="0,16,16,0" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
C#
public enum CellType {
TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left,
SingleRowLeft, SingleRowRight, Inner
}
public class CellTypeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var itemsCount = System.Convert.ToInt32(values[0]);
var itemIndex = System.Convert.ToInt32(values[1]);
var columnCount = System.Convert.ToInt32(values[2]);
int rowCount = itemsCount / columnCount;
if (itemsCount % columnCount > 0)
++rowCount;
int lowerRightIndex = (rowCount * columnCount) - 1;
int lowerLeftIndex = (rowCount - 1) * columnCount;
if (itemIndex == 0)
{
return (rowCount == 1) ? CellType.SingleRowLeft : CellType.TopLeft;
}
else if (itemIndex == columnCount - 1)
{
return (rowCount == 1) ? CellType.SingleRowRight : CellType.TopRight;
}
else if (itemIndex < columnCount)
return CellType.Top;
else if (itemIndex == lowerRightIndex)
return CellType.BottomRight;
else if ((itemIndex + 1) % columnCount == 0)
return CellType.Right;
else if (itemIndex == lowerLeftIndex)
return CellType.BottomLeft;
else if (itemIndex > lowerLeftIndex)
return CellType.Bottom;
else if (itemIndex % columnCount == 0)
return CellType.Left;
return CellType.Inner;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public static class GridLineListBox
{
public static CellType GetCellType(ListBoxItem obj)
{
return (CellType)obj.GetValue(CellTypeProperty);
}
public static void SetCellType(ListBoxItem obj, CellType value)
{
obj.SetValue(CellTypeProperty, value);
}
public static readonly DependencyProperty CellTypeProperty =
DependencyProperty.RegisterAttached("CellType", typeof(CellType), typeof(GridLineListBox),
new PropertyMetadata((CellType)(-1)));
public static int GetColumnCount(ListBox obj)
{
return (int)obj.GetValue(ColumnCountProperty);
}
public static void SetColumnCount(ListBox obj, int value)
{
obj.SetValue(ColumnCountProperty, value);
}
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached("ColumnCount", typeof(int), typeof(GridLineListBox),
new PropertyMetadata(0, ColumnCount_PropertyChanged));
private static void ColumnCount_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as ListBox;
}
}
示例:
<ListBox
ItemsSource="{Binding CollectionOfStrings}"
Style="{StaticResource GridLineListBox}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Label
Content="{Binding}"
HorizontalAlignment="Center"
/>
<Label
Content="{Binding (local:GridLineListBox.CellType), RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
HorizontalAlignment="Center"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>