WPF 改进 WrapPanel 右侧填充

  • 目的:改进下WrapPanel, 多实现一个空间超出时候的充满效果,可以参考VSTeamgit面板的那个,实现一个 类似git提交代码 最后一个item充满整行的效果。.
WPF 改进 WrapPanel 右侧填充
WPF 改进 WrapPanel 右侧填充

1) WrapPanelFill.cs 代码如下:

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

namespace WrapPanelFillDemo
{
    /// <summary>    
    /// 2023-02-02
    /// WrapPanel改进后,增加了填充式布局,转载请保留博客地址!
    /// 来源:https://www.cnblogs.com/wandia/p/17084881.html
    /// 作者:陈-林-赵-魏
    /// WrapPanel改进,增加了设置元素宽度高度时候填充式铺满
    /// 代码改自 Microsoft WrapPanel 源码
    /// </summary>
    public class WrapPanelFill : WrapPanel
    {
        #region 依赖属性 是否填充式布局
        public bool IsAdaptiveLayout
        {
            get { return (bool)GetValue(IsAdaptiveLayoutProperty); }
            set { SetValue(IsAdaptiveLayoutProperty, value); }
        }
        public static readonly DependencyProperty IsAdaptiveLayoutProperty =
            DependencyProperty.Register("IsAdaptiveLayout", typeof(bool), typeof(WrapPanelFill), new UIPropertyMetadata(false, OnIsAdaptiveLayout_ProperthChanged));

        private static void OnIsAdaptiveLayout_ProperthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            WrapPanelFill fill = d as WrapPanelFill;
            if (fill != null) fill.InvalidateMeasure();
        }
        #endregion

        #region 浮点数比较静态方法
        private const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
        private static bool DoubleAreClose(double value1, double value2)
        {
            //in case they are Infinities (then epsilon check does not work)
            if (value1 == value2)
            {
                return true;
            }
            // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
            double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
            double delta = value1 - value2;
            return -eps < delta && eps > delta;
        }
        private static bool DoubleGreaterThan(double value1, double value2)
        {
            return value1 > value2 && !DoubleAreClose(value1, value2);
        }
        #endregion

        #region 是否设置元素宽度高度
        private bool IsItemWidthSet { get { return !double.IsNaN(this.ItemWidth) && this.ItemWidth > 0; } }
        private bool IsItemHeightSet { get { return !double.IsNaN(this.ItemHeight) && this.ItemHeight > 0; } }
        #endregion

