C# 如何正确获取WinForms按钮控件以绘制自定义文本

C# 如何正确获取WinForms按钮控件以绘制自定义文本,c#,winforms,onpaint,C#,Winforms,Onpaint,我正在尝试创建一个自定义winforms按钮控件,该控件允许通过旋转属性旋转按钮文本。我基本上都能让它工作,但它非常困难,我想知道正确的方法来做到这一点 尤其是现在,文本重画的行为异常。如果控件移离屏幕,然后缓慢移回文本,则文本要么变得非常混乱(例如只画了一半),要么完全消失,直到鼠标移过。很明显,我做错了什么,但不知道是什么 我从button控件继承并重写其OnPaint方法 代码如下: public class RotateButton : Button { private stri

我正在尝试创建一个自定义winforms按钮控件,该控件允许通过旋转属性旋转按钮文本。我基本上都能让它工作,但它非常困难,我想知道正确的方法来做到这一点

尤其是现在,文本重画的行为异常。如果控件移离屏幕,然后缓慢移回文本,则文本要么变得非常混乱(例如只画了一半),要么完全消失,直到鼠标移过。很明显,我做错了什么,但不知道是什么

我从button控件继承并重写其OnPaint方法

代码如下:

public class RotateButton : Button
{
    private string text;
    private bool painting = false;

    public enum RotationType { None, Right, Flip, Left}

    [DefaultValue(RotationType.None), Category("Appearance"), Description("Rotates Button Text")]
    public RotationType Rotation { get; set; }

    public override string Text
    {
        get
        {
            if (!painting)
                return text;
            else
                return "";
        }
        set
        {
            text = value;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        painting = true;

        base.OnPaint(e);

        StringFormat format = new StringFormat();
        Int32 lNum = (Int32)Math.Log((Double)this.TextAlign, 2);
        format.LineAlignment = (StringAlignment)(lNum / 4);
        format.Alignment = (StringAlignment)(lNum % 4);

        int padding = 2;

        SizeF txt = e.Graphics.MeasureString(Text, this.Font);
        SizeF sz = e.Graphics.VisibleClipBounds.Size;

        if (Rotation == RotationType.Right)
        {
            //90 degrees
            e.Graphics.TranslateTransform(sz.Width, 0);
            e.Graphics.RotateTransform(90);
            e.Graphics.DrawString(text, this.Font, Brushes.Black, new RectangleF(padding, padding, sz.Height - padding, sz.Width - padding), format);
            e.Graphics.ResetTransform();
        }
        else if (Rotation == RotationType.Flip)
        {
            //180 degrees
            e.Graphics.TranslateTransform(sz.Width, sz.Height);
            e.Graphics.RotateTransform(180);
            e.Graphics.DrawString(text, this.Font, Brushes.Black, new RectangleF(padding, padding, sz.Width - padding, sz.Height - padding), format);
            e.Graphics.ResetTransform();
        }
        else if (Rotation == RotationType.Left)
        {
            //270 degrees
            e.Graphics.TranslateTransform(0, sz.Height);
            e.Graphics.RotateTransform(270);
            e.Graphics.DrawString(text, this.Font, Brushes.Black, new RectangleF(padding, padding, sz.Height - padding, sz.Width - padding), format);
            e.Graphics.ResetTransform();
        }
        else
        {
            //0 = 360 degrees
            e.Graphics.TranslateTransform(0, 0);
            e.Graphics.RotateTransform(0);
            e.Graphics.DrawString(text, this.Font, Brushes.Black, new RectangleF(padding, padding, sz.Width - padding, sz.Height - padding), format);
            e.Graphics.ResetTransform();
        }

        painting = false;
    }
}
所以我的主要问题是如何解决文本重画的问题

此外,我对上述代码还有一些其他问题/意见:

  • 起初,文本显示两次,一次在默认位置,一次在旋转位置。我假设这是因为调用
    base.OnPaint
    方法时首先绘制文本。如果是这种情况,如何防止文字最初绘制

    我的解决方案是重写文本字符串并在调用
    base.OnPaint
    之前使用布尔值清除它,这不是我特别满意的解决方案

  • 我是否应该用
    e.dispose
    处理末尾的PaintEventArgs?我想我不确定PaintEventArgs对象是如何处理的

  • 提前谢谢

    注:这是我的第一个帖子/问题,如果我无意中忽略了一些礼仪或规则,我会提前道歉

