WPF 實作拟物音量控件
控件名:Wheel
作 者:WPFDevelopersOrg - 俞宏偉
原文連結[1]: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]
原文連結: https://github.com/WPFDevelopersOrg/SimulationControl