2

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

所需的导航选项卡.

我正在使用 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);
        }
    }
}

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

4

1 回答 1

3

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

  • 您将大多数浮点值转换为整数值:绘图时不要这样做,除非您没有其他直接选择。大多数情况下,绘图需要浮点度量 ( float)。总是产生正确的计算。
    例如: 围绕枢轴点反复旋转点,以查看差异。

  • 您正在使用似乎是某种类型的固定措施,未在 OP 中定义且显然从未修改过:DEFAULT_WIDTHDEFAULT_HEIGHT. 由于控件可以在任何时候调整大小,无论是在设计时还是运行时,使用固定度量并不是真正有用的(假设这是这些值所代表的)。在任何情况下,您都需要使用 Control 的当前客户区作为主要参考:此值由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 对象。
然后它使用一个简单的 Matrix 缩放 GraphicsPath:(
请参阅 Flip the GraphicsPath 中的 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

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

这是它在设计时的样子:

区域控制设计时

在运行时:

区域控制运行时


另请参阅:
如何避免带有圆角的可缩放 UserControl 的彩色边框的视觉伪影?
如何绘制圆角矩形作为圆角窗体的边框?
使用 PathGradientBrush 创建的阴影显示了不想要的刺结果


自定义控件

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
}
于 2021-09-06T13:58:46.523 回答