我正在尝试创建一个自定义winforms按钮控件,该控件允许通过rotation属性旋转按钮文本。我大部分时间都在使用它,但它非常有用,我想知道正确的方法。
特别是现在重新绘制文本的行为很奇怪。如果控件移出屏幕,然后慢慢向后移动文本或者变得非常混乱(例如只画了一半),或者完全消失,直到被鼠标移开。显然我做错了什么,但无法搞清楚是什么。
我继承了按钮控件并重写了它的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
之前覆盖Text字符串并清除它,这不是我特别满意的解决方案。
我应该在e.dispose
的末尾处理PaintEventArgs吗?我想我不确定如何处理PaintEventArgs对象。
提前致谢!
聚苯乙烯。这是我的第一篇帖子/问题,所以如果我无意中忽略了一些礼仪或规则,我会提前道歉。
答案 0 :(得分:2)
VisibleClipBounds返回需要重新绘制的区域,例如,如果需要重新绘制一半按钮(覆盖按钮一半的顶部窗体),VisibleClipBounds仅返回该区域。所以你不能用它来画中心文本。 SizeF sz = new SizeF(宽度,高度); 应该注意重涂问题。
按钮不支持所有者绘图,你的方式似乎很好。
作为一项规则,您不应该处置您尚未创建的对象,并且一次性事件参数由创建它们的逻辑处理(并且首先称为On ...)所以不要担心处理PaintEventArgs。
欢迎使用Stack Overflow:)
答案 1 :(得分:1)
首先,我通过将if... else...
替换为switch... 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
,如在您的示例代码中)。
关于您的其他问题:
text
之前将base.OnPaint()
设置为空字符串,然后再恢复正确的值。PaintEventArgs
对象是在OnPaint
方法之外的某处创建的,处理(如果需要)应该在那里处理 - 只有对象的'创建者'知道如何处理 处理它恰当。 (您不知道之后引发Paint
事件的代码是否需要它。)