C#+ WinForm 自定义控件 实现精美的旋转开关按钮

521 阅读9分钟

前言

在 WinForm 程序开发中,自定义控件是提升用户界面体验的重要手段。本文将详细介绍一个具有精美视觉效果的旋转开关按钮控件(RotatingSwitchButton)的实现。

该控件结合现代 UI 设计元素,包括平滑动画、阴影效果、渐变和金属质感等特性,帮助用户提供直观且吸引人的交互体验。

控件介绍

RotatingSwitchButton 是一个模拟物理旋转开关的自定义控件,具备以下主要特征:

1、旋转动画

开关状态切换时,控件会执行平滑的旋转动画,提供流畅的视觉过渡效果。

2、视觉反馈

通过颜色变化和位置移动清晰指示开关状态,确保用户能够直观地理解当前的操作结果。

3、阴影效果

支持可自定义的控件阴影,增加深度感和立体感,使控件在界面上更加突出。

4、金属质感

运用渐变和光泽效果营造出逼真的金属质感,提升整体视觉档次。

5、精细刻度

环形刻度显示增强了控件的专业性和精确度,适用于需要高精度操作的场景。

核心功能

1、状态管理与动画

控件使用布尔值isOn追踪当前状态,并通过currentAngle控制旋钮的旋转角度。

动画效果通过Timer实现,使用缓动函数使动画更自然:

private bool isOn = false;
private float currentAngle = 0f;
private readonly float targetOnAngle = -90f;  // 向上位置
private readonly float targetOffAngle = 90f;   // 向下位置

2、阴影效果

控件支持可自定义的阴影效果,通过以下属性控制:

EnableShadow:启用/禁用阴影

ShadowDepth:阴影深度

ShadowOpacity:阴影透明度

ShadowColor:阴影颜色

ShadowBlur:阴影模糊程度

阴影实现使用PathGradientBrush创建径向渐变,实现柔和的阴影效果:

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);
        }
    }
}

3、视觉元素绘制

外环绘制

外环采用渐变效果和高光,创造金属质感:

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, ...);
    }
}

刻度线绘制

通过循环绘制不同长度的刻度线,增强专业感:

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; // 主要刻度线
    // 绘制刻度线...
}

旋钮绘制

旋钮采用多层渲染,包括基础形状、阴影、高光和指示线:

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应用程序中:

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);
        }
    }
}

自定义选项

控件提供多个可自定义属性:

IsOn:控制/获取开关状态

EnableShadow:控制阴影效果

ShadowDepth:设置阴影深度

ShadowOpacity:设置阴影透明度

ShadowColor:设置阴影颜色

ShadowBlur:设置阴影模糊程度

总结

RotatingSwitchButton 是一个融合了现代 UI 设计理念与经典开关外观的自定义控件。

通过精心设计的视觉效果和流畅的动画,它为 Windows Forms 应用程序提供了一个既美观又实用的用户界面元素。

该控件的实现不仅展示了在 .NET 环境下创建高质量自定义控件的方法,还详细介绍了如何运用 GDI+ 绘图技术来实现各种视觉效果。其主要特点包括:

明确的开关状态显示:通过颜色变化和位置移动,清晰指示开关状态,确保用户能够直观地理解当前的操作结果。

渐变与光泽:采用渐变色和光泽效果,营造出逼真的金属质感,提升整体视觉档次。

阴影效果:支持可自定义的控件阴影,增加深度感和立体感,使控件在界面上更加突出。

环形刻度:精细的环形刻度显示增强了控件的专业性和精确度,适用于需要高精度操作的场景。

平滑动画:开关状态切换时,控件执行平滑的旋转动画,提供流畅的视觉过渡效果,提升了用户体验。

RotatingSwitchButton 特别适合以下应用场景:

需要明确开关状态显示的场景:如设备控制面板、设置界面等,确保用户可以一目了然地了解当前状态。

追求精致视觉效果的专业软件界面:适用于高端应用和专业工具,提供一流的视觉体验。

模拟物理设备控制面板的应用程序:如工业控制系统、智能家居界面等,增强真实感和交互性。

通过合理运用渐变、阴影和动画等效果,RotatingSwitchButton 成功地将实用性和美观性结合在一起,为 Windows Forms 应用程序开发提供了一个优秀的 UI 控件范例。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:技术老小子

出处:mp.weixin.qq.com/s/7D6CyGvm3rACjWW6i64iBg

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!