如何旋转/翻转自定义ToggleButton控件?解决垂直显示异常
问题描述
我基于CheckBox自定义了ToggleButton控件,添加了IsVertical属性用于切换水平/垂直显示:
- 当
IsVertical=false时,控件默认水平显示 - 当
IsVertical=true时,控件及内部绘制内容应旋转90度
但设置IsVertical=true后,控件仍保持水平状态,内部图形变为方形,旋转效果未达到预期。相关代码如下:
控件定义
public class ToggleButton : CheckBox { private bool _isVertical = false; [Category("Custom Appearance")] public bool IsVertical { get => _isVertical; set { _isVertical = value; Invalidate(); } } }
OnPaint方法
protected override void OnPaint(PaintEventArgs pevent) { base.OnPaint(pevent); Graphics g = pevent.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; // Handle rotation if vertical if (_isVertical) { g.TranslateTransform(Width / 2, Height / 2); g.RotateTransform(90); g.TranslateTransform(-Width / 2, -Height / 2); } // Draw the background Rectangle backgroundRect = new Rectangle(0, 0, Width, Height); g.FillRectangle(new SolidBrush(currentBackColor), backgroundRect); // Draw the toggle int toggleSize = Math.Min(Width, Height) - 4; // Toggle size int toggleX = (int)currentPosition; int toggleY = (Height - toggleSize) / 2; // Center vertically g.FillEllipse(new SolidBrush(currentToggleColor), toggleX, toggleY, toggleSize, toggleSize); // Reset transformations if vertical if (_isVertical) { g.ResetTransform(); } // Optional: Draw border using (Pen borderPen = new Pen(Color.Black, 1)) { g.DrawRectangle(borderPen, 0, 0, Width - 1, Height - 1); } }
问题根源与解决方案
问题核心
base.OnPaint在变换前调用,导致默认CheckBox内容未被旋转,干扰自定义绘制- 提前重置变换,使边框绘制未应用旋转效果
- 旋转后未适配宽高互换的坐标系,导致绘制区域计算错误
- Toggle的位置逻辑未针对垂直模式调整
修正步骤
1. 调整base.OnPaint调用(或移除)
如果完全自定义绘制,直接移除base.OnPaint(pevent)避免默认内容干扰;若需保留CheckBox默认行为,将其移到变换逻辑之后调用。
2. 修正旋转变换逻辑
旋转90度后,控件宽高逻辑互换,需调整变换原点:
if (_isVertical) { // 平移到控件中心,旋转后再平移到新坐标系原点 g.TranslateTransform(Width / 2, Height / 2); g.RotateTransform(90); // 旋转后原Height变为Width,原Width变为Height,调整平移量 g.TranslateTransform(-Height / 2, -Width / 2); }
3. 适配旋转后的绘制区域
旋转后背景矩形的宽高需互换:
Rectangle backgroundRect = _isVertical ? new Rectangle(0, 0, Height, Width) : new Rectangle(0, 0, Width, Height);
4. 修正Toggle位置计算
垂直模式下,Toggle的移动方向变为垂直,调整坐标逻辑:
int toggleSize = Math.Min(Width, Height) - 4; int toggleX, toggleY; if (_isVertical) { // 垂直模式:X坐标居中,Y坐标由currentPosition控制 toggleX = (Height - toggleSize) / 2; toggleY = (int)currentPosition; } else { // 水平模式:原逻辑不变 toggleX = (int)currentPosition; toggleY = (Height - toggleSize) / 2; }
5. 延迟重置变换
将ResetTransform移到所有绘制操作完成后,确保边框也应用旋转:
// 先完成所有绘制操作 // ... // 最后重置变换 if (_isVertical) { g.ResetTransform(); }
6. 同步控件尺寸(可选)
当IsVertical变化时,交换控件宽高让布局更合理:
[Category("Custom Appearance")] public bool IsVertical { get => _isVertical; set { if (_isVertical != value) { _isVertical = value; // 交换宽高 var temp = Width; Width = Height; Height = temp; Invalidate(); } } }
修正后的完整OnPaint代码
protected override void OnPaint(PaintEventArgs pevent) { // 移除base.OnPaint避免默认内容干扰,如需保留可移到变换之后 // base.OnPaint(pevent); Graphics g = pevent.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; bool isVertical = _isVertical; if (isVertical) { g.TranslateTransform(Width / 2, Height / 2); g.RotateTransform(90); g.TranslateTransform(-Height / 2, -Width / 2); } // 绘制背景 Rectangle backgroundRect = isVertical ? new Rectangle(0, 0, Height, Width) : new Rectangle(0, 0, Width, Height); using (var backBrush = new SolidBrush(currentBackColor)) { g.FillRectangle(backBrush, backgroundRect); } // 绘制Toggle int toggleSize = Math.Min(Width, Height) - 4; int toggleX, toggleY; if (isVertical) { toggleX = (Height - toggleSize) / 2; toggleY = (int)currentPosition; } else { toggleX = (int)currentPosition; toggleY = (Height - toggleSize) / 2; } using (var toggleBrush = new SolidBrush(currentToggleColor)) { g.FillEllipse(toggleBrush, toggleX, toggleY, toggleSize, toggleSize); } // 绘制边框 using (Pen borderPen = new Pen(Color.Black, 1)) { g.DrawRectangle(borderPen, backgroundRect.X, backgroundRect.Y, backgroundRect.Width - 1, backgroundRect.Height - 1); } // 重置变换 if (isVertical) { g.ResetTransform(); } }
内容的提问来源于stack exchange,提问作者ShortyMelon
相关产品推荐
相关产品推荐

