一个优化奇怪的 WrapPanel

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个优化奇怪的 WrapPanel相关的知识,希望对你有一定的参考价值。

 一个优化奇怪的 WrapPanel

本文经原作者授权以原创方式二次分享,欢迎转载、分享。

一个优化奇怪的 WrapPanel

作者:陈-林-赵-魏

原文链接[1]:https://www.cnblogs.com/wandia/p/17092221.html

  • FixToRB 附加属性,固定到【右边(水平模式)|底边(垂直模式)】,这将会导致换行。

  • IsFillHorizontal 依赖属性 是否充满。

  • DoubleGreaterThan 浮点数比较静态方法。

  • MeasureOverride 测量所需元素空间大小。

  • ArrangeOverride 排列元素。

  • UVSize 内部结构体,用于 Orientation 方向上存值及其比较。

1) WrapPanelEx.cs 代码如下:

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

namespace WrapPanelEx.Controls

    /// <summary>    
    /// WrapPanel改进后,增加了填充式布局,转载请保留博客地址!
    /// 来源:https://www.cnblogs.com/wandia/p/17092221.html
    /// 作者:陈-林-赵-魏
    /// WrapPanel改进,增加了设置元素宽度高度时候填充式铺满
    /// 代码改自 Microsoft WrapPanel 源码
    /// </summary>
    public class WrapPanelEx : WrapPanel
    
        #region 附加属性,固定到【右边(水平模式)|底边(垂直模式)】,这将会导致换行
        public static readonly DependencyProperty FixToRBProperty = DependencyProperty.RegisterAttached("FixToRB",
           typeof(bool),
           typeof(WrapPanelEx),
           new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
        public static void SetFixToRB(UIElement element, bool value)
        
            element.SetValue(FixToRBProperty, value);
        
        public static bool GetIsFixToRB(UIElement element)
        
            return (bool)element.GetValue(FixToRBProperty);
        
        #endregion

        #region 依赖属性 是否充满
        public bool IsFillHorizontal
        
            get  return (bool)GetValue(IsFillHorizontalProperty); 
            set  SetValue(IsFillHorizontalProperty, value); 
        
        public static readonly DependencyProperty IsFillHorizontalProperty =
            DependencyProperty.Register("IsFillHorizontal", typeof(bool), typeof(WrapPanelEx),
                new UIPropertyMetadata(false, OnIsFill_ProperthChanged));
        public bool IsFillVertical
        
            get  return (bool)GetValue(IsFillVerticalProperty); 
            set  SetValue(IsFillVerticalProperty, value); 
        
        public static readonly DependencyProperty IsFillVerticalProperty = DependencyProperty.Register(
            "IsFillVertical", typeof(bool), typeof(WrapPanelEx),
             new UIPropertyMetadata(false, OnIsFill_ProperthChanged));

        private static void OnIsFill_ProperthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        
            WrapPanelEx fill = d as WrapPanelEx;
            if (fill != null) fill.InvalidateMeasure();
        
        #endregion

        #region 浮点数比较静态方法
        private const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
        //计算2个浮点数是否接近
        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 测量所需元素空间大小
        /// <summary>
        ///     <see cref="FrameworkElement.MeasureOverride" />
        /// </summary>
        protected override Size MeasureOverride(Size availbleSize)
           //设否元素宽的设置,是否元素高度设置
            bool isItemWidthSet = !double.IsNaN(this.ItemWidth) && this.ItemWidth > 0;
            bool isItemHeightSet = !double.IsNaN(this.ItemHeight) && this.ItemHeight > 0;

            //非FixToRB=True元素使用测量需求空间Size
            Size childConstraint = new Size(
                (isItemWidthSet ? this.ItemWidth : availbleSize.Width),
                (isItemHeightSet ? this.ItemHeight : availbleSize.Height));
            //FixToRB=True元素使用测量需求空间Size
            Size childFixConstraint = new Size(availbleSize.Width, availbleSize.Height);
            if (Orientation == Orientation.Horizontal && isItemHeightSet)
            
                childFixConstraint.Height = this.ItemHeight;
            
            if (Orientation == Orientation.Vertical && isItemWidthSet)
            
                childConstraint.Width = this.ItemWidth;
            

            //这个给非空间测量大小
            UVSize ItemSetSize = new UVSize(this.Orientation,
                (isItemWidthSet ? this.ItemWidth : 0),
                (isItemHeightSet ? this.ItemHeight : 0));
            //给定的空间大小测量UvSize,用于没有ItemWidth和ItemHeight时候测量元素空间大小, FixToRB=True的元素也使用这个
            UVSize uvConstraint = new UVSize(Orientation, availbleSize.Width, availbleSize.Height);

            UVSize curLineSize = new UVSize(Orientation);           //将元素按照水平/垂直排列的方式得出同一行/列所需的空间需求
            UVSize desireResultSize = new UVSize(Orientation);      //计算处此WrapPanelEx所需的空间大小需求结果

            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();
                if (WrapPanelEx.GetIsFixToRB(child) == true)
                   //此元素需要设置到固定靠右/底的型为操作,测量元素大小时需要放开
                    child.Measure(childFixConstraint);
                    sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
                    //主要是我对与这个固定住的元素的需求宽度高度按照那个标准有点头疼,干脆放开用最大控件算了
                    if (sz.U > 0 && ItemSetSize.U > 0)
                    
                        if (sz.U < ItemSetSize.U)
                           //保持比例
                            sz.U = ItemSetSize.U;
                        
                        else
                           //设置了同方向中元素的长度,所以这里要按照比例
                            //double lengthCount = Math.Ceiling(sz.U / ItemSetSize.U);
                            //sz.U = lengthCount * ItemSetSize.U;
                            sz.U = Math.Min(sz.U, uvConstraint.U);//这里防止意外
                        
                    
                    if (sz.V > 0 && ItemSetSize.V > 0 && sz.V < ItemSetSize.V)
                       //设置了崔志方向元素长度,如果此元素空间需求小于,则按照ItemSetSize.V
                        sz.V = ItemSetSize.V;
                    

                    if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U))
                       //当前同一 列/行 如果容纳 此元素空间将超出
                        desireResultSize.U = Math.Max(curLineSize.U, desireResultSize.U);   //取Orientation方向最大长度
                        desireResultSize.V += curLineSize.V;                                //累加垂直Orientation方向的长度

                        curLineSize = sz;
                        //当前元素需要启1个新行
                        desireResultSize.U = Math.Max(curLineSize.U, desireResultSize.U);   //取Orientation方向最大长度
                        desireResultSize.V += curLineSize.V;                                //累加垂直Orientation方向的长度
                    
                    else
                       //这里是元素空间足够 填充式布局
                        curLineSize.U += sz.U;
                        curLineSize.V = Math.Max(sz.V, curLineSize.V);

                        desireResultSize.U = Math.Max(curLineSize.U, desireResultSize.U);   //取Orientation方向最大长度
                        desireResultSize.V += curLineSize.V;                                //累加垂直Orientation方向的长度
                    

                    //下一个可能是换行元素....


                    curLineSize = new UVSize(Orientation);                                  //用于存放全新1行
                
                else
                
                    child.Measure(childConstraint); //如果设置元素宽度或高度, 其高度宽度尽可能按照设置值给定
                    sz = new UVSize(Orientation,
                        (isItemWidthSet ? this.ItemWidth : child.DesiredSize.Width),
                        (isItemHeightSet ? this.ItemHeight : child.DesiredSize.Height));

                    if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U))
                        //当前同一 列/行 如果容纳 此元素空间将超出
                        desireResultSize.U = Math.Max(curLineSize.U, desireResultSize.U); //取Orientation方向最大长度
                        desireResultSize.V += curLineSize.V;                              //累加垂直Orientation方向的长度
                        curLineSize = sz;                                   //当前行宽度
                        if (DoubleGreaterThan(sz.U, uvConstraint.U))
                           //此元素同Orientation方向长度大于给定约束长度,则当前元素为一行
                            desireResultSize.U = Math.Max(sz.U, desireResultSize.U);
                            desireResultSize.V += sz.V;
                            curLineSize = new UVSize(Orientation);  //用于存放全新1行狂赌
                        
                    
                    else
                       //当前同一 列/行 元素空间足够,可以容纳当前元素
                        curLineSize.U += sz.U;
                        curLineSize.V = Math.Max(sz.V, curLineSize.V);
                    
                
            

            //the last line size, if any should be added  因为超出才累计,最后1行不超出,需要重新添加
            desireResultSize.U = Math.Max(curLineSize.U, desireResultSize.U);
            desireResultSize.V += curLineSize.V;
            //go from UV space to W/H space               将UV值转换成 高度/宽度值
            return new Size(desireResultSize.Width, desireResultSize.Height);
        
        #endregion

        #region 排列元素
        /*
            var count = Math.Floor(constraint.Width / this.ItemWidth);  //元素个数
            var averageWidth = constraint.Width / Math.Max(count, 1);   //平均宽度
            childConstraint.Width = Math.Max(averageWidth, this.ItemWidth);
        */
        /// <summary>
        ///     <see cref="FrameworkElement.ArrangeOverride" />
        /// </summary>
        protected override Size ArrangeOverride(Size finalSize)
        
            //设否元素宽的设置,是否元素高度设置
            bool isItemWidthSet = !double.IsNaN(this.ItemWidth) && this.ItemWidth > 0;
            bool isItemHeightSet = !double.IsNaN(this.ItemHeight) && this.ItemHeight > 0;

            //这个给非空间测量大小
            UVSize ItemSetSize = new UVSize(this.Orientation,
                (isItemWidthSet ? this.ItemWidth : 0),
                (isItemHeightSet ? this.ItemHeight : 0));
            //给定的空间大小测量UvSize,用于没有ItemWidth和ItemHeight时候测量元素空间大小, FixToRB=True的元素也使用这个
            UVSize uvConstraint = new UVSize(Orientation, finalSize.Width, finalSize.Height);


            //用于存放同一方向的元素列/行 集合
            List<UVCollection> lineUiCollection = new List<UVCollection>();
            #region 得到同一方向元素集合的集合
            //if (lineUiCollection != null)   //写一个If只是用于减少外层变量,反感的请将if注释
            
                //当前元素集合行/列
                UVCollection curLineUis = new UVCollection(this.Orientation, ItemSetSize);
                //遍历内部元素
                UIElementCollection children = InternalChildren;        //拿到内部元素,用于遍历
                for (int i = 0, count = children.Count; i < count; i++)
                
                    UIElement child = children[i];               //此元素/当前元素
                    if (child == null || child.Visibility == Visibility.Collapsed) continue;

                    UVSize sz = new UVSize();
                    if (WrapPanelEx.GetIsFixToRB(child) == true)
                       //此元素需要设置到固定靠右/底的型为操作,测量元素大小时需要放开
                        sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
                        double lengthCount = 1;
                        if (sz.U > 0 && ItemSetSize.U > 0)
                        
                            if (sz.U < ItemSetSize.U)
                               //保持比例
                                sz.U = ItemSetSize.U;
                            
                            else
                               //设置了同方向中元素的长度,所以这里要按照比例
                                lengthCount = Math.Ceiling(sz.U / ItemSetSize.U);
                                //sz.U = lengthCount * ItemSetSize.U;
                                sz.U = Math.Min(sz.U, uvConstraint.U);
                            
                        
                        if (sz.V > 0 && ItemSetSize.V > 0 && sz.V < ItemSetSize.V)
                           //设置了崔志方向元素长度,如果此元素空间需求小于,则按照ItemSetSize.V
                            sz.V = ItemSetSize.V;
                        

                        if (DoubleGreaterThan(curLineUis.TotalU + sz.U, uvConstraint.U))
                           //当前同一 列/行 如果容纳 此元素空间将超出
                            if (curLineUis.Count > 0)
                            
                                lineUiCollection.Add(curLineUis);
                            
                            curLineUis = new UVCollection(Orientation, ItemSetSize);

                            curLineUis.Add(child, sz, Convert.ToInt32(lengthCount));
                        
                        else
                           //这里是元素空间足够
                            curLineUis.Add(child, sz, Convert.ToInt32(lengthCount));
                        
                        lineUiCollection.Add(curLineUis);

                        //下一个可能是换行元素....不管了以后闲得蛋疼再弄吧
                        curLineUis = new UVCollection(Orientation, ItemSetSize);
                    
                    else
                    
                        sz = new UVSize(Orientation,
                            (isItemWidthSet ? this.ItemWidth : child.DesiredSize.Width),
                            (isItemHeightSet ? this.ItemHeight : child.DesiredSize.Height));

                        if (DoubleGreaterThan(curLineUis.TotalU + sz.U, uvConstraint.U))
                            //当前同一 列/行 如果容纳 此元素空间将超出
                            if (curLineUis.Count > 0)
                            
                                lineUiCollection.Add(curLineUis);
                            
                            curLineUis = new UVCollection(Orientation, ItemSetSize);
                            curLineUis.Add(child, sz);
                            if (DoubleGreaterThan(sz.U, uvConstraint.U))
                            
                                lineUiCollection.Add(curLineUis);
                                curLineUis = new UVCollection(Orientation, ItemSetSize);
                            
                        
                        else
                           //空间足够
                            curLineUis.Add(child, sz);
                        
                    
                
                if (curLineUis.Count > 0 && !lineUiCollection.Contains(curLineUis))
                
                    lineUiCollection.Add(curLineUis);
                
            
            #endregion



            bool isFillU = false;
            bool isFillV = false;
            switch (this.Orientation)
            
                case Orientation.Horizontal:
                    isFillU = this.IsFillHorizontal;
                    isFillV = this.IsFillVertical;
                    break;
                case Orientation.Vertical:
                    isFillU = this.IsFillVertical;
                    isFillV = this.IsFillHorizontal;
                    break;
            


            if (lineUiCollection.Count > 0)
            
                double accumulatedV = 0;
                double adaptULength = 0;
                bool isAdaptV = false;
                double adaptVLength = 0;
                if (isFillU == true)
                
                    if (ItemSetSize.U > 0)
                    
                        int maxElementCount = lineUiCollection.Max(uiSet => uiSet.UiCollection.Sum(p => p.Value.ULengthCount));
                        adaptULength = (uvConstraint.U - maxElementCount * ItemSetSize.U) / maxElementCount;
                        adaptULength = Math.Max(adaptULength, 0);
                    
                
                if (isFillV == true)
                
                    if (ItemSetSize.V > 0)
                    
                        isAdaptV = true;
                        adaptVLength = uvConstraint.V / lineUiCollection.Count;
                    
                

                bool isHorizontal = (Orientation == Orientation.Horizontal);
                foreach (UVCollection lineUvCollection in lineUiCollection)
                
                    double u = 0;
                    List<UIElement> lineUiEles = lineUvCollection.UiCollection.Keys.ToList();
                    double linevV = isAdaptV ? adaptVLength : lineUvCollection.LineV;
                    for (int i = 0; i < lineUiEles.Count; i++)
                    
                        UIElement child = lineUiEles[i];
                        UVLengthSize childSize = lineUvCollection.UiCollection[child];


                        double layoutSlotU = childSize.UVSize.U + childSize.ULengthCount * adaptULength;
                        double layoutSlotV = isAdaptV ? linevV : childSize.UVSize.V;
                        if (WrapPanelEx.GetIsFixToRB(child) == false)
                        
                            child.Arrange(new Rect(
                              (isHorizontal ? u : accumulatedV),
                              (isHorizontal ? accumulatedV : u),
                              (isHorizontal ? layoutSlotU : layoutSlotV),
                              (isHorizontal ? layoutSlotV : layoutSlotU)));
                        
                        else
                        
                            if (ItemSetSize.U > 0)
                               //说明同方向有宽度设置,这里尽量按照ItemULength保持
                                layoutSlotU = childSize.ULengthCount * ItemSetSize.U + childSize.ULengthCount * adaptULength;

                                double leaveULength = uvConstraint.U - u;
                                layoutSlotU = Math.Min(leaveULength, layoutSlotU);
                            
                            child.Arrange(new Rect(
                              (isHorizontal ? Math.Max(0, (uvConstraint.U - layoutSlotU)) : accumulatedV),
                              (isHorizontal ? accumulatedV : Math.Max(0, (uvConstraint.U - layoutSlotU))),
                              (isHorizontal ? layoutSlotU : layoutSlotV),
                              (isHorizontal ? layoutSlotV : layoutSlotU)));
                        
                        u += layoutSlotU;
                    
                    accumulatedV += linevV;
                    lineUiEles.Clear();
                
            
            lineUiCollection.ForEach(col => col.Dispose());
            lineUiCollection.Clear();
            return finalSize;
        
        #endregion

        #region 内部结构体,用于 Orientation 方向上存值及其比较 
        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; 
            
        

        private class UVLengthSize
        
            public UVSize UVSize  get; set; 
            public int ULengthCount  get; set; 
            public UVLengthSize(UVSize uvSize, int uLengthCount)
            
                this.UVSize = uvSize;
                this.ULengthCount = uLengthCount;
            
        
        /// <summary>
        /// 用于存放同一行/列的元素
        /// </summary>
        private class UVCollection : IDisposable
        
            public Dictionary<UIElement, UVLengthSize> UiCollection  get; private set; 
            private UVSize LineDesireUvSize;
            private UVSize ItemSetSize;

            public UVCollection(Orientation orientation, UVSize ItemSetSize)
            
                this.UiCollection = new Dictionary<UIElement, UVLengthSize>();
                LineDesireUvSize = new UVSize(orientation);
                this.ItemSetSize = ItemSetSize;
            
            public double TotalU
            
                get
                
                    return LineDesireUvSize.U;
                
            
            //垂直方向长度
            public double LineV
            
                get
                
                    return LineDesireUvSize.V;
                
            

            public void Add(UIElement element, UVSize childSize, int itemULength = 1)
            
                if (this.UiCollection.ContainsKey(element))
                    throw new InvalidOperationException("元素已存在,不可重复添加");

                this.UiCollection[element] = new UVLengthSize(childSize, itemULength);
                LineDesireUvSize.U += childSize.U;
                LineDesireUvSize.V = Math.Max(LineDesireUvSize.V, childSize.V);
            

            public int Count
            
                get
                
                    return this.UiCollection.Count;
                
            
            public void Dispose()
            
                if (this.UiCollection != null)
                
                    this.UiCollection.Clear();
                
                this.UiCollection = null;
            
        
        #endregion
    

