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

时间:2014-01-29 16:43:42

标签: c# wpf panel

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

这就是它的样子:
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>

2 个答案:

答案 0 :(得分: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);
}

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

答案 1 :(得分: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