Home > Mobile >  How to implement a WPF control that is moved by the mouse, like a slider control but 2D?
How to implement a WPF control that is moved by the mouse, like a slider control but 2D?

Time:11-19

I want to control the rendering of a enter image description here

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);
}
  • Related