在Windows Forms应用程序开发中,自定义控件是提升用户界面体验的重要手段。本文将详细介绍一个具有精美视觉效果的旋转开关按钮控件(RotatingSwitchButton)的实现。这个控件融合了现代UI设计元素,包括平滑动画、阴影效果、渐变和金属质感等特性。
RotatingSwitchButton是一个模拟物理旋转开关的自定义控件,具有以下主要特征:
让我生成一个这个控件的示意图:
控件使用布尔值isOn
追踪当前状态,并通过currentAngle
控制旋钮的旋转角度。动画效果通过Timer实现,使用缓动函数使动画更自然:
C#private bool isOn = false;
private float currentAngle = 0f;
private readonly float targetOnAngle = -90f; // 向上位置
private readonly float targetOffAngle = 90f; // 向下位置
控件支持可自定义的阴影效果,通过以下属性控制:
阴影实现使用PathGradientBrush
创建径向渐变,实现柔和的阴影效果:
C#private void DrawShadow(Graphics g, float centerX, float centerY, float radius)
{
using (var shadowPath = new GraphicsPath())
{
float shadowSize = radius * 2 + shadowBlur * 2;
shadowPath.AddEllipse(/*...*/);
using (var shadowBrush = new PathGradientBrush(shadowPath))
{
// 设置渐变参数
shadowBrush.CenterColor = centerShadowColor;
shadowBrush.SurroundColors = new[] { Color.FromArgb(0, shadowColor) };
g.FillPath(shadowBrush, shadowPath);
}
}
}
外环采用渐变效果和高光,创造金属质感:
C#private void DrawOuterRing(Graphics g, float centerX, float centerY, float radius)
{
// 主体渐变
using (var gradientBrush = new LinearGradientBrush(...))
{
g.FillEllipse(gradientBrush, ...);
}
// 边缘高光
using (var pen = new Pen(...))
{
g.DrawEllipse(pen, ...);
}
}
通过循环绘制不同长度的刻度线,增强专业感:
C#for (int i = 0; i < 360; i += 6)
{
float scaleLength = (i % 30 == 0) ? 0.12f : 0.08f;
if (i == 90 || i == 270)
scaleLength = 0.15f; // 主要刻度线
// 绘制刻度线...
}
旋钮采用多层渲染,包括基础形状、阴影、高光和指示线:
C#private void DrawKnob(Graphics g, float centerX, float centerY, float radius)
{
// 计算旋钮位置
float knobRadius = radius * 0.3f;
float knobX = centerX + (float)(radius * 0.6f * Math.Cos(currentAngle * Math.PI / 180));
float knobY = centerY + (float)(radius * 0.6f * Math.Sin(currentAngle * Math.PI / 180));
// 绘制旋钮本体、阴影和高光
// ...
// 绘制指示线
using (var indicatorPen = new Pen(...))
{
// 绘制方向指示线
}
}
控件可以轻松集成到Windows Forms应用程序中:
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Timer = System.Windows.Forms.Timer;
namespace AppControls
{
public class RotatingSwitchButton : Control
{
private bool isOn = false;
private float currentAngle = 0f;
// 修改目标角度为90度(上)和-90度(下)
private readonly float targetOnAngle = -90f; // 负角度表示向上
private readonly float targetOffAngle = 90f; // 正角度表示向下
private Timer animationTimer;
// 定义颜色
private readonly Color onColor = Color.FromArgb(76, 217, 100);
private readonly Color offColor = Color.FromArgb(255, 59, 48);
private readonly Color knobColor = Color.White;
private readonly Color gradientStart = Color.FromArgb(240, 240, 240);
private readonly Color gradientEnd = Color.FromArgb(200, 200, 200);
// 阴影相关属性
private bool enableShadow = true;
private float shadowDepth = 5f;
private float shadowOpacity = 0.3f;
private Color shadowColor = Color.FromArgb(76, 0, 0, 0);
private float shadowBlur = 10f;
// 公开的属性设置
[Category("Appearance")]
[Description("启用或禁用控件阴影")]
public bool EnableShadow
{
get => enableShadow;
set
{
if (enableShadow != value)
{
enableShadow = value;
Invalidate();
}
}
}
[Category("Appearance")]
[Description("设置阴影深度")]
public float ShadowDepth
{
get => shadowDepth;
set
{
if (shadowDepth != value)
{
shadowDepth = value;
Invalidate();
}
}
}
[Category("Appearance")]
[Description("设置阴影透明度 (0.0 - 1.0)")]
public float ShadowOpacity
{
get => shadowOpacity;
set
{
value = Math.Max(0, Math.Min(1, value));
if (shadowOpacity != value)
{
shadowOpacity = value;
Invalidate();
}
}
}
[Category("Appearance")]
[Description("设置阴影颜色")]
public Color ShadowColor
{
get => shadowColor;
set
{
if (shadowColor != value)
{
shadowColor = value;
Invalidate();
}
}
}
[Category("Appearance")]
[Description("设置阴影模糊程度")]
public float ShadowBlur
{
get => shadowBlur;
set
{
if (shadowBlur != value)
{
shadowBlur = value;
Invalidate();
}
}
}
public bool IsOn
{
get => isOn;
set
{
if (isOn != value)
{
isOn = value;
StartAnimation();
OnValueChanged(EventArgs.Empty);
}
}
}
public event EventHandler ValueChanged;
public RotatingSwitchButton()
{
// 构造函数内容保持不变
SetStyle(ControlStyles.SupportsTransparentBackColor |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
Size = new Size(100, 100);
BackColor = Color.Transparent;
// 初始化角度为OFF位置(下方)
currentAngle = targetOffAngle;
animationTimer = new Timer();
animationTimer.Interval = 16;
animationTimer.Tick += AnimationTimer_Tick;
// 确保控件有足够的空间显示阴影
Padding = new Padding((int)(shadowDepth + shadowBlur));
}
private void StartAnimation()
{
animationTimer.Start();
}
private float EaseInOutQuad(float t)
{
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
private void AnimationTimer_Tick(object sender, EventArgs e)
{
float targetAngle = isOn ? targetOnAngle : targetOffAngle;
float totalDistance = Math.Abs(targetAngle - currentAngle);
float progress = Math.Min(1f, 0.15f); // 控制动画速度
if (Math.Abs(currentAngle - targetAngle) < 0.1f)
{
currentAngle = targetAngle;
animationTimer.Stop();
}
else
{
float step = totalDistance * EaseInOutQuad(progress);
if (currentAngle < targetAngle)
currentAngle += step;
else
currentAngle -= step;
}
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
// 计算中心点和半径
float centerX = Width / 2f;
float centerY = Height / 2f;
float outerRadius = Math.Min(Width, Height) / 2f - (shadowDepth + shadowBlur);
float innerRadius = outerRadius * 0.7f;
// 如果启用阴影,先绘制阴影
if (enableShadow)
{
DrawShadow(e.Graphics, centerX, centerY, outerRadius);
}
// 继续绘制其他部分
using (var path = new GraphicsPath())
{
DrawOuterRing(e.Graphics, centerX, centerY, outerRadius);
DrawLabels(e.Graphics, centerX, centerY, outerRadius);
DrawInnerRing(e.Graphics, centerX, centerY, innerRadius);
DrawKnob(e.Graphics, centerX, centerY, innerRadius);
}
}
// 阴影绘制方法
private void DrawShadow(Graphics g, float centerX, float centerY, float radius)
{
// 创建阴影路径
using (var shadowPath = new GraphicsPath())
{
// 阴影椭圆的大小要略大于实际控件
float shadowSize = radius * 2 + shadowBlur * 2;
float shadowX = centerX - radius - shadowBlur + shadowDepth * 0.5f;
float shadowY = centerY - radius - shadowBlur + shadowDepth;
shadowPath.AddEllipse(shadowX, shadowY, shadowSize, shadowSize);
// 创建径向渐变画刷来实现柔和阴影
using (var shadowBrush = new PathGradientBrush(shadowPath))
{
// 设置中心色和边缘色
Color centerShadowColor = Color.FromArgb(
(int)(255 * shadowOpacity),
shadowColor.R,
shadowColor.G,
shadowColor.B);
shadowBrush.CenterColor = centerShadowColor;
shadowBrush.SurroundColors = new[] { Color.FromArgb(0, shadowColor) };
// 设置阴影的渐变焦点
shadowBrush.FocusScales = new PointF(0.8f, 0.8f);
// 绘制阴影
g.FillPath(shadowBrush, shadowPath);
}
}
}
private void DrawOuterRing(Graphics g, float centerX, float centerY, float radius)
{
// 主体渐变背景
using (var gradientBrush = new LinearGradientBrush(
new RectangleF(centerX - radius, centerY - radius, radius * 2, radius * 2),
gradientStart,
gradientEnd,
45f))
{
g.FillEllipse(gradientBrush, centerX - radius, centerY - radius, radius * 2, radius * 2);
}
// 添加边缘高光
using (var pen = new Pen(Color.FromArgb(100, 255, 255, 255), 1.5f))
{
g.DrawEllipse(pen, centerX - radius, centerY - radius, radius * 2, radius * 2);
}
// 添加内部光泽效果
using (var highlightPath = new GraphicsPath())
{
highlightPath.AddEllipse(centerX - radius * 0.9f, centerY - radius * 0.9f,
radius * 1.8f, radius * 1.8f);
using (var highlightBrush = new PathGradientBrush(highlightPath))
{
highlightBrush.CenterColor = Color.FromArgb(30, 255, 255, 255);
highlightBrush.SurroundColors = new[] { Color.FromArgb(0, 255, 255, 255) };
g.FillPath(highlightBrush, highlightPath);
}
}
}
private void DrawLabels(Graphics g, float centerX, float centerY, float radius)
{
using (var font = new Font("Arial", 12f, FontStyle.Bold))
{
// 绘制ON标签(上方)
DrawRotatedText(g, "ON", font, isOn ? onColor : Color.Gray,
centerX, centerY, radius * 0.8f, -90); // -90度位置
// 绘制OFF标签(下方)
DrawRotatedText(g, "OFF", font, !isOn ? offColor : Color.Gray,
centerX, centerY, radius * 0.8f, 90); // 90度位置
}
}
private void DrawRotatedText(Graphics g, string text, Font font, Color color,
float centerX, float centerY, float radius, float angle)
{
using (var brush = new SolidBrush(color))
{
var size = g.MeasureString(text, font);
// 计算文本位置,考虑垂直方向
float x = centerX + (float)(radius * Math.Cos(angle * Math.PI / 180)) - size.Width / 2;
float y = centerY + (float)(radius * Math.Sin(angle * Math.PI / 180)) - size.Height / 2;
// 保持文本水平显示
float rotationAngle = 0;
if (angle == 90) // OFF位置
rotationAngle = 0;
else if (angle == -90) // ON位置
rotationAngle = 0;
g.TranslateTransform(x + size.Width / 2, y + size.Height / 2);
g.RotateTransform(rotationAngle);
g.DrawString(text, font, brush, -size.Width / 2, -size.Height / 2);
g.ResetTransform();
}
}
private void DrawInnerRing(Graphics g, float centerX, float centerY, float radius)
{
using (var pen = new Pen(Color.FromArgb(100, 130, 130, 130), 1f))
{
g.DrawEllipse(pen, centerX - radius, centerY - radius, radius * 2, radius * 2);
// 绘制精细刻度线
for (int i = 0; i < 360; i += 6)
{
float scaleLength = (i % 30 == 0) ? 0.12f : 0.08f;
if (i == 90 || i == 270)
scaleLength = 0.15f; // 主要刻度线
float startX = centerX + (float)(radius * (1 - scaleLength) * Math.Cos(i * Math.PI / 180));
float startY = centerY + (float)(radius * (1 - scaleLength) * Math.Sin(i * Math.PI / 180));
float endX = centerX + (float)(radius * Math.Cos(i * Math.PI / 180));
float endY = centerY + (float)(radius * Math.Sin(i * Math.PI / 180));
using (var scalePen = new Pen(Color.FromArgb(
i % 30 == 0 ? 120 : 80,
130, 130, 130), i % 30 == 0 ? 1.5f : 0.8f))
{
g.DrawLine(scalePen, startX, startY, endX, endY);
}
}
}
}
private void DrawKnob(Graphics g, float centerX, float centerY, float radius)
{
float knobRadius = radius * 0.3f;
float knobX = centerX + (float)(radius * 0.6f * Math.Cos(currentAngle * Math.PI / 180));
float knobY = centerY + (float)(radius * 0.6f * Math.Sin(currentAngle * Math.PI / 180));
// 绘制旋转钮阴影
using (var shadowBrush = new SolidBrush(Color.FromArgb(30, 0, 0, 0)))
{
g.FillEllipse(shadowBrush, knobX - knobRadius + 2, knobY - knobRadius + 2,
knobRadius * 2, knobRadius * 2);
}
// 绘制金属质感旋转钮
using (var knobPath = new GraphicsPath())
{
knobPath.AddEllipse(knobX - knobRadius, knobY - knobRadius, knobRadius * 2, knobRadius * 2);
using (var knobBrush = new PathGradientBrush(knobPath))
{
knobBrush.CenterColor = Color.White;
knobBrush.SurroundColors = new[] { Color.FromArgb(230, 230, 230) };
g.FillPath(knobBrush, knobPath);
}
}
// 添加高光效果
using (var highlightBrush = new PathGradientBrush(new PointF[] {
new PointF(knobX - knobRadius * 0.5f, knobY - knobRadius * 0.5f),
new PointF(knobX + knobRadius * 0.5f, knobY - knobRadius * 0.5f),
new PointF(knobX, knobY + knobRadius * 0.5f)
}))
{
highlightBrush.CenterColor = Color.FromArgb(50, 255, 255, 255);
highlightBrush.SurroundColors = new[] { Color.Transparent };
g.FillPath(highlightBrush, new GraphicsPath());
}
// 绘制指示线
using (var indicatorPen = new Pen(Color.FromArgb(100, 0, 0, 0), 2f))
{
float lineLength = knobRadius * 0.8f;
float endX = knobX + (float)(lineLength * Math.Cos(currentAngle * Math.PI / 180));
float endY = knobY + (float)(lineLength * Math.Sin(currentAngle * Math.PI / 180));
g.DrawLine(indicatorPen, knobX, knobY, endX, endY);
}
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
IsOn = !IsOn;
}
protected virtual void OnValueChanged(EventArgs e)
{
ValueChanged?.Invoke(this, e);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
animationTimer?.Dispose();
}
base.Dispose(disposing);
}
}
}
控件提供多个可自定义属性:
RotatingSwitchButton是一个结合了现代UI设计理念和经典开关外观的自定义控件。通过精心设计的视觉效果和流畅的动画,为Windows Forms应用程序提供了一个既美观又实用的用户界面元素。该控件的实现展示了如何在.NET环境下创建高质量的自定义控件,以及如何运用GDI+绘图技术实现各种视觉效果。
该控件特别适合用于:
通过合理运用渐变、阴影和动画等效果,这个控件成功地将实用性和美观性结合在一起,为Windows Forms应用程序开发提供了一个优秀的UI控件范例。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!