如何实现一个由鼠标移动的 WPF 控件,如滑块控件但 2D?
Posted
技术标签:
【中文标题】如何实现一个由鼠标移动的 WPF 控件,如滑块控件但 2D?【英文标题】:How to implement a WPF control that is moved by the mouse, like a slider control but 2D? 【发布时间】:2021-12-27 22:24:05 【问题描述】:我想通过其控制点 Q(Segment 的 Point1 属性)在运行时控制 QuadraticBezierSegment 的呈现。我可以使用单独的滑块控件来控制点的 X 和 Y 值。但我最终希望能够使用拖动控制点来重塑段。在下面的代码中,我可以绘制控制点和线段,它们都响应滑块。但我不知道如何拖动点来控制段(然后我会放弃滑块)。
目前没有后面的代码,我正在尝试将所有内容保存在 XAML/MVVM 中,但不确定这是否可能。谢谢。
这是视图模型:
namespace BezierDemo
class MainViewModel : INotifyPropertyChanged
private System.Windows.Point _q;
private double _qy;
private double _qx;
public MainViewModel()
_q.X = 50;
_q.Y = 0;
// https://www.danrigby.com/2015/09/12/inotifypropertychanged-the-net-4-6-way/
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
this.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;
this.OnPropertyChanged(propertyName);
return true;
public double QX
get return _q.X;
set Q = new System.Windows.Point(value, Q.Y); SetProperty(ref this._qx, value);
public double QY
get return _q.Y;
set Q = new System.Windows.Point(Q.X, value); SetProperty(ref this._qy, value);
public System.Windows.Point Q
get return _q;
set SetProperty(ref this._q, value);
...这里是 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"
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>
<!-- Bezier Control Point -->
<Canvas Grid.Column="0">
<Ellipse Width="5" Height="5" Fill="Indigo" Stroke="Indigo" Cursor="Hand" >
<Ellipse.RenderTransform>
<TranslateTransform X="Binding Path=QX" Y="Binding Path=QY"/>
</Ellipse.RenderTransform>
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Ellipse.MouseMove">
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
</Canvas>
<!-- QuadraticBezierSegment -->
<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>
<!-- X & Y Slider Controls -->
<Grid Grid.Column="2" >
<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>
【问题讨论】:
将 MouseMove 处理程序添加到设置 Ellipse 位置的 Canvas。画布需要具有非空背景,即透明。通过 Canvas.Left 和 Top 而不是 RenderTransform 设置位置。 请注意,在视图模型中实现 x/y 值的双重存储似乎毫无意义。存储 x 和 y,或点,而不是两者。 如果用户可以快速移动鼠标,你会发现他们失去了一个“拇指”,不得不回去重新拿起它。正是出于这个原因,我制作了可拖动的用户控件,并在它们的包含面板中处理预览鼠标向下、移动和鼠标向上以获取可拖动的东西。 【参考方案1】:如果您将贝塞尔控制点包装在 Thumb
元素中,那么您可以很容易地完成您想要的。
<!-- Bezier Control Point -->
<Canvas Grid.Column="0">
<Thumb DragDelta="Thumb_DragDelta" Canvas.Left="Binding QX, Mode=TwoWay" Canvas.Top="Binding QY, Mode=TwoWay">
<Thumb.Template>
<ControlTemplate>
<Ellipse Width="5" Height="5" Fill="Indigo" Stroke="Indigo" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
注意:我必须将 Panel.ZIndex="-1"
添加到 QuadraticBezierSegment 中,这样椭圆才能呈现在 Bezier 段的前面。或者您可以在贝塞尔线段声明之后移动拇指部分。
代码隐藏:
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
UIElement thumb = e.Source as UIElement;
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
您可以使用Microsoft.Xaml.Behaviors.Wpf nuget 包将后面的代码转换为视图模型中的事件处理程序。
应该是这样的
<Canvas Grid.Column="0">
<Thumb Canvas.Left="Binding QX, Mode=TwoWay" Canvas.Top="Binding QY, Mode=TwoWay">
<b:Interaction.Triggers>
<b:EventTrigger EventName="DragDelta">
<b:InvokeCommandAction Command="Binding HandleDragDelta" PassEventArgsToCommand="True" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Thumb.Template>
<ControlTemplate>
<Ellipse Width="5" Height="5" Fill="Indigo" Stroke="Indigo" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
其中HandleDragDelta
是某种ICommand
实现,它可以采用DragDeltaEventArgs
参数,因为您需要它。
private DelegateCommand<DragDeltaEventArgs> handleDragDelta;
public ICommand HandleDragDelta => handleDragDelta ??= new DelegateCommand<DragDeltaEventArgs>(PerformHandleDragDelta);
private void PerformHandleDragDelta(DragDeltaEventArgs e)
UIElement thumb = e.Source as UIElement;
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
【讨论】:
谢谢,Thumb 正是我想要的。我还没有让事件处理程序工作 - DelegateCommand 需要 Prism 吗?但能够移动拇指是拼图中缺失的部分。 可以是棱镜。那里有许多 ICommand 实现。如果您不想使用 Prism,请参阅 gist.github.com/InKahootz/0fb62ad9330c07aec2f89a34e3c3e5a4 以获得简单的方法。一旦你连接了事件处理程序,它应该开始移动/可拖动!以上是关于如何实现一个由鼠标移动的 WPF 控件,如滑块控件但 2D?的主要内容,如果未能解决你的问题,请参考以下文章