在自定义面板上绘制线条/边框

3

我已经制作了一个自定义面板,类似于WrapPanel但具有列(或像Grid一样,但是项目会自动在网格中定位)。

这就是它的外观:
My custom panel

我想在我的面板上有一个属性,可以在每个列之间绘制一条线。是否可以在自定义面板上进行绘制?
我希望得到的结果类似于这样(请注意黑线): Result wanted
编辑:如果我让窗口变宽,面板将自动创建更多列,因此分隔符线必须是动态的-也就是说,可能是零、一个、两个、三个或更多的分隔符线。

这是我面板的代码:

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

namespace WpfApplication26
{
    public class AutoSizingColumnsWrapPanel : Panel
    {
        public double MinColumnWidth { get; set; }
        //public bool ShowColumnSeparator { get; set; }

        public AutoSizingColumnsWrapPanel()
        {
            MinColumnWidth = 100;
        }

        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
            return DoLayout(availableSize, (uiElement, size, pos) => uiElement.Measure(size));
        }

        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            return DoLayout(finalSize, (uiElement, size, pos) => uiElement.Arrange(pos));
        }

        private Size DoLayout(Size availableSize, Action<UIElement, Size, Rect> layoutAction)
        {
            // Calculate number of columns and column width
            int numberOfColumns = 0;
            double columnWidth = MinColumnWidth;
            if (double.IsInfinity(availableSize.Width))
            {
                numberOfColumns = InternalChildren.Count;
            }
            else
            {
                numberOfColumns = (int)Math.Max(Math.Floor(availableSize.Width / MinColumnWidth), 1);
                columnWidth = availableSize.Width / numberOfColumns;
            }

            // Init layout parameters
            Size measureSize = new Size(columnWidth, availableSize.Height);
            int currentColumn = 0;
            int currentRow = 0;
            double currentY = 0;
            double currentRowHeight = 0;
            // Place all items.
            foreach (UIElement item in InternalChildren)
            {
                var position = new Rect(currentColumn++ * columnWidth, currentY, columnWidth, item.DesiredSize.Height);
                // Execute action passing: item = The child item to layout | measureSize = The size allocated for the child item | position = The final position and height of the child item.
                layoutAction(item, measureSize, position);
                // Keep the highest item on the row (so that we know where to start the next row).
                currentRowHeight = Math.Max(currentRowHeight, item.DesiredSize.Height);
                if (currentColumn == numberOfColumns)
                {
                    // The item placed was in the last column. Increment/reset layout counters.
                    currentRow++;
                    currentColumn = 0;
                    currentY += currentRowHeight;
                    currentRowHeight = 0;
                }
            }
            // Return total size of the items/panel.
            return new Size(numberOfColumns * columnWidth, currentY + currentRowHeight);
        }
    }
}

这是我的 WPF 窗口,用于托管该面板:

<Window x:Class="WpfApplication26.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication26"
        Title="MainWindow" Height="350" Width="525">

    <local:AutoSizingColumnsWrapPanel MinColumnWidth="200" VerticalAlignment="Top">
        <TextBlock Text="One" Background="AliceBlue"/>
        <DockPanel >
            <TextBlock Text="Two: " Background="Beige"/>
            <TextBox HorizontalAlignment="Stretch" />
        </DockPanel>
        <TextBlock Text="Three" Background="DarkKhaki"/>
        <TextBlock Text="Four" Background="AliceBlue"/>
        <TextBlock Text="Five" Background="Beige" Height="50"/>
        <TextBlock Text="Six" Background="DarkKhaki"/>
        <TextBlock Text="Seven" Background="AliceBlue"/>
        <TextBlock Text="Eight" Background="Beige"/>
        <TextBlock Text="Nine" Background="DarkKhaki"/>
        <TextBlock Text="Ten" Background="AliceBlue"/>
        <TextBlock Text="Eleven" Background="Beige"/>
    </local:AutoSizingColumnsWrapPanel>
</Window>

可以通过将文本块的边框厚度设置为类似于 BorderThickness="0,0,1,0" 的值来实现。 - ywm
@ywm 如果我没有表达清楚,对不起。我正在寻找一种解决方案,使我不必修改面板的内容。我希望面板可以绘制线条,无论我在面板中放置什么。 - Björn
在这种情况下,您可以在面板上设置边框厚度。 - ywm
@ywm 谢谢您的建议,但我想让每列之间出现一条竖线(就像第二张图片中那样)。无论如何,我认为我可以通过在 OnRender 方法中绘制来完成它。 - Björn
2个回答

0

这是我请求的面板代码,如果还有其他人需要类似的面板:

AutoSizingColumnsWrapPanel.cs

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

namespace WpfApplication26
{
    public class AutoSizingColumnsWrapPanel : Panel
    {
        // The least acceptable width of the columns show in the panel. 
        public double MinColumnWidth { get; set; }
        // Spacing between columns.
        public double HorizontalColumnPadding { get; set; }
        // If a vertical line should be shown between the columns.
        public bool ShowColumnSeparator { get; set; }
        // The pen used to draw the column separator.
        public System.Windows.Media.Pen ColumnSeparatorPen { get; set; }

        private double _columnWidth = 0;
        private int _numberOfColumns = 0;

