简体   繁体   中英

How to draw a WrapPanel in a specific way in XAML / WPF

I have a listbox of elements, and I set the ItemPanel as a WrapPanel as I want my Panel to wrap every 4 elements.

I used the following:

 <ListBox.ItemsPanel>
     <ItemsPanelTemplate>
         <WrapPanel Orientation="Horizontal" IsItemsHost="True" />
     </ItemsPanelTemplate>
 </ListBox.ItemsPanel> 

Now, I want thay my items will be displayed as follows:

When only 4 Items or less: 在此处输入图片说明

When more than 4 Items: 在此处输入图片说明

I need that the cornerRadius will be applied on the four edges whether it's only one row or not.

This turned out to be a bit painful. If you want the corner radius and the border thickness to be properly parameterized, that'll take more work: You'd need value converters to create or modify CornerRadius and Thickness values as needed.

Another approach would have been to omit the triggers and write two big multiconverters, for Thickness and CornerRadius, that take the same parameters as the one I wrote, plus the "default" border thickness and corner radius values, and then return Thickness and CornerRadius respectively.

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

Example:

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

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM