wpf 自定义面板布局

file

这里实现了一个横向的简易版 StackPanel


    // 1. 计算需要的尺寸 
    protected override Size MeasureOverride(Size availableSize)
    {
        var resultSize = new Size(); // 需要的总尺寸
        var currentLineSize = new Size(); // 当前行尺寸

        foreach (UIElement element in this.InternalChildren)
        {
            element.Measure(availableSize); // 让子元素计算出需要的尺寸 element.DesiredSize

            // 如果新元素添加到当前行会超出当前行的宽度, 则换行
            if (currentLineSize.Width + element.DesiredSize.Width > availableSize.Width)
            {
                // 进行换行计算
                AppendNewLine(ref currentLineSize, ref resultSize);
            }

            // 生成新行
            currentLineSize.Width += element.DesiredSize.Width;
            currentLineSize.Height = Math.Max(currentLineSize.Height, element.DesiredSize.Height);
        }

        // 将可能存在的最后一行添加到结果中
        AppendNewLine(ref currentLineSize, ref resultSize);

        return this.CountResultSize(resultSize, availableSize);
    }

    //  2. 正式布局, 注意元素是缓存一行,要输出下一行时,一次性输出
    protected override Size ArrangeOverride(Size finalSize)
    {
        var resultSize = new Size(); // 最终于的尺寸
        var currentLineSize = new Size(); // 缓存某一行的尺寸, 供该行元素一次性输出 
        var elements = new List<UIElement>(); // 缓存某一行的元素, 一次性绘出

        foreach (UIElement element in this.InternalChildren)
        {
            // MeasureOverride 中已经计算过子元素需要的尺寸,这里直接添加到当前行,计算要不要换行
            if (currentLineSize.Width + element.DesiredSize.Width > finalSize.Width)
            {
                // 如果要换行,则对当前行的子元素进行输出 
                ArrangeElements(elements, resultSize.Height, currentLineSize.Height, finalSize.Width);

                // 清空缓存的当前行
                elements.Clear();

                // 进行换行尺寸计算
                AppendNewLine(ref currentLineSize, ref resultSize);
            }

            // 缓存当前行要添加的元素
            elements.Add(element);

            // 计算当前行长度
            currentLineSize.Width += element.DesiredSize.Width;
            currentLineSize.Height = Math.Max(currentLineSize.Height, element.DesiredSize.Height);
        }

        ArrangeElements(elements, resultSize.Height, currentLineSize.Height, finalSize.Width);

        AppendNewLine(ref currentLineSize, ref resultSize);

        /*return resultSize;*/
        return this.CountResultSize(resultSize, finalSize);
        /*return finalSize;*/
    }

    // 进行换行计算, 简单的将当前行的尺寸加到总尺寸中
    private static void AppendNewLine(ref Size currentLineSize, ref Size resultSize)
    {
        resultSize.Height += currentLineSize.Height;
        resultSize.Width = Math.Max(resultSize.Width, currentLineSize.Width);

        currentLineSize = new Size();
    }

    // 输出当前行的元素, 按顺序从左到右进行排列输出,给子元素的尺寸要实际计算出剩余的空间
    private static void ArrangeElements(List<UIElement> elements, double y, double limitHeight, double limitWidth)
    {
        var x = 0d;

        foreach (var element in elements)
        {
            var width = Math.Min(limitWidth - x, element.DesiredSize.Width);
            var height = Math.Min(limitHeight, element.DesiredSize.Height);

            // 因为元素的高度可能不一样,这里将元素上下居中一下。可以直接用 y 值,就是上对齐
            var newY = y + (limitHeight - element.DesiredSize.Height) / 2;

            element.Arrange(new Rect(x, newY, width, height));

            x += element.DesiredSize.Width;
        }
    }

    // 计算最终需要的尺寸。比如计算的尺寸与分配的有效尺寸,哪个小用哪个
    private Size CountResultSize(Size resultSize, Size availableSize)
    {
        // 计算最终的尺寸, 如果大于可用尺寸,哪个小用哪个
        var width = resultSize.Width;
        var height = resultSize.Height;

        if (double.IsFinite(availableSize.Width))
        {
            width = Math.Min(resultSize.Width, availableSize.Width);
        }

        if (double.IsFinite(availableSize.Height))
        {
            height = Math.Min(resultSize.Height, availableSize.Height);
        }

        return new Size(width, height);
    }
上一篇
下一篇