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

5t7ly7z5  于 2022-11-17  发布在  其他
关注(0)|答案(1)|浏览(242)

我目前正在尝试覆写正在建置之自订控件的OnPaint()方法。
这个对象只是一个简单的Panel,但我试图让它看起来像一个不同的类型,就像这样:
x1c 0d1x。
我正在使用GraphicsPath来帮助我尝试并完成此任务,但它的外观/行为与我预期的工作方式不符,它当前的外观如下:


的数据。
下面是我一直在重新创建图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);
        }
    }
}

谁能告诉我我错过了什么或者我做错了什么?

wmomyfyw

wmomyfyw1#

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

  • 您正在将大多数浮点值转换为整数值:绘图时不要这样做,除非你没有其他直接的选择。大多数情况下绘图需要浮点度量(float)。总是产生正确的计算。

例如:Rotate Point around pivot Point repeatedly,以查看差异。

  • 您使用的是某种类型的固定测量,OP中未定义,而且显然从未修改过:DEFAULT_WIDTHDEFAULT_HEIGHT。由于控件的大小可以随时调整,无论是在设计阶段还是在执行阶段,因此使用固定的量值并没有实际的用处(假设这就是这些值所代表的意义)。在任何情况下,您都需要使用控件的目前工作区做为主要指涉:该值由Control.ClientRectangle属性返回。
  • 控件的区域不是在OnPaint()覆盖中设置的,而是在OnResize()OnLayout()覆盖中设置的,这取决于您正在生成的控件的功能。

设置属性,如您在那里拥有的FlatStyle = FlatStyle.Flat;(您是否从Label派生控件?),也不属于绘图过程:您可能会生成级联事件,导致控件不断地自我重画(直到崩溃)。

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

当你设置一个控件的区域时,要修改它的形状,你需要考虑到区域不支持抗锯齿,因此你不能沿着它创建的边框绘制。你需要 * 缩小 * 绘图区域,这样你总是在你定义的区域内绘制。或者,您可以创建一个完全透明/半透明的控件并绘制所需的任何内容(形状和/或位图)。但这是一个不同的问题,让我们坚持区域和创建它的GraphicsPath
在范例程式码中,GetRegionPath()方法会产生GraphicsPath对象,该对象是透过定义形体的PointF坐标建立,然后使用AddLines()方法建立形体。
此处显示的自定义控件使用SetStyle()在其构造函数中进行设置:

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

这将启用双缓冲,并使控件在调整大小时重画。
然后在OnResize()覆盖中重置区域。
OnPaint()中,会根据目前的工作区,再次呼叫GetRegionPath()方法以取得GraphicsPath对象。
然后使用简单的Matrix缩放GraphicsPath:
(see 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);

这将根据Border的大小缩放GraphicsPath(大于1.0f的值将放大,小于1.0f的值将缩小)。
然后,它会以边界的测量值向右和向下移动(平移)。
如果未设置Border,则不会缩放或 * 移动 * GraphicsPath:例如:

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

这允许在Region内绘制形状及其边框(如果有)。
在这种情况下,可以应用消除锯齿,并且形状的边框看起来 * 平滑 *。
这是在设计阶段的外观:

在运行时:

另请参阅:
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
}

相关问题