        #region MeasureOverride
        /// <summary>
        ///     <see cref="FrameworkElement.MeasureOverride" />
        /// </summary>
        protected override Size MeasureOverride(Size constraint)
        {
            UVSize uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
            UVSize curLineSize = new UVSize(Orientation);
            UVSize panelSize = new UVSize(Orientation);
            Size childConstraint = GetConstraintSize(constraint);

            UIElementCollection children = InternalChildren;
            for (int i = 0, count = children.Count; i < count; i++)
            {
                UIElement child = children[i];
                if (child == null) continue;
                //Flow passes its own constrint to children
                child.Measure(childConstraint);
                //this is the size of the child in UV space
                UVSize sz = new UVSize(
                    Orientation,
                    (IsItemWidthSet ? childConstraint.Width : child.DesiredSize.Width),
                    (IsItemHeightSet ? childConstraint.Height : child.DesiredSize.Height));
                if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U))
                {   //need to switch to another line
                    panelSize.U = Math.Max(curLineSize.U, panelSize.U);
                    panelSize.V += curLineSize.V;
                    curLineSize = sz;
                    if (DoubleGreaterThan(sz.U, uvConstraint.U))
                    {   //the element is wider then the constrint - give it a separate line 
                        panelSize.U = Math.Max(sz.U, panelSize.U);
                        panelSize.V += sz.V;
                        curLineSize = new UVSize(Orientation);  //用于存放全新1行狂赌
                    }
                }
                else
                {//continue to accumulate a line
                    curLineSize.U += sz.U;
                    curLineSize.V = Math.Max(sz.V, curLineSize.V);
                }
            }
            //the last line size, if any should be added
            panelSize.U = Math.Max(curLineSize.U, panelSize.U);
            panelSize.V += curLineSize.V;
            //go from UV space to W/H space
            return new Size(panelSize.Width, panelSize.Height);
        }
        //得到元素测量时所用宽度
        private Size GetConstraintSize(Size constraint)
        {
            int visibleCount = this.InternalChildren.Cast<UIElement>().Count(p => p != null && p.Visibility != Visibility.Collapsed);

            Size childConstraint = new Size(constraint.Width, constraint.Height);
            if (IsItemWidthSet == true)
            {
                if (Orientation == Orientation.Horizontal && !double.IsInfinity(constraint.Width) && this.IsAdaptiveLayout == true)
                {
                    var count = Math.Floor(constraint.Width / this.ItemWidth);  //元素个数
                    if (visibleCount >= count)
                    {
                        var averageWidth = constraint.Width / Math.Max(count, 1);        //平均宽度
                        childConstraint.Width = averageWidth;
                    }
                    else if (visibleCount > 0)
                    {
                        childConstraint.Width = constraint.Width / visibleCount;
                    }
                }
                else
                {
                    childConstraint.Width = this.ItemWidth;
                }
            }
            if (IsItemHeightSet == true)
            {
                if (Orientation == Orientation.Vertical && !double.IsInfinity(constraint.Height) && this.IsAdaptiveLayout == true)
                {
                    var count = Math.Floor(constraint.Height / this.ItemHeight);  //元素个数
                    if (visibleCount >= count)
                    {
                        var averageHeight = constraint.Height / Math.Max(count, 1);      //平均宽度
                        childConstraint.Height = averageHeight;
                    }
                    else if (visibleCount > 0)
                    {
                        childConstraint.Height = constraint.Height / visibleCount;
                    }
                }
                else
                {
                    childConstraint.Height = this.ItemHeight;
                }
            }
            return childConstraint;
        }
        #endregion

        #region ArrangeOverride
        /// <summary>
        ///     <see cref="FrameworkElement.ArrangeOverride" />
        /// </summary>
        protected override Size ArrangeOverride(Size finalSize)
        {
            int firstInLine = 0;
            double accumulatedV = 0;
            UVSize uvConstraint = new UVSize(Orientation, finalSize.Width, finalSize.Height);
            UVSize curLineSize = new UVSize(Orientation);
            Size childConstraint = GetConstraintSize(finalSize);
            bool useItemU = (Orientation == Orientation.Horizontal ? IsItemWidthSet : IsItemHeightSet);
            double itemU = (Orientation == Orientation.Horizontal ? childConstraint.Width : childConstraint.Height);


            UIElementCollection children = InternalChildren;
            for (int i = 0, count = children.Count; i < count; i++)
            {
                UIElement child = children[i];
                if (child == null) continue;

                UVSize sz = new UVSize(
                    Orientation,
                    (IsItemWidthSet ? childConstraint.Width : child.DesiredSize.Width),
                    (IsItemHeightSet ? childConstraint.Height : child.DesiredSize.Height));
                if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U))
                {    //need to switch to another line
                    arrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
                    accumulatedV += curLineSize.V;

                    curLineSize = sz;
                    if (DoubleGreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constraint - give it a separate line
                    {
                        //switch to next line which only contain one element
                        arrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
                        accumulatedV += sz.V;
                        curLineSize = new UVSize(Orientation);
                    }
                    firstInLine = i;
                }
                else
                {   //continue to accumulate a line
                    curLineSize.U += sz.U;
                    curLineSize.V = Math.Max(sz.V, curLineSize.V);
                }
            }

            //arrange the last line, if any
            if (firstInLine < children.Count)
            {
                arrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
            }

            return finalSize;
        }

        private void arrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
        {
            double u = 0;
            bool isHorizontal = (Orientation == Orientation.Horizontal);
            UIElementCollection children = InternalChildren;
            for (int i = start; i < end; i++)
            {
                UIElement child = children[i];
                if (child != null)
                {
                    UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
                    double layoutSlotU = (useItemU ? itemU : childSize.U);
                    child.Arrange(new Rect(
                            (isHorizontal ? u : v),
                            (isHorizontal ? v : u),
                            (isHorizontal ? layoutSlotU : lineV),
                            (isHorizontal ? lineV : layoutSlotU)));
                    u += layoutSlotU;
                }
            }
        }
        #endregion

        #region Private Struct
        private struct UVSize
        {
            internal UVSize(Orientation orientation, double width, double height)
            {
                U = V = 0d;
                _orientation = orientation;
                Width = width;
                Height = height;
            }
            internal UVSize(Orientation orientation)
            {
                U = V = 0d;
                _orientation = orientation;
            }
            internal double U;
            internal double V;
            private Orientation _orientation;
            internal double Width
            {
                get { return (_orientation == Orientation.Horizontal ? U : V); }
                set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
            }
            internal double Height
            {
                get { return (_orientation == Orientation.Horizontal ? V : U); }
                set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
            }
        }
        #endregion
    }
}

