带有最后一行填充的WrapPanel

时间:2014-09-24 05:33:32

标签: c# wpf wrappanel

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

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

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

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

这是有效的,但仅在一侧 - 文本框宽度永远不会缩小。

任何帮助表示感谢。

2 个答案:

答案 0 :(得分:0)

误解了宽度校正的位置。这必须在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;
    }
}

答案 1 :(得分:0)

我最近做过。 您可以在以下位置查看我的代码: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;
                }
            }
        }

        // ************************************************************************

    }
}