【问题标题】:WrapPanel with last-in-row fill具有最后一行填充的 WrapPanel
【发布时间】:2023-04-05 23:06:01
【问题描述】:

我需要什么:里面有文本框的列表框,文本框换行,最后一行填充剩余空间:

|word 1||word 2___|
|word 3___________|

我正在尝试使用that advice 来实现此行为。我的xml:

<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
         ItemsSource="{Binding Tags}"
         HorizontalContentAlignment="Stretch">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <controls:WrapPanelLastChildFill />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Text}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

MyWrapPanel(继承自WrapPanel)代码:

protected override Size MeasureOverride(Size constraint)
{
    Size curLineSize = new Size();
    Size panelSize = new Size(constraint.Width, 0);
    UIElementCollection children = base.InternalChildren;

    for (int i = 0; i < children.Count; i++)
    {
        UIElement child = children[i] as UIElement;

        child.Measure(constraint);
        Size sz = child.DesiredSize;
        if (curLineSize.Width + sz.Width > constraint.Width) // new line
        {
            panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
            panelSize.Height += curLineSize.Height;
            if (i > 0)
            {
                // change width of prev control here
                var lastChildInRow = children[i - 1] as Control;
                lastChildInRow.Width = lastChildInRow.ActualWidth + panelSize.Width - curLineSize.Width;
            }
            curLineSize = sz;
        }
        else
        {
            curLineSize.Width += sz.Width;
            curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
        }
    }
    panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
    panelSize.Height += curLineSize.Height;

    return panelSize;
}

这行得通,但仅在一侧 - 文本框宽度永远不会缩小。

任何帮助表示赞赏。