2) MainWindow.xaml 代码如下:

<ws:Window x:Class="WrapPanelFillDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ws="https://github.com/WPFDevelopersOrg/WPFDevelopers"
        xmlns:local="clr-namespace:WrapPanelFillDemo"
        mc:Ignorable="d"
        Title="Team Explorer-Home" Height="414" Width="324">
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Margin" Value="2 0"/>
        </Style>
    </Window.Resources>
    <TabControl>
        <TabItem Header="情景1(高度充满)">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <TextBlock FontWeight="Bold" Text="元素宽度高度设置时 填充式 WrapPanel(陈-林-赵-魏)" Margin="0,5"/>
                <WrapPanel Grid.Row="1" Margin="0,2">
                    <CheckBox x:Name="ChkAdaptiveLayout" IsChecked="True" Content="填充式布局" 
                      VerticalAlignment="Center" VerticalContentAlignment="Center"
                      Margin="0,0,50,0"/>
                    <CheckBox x:Name="ChkVertical" IsChecked="False" Content="垂直布局" 
                      VerticalAlignment="Center" VerticalContentAlignment="Center"
                      Margin="0,0,50,0"/>
                    <TextBlock Text="元素宽度" VerticalAlignment="Center"/>
                    <TextBox x:Name="txtItemWidth" MinWidth="120" VerticalContentAlignment="Center"
                     Text="{Binding ElementName=WrapPanelFill,Path=ItemWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    <TextBlock Text="元素高度" VerticalAlignment="Center"/>
                    <TextBox x:Name="txtItemHeight" MinWidth="120"  VerticalContentAlignment="Center"
                     Text="{Binding ElementName=WrapPanelFill,Path=ItemHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    <TextBlock Text="输入NaN代表宽度不确定"/>
                </WrapPanel>
                <local:WrapPanelFill x:Name="WrapPanelFill" IsAdaptiveLayout="{Binding ElementName=ChkAdaptiveLayout,Path=IsChecked,Mode=TwoWay}" ItemWidth="150" Grid.Row="3"
                             Orientation="Horizontal" >
                    <DockPanel MinHeight="35" Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#F05033" Width="4"/>
                        <Grid Margin="5">
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB8B2B2" />
                        </Grid>
                        <TextBlock Text="Changed" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#F05033" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFDCA1A1" />
                        </Grid>
                        <TextBlock Text="Branches" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#FF3333F0" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB5B9BD" />
                        </Grid>
                        <TextBlock Text="Sync" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#FF616161" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF3E3E3E" />
                        </Grid>
                        <TextBlock Text="Setting" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FF555E85" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#FF616161" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF93B6AA" />
                        </Grid>
                        <TextBlock Text="Others" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                </local:WrapPanelFill>
                <Border Grid.Row="3" BorderThickness="1" BorderBrush="Red" Margin="0,4" Visibility="Collapsed">
                    <Label Content="这里是VS扩展git源码提交部分界面不实现!"/>
                </Border>
            </Grid>
        </TabItem>
        <TabItem Header="情景2(高度Auto)">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <TextBlock FontWeight="Bold" Text="元素宽度高度设置时 填充式 WrapPanel(陈-林-赵-魏)" Margin="0,5"/>
                <WrapPanel Grid.Row="1" Margin="0,2">
                    <CheckBox x:Name="ChkAdaptiveLayout2" IsChecked="True" Content="填充式布局" 
                      VerticalAlignment="Center" VerticalContentAlignment="Center"
                      Margin="0,0,50,0"/>
                    <CheckBox x:Name="ChkVertical2" IsChecked="False" Content="垂直布局" 
                      VerticalAlignment="Center" VerticalContentAlignment="Center"
                      Margin="0,0,50,0"/>
                    <TextBlock Text="元素宽度" VerticalAlignment="Center"/>
                    <TextBox x:Name="txtItemWidth2" MinWidth="120"  VerticalContentAlignment="Center"
                     Text="{Binding ElementName=WrapPanelFill2,Path=ItemWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    <TextBlock Text="元素高度" VerticalAlignment="Center"/>
                    <TextBox x:Name="txtItemHeight2" MinWidth="120"  VerticalContentAlignment="Center"
                     Text="{Binding ElementName=WrapPanelFill2,Path=ItemHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                </WrapPanel>
                <local:WrapPanelFill x:Name="WrapPanelFill2" IsAdaptiveLayout="{Binding ElementName=ChkAdaptiveLayout2,Path=IsChecked,Mode=TwoWay}" ItemWidth="150" Grid.Row="2"
                             Orientation="Horizontal" ItemHeight="40">
                    <DockPanel MinHeight="35" Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#F05033" Width="4"/>
                        <Grid Margin="5">
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB8B2B2" />
                        </Grid>
                        <TextBlock Text="Changed" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#F05033" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFDCA1A1" />
                        </Grid>
                        <TextBlock Text="Branches" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#FF3333F0" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB5B9BD" />
                        </Grid>
                        <TextBlock Text="Sync" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FFCFCFCF" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#FF616161" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF3E3E3E" />
                        </Grid>
                        <TextBlock Text="Setting" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                    <DockPanel MinHeight="35"  Margin="0,0,5,2">
                        <DockPanel.Background>
                            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                <GradientStop Color="#FF555E85" Offset="1"/>
                            </LinearGradientBrush>
                        </DockPanel.Background>
                        <Rectangle Fill="#FF616161" Width="4"/>
                        <Grid Margin="5" >
                            <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                            <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF93B6AA" />
                        </Grid>
                        <TextBlock Text="Others" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                    </DockPanel>
                </local:WrapPanelFill>
                <Border Grid.Row="3" BorderThickness="1" BorderBrush="Red" Margin="0,4" Visibility="Visible">
                    <Label Content="这里是VS扩展git源码提交部分界面不实现!"/>
                </Border>
            </Grid>
        </TabItem>
        <TabItem Header="情景3(ScrollView中表现)">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <TextBlock FontWeight="Bold" Text="元素宽度高度设置时 填充式 WrapPanel(陈-林-赵-魏)" Margin="0,5"/>
                <WrapPanel Grid.Row="1" Margin="0,2">
                    <CheckBox x:Name="ChkAdaptiveLayout3" IsChecked="True" Content="填充式布局" 
                      VerticalAlignment="Center" VerticalContentAlignment="Center"
                      Margin="0,0,50,0"/>
                    <CheckBox x:Name="ChkVertical3" IsChecked="False" Content="垂直布局" 
                      VerticalAlignment="Center" VerticalContentAlignment="Center"
                      Margin="0,0,50,0"/>
                    <TextBlock Text="元素宽度" VerticalAlignment="Center"/>
                    <TextBox x:Name="txtItemWidth3" MinWidth="120"  VerticalContentAlignment="Center"
                     Text="{Binding ElementName=WrapPanelFill3,Path=ItemWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    <TextBlock Text="元素高度" VerticalAlignment="Center"/>
                    <TextBox x:Name="txtItemHeight3" MinWidth="120"  VerticalContentAlignment="Center"
                     Text="{Binding ElementName=WrapPanelFill3,Path=ItemHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    <TextBlock Text="输入NaN代表宽度不确定"/>
                </WrapPanel>
                <Border  Grid.Row="3" Margin="40" BorderBrush="Red" BorderThickness="1">
                    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                        <local:WrapPanelFill x:Name="WrapPanelFill3" IsAdaptiveLayout="{Binding ElementName=ChkAdaptiveLayout3,Path=IsChecked,Mode=TwoWay}" ItemWidth="150" Grid.Row="3"
                             Orientation="Horizontal" >
                            <DockPanel MinHeight="35" Margin="0,0,5,2">
                                <DockPanel.Background>
                                    <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                        <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                        <GradientStop Color="#FFCFCFCF" Offset="1"/>
                                    </LinearGradientBrush>
                                </DockPanel.Background>
                                <Rectangle Fill="#F05033" Width="4"/>
                                <Grid Margin="5">
                                    <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                                    <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB8B2B2" />
                                </Grid>
                                <TextBlock Text="Changed" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                            </DockPanel>
                            <DockPanel MinHeight="35"  Margin="0,0,5,2">
                                <DockPanel.Background>
                                    <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                        <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                        <GradientStop Color="#FFCFCFCF" Offset="1"/>
                                    </LinearGradientBrush>
                                </DockPanel.Background>
                                <Rectangle Fill="#F05033" Width="4"/>
                                <Grid Margin="5" >
                                    <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                                    <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFDCA1A1" />
                                </Grid>
                                <TextBlock Text="Branches" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                            </DockPanel>
                            <DockPanel MinHeight="35"  Margin="0,0,5,2">
                                <DockPanel.Background>
                                    <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                        <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                        <GradientStop Color="#FFCFCFCF" Offset="1"/>
                                    </LinearGradientBrush>
                                </DockPanel.Background>
                                <Rectangle Fill="#FF3333F0" Width="4"/>
                                <Grid Margin="5" >
                                    <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                                    <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB5B9BD" />
                                </Grid>
                                <TextBlock Text="Sync" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                            </DockPanel>
                            <DockPanel MinHeight="35"  Margin="0,0,5,2">
                                <DockPanel.Background>
                                    <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                        <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                        <GradientStop Color="#FFCFCFCF" Offset="1"/>
                                    </LinearGradientBrush>
                                </DockPanel.Background>
                                <Rectangle Fill="#FF616161" Width="4"/>
                                <Grid Margin="5" >
                                    <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                                    <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF3E3E3E" />
                                </Grid>
                                <TextBlock Text="Setting" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                            </DockPanel>
                            <DockPanel MinHeight="35"  Margin="0,0,5,2">
                                <DockPanel.Background>
                                    <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                                        <GradientStop Color="#FFA6A6A6" Offset="0"/>
                                        <GradientStop Color="#FF555E85" Offset="1"/>
                                    </LinearGradientBrush>
                                </DockPanel.Background>
                                <Rectangle Fill="#FF616161" Width="4"/>
                                <Grid Margin="5" >
                                    <Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
                                    <Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF93B6AA" />
                                </Grid>
                                <TextBlock Text="Others" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
                            </DockPanel>
                        </local:WrapPanelFill>
                    </ScrollViewer>
                </Border>
            </Grid>
        </TabItem>
    </TabControl>
</ws:Window>

WPF 改进 WrapPanel 右侧填充