You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现可鼠标拖动的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 09:54:03