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

标签 c# winforms graphics custom-controls graphicspath

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

Desired Navigation Tab .

我正在使用 GraphicsPath 来帮助我尝试完成此任务,但它的外观/行为并不像我预期的那样工作,因为它目前看起来像这样:

Failed Navigation Tab .

这是我一直致力于重新创建图 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)。始终产生正确的计算。
    例如:Rotate Point around pivot Point repeatedly ,看看差异。

  • 您使用的似乎是某种类型的固定度量,未在OP中定义并且显然从未修改:DEFAULT_WIDTHDEFAULT_HEIGHT。由于控件可以在设计时和运行时随时调整大小,因此使用固定度量并不是真正有用(假设这就是这些值所代表的内容)。无论如何,您需要使用控件的当前客户区作为主要引用:该值由 Control.ClientRectangle 返回。属性。

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

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


当您设置控件的区域以修改其形状时,您需要考虑该区域不支持抗锯齿,因此您无法沿着它创建的边框进行绘制。您需要缩小绘图区域,以便始终在您定义的区域内进行绘制。或者,您可以创建一个完全透明/半透明的控件,并在透明区域内绘制您需要的任何内容(形状和/或位图)。但这是另一回事,让我们坚持使用 Region 和 GraphicsPath创建它。

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

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

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

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

OnPaint() 中,再次调用 GetRegionPath() 方法,根据当前客户区域获取 GraphicsPath 对象。
然后它使用一个简单的矩阵缩放 GraphicsPath:
(请参阅 Flip the GraphicsPath that draws the text/string 中对 Matrix 功能的描述)

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 的值会放大,低于它的值会缩小)。
然后它会根据边界的大小向右和向下移动(平移)。
如果未设置边框,则 GraphicsPath 不会缩放或移动:例如

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

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

这就是设计时的样子:

Region Control Design-time

在运行时:

Region Control Run-time


另请参阅:
How to avoid visual artifacts of colored border of zoomable UserControl with rounded corners?
How can I draw a rounded rectangle as the border for a rounded Form?
A shadow created with PathGradientBrush shows undesired thorn results


自定义控件:

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
}

关于c# - 如何使用 GraphicsPath 绘制形状来创建自定义控件的区域?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69071220/

相关文章:

c# - 如何在后台获取数据并在准备好后用数据异步更新 UI?

c# - LINQ to Entities 无法识别该方法,并且无法将此方法转换为存储表达式

c# - 如何解决 Unable to cast object of type 'System.Data.DataView' to type 'System.Data.DataTable' ERROR

c# - 为什么这里的 TextRenderer.MeasureText 不准确?

java - 在 Java 中为网格创建绘制矩形(填充为黑色)函数

c# - ASP.NET MVC 找不到显示模板部分 View

c# - 无需创建 .edmx 的 Entity Framework

C# - 将分隔符添加到 TreeView

java - Java 中基于代理的建模 - 动画问题

java - 累积所有 float