如何使用 GraphicsPath 绘制形状来创建自定义控件的区域?

How to draw a shape using GraphicsPath to create the Region of a Custom Control?

我目前正在尝试重写我正在构建的自定义控件的 OnPaint() 方法。
该对象只是一个简单的 Panel,但我试图让它看起来是一种不同类型的方式,如下所示:

.

我正在使用 GraphicsPath 来帮助我尝试完成此任务,但这并不是我所期望的 looking/behaving 它的工作方式,因为它目前看起来像这样:

.

这是我一直致力于重新创建 图 1 的代码:

private GraphicsPath GetFigurePath(RectangleF rect)
{
    GraphicsPath path = new GraphicsPath();

    Point TopLeft = new Point((int)rect.X, (int)rect.Y);
    Point TopRight = new Point((int)rect.X + (int)rect.Width, (int)rect.Height);
    Point BottomLeft = new Point((int)rect.X, (int)rect.Y + (int)rect.Height);
    Point BottomRight = new Point((int)rect.X + (int)rect.Width, (int)rect.Y + (int)rect.Height);

    Point MidPoint = new Point((TopLeft.X + BottomLeft.X) / 2, (TopLeft.Y + BottomLeft.Y) / 2);
    Point Fulcrum = new Point((int)MidPoint.X + (int)rect.Width, MidPoint.Y);

    path.StartFigure();

    // The rectangle
    path.AddLine(TopLeft, TopRight);
    path.AddLine(TopRight, BottomRight);
    path.AddLine(BottomRight, BottomLeft);
    path.AddLine(BottomLeft, TopLeft);

    // The pointy end
    path.AddLine(TopRight, Fulcrum);
    path.AddLine(Fulcrum, BottomRight);

    path.CloseFigure();

    return path;
}

protected override void OnPaint(PaintEventArgs e)
{
    if (HasBorderStyle) {
        base.OnPaint(e);

        this.FlatStyle = FlatStyle.Flat;
        this.Size = new Size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        this.BackColor = Color.Silver;
        this.ForeColor = Color.White;
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;


        RectangleF rectSurface = new RectangleF(0, 0, this.DEFAULT_WIDTH, this.DEFAULT_HEIGHT);
        RectangleF rectBorder = new RectangleF(1, 1, this.DEFAULT_WIDTH - 0.8f, this.DEFAULT_HEIGHT - 1);

        using (GraphicsPath pathSurface = GetFigurePath(rectSurface))
        using (GraphicsPath pathBorder = GetFigurePath(rectBorder))
        using (Pen penSurface = new Pen(this.Parent.BackColor, 2))
        using (Pen penBorder = new Pen(borderColour, borderSize)) {
            penBorder.Alignment = PenAlignment.Inset;
            this.Region = new Region(pathSurface);

            e.Graphics.DrawPath(penSurface, pathSurface);
            e.Graphics.DrawPath(penBorder, pathBorder);
        }
    }
}

谁能告诉我我遗漏了什么或做错了什么?

有关使用 Regions 定义表示非矩形形状的自定义控件的可见区域的一些提示和示例。

  • 您正在将大多数浮点值转换为整数值:绘图时不要这样做,除非您别无选择。大多数情况下,绘图需要浮点度量 (float)。始终产生正确的计算。
    例如:,看区别。

  • 您使用的似乎是某种类型的固定度量,未在 OP 中定义并且显然从未修改过:DEFAULT_WIDTHDEFAULT_HEIGHT。由于控件可以随时调整大小,无论是在设计时还是 运行 时,使用固定度量值并不是很有用(假设这是这些值所代表的)。在任何情况下,您都需要使用您的控件的当前客户区作为主要参考:此值由 Control.ClientRectangle 属性.

    返回
  • 控件的区域未在 OnPaint() 覆盖中设置,而是在 OnResize()OnLayout() 覆盖中设置,具体取决于控制你正在建设。
    设置属性,如 FlatStyle = FlatStyle.Flat; 你在那里(你是从标签派生你的控件吗?),也不属于绘图过程:你可能会生成级联事件,导致控件不断重新绘制自身(直到它崩溃,即)。

  • 使用 GraphicsPath,Pen.Alignment 属性 并不是很有用。另请参阅文档中的备注部分。


当你设置一个控件的区域,修改它的形状时,你需要考虑到该区域不支持抗锯齿,因此你不能沿着它创建的边界绘制。您需要缩小 绘图区域,因此您总是在您定义的区域内绘图。或者,您可以创建一个完全透明/半透明的控件,并在透明区域内绘制您需要的任何东西(形状 and/or 位图)。但这是另一回事,让我们坚持区域和创建它的GraphicsPath