        public AutoSizingColumnsWrapPanel()
        {
            MinColumnWidth = 100;
            ShowColumnSeparator = true;
            HorizontalColumnPadding = 2.5;
            ColumnSeparatorPen = new System.Windows.Media.Pen(SystemColors.ActiveBorderBrush, 1);
        }

        protected override void OnRender(System.Windows.Media.DrawingContext dc)
        {
            if (ShowColumnSeparator)
            {
                // Draw vertical lines as column separators.
                for (int i = 0; i < _numberOfColumns - 1; i++)
                {
                    double x = (i * HorizontalColumnPadding * 2) + (i + 1) * _columnWidth + HorizontalColumnPadding;
                    dc.DrawLine(ColumnSeparatorPen, new Point(x, 0), new Point(x, ActualHeight));
                }
            }
            base.OnRender(dc);
        }

        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
            return DoLayout(availableSize, (uiElement, size, pos) => uiElement.Measure(size));
        }

        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            return DoLayout(finalSize, (uiElement, size, pos) => uiElement.Arrange(pos));
        }

        private Size DoLayout(Size availableSize, Action<UIElement, Size, Rect> layoutAction)
        {
            // Calculate number of columns and column width
            int numberOfColumns = 0;
            double columnWidth = MinColumnWidth;
            if (double.IsInfinity(availableSize.Width))
            {
                numberOfColumns = InternalChildren.Count;
            }
            else
            {
                numberOfColumns = (int)Math.Max(Math.Floor(availableSize.Width / MinColumnWidth), 1);
                // Give the columns equal space. Subtract any margin needed (MarginWidth is applied to both sides of the column, but not before the first column or after the last). The margin may cause the column to be smaller than MinColumnWidth.
                columnWidth = ((availableSize.Width - ((numberOfColumns - 1) * HorizontalColumnPadding * 2)) / numberOfColumns);
            }

            // Init layout parameters
            Size measureSize = new Size(columnWidth, availableSize.Height);
            int currentColumn = 0;
            int currentRow = 0;
            double currentY = 0;
            double currentRowHeight = 0;
            // Place all items.
            foreach (UIElement item in InternalChildren)
            {
                var position = new Rect((currentColumn * HorizontalColumnPadding * 2) + (currentColumn * columnWidth), currentY, columnWidth, item.DesiredSize.Height);
                currentColumn++;
                // Execute action passing: item = The child item to layout | measureSize = The size allocated for the child item | position = The final position and height of the child item.
                layoutAction(item, measureSize, position);
                // Keep the highest item on the row (so that we know where to start the next row).
                currentRowHeight = Math.Max(currentRowHeight, item.DesiredSize.Height);
                if (currentColumn == numberOfColumns)
                {
                    // The item placed was in the last column. Increment/reset layout counters.
                    currentRow++;
                    currentColumn = 0;
                    currentY += currentRowHeight;
                    currentRowHeight = 0;
                }
            }

            _columnWidth = columnWidth;
            _numberOfColumns = numberOfColumns;

            // Return total size of the items/panel.
            return new Size(numberOfColumns * columnWidth + ((numberOfColumns - 1) * HorizontalColumnPadding * 2), currentY + currentRowHeight);
        }
    }
}

示例 XAML:

<Window x:Class="WpfApplication26.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication26"
        Title="MainWindow" Height="350" Width="525">

    <local:AutoSizingColumnsWrapPanel MinColumnWidth="200" HorizontalColumnPadding="5" VerticalAlignment="Top">
        <TextBlock Text="One" Background="AliceBlue"/>
        <DockPanel >
            <TextBlock Text="Two: " Background="Beige"/>
            <TextBox HorizontalAlignment="Stretch" />
        </DockPanel>
        <TextBlock Text="Three" Background="DarkKhaki"/>
        <TextBlock Text="Four" Background="AliceBlue"/>
        <TextBlock Text="Five" Background="Beige" Height="50"/>
        <TextBlock Text="Six" Background="DarkKhaki"/>
        <TextBlock Text="Seven" Background="AliceBlue"/>
        <TextBlock Text="Eight" Background="Beige"/>
        <TextBlock Text="Nine" Background="DarkKhaki"/>
        <TextBlock Text="Ten" Background="AliceBlue"/>
        <TextBlock Text="Eleven" Background="Beige"/>
    </local:AutoSizingColumnsWrapPanel>
</Window>

这将呈现如下:
enter image description here

...当窗口被拉伸时:
enter image description here


0
我发现我可以在我的面板中重写OnRender方法。 OnRender方法提供了一个DrawingContext,使我能够绘制线条(和其他形状)。
我进行了一个快速而简单的测试,并进行了一些修改,我认为我可以让它工作。
这是一个快速而简单的代码,如果有人需要了解它的工作原理:
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
    var pen = new System.Windows.Media.Pen(System.Windows.Media.Brushes.Black, 2);
    for (int i = 0; i < _numberOfColumns; i++)
    {
        double x = (i + 1) * _columnWidth;
        dc.DrawLine(pen, new Point(x, 0), new Point(x, 1000));
    }
    base.OnRender(dc);
}

我会尽力记得在完成后发布完整的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接