WPF 实现拟物音量控件
控件名:Wheel
作 者:WPFDevelopersOrg - 俞宏伟
.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
源码地址
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.