【问题讨论】:

    标签: c# wpf wrappanel


    【解决方案1】:

    我最近做了。 您可以在以下位置查看我的代码:CodeProject 或者直接使用这个类如图:

    用法:

    <TextBox MinWidth="120"  wrapPanelWithFill:WrapPanelFill.UseToFill="True">*</TextBox>
    

    代码:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WrapPanelWithFill
    {
        public class WrapPanelFill : WrapPanel
        {
            // ******************************************************************
            public static readonly DependencyProperty UseToFillProperty = DependencyProperty.RegisterAttached("UseToFill", typeof(Boolean), 
                typeof(WrapPanelFill), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
    
            // ******************************************************************
            public static void SetUseToFill(UIElement element, Boolean value)
            {
                element.SetValue(UseToFillProperty, value);
            }
            // ******************************************************************
            public static Boolean GetUseToFill(UIElement element)
            {
                return (Boolean)element.GetValue(UseToFillProperty);
            }
    
            // ******************************************************************
            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);
            }
    
            // ******************************************************************
            private bool _atLeastOneElementCanHasItsWidthExpanded = false;
    
            // ******************************************************************
            /// <summary> 
            /// <see cref="FrameworkElement.MeasureOverride"/>
            /// </summary> 
            protected override Size MeasureOverride(Size constraint)
            {
                UVSize curLineSize = new UVSize(Orientation);
                UVSize panelSize = new UVSize(Orientation);
                UVSize uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
                double itemWidth = ItemWidth;
                double itemHeight = ItemHeight;
                bool itemWidthSet = !Double.IsNaN(itemWidth);
                bool itemHeightSet = !Double.IsNaN(itemHeight);
    
                Size childConstraint = new Size(
                    (itemWidthSet ? itemWidth : constraint.Width),
                    (itemHeightSet ? itemHeight : constraint.Height));
    
                UIElementCollection children = InternalChildren;
    
                // EO
                LineInfo currentLineInfo = new LineInfo(); // EO, the way it works it is always like we are on the current line
                _lineInfos.Clear();
                _atLeastOneElementCanHasItsWidthExpanded = false;
    
                for (int i = 0, count = children.Count; i < count; i++)
                {
                    UIElement child = children[i] as UIElement;
                    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,
                        (itemWidthSet ? itemWidth : child.DesiredSize.Width),
                        (itemHeightSet ? itemHeight : child.DesiredSize.Height));
    
                    if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line 
                    {
                        // EO
                        currentLineInfo.Size = curLineSize;
                        _lineInfos.Add(currentLineInfo);
    
                        panelSize.U = Math.Max(curLineSize.U, panelSize.U);
                        panelSize.V += curLineSize.V;
                        curLineSize = sz;
    
                        // EO
                        currentLineInfo = new LineInfo();
                        var feChild = child as FrameworkElement;
                        if (GetUseToFill(feChild))
                        {
                            currentLineInfo.ElementsWithNoWidthSet.Add(feChild);
                            _atLeastOneElementCanHasItsWidthExpanded = true;
                        }
    
                        if (DoubleGreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line
                        {
                            currentLineInfo = new LineInfo();
    
                            panelSize.U = Math.Max(sz.U, panelSize.U);
                            panelSize.V += sz.V;
                            curLineSize = new UVSize(Orientation);
                        }
                    }
                    else //continue to accumulate a line
                    {
                        curLineSize.U += sz.U;
                        curLineSize.V = Math.Max(sz.V, curLineSize.V);
    
                        // EO
                        var feChild = child as FrameworkElement;
                        if (GetUseToFill(feChild))
                        {
                            currentLineInfo.ElementsWithNoWidthSet.Add(feChild);
                            _atLeastOneElementCanHasItsWidthExpanded = true;
                        }
                    }
                }
    
                if (curLineSize.U > 0)
                {
                    currentLineInfo.Size = curLineSize;
                    _lineInfos.Add(currentLineInfo);
                }
    
                //the last line size, if any should be added 
                panelSize.U = Math.Max(curLineSize.U, panelSize.U);
                panelSize.V += curLineSize.V;
    
                // EO
                if (_atLeastOneElementCanHasItsWidthExpanded)
                {
                    return new Size(constraint.Width, panelSize.Height);
                }
    
                //go from UV space to W/H space
                return new Size(panelSize.Width, panelSize.Height);
            }
    
            // ************************************************************************
            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 LineInfo
            {
                public List<UIElement> ElementsWithNoWidthSet = new List<UIElement>();
                //          public double SpaceLeft = 0;
                //          public double WidthCorrectionPerElement = 0;
                public UVSize Size;
                public double Correction = 0;
            }
    
            private List<LineInfo> _lineInfos = new List<LineInfo>();
    
            // ************************************************************************
            /// <summary>
            /// <see cref="FrameworkElement.ArrangeOverride"/> 
            /// </summary> 
            protected override Size ArrangeOverride(Size finalSize)
            {
                int lineIndex = 0;
                int firstInLine = 0;
                double itemWidth = ItemWidth;
                double itemHeight = ItemHeight;
                double accumulatedV = 0;
                double itemU = (Orientation == Orientation.Horizontal ? itemWidth : itemHeight);
                UVSize curLineSize = new UVSize(Orientation);
                UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
                bool itemWidthSet = !Double.IsNaN(itemWidth);
                bool itemHeightSet = !Double.IsNaN(itemHeight);
                bool useItemU = (Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet);
    
                UIElementCollection children = InternalChildren;
    
                for (int i = 0, count = children.Count; i < count; i++)
                {
                    UIElement child = children[i] as UIElement;
                    if (child == null) continue;
    
                    UVSize sz = new UVSize(
                        Orientation,
                        (itemWidthSet ? itemWidth : child.DesiredSize.Width),
                        (itemHeightSet ? itemHeight : child.DesiredSize.Height));
    
                    if (DoubleGreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line 
                    {
                        arrangeLine(lineIndex, accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, uvFinalSize);
                        lineIndex++;
    
                        accumulatedV += curLineSize.V;
                        curLineSize = sz;
    
                        if (DoubleGreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line 
                        {
                            //switch to next line which only contain one element 
                            arrangeLine(lineIndex, accumulatedV, sz.V, i, ++i, useItemU, itemU, uvFinalSize);
    
                            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(lineIndex, accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU, uvFinalSize);
                }
    
                return finalSize;
            }
    
            // ************************************************************************
            private void arrangeLine(int lineIndex, double v, double lineV, int start, int end, bool useItemU, double itemU, UVSize uvFinalSize)
            {
                double u = 0;
                bool isHorizontal = (Orientation == Orientation.Horizontal);
    
                Debug.Assert(lineIndex < _lineInfos.Count);
    
                LineInfo lineInfo = _lineInfos[lineIndex];
                double lineSpaceAvailableForCorrection = Math.Max(uvFinalSize.U - lineInfo.Size.U, 0);
                double perControlCorrection = 0;
                if (lineSpaceAvailableForCorrection > 0 && lineInfo.Size.U > 0)
                {
                    perControlCorrection = lineSpaceAvailableForCorrection / lineInfo.ElementsWithNoWidthSet.Count;
                    if (double.IsInfinity(perControlCorrection))
                    {
                        perControlCorrection = 0;
                    }
                }
                int indexOfControlToAdjustSizeToFill = 0;
                UIElement uIElementToAdjustNext = indexOfControlToAdjustSizeToFill < lineInfo.ElementsWithNoWidthSet.Count ? lineInfo.ElementsWithNoWidthSet[indexOfControlToAdjustSizeToFill] : null;
    
                UIElementCollection children = InternalChildren;
                for (int i = start; i < end; i++)
                {
                    UIElement child = children[i] as UIElement;
                    if (child != null)
                    {
                        UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
                        double layoutSlotU = (useItemU ? itemU : childSize.U);
    
                        if (perControlCorrection > 0 && child == uIElementToAdjustNext)
                        {
                            layoutSlotU += perControlCorrection;
    
                            indexOfControlToAdjustSizeToFill++;
                            uIElementToAdjustNext = indexOfControlToAdjustSizeToFill < lineInfo.ElementsWithNoWidthSet.Count ? lineInfo.ElementsWithNoWidthSet[indexOfControlToAdjustSizeToFill] : null;
                        }
    
                        child.Arrange(new Rect(
                            (isHorizontal ? u : v),
                            (isHorizontal ? v : u),
                            (isHorizontal ? layoutSlotU : lineV),
                            (isHorizontal ? lineV : layoutSlotU)));
                        u += layoutSlotU;
                    }
                }
            }
    
            // ************************************************************************
    
        }
    }
    

    【讨论】:

      【解决方案2】:

      对宽度校正的位置有误解。这必须在 ArrangeOverride 中:

      private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
      {
          double x = 0;
          UIElementCollection children = InternalChildren;
          for (int i = start; i < end; i++)
          {
              UIElement child = children[i];
              var w = child.DesiredSize.Width;
              if (LastChildFill && i == end - 1) // last сhild fills remaining space
              {
                  w = boundsWidth - x;
              }
              child.Arrange(new Rect(x, y, w, lineSize.Height));
              x += w;
          }
      }
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-08
      • 2015-04-01
      • 1970-01-01
      • 2012-06-01
      • 1970-01-01
      • 2015-11-23
      • 2021-08-08
      相关资源
      最近更新 更多