I want to control the rendering of a
Currently there is no code behind, I'm trying to keep everything in XAML/MVVM but not sure if that's possible. Thanks.
Here is the ViewModel:
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); }
}
}
}
...and here is the 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>
CodePudding user response:
If you wrap your Bezier Control Point in a Thumb
element, then you can accomplish what you want quite easily.
<!-- 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>
Note: I had to add Panel.ZIndex="-1"
to the QuadraticBezierSegment so the ellipse would render in front of the Bezier segment. Or you could move the thumb part after the bezier segment declaration.
The code-behind:
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);
}
You can convert the code behind to an event handler in your viewmodel by using the Microsoft.Xaml.Behaviors.Wpf nuget package.
That would look like this
<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>
Where HandleDragDelta
is a some sort of ICommand
implementation that can take the DragDeltaEventArgs
parameter because you'll need it.
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);
}