如何在 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 应用于四个边缘,无论是否只有一行。

结果这有点痛苦。如果您希望正确参数化角半径和边框厚度,则需要做更多的工作:您需要值转换器来根据需要创建或修改 CornerRadiusThickness 值。

另一种方法是省略触发器并编写两个大型多转换器,用于 Thickness 和 CornerRadius,它们采用与我编写的参数相同的参数,加上 "default" 边界厚度和角半径值,然后分别是 return ThicknessCornerRadius

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