如何实现可鼠标拖动的2D WPF控件以调整QuadraticBezierSegment控制点
实现方案(纯MVVM无后台代码)
1. 先安装依赖包
安装NuGet包Microsoft.Xaml.Behaviors.Wpf,这是微软官方提供的MVVM事件绑定工具,无需编写窗口后台代码即可实现事件到ViewModel命令的绑定。
2. 调整ViewModel代码
using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Input; namespace BezierDemo { // 轻量RelayCommand实现,用于命令绑定 public class RelayCommand : ICommand { private readonly Action<object> _execute; private readonly Func<object, bool> _canExecute; public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); public void Execute(object parameter) => _execute(parameter); public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } } class MainViewModel : INotifyPropertyChanged { private Point _q; private double _qy; private double _qx; private bool _isDragging; public MainViewModel() { _q.X = 50; _q.Y = 0; // 初始化拖拽相关命令 MouseDownCommand = new RelayCommand(_ => _isDragging = true); MouseUpCommand = new RelayCommand(_ => _isDragging = false); MouseMoveCommand = new RelayCommand(param => { if (!_isDragging || param is not MouseEventArgs e) return; var pos = e.GetPosition((IInputElement)e.Source); // 保持和原Slider一致的坐标范围限制 QX = Math.Clamp(pos.X, 0, 100); QY = Math.Clamp(pos.Y, 0, 100); }); } public ICommand MouseDownCommand { get; } public ICommand MouseUpCommand { get; } public ICommand MouseMoveCommand { get; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (Equals(storage, value)) return false; storage = value; OnPropertyChanged(propertyName); return true; } // 修正属性赋值顺序,确保通知逻辑正确 public double QX { get => _q.X; set { if (SetProperty(ref _qx, value)) { Q = new Point(value, Q.Y); } } } public double QY { get => _q.Y; set { if (SetProperty(ref _qy, value)) { Q = new Point(Q.X, value); } } } public Point Q { get => _q; set => SetProperty(ref _q, value); } } }
3. 调整XAML代码
<Window x:Class="BezierDemo.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:BezierDemo" <!-- 添加行为命名空间 --> xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors" mc:Ignorable="d" Title="MainWindow" Height="450" Width="506"> <Window.DataContext> <local:MainViewModel/> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="400*"/> <ColumnDefinition Width="100*"/> </Grid.ColumnDefinitions> <!-- 给Canvas命名用于坐标计算 --> <Canvas Grid.Column="0" x:Name="BezierCanvas"> <Ellipse Width="5" Height="5" Fill="Indigo" Stroke="Indigo" Cursor="Hand" > <Ellipse.RenderTransform> <TranslateTransform X="{Binding Path=QX}" Y="{Binding Path=QY}"/> </Ellipse.RenderTransform> <!-- 绑定鼠标事件到ViewModel命令 --> <behaviors:Interaction.Triggers> <behaviors:EventTrigger EventName="MouseLeftButtonDown"> <behaviors:InvokeCommandAction Command="{Binding MouseDownCommand}" /> </behaviors:EventTrigger> <behaviors:EventTrigger EventName="MouseLeftButtonUp"> <behaviors:InvokeCommandAction Command="{Binding MouseUpCommand}" /> </behaviors:EventTrigger> <!-- 监听Canvas的鼠标移动,避免拖出控制点后失效 --> <behaviors:EventTrigger EventName="MouseMove" SourceObject="{Binding ElementName=BezierCanvas}"> <behaviors:InvokeCommandAction Command="{Binding MouseMoveCommand}" PassEventArgsToCommand="True" /> </behaviors:EventTrigger> </behaviors:Interaction.Triggers> </Ellipse> </Canvas> <!-- 贝塞尔曲线 --> <Path Stroke="Black" Fill="Gray" Grid.Column="0"> <Path.Data> <PathGeometry> <PathFigure> <PathFigure.StartPoint> <Point X="0" Y="100" /> </PathFigure.StartPoint> <QuadraticBezierSegment Point1="{Binding Path=Q}" Point2="100, 100" /> </PathFigure> </PathGeometry> </Path.Data> </Path> <!-- 原有Slider控件,验证没问题可直接删除 --> <Grid Grid.Column="1" > <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" VerticalAlignment="Center"> <Slider Name="X" Orientation="Vertical" Maximum="100" HorizontalAlignment="Center" VerticalAlignment="Center" Height="234" Value="{Binding Path=QX}" Minimum="0.0"/> <Label HorizontalAlignment="Center">X</Label> </StackPanel> <StackPanel Grid.Column="1" VerticalAlignment="Center"> <Slider Name="Y" Orientation="Vertical" Maximum="100" HorizontalAlignment="Center" VerticalAlignment="Center" Height="234" Value="{Binding Path=QY}" Minimum="0.0"/> <Label HorizontalAlignment="Center">Y</Label> </StackPanel> </Grid> </Grid> </Window>
修改后运行即可直接拖动紫色控制点调整贝塞尔曲线,控制点坐标会和原有Slider双向同步,不需要写任何窗口后台代码,完全符合MVVM规范,确认功能正常后可直接删除Slider部分的代码。
内容的提问来源于stack exchange,提问作者NⵙⵙB
相关产品推荐
相关产品推荐

