c# - 如何进行 XAML 布局 - 堆栈面板中的居中项目

标签 c# .net wpf xaml

我的布局中有一个可调整大小的容器控件。它应该包含任意数量的我的用户控件(它们也可以调整大小,并且目前始终为 3*2),如下所示:

Desired layout

我希望你已经明白了。我尝试使用 StackPanel 但失败了。它没有让我的元素居中。

那么,如何使用 XAML 获得所需的布局?希望尽可能避免编码......

我最终得到的代码:

我相信我必须分享结果。我从这个答案 WPF - How can I center all items in a WrapPanel? 中获取了代码并添加了调整大小的子控件。

    private Size elementFinalSize = new Size(0, 0);

    private Size MeasureDesiredSize(Size containerSize)
    {
        if (base.InternalChildren.Count == 0)
        {
            return new Size(0, 0);
        }

        // NB!: We assume all the items in the panel are of the same size.
        var child = base.InternalChildren[0];
        double elementAspectRatio = child.DesiredSize.Height == 0 ? 2.0 / 3.0 : child.DesiredSize.Width / child.DesiredSize.Height;
        Size finalElementSize = new Size(0, 0);
        Size newElementSize = finalElementSize;
        for (int possibleRowsNumber = 1; ; possibleRowsNumber++)
        {
            int numberOfElementInRow = this.GetElementsNumberInRow(base.InternalChildren.Count, possibleRowsNumber);
            double maxElementHeight = containerSize.Height / possibleRowsNumber;
            double maxElementWidth = containerSize.Width / numberOfElementInRow;
            if (maxElementWidth / elementAspectRatio > maxElementHeight)
            {
                // So many elements is more in width than container size, thus use Height.
                newElementSize = new Size(maxElementHeight * elementAspectRatio, maxElementHeight);
            }
            else
            {
                // The element Height is greater than container row size, thus use Width.
                newElementSize = new Size(maxElementWidth, maxElementWidth / elementAspectRatio);
            }

            if (newElementSize.Height > finalElementSize.Height)
            {
                // With such number of row a single element would be bigger than with previous number of rows.
                finalElementSize = newElementSize;
            }
            else
            {
                // With such number of rows a single element is less than the previous biggest one, thus stop searching.
                break;
            }
        }

        return finalElementSize;
    }

    private int GetElementsNumberInRow(int elementsCount, int rowsNumber)
    {
        int x = elementsCount % rowsNumber;
        if (x == 0)
        {
            return elementsCount / rowsNumber;
        }

        int maxPossibleElementsCount = elementsCount + rowsNumber - x;
        return maxPossibleElementsCount / rowsNumber;
    }

    protected override Size MeasureOverride(Size constraint)
    {
        // This MeasureOverride functions is called the first of the three overridden.
        // We need to understand the best (maximum) size for future element and arrange items accordingly.

        // Drop the desired size to zero in order to recalculate it as soon as necessary.
        this.elementFinalSize = new Size(0, 0);

        Size curLineSize = new Size();
        Size panelSize = new Size();

        UIElementCollection children = base.InternalChildren;

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

            // Flow passes its own constraint to children
            if (this.elementFinalSize.Height == 0)
            {
                child.Measure(constraint);
                this.elementFinalSize = this.MeasureDesiredSize(constraint);
                child.InvalidateMeasure();
            }

            if (curLineSize.Width + this.elementFinalSize.Width > constraint.Width) //need to switch to another line
            {
                panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
                panelSize.Height += curLineSize.Height;
                curLineSize = this.elementFinalSize;

                if (this.elementFinalSize.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line                    
                {
                    panelSize.Width = Math.Max(this.elementFinalSize.Width, panelSize.Width);
                    panelSize.Height += this.elementFinalSize.Height;
                    curLineSize = new Size();
                }
            }
            else //continue to accumulate a line
            {
                curLineSize.Width += this.elementFinalSize.Width;
                curLineSize.Height = Math.Max(this.elementFinalSize.Height, curLineSize.Height);
            }
        }

        foreach (UIElement child in children)
        {
            child.Measure(this.elementFinalSize);
        }

        // the last line size, if any need to be added
        panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
        panelSize.Height += curLineSize.Height;

        return panelSize;
    }

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        int firstInLine = 0;
        Size curLineSize = new Size();
        double accumulatedHeight = 0;
        UIElementCollection children = this.InternalChildren;

        for (int i = 0; i < children.Count; i++)
        {
            if (curLineSize.Width + this.elementFinalSize.Width > arrangeBounds.Width) //need to switch to another line
            {
                ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);

                accumulatedHeight += curLineSize.Height;
                curLineSize = this.elementFinalSize;

                if (this.elementFinalSize.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line                    
                {
                    ArrangeLine(accumulatedHeight, this.elementFinalSize, arrangeBounds.Width, i, ++i);
                    accumulatedHeight += this.elementFinalSize.Height;
                    curLineSize = new Size();
                }
                firstInLine = i;
            }
            else //continue to accumulate a line
            {
                curLineSize.Width += this.elementFinalSize.Width;
                curLineSize.Height = Math.Max(this.elementFinalSize.Height, curLineSize.Height);
            }
        }

        if (firstInLine < children.Count)
            ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);

        return arrangeBounds;
    }

    private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
    {
        double x = 0;
        if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
        {
            x = (boundsWidth - lineSize.Width) / 2;
        }
        else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
        {
            x = (boundsWidth - lineSize.Width);
        }

        UIElementCollection children = InternalChildren;
        for (int i = start; i < end; i++)
        {
            UIElement child = children[i];
            child.Arrange(new Rect(x, y, this.elementFinalSize.Width, this.elementFinalSize.Height));
            x += this.elementFinalSize.Width;
        }
    }

最佳答案

以下将产生几乎相同的效果:

<Grid>
    <WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" >
        <Rectangle Stroke="Red" StrokeThickness="2" Width="200" Height="200"/>
        <Rectangle Stroke="Red" StrokeThickness="2" Width="200" Height="200"/>
        <Rectangle Stroke="Red" StrokeThickness="2" Width="200" Height="200"/>
    </WrapPanel>
</Grid>

但这些项目是左对齐的,因此在您的情况下,您的最后一个屏幕截图将显示底部矩形左对齐。要解决这个问题,您可以 create a custom implementation of WrapPanel just for that purpose

编辑:当然,这只是一个带有矩形和所有内容的虚拟示例,通常会创建一个 ListView/ListBox/ItemContainer (取决于场景)并使 ItemsPanel 成为 WrapPanel:

<ListView>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel .../>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>

关于c# - 如何进行 XAML 布局 - 堆栈面板中的居中项目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8047177/

相关文章:

java - 'weka.core.WekaPackageManager' 的类型初始值设定项抛出异常 - weka c#

c# - 有没有更好的方法来确定文件是否被 .NET 中的另一个应用程序打开?

c# - SqlConnection 池似乎不起作用

c# - 奇怪的 wpf 绑定(bind)

c# - 在 WPF 中自动调整窗口内容

c# - 为 UIBarButtonItem 添加/删除 EventHandler

c# - 内部 protected 属性(property)仍然可以从不同的程序集访问

c# - "Unable to set the FreezePanes property of the Window class"Excel 2016 (办公室 365)

php - 我可以将哪些服务器端 PDF 呈现组件与 .NET、PHP、Ruby 等一起使用?

c# - 在 Wpf Datagrid Column 中使用不同的控件编辑不同类型的对象