在示例代码中,GetRegionPath() 方法生成一个 GraphicsPath 对象,创建传递 PointF 定义形状的坐标,然后使用 AddLines() 方法构建形状。

这里显示的自定义控件,使用SetStyle()在其构造函数中设置:

SetStyle(ControlStyles.OptimizedDoubleBuffer | 
        ControlStyles.AllPaintingInWmPaint | 
        ControlStyles.ResizeRedraw, true);

这会启用双缓冲并导致控件在调整大小时重新绘制。
然后在 OnResize() 覆盖中重置区域。

OnPaint()中,再次调用GetRegionPath()获取GraphicsPath对象,基于当前客户区。
然后它使用简单的 Matrix:
缩放 GraphicsPath (请参阅 中矩阵功能的描述)

float scaleX = 1.0f - ((border * 2.5f) / rect.Width);
float scaleY = 1.0f - ((border * 2.0f) / rect.Height);
var mx = new Matrix(scaleX, 0, 0, scaleY, border, border);
[GraphicsPath].Transform(mx);

这会根据边框的大小缩放 GraphicsPath(高于 1.0f 的值会放大,低于 1.0f 的值会缩小)。
然后按边界的尺寸向右和向下移动(翻译)。
如果未设置边框,则 GraphicsPath 不会缩放或移动:例如

 1.0f + (((border * 2.5f) / rect.Width)) = 1.0f + 0.0f

这允许在区域内绘制形状及其边框(如果有)。
在这种情况下,可以应用抗锯齿,形状的边界看起来 平滑.

这是它在设计时的样子:

并且在 运行-时间:


另请参阅:



自定义控件:

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

[ToolboxItem(true)]
[DesignerCategory("code")]
public class NavigationShape : Control
{
    private Color m_ArrowColor = Color.SteelBlue;
    private Color m_BorderColor = Color.Orange;
    private float m_BorderSize = 1.5f;
    private bool m_BorderVisible = true;

    public NavigationShape() {
        SetStyle(ControlStyles.OptimizedDoubleBuffer | 
                 ControlStyles.AllPaintingInWmPaint | 
                 ControlStyles.ResizeRedraw, true);
        MinimumSize = new Size(40, 20);
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        Region = new Region(GetRegionPath());
    }

    private GraphicsPath GetRegionPath()
    {
        // The arrow shape begins at 3/4 or the current width of the container
        float arrowSection = ClientSize.Width * .75f;
        PointF[] arrowPoints = new PointF[] {
            new PointF (0, 0), 
            new PointF (arrowSection, 0),
            new PointF(ClientSize.Width, ClientSize.Height / 2.0f),
            new PointF (arrowSection, ClientSize.Height),
            new PointF (0, ClientSize.Height),
            new PointF (0, 0)
        };
        var path = new GraphicsPath();
        path.AddLines(arrowPoints);
        path.CloseFigure();
        return path;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        float border = m_BorderVisible ? m_BorderSize : .5f;

        using (var path = GetRegionPath()) {
            var rect = path.GetBounds();
            float scaleX = 1.0f - ((border * 2.5f) / rect.Width);
            float scaleY = 1.0f - ((border * 2.0f) / rect.Height);

            using (var mx = new Matrix(scaleX, 0, 0, scaleY, border, border))
            using (var brush = new SolidBrush(m_ArrowColor)) {
                path.Transform(mx);
                e.Graphics.FillPath(brush, path);
                if (m_BorderVisible) {
                    using (Pen pen = new Pen(m_BorderColor, m_BorderSize)) {
                        e.Graphics.DrawPath(pen, path);
                    }
                }
            }
        }
        base.OnPaint(e);
    }


    [DefaultValue(typeof(Color), "SteelBlue")]
    [Description("Color of the shape")]
    public Color ArrowColor {
        get => m_ArrowColor;
        set {
            if (m_ArrowColor != value) {
                m_ArrowColor = value;
                Invalidate();
            }
        }
    }

    [DefaultValue(true)]
    [Description("Show or hide the Border")]
    public bool BorderVisible {
        get => m_BorderVisible;
        set {
            m_BorderVisible = value;
            Invalidate();
        }
    }

    [DefaultValue(typeof(Color), "Orange")]
    [Description("Color of the Border")]
    public Color BorderColor {
        get => m_BorderColor;
        set {
            if (m_BorderColor != value) {
                m_BorderColor = value;
                Invalidate();
            }
        }
    }

    [DefaultValue(1.5f)]
    [Description("Size of the Border [1.0, 6.0]")]
    public float BorderSize {
        get => m_BorderSize;
        set {
            if (m_BorderSize != value) {
                m_BorderSize = Math.Max(Math.Min(value, 6.0f), 1.0f);
                Invalidate();
            }
        }
    }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public BorderStyle BorderStyle{ get; set; }  // Implement if needed
}