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; | |
} | |
public int MinValue { get; set; } = -720; | |
public int MaxValue { get; set; } = 720; | |
public int Value { get; set; } = -720; | |
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(); | |
} | |
} | |
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(); | |
} | |
/// <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; | |
} | |
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; | |
} | |
} |
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 | |
{ | |
/// <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; | |
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); | |
} | |
/// <summary> | |
/// 绘制水平线 | |
/// </summary> | |
[ | ]|
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)); | |
} | |
/// <summary>圆心</summary> | |
private Point2D _circleCenter = new Point2D(0, 0); | |
/// <summary>半径</summary> | |
private double _radius = 0; | |
/// <summary>刻度线之间的夹角</summary> | |
private double _anglePerLine = 0; | |
} | |
} |
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> |
效果图