【问题标题】:Draw a line/border on a custom panel在自定义面板上绘制线条/边框
【发布时间】:2014-02-21 14:51:51
【问题描述】:

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

这就是它的样子:

我想在我的面板上有一个属性,它在每列之间划一条线。是否可以在自定义面板上绘图?
我正在寻找的结果是这样的(注意黑线):
编辑:如果我使窗口更宽,面板将自动创建更多列,因此分隔线必须是动态的 - 也就是说,它可能是零、一、二、三或更多分隔线。

这是我的面板的代码:

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 感谢您的建议,但我希望在每列之间出现一条垂直线(如第二张图片所示)。无论如何,我想我可以通过绘制 OnRender 方法来完成它。

标签: c# wpf panel


【解决方案1】:

我发现我可以覆盖面板中的 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);
}

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

【讨论】:

    【解决方案2】:

    这里是我请求的面板的代码,如果其他人需要这样的面板:

    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>
    

    这将呈现如下:

    ...当窗口被拉伸时:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-24
      • 1970-01-01
      • 2012-01-15
      相关资源
      最近更新 更多