2) MainWindow.xaml 代码如下:

<wd:Window x:Class="WrapPanelEx.Demo.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:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
        xmlns:local="clr-namespace:WrapPanelEx.Demo"
        xmlns:Controls="clr-namespace:WrapPanelEx.Controls"
        mc:Ignorable="d"
        Title="陈-林-赵-魏 WrapPanelEx" Height="350" Width="525">
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Foreground" Value="Black"/>
        </Style>
    </Window.Resources>
    <Grid Margin="4">
        <DockPanel>
            <TextBlock Text="一个优化奇怪的WrapPanel" DockPanel.Dock="Top" FontWeight="Bold"/>
            <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                <TextBlock Text="排列方式" VerticalAlignment="Center"/>
                <ComboBox x:Name="CmbOrientation" Width="90" SelectedIndex="1">
                    <Orientation>Vertical</Orientation>
                    <Orientation>Horizontal</Orientation>
                </ComboBox>
                <TextBlock Text="水平滚动条" VerticalAlignment="Center"/>
                <ComboBox x:Name="ScrollViewHVisb" Width="90" SelectedIndex="0">
                    <ScrollBarVisibility>Auto</ScrollBarVisibility>
                    <ScrollBarVisibility>Disabled</ScrollBarVisibility>
                    <ScrollBarVisibility>Hidden</ScrollBarVisibility>
                    <ScrollBarVisibility>Visible</ScrollBarVisibility>
                </ComboBox>
                <TextBlock Text="垂直滚动条" VerticalAlignment="Center"/>
                <ComboBox x:Name="ScrollViewVVisb" Width="90" SelectedIndex="0">
                    <ScrollBarVisibility>Auto</ScrollBarVisibility>
                    <ScrollBarVisibility>Disabled</ScrollBarVisibility>
                    <ScrollBarVisibility>Hidden</ScrollBarVisibility>
                    <ScrollBarVisibility>Visible</ScrollBarVisibility>
                </ComboBox>
            </StackPanel>
            <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                <CheckBox x:Name="chkFillWidth" IsChecked="True" Content="水平平铺" 
                        VerticalAlignment="Center" VerticalContentAlignment="Center"
                        Margin="0,0,10,0"/>
                <TextBlock Text="宽度" VerticalAlignment="Center"/>
                <Grid>
                    <Rectangle x:Name="Rect1" Width="Auto" Visibility="Collapsed"/>
                    <TextBox x:Name="txtItemWidth" MinWidth="60" VerticalContentAlignment="Center"
                     Text="Binding ElementName=Rect1,Path=Width,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged"/>
                </Grid>
                <CheckBox x:Name="chkFillHeight" IsChecked="True" Content="垂直平铺" 
                        VerticalAlignment="Center" VerticalContentAlignment="Center"
                        Margin="0,0,10,0"/>
                <TextBlock Text="高度" VerticalAlignment="Center"/>
                <Grid>
                    <TextBox x:Name="txtItemHeight" MinWidth="60" VerticalContentAlignment="Center"
                     Text="Binding ElementName=Rect1,Path=Height,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged"/>
                </Grid>
                <TextBlock Text="* 数字可填入NaN" Foreground="Red" VerticalAlignment="Center"/>
            </StackPanel>
            <TabControl>
                <TabItem Header="平铺Demo">
                    <TabControl>
                        <TabItem Header="情景1(高度充满)">
                            <Controls:WrapPanelEx x:Name="WrapPanelFill"  Grid.Row="3"
                                        IsFillHorizontal="Binding ElementName=chkFillWidth,Path=IsChecked,Mode=TwoWay"
                                        IsFillVertical="Binding ElementName=chkFillHeight,Path=IsChecked,Mode=TwoWay"
                                        ItemWidth="Binding ElementName=Rect1,Path=Width"  
                                        ItemHeight="Binding ElementName=Rect1,Path=Height" 
                                        Orientation="Binding ElementName=CmbOrientation,Path=SelectedValue" >
                                <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>
                            </Controls:WrapPanelEx>
                        </TabItem>
                        <TabItem Header="情景2(高度Auto)">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="auto"/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <Controls:WrapPanelEx 
                                    IsFillHorizontal="Binding ElementName=chkFillWidth,Path=IsChecked,Mode=TwoWay"
                                    IsFillVertical="Binding ElementName=chkFillHeight,Path=IsChecked,Mode=TwoWay"
                                    ItemWidth="Binding ElementName=Rect1,Path=Width"  
                                    ItemHeight="Binding ElementName=Rect1,Path=Height" 
                                    Orientation="Binding ElementName=CmbOrientation,Path=SelectedValue">
                                    <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>
                                </Controls:WrapPanelEx>
                                <Border Grid.Row="1" BorderThickness="1" BorderBrush="Red" Margin="0,4" Visibility="Visible">
                               

以上是关于一个优化奇怪的 WrapPanel的主要内容,如果未能解决你的问题,请参考以下文章

添加WrapPanel时出错

使用 WrapPanel 添加一个 TextBox 作为 ItemsControl 的最后一个元素

WPF: WrapPanel 容器的数据绑定(动态生成控件遍历)

WPF: WrapPanel 容器的模板数据绑定(ItemsControl)

WrapPanel 和 DockPanel

WPF 改进 WrapPanel 右侧填充