  • VisibleClipBounds返回需要重新绘制的区域,例如,如果一半按钮需要重新绘制(覆盖一半按钮的顶部窗体关闭),VisibleClipBounds仅返回该区域。所以你不能用它来画居中的文本。 SizeF sz=新的SizeF(宽度、高度); 应该注意重新喷漆的问题

  • 按钮不支持所有者绘图,您的方式似乎很好

  • 作为一项规则,您不应该处理您尚未创建的对象,并且一次性事件参数由创建它们的逻辑处理(首先调用On…),因此不必担心处理PaintEventArgs


  • 欢迎使用堆栈溢出:)

    首先,我通过替换
    来重构代码,如果。。。否则…
    带有
    开关。。。case…
    ,并将每种情况下相同的两行移出。但这只是为了让它更具可读性:

        protected override void OnPaint(PaintEventArgs e)
        {
            painting = true;
    
            base.OnPaint(e);
    
            StringFormat format = new StringFormat();
            Int32 lNum = (Int32)Math.Log((Double)this.TextAlign, 2);
            format.LineAlignment = (StringAlignment)(lNum / 4);
            format.Alignment = (StringAlignment)(lNum % 4);
    
            int padding = 2;
    
            SizeF txt = e.Graphics.MeasureString(Text, this.Font);
            SizeF sz = e.Graphics.VisibleClipBounds.Size;
            switch (Rotation)
            {
                case RotationType.Right:  //90 degrees
                    {
                        e.Graphics.TranslateTransform(sz.Width, 0);
                        e.Graphics.RotateTransform(90);
                        break;
                    }
                case RotationType.Flip: //180 degrees
                    {
                        e.Graphics.TranslateTransform(sz.Width, sz.Height);
                        e.Graphics.RotateTransform(180);
                        break;
                    }
                case RotationType.Left: //270 degrees
                    {
                        e.Graphics.TranslateTransform(0, sz.Height);
                        e.Graphics.RotateTransform(270);
                        break;
                    }
                default: //0 = 360 degrees
                    {
                        e.Graphics.TranslateTransform(0, 0);
                        e.Graphics.RotateTransform(0);
                        break;
                    }
            }
    
            e.Graphics.DrawString(text, this.Font, Brushes.Black, new RectangleF(padding, padding, sz.Height - padding, sz.Width - padding), format);
            e.Graphics.ResetTransform();
    
            painting = false;
        }
    
    关于你的主要问题: 我通过创建
    RotateButton
    并将旋转类型设置为
    Right
    来测试这一点。我可以证实你描述的行为。调试
    OnPaint
    很困难,因为每次中断后恢复程序时,窗体都会重新获得焦点,从而触发新的
    Paint
    事件。通过在方法末尾添加两行代码,我最终找到了这种行为的原因:

    System.Diagnostics.Debug.WriteLine(sz.Width.ToString());
    System.Diagnostics.Debug.WriteLine(sz.Height.ToString());
    
    这会将宽度和高度的值写入Visual Studio中的输出窗口。在那里,我可以看到当控件移回到屏幕上时,
    sz.Width
    的值设置为1。因此,文本是在控件上绘制的,但是在一个太小的矩形中,因此它不可见。这意味着您不能使用
    e.Graphics.VisibleClipBounds.Size
    ,您必须自己计算大小(如果您使用
    MeasureString
    ,请小心将
    text
    作为参数传递,而不是像示例代码中那样传递
    text

    关于你的其他问题:

  • 我认为你的解决方案是可以的。在调用<代码> Base.OnPrPult()/代码>之前,您可以考虑将<代码>文本<代码>设置为空字符串,然后恢复正确的值。
  • 不,绝对不是。
    PaintEventArgs
    对象是在
    OnPaint
    方法之外的某个地方创建的,处理(如有必要)应该在那里进行-只有对象的“创建者”知道如何和何时正确地处理它。(您不知道引发
    Paint
    事件的代码是否需要它)

  • 我很清楚可见剪贴簿是如何工作的,但现在你解释清楚了,这是有道理的。无论如何,谢谢你。这个解决方案非常有效。也谢谢你的热情欢迎。谢谢你,我感谢你的工作和反馈。我从未使用过
    开关。。。case…
    以前,但现在我看到它看起来非常有用。我将阅读更多关于它的内容,并开始尝试使用它。另外,这是关于
    测量的一个好观点。我真的忘了它还在里面。不客气,我真的很喜欢这个小拼图!