V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yanjinhua
V2EX  ›  C#

WPF 实现拟物音量控件

  •  
  •   yanjinhua · 330 天前 · 1018 次点击
    这是一个创建于 330 天前的主题,其中的信息可能已经有所发展或是发生改变。

    WPF 实现拟物音量控件

    控件名:Wheel

    作 者:WPFDevelopersOrg - 俞宏伟

    原文链接https://github.com/WPFDevelopersOrg/SimulationControl

    • 框架使用.NET6
    • Visual Studio 2022;
    • 绘制使用了Canvas作为容器控件,DrawingContext上绘制水平线。
    • 当鼠标滚动滚轮时或按下鼠标向上向下拖动时计算角度偏移并更新图层。

    1 )创建 Wheel.xaml 代码如下:

    <UserControl x:Class="TopControl.Wheel"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:TopControl"
                 mc:Ignorable="d" 
                 Width="30" Height="180">
        <Grid>
            <!-- 进度 -->
            <Grid Width="2" Background="#0a0a0a" Height="180" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
            <Grid Width="2" x:Name="Grid_Value" Height="0" HorizontalAlignment="Left" VerticalAlignment="Bottom">
                <Grid Height="180" VerticalAlignment="Bottom">
                    <Grid.Background>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                            <GradientStop Offset="0" Color="#f0d967"/>
                            <GradientStop Offset="1" Color="#33b08d"/>
                        </LinearGradientBrush>
                    </Grid.Background>
                </Grid>
            </Grid>
            <Grid Background="#0a0a0a" Width="26" HorizontalAlignment="Right" Height="180" Margin="2,0,0,0"/>
            <!-- 滚轮 -->
            <Grid x:Name="WheelArea" Height="176" Width="22" HorizontalAlignment="Right" Margin="0,0,2,0"
                  MouseDown="WheelArea_MouseDown" MouseMove="WheelArea_MouseMove" MouseUp="WheelArea_MouseUp" MouseWheel="WheelArea_MouseWheel">
                <Grid.Background>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                        <GradientStop Color="#141414" Offset="0"/>
                        <GradientStop Color="#3c3c3c" Offset="0.5"/>
                        <GradientStop Color="#141414" Offset="1"/>
                    </LinearGradientBrush>
                </Grid.Background>
                <Grid x:Name="LayerBox" IsHitTestVisible="False"/>
            </Grid>
        </Grid>
    </UserControl>
    

    2 )创建 Wheel.xaml.cs 代码如下:

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    namespace TopControl
    {
        public partial class Wheel : UserControl
        {
            public Wheel()
            {
                InitializeComponent();
                Loaded += Wheel_Loaded;
            }
    
            #region 属性
    
            public int MinValue { get; set; } = -720;
    
            public int MaxValue { get; set; } = 720;
    
            public int Value { get; set; } = -720;
    
            #endregion
    
            #region 控件事件
    
            private void Wheel_Loaded(object sender, RoutedEventArgs e)
            {
                InitLayer();
            }
    
            private void WheelArea_MouseDown(object sender, MouseButtonEventArgs e)
            {
                if (e.ChangedButton == MouseButton.Left)
                {
                    BeginDrag();
                }
            }
    
            private void WheelArea_MouseMove(object sender, MouseEventArgs e)
            {
                if (_dragMode)
                {
                    Drag();
                }
            }
    
            private void WheelArea_MouseUp(object sender, MouseButtonEventArgs e)
            {
                if (e.ChangedButton == MouseButton.Left && _dragMode)
                {
                    EndDrag();
                }
            }
    
            private void WheelArea_MouseWheel(object sender, MouseWheelEventArgs e)
            {
                if (_dragMode) return;
    
                int offset = e.Delta / 120;
                if (offset < 0 && Value > MinValue)
                {
                    Value += offset;
                    UpdateProgress();
                    _wheelLayer.AngleOffset -= offset * _wheelSpeed;
                    _wheelLayer.UpdateLayer();
                }
                else if (offset > 0 && Value < MaxValue)
                {
                    Value += offset;
                    UpdateProgress();
                    _wheelLayer.AngleOffset -= offset * _wheelSpeed;
                    _wheelLayer.UpdateLayer();
                }
            }
    
            #endregion
    
            #region 鼠标操作
    
            private void BeginDrag()
            {
                _dragMode = true;
                WheelArea.CaptureMouse();
    
                _dragStart = Mouse.GetPosition(WheelArea);
                _angleStart = _wheelLayer.AngleOffset;
    
                _valueStart = Value;
                _offsetDown = Value - MinValue;
                _offsetUp = Value - MaxValue;
            }
    
            private void Drag()
            {
                double offset_y = Mouse.GetPosition(WheelArea).Y - _dragStart.Y;
                if (offset_y < _offsetUp) offset_y = _offsetUp;
                else if (offset_y > _offsetDown) offset_y = _offsetDown;
    
                double offset_angle = offset_y * _wheelSpeed;
                Value = _valueStart - (int)offset_y;
                _wheelLayer.AngleOffset = _angleStart + offset_angle;
                UpdateProgress();
                _wheelLayer.UpdateLayer();
            }
    
            private void EndDrag()
            {
                _dragMode = false;
                WheelArea.ReleaseMouseCapture();
            }
    
            #endregion
    
            #region 私有方法
    
            /// <summary>
            /// 初始化图层
            /// </summary>
            private void InitLayer()
            {
                _wheelLayer.Width = LayerBox.ActualWidth;
                _wheelLayer.Height = LayerBox.ActualHeight;
                LayerBox.Children.Add(_wheelLayer);
                _wheelLayer.Init();
                _wheelLayer.UpdateLayer();
            }
    
            /// <summary>
            /// 更新进度
            /// </summary>
            private void UpdateProgress()
            {
                Grid_Value.Height = (double)(Value - MinValue) / (MaxValue - MinValue) * 180;
            }
    
            #endregion
    
            #region 字段
    
            private readonly WheelLayer _wheelLayer = new WheelLayer();
    
            private Point _dragStart = new Point();
            private double _angleStart = 0;
            private int _valueStart = 0;
            private bool _dragMode = false;
    
            /// <summary>滚轮速度</summary>
            private readonly double _wheelSpeed = 0.7;
    
            /// <summary>最大向上偏移</summary>
            private double _offsetUp;
            /// <summary>最大向下偏移</summary>
            private double _offsetDown;
    
            #endregion
        }
    }
    

    3 )创建 WheelLayer.cs 代码如下:

    using MathUtil;
    using System;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Media;
    using WpfUtil;
    
    namespace TopControl
    {
        public class WheelLayer : SingleLayer
        {
            #region 属性
    
            /// <summary>槽高度:180px - 上下边框( 4px )</summary>
            public int GrooveHeight { get; set; } = 176;
    
            /// <summary>角度:圆弧切线与槽边的夹角</summary>
            public int Angle { get; set; } = 90;
    
            /// <summary>刻度线数量</summary>
            public int LineCount { get; set; } = 90;
    
            /// <summary>角度偏移</summary>
            public double AngleOffset { get; set; } = 0;
    
            #endregion
    
            #region 公开方法
    
            public override void Init()
            {
                // 起点、终点、中点
                Point2D startPoint = new Point2D(0, 0);
                Point2D endPoint = new Point2D(GrooveHeight, 0);
                Point2D centerPoint = startPoint.CenterWith(endPoint);
                // 向量:中点 -> 起点
                Vector2 centerToStart = new Vector2(centerPoint, startPoint);
                centerToStart.Rotate(-90);
                // 向量:终点 -> 中点
                Vector2 endToCenter = new Vector2(endPoint, centerPoint);
                endToCenter.Rotate(-90 + Angle);
                // 圆心
                _circleCenter = centerToStart.IntersectionWith(endToCenter);
                // 向量:圆心 -> 起点
                Vector2 vector = new Vector2(_circleCenter, startPoint);
                _radius = vector.Distance;
                _anglePerLine = 360.0 / LineCount;
            }
    
            protected override void OnUpdate()
            {
                // 最高点
                Point2D top = new Point2D(_circleCenter.X, _circleCenter.Y - _radius);
                // 向量:圆心 -> 最高点
                Vector2 vector = new Vector2(_circleCenter, top);
                double max = Math.Abs(vector.Target.Y);
                // 偏移角度
                vector.Rotate(AngleOffset);
                // 开始旋转计算刻度位置
                List<Point2D> pointList = new List<Point2D>();
                for (int counter = 0; counter < LineCount; counter++)
                {
                    if (vector.Target.Y < 0) pointList.Add(vector.Target);
                    vector.Rotate(-_anglePerLine);
                }
    
                // 绘制刻度线
                foreach (var item in pointList)
                    DrawHorizontalLine(item.X, Math.Abs(item.Y) / max);
            }
    
            #endregion
    
            #region 私有方法
    
            /// <summary>
            /// 绘制水平线
            /// </summary>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            private void DrawHorizontalLine(double y, double opacity)
            {
                Pen pen = new Pen(new SolidColorBrush(Color.FromArgb((byte)(opacity * 255), 32, 32, 32)), 1);
                Pen pen2 = new Pen(new SolidColorBrush(Color.FromArgb((byte)(opacity * 255), 64, 64, 64)), 1);
                _dc.DrawLine(pen, new Point(2, y - 0.5), new Point(Width - 2, y - 0.5));
                _dc.DrawLine(pen2, new Point(2, y + 0.5), new Point(Width - 2, y + 0.5));
            }
    
            #endregion
    
            #region 字段
    
            /// <summary>圆心</summary>
            private Point2D _circleCenter = new Point2D(0, 0);
            /// <summary>半径</summary>
            private double _radius = 0;
            /// <summary>刻度线之间的夹角</summary>
            private double _anglePerLine = 0;
    
            #endregion
        }
    }
    

    4 )创建 WheelExample.xaml 代码如下:

    <wd:Window x:Class="TopControl.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:TopControl"
            xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
            mc:Ignorable="d" Background="#1e1e1e"
            Title="https://github.com/WPFDevelopersOrg/SimulationControl - 俞宏伟" Height="450" Width="800">
        <Grid>
            <local:Wheel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="50"/>
        </Grid>
    </wd:Window>
    

    更多代码请访问GitHub源码地址

    1 条回复    2023-08-20 17:37:26 +08:00
    Soar360
        1
    Soar360  
       250 天前
    支持支持
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2770 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:37 · PVG 13:37 · LAX 22:37 · JFK 01:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.