WPF - Path Geometry on Canvas scaling differently than Thumb control

129 Views Asked by At

This is a WPF question.

I am trying to track / drag the vertices of a PathGeometry on a Canvas using Thumb Controls to drag the vertices.

It seems that the PathGeometry is scaled differently than the Thumb positions relative to the Canvas.

How can I compute the scaling ratio? Once I have that, I can use a ScaleTransform to correct it.

Thanks in advance.

Here is my XAML. I have a scale value of 3 hard coded, but it doesn't work if my window size changes:

MyControl.XAML


<UserControl x:Class="WPF_Discovery_Client.ColorOpacityControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPF_Discovery_Client"
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Style TargetType="{x:Type Thumb}" x:Key="RoundThumb">
            <Style.Resources>
                <Style TargetType="Border">
                    <Setter Property="CornerRadius" Value="10" />
                    <Setter Property="BorderThickness" Value="1" />
                </Style>
            </Style.Resources>
        </Style>
    </UserControl.Resources>
    <Grid>
        <Canvas Background="aqua" x:Name="MyCanvas" SizeChanged="MyCanvas_SizeChanged" Loaded="MyCanvas_Loaded" >
            <Path Stroke="Black" StrokeThickness="1" Height="450" Stretch="Fill" Width="800" >
                <Path.Fill>
                    <LinearGradientBrush ColorInterpolationMode="ScRgbLinearInterpolation" StartPoint="0,0" EndPoint="1,0">
                        <GradientStop Offset="0" Color="Red"/>
                        <GradientStop Offset="0.17" Color="Orange"/>
                        <GradientStop Offset="0.34" Color="Yellow"/>
                        <GradientStop Offset="0.51" Color="Green"/>
                        <GradientStop Offset="0.68" Color="Blue"/>
                        <GradientStop Offset="0.85" Color="Indigo"/>
                        <GradientStop Offset="1.0" Color="Violet"/>
                    </LinearGradientBrush>
                </Path.Fill>
                <Path.Data>
                    <PathGeometry>
                        <PathGeometry.Figures>
                            <PathFigureCollection>
                                <PathFigure x:Name="MyPath" IsClosed="True" StartPoint="{Binding BottomLeftCorner, Mode=TwoWay}">
                                    <PathFigure.Segments>
                                        <PathSegmentCollection>
                                            <LineSegment Point="{Binding LeftVertex, Mode=TwoWay}"/>
                                            <LineSegment Point="{Binding MiddleVertex, Mode=TwoWay}" />
                                            <LineSegment Point="{Binding RightVertex, Mode=TwoWay}" />
                                            <LineSegment Point="{Binding BottomRightCorner, Mode=TwoWay}" />
                                        </PathSegmentCollection>
                                    </PathFigure.Segments>
                                </PathFigure>
                            </PathFigureCollection>
                        </PathGeometry.Figures>
                    </PathGeometry>
                </Path.Data>
                <Path.RenderTransform>
                    <ScaleTransform ScaleX="2.0" ScaleY="2.0"/>
                </Path.RenderTransform>
            </Path>

            <Thumb Name="LeftThumb" Style="{DynamicResource RoundThumb}" Background="White"
                Width="20" Height="20" DragDelta="LeftThumb_DragDelta" 
                DragStarted="LeftThumb_DragStarted"  DragCompleted="LeftThumb_DragCompleted"/>

            <Thumb Name="MiddleThumb" Style="{DynamicResource RoundThumb}" Background="White"
                Width="20" Height="20" DragDelta="MiddleThumb_DragDelta"
                DragStarted="MiddleThumb_DragStarted"  DragCompleted="MiddleThumb_DragCompleted"/>

            <Thumb Name="RightThumb" Style="{DynamicResource RoundThumb}" Background="White" 
                Width="20" Height="20" DragDelta="RightThumb_DragDelta"
                DragStarted="RightThumb_DragStarted"  DragCompleted="RightThumb_DragCompleted"/>
            
        </Canvas>
    </Grid>
</UserControl>

And here is the code behind for the control:

MyControl.xaml.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
using System.Windows.Controls.Primitives;
using System.ComponentModel;

namespace WPF_Discovery_Client
{
    /// <summary>
    /// Interaction logic for ColorOpacityControl.xaml
    /// </summary>
    public partial class ColorOpacityControl : UserControl, INotifyPropertyChanged
    {
        const int ThumbRadius = 10;

        // Bottom corners
        public Point BottomRightCorner
        {
            get { return (Point)GetValue(BottomRightCornerProperty); }
            set { SetValue(BottomRightCornerProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BottomRightCorner.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BottomRightCornerProperty =
            DependencyProperty.Register("BottomRightCorner", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0, 100)));



        public Point BottomLeftCorner
        {
            get { return (Point)GetValue(BottomLeftCornerProperty); }
            set { SetValue(BottomLeftCornerProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BottomLeftCorner.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BottomLeftCornerProperty =
            DependencyProperty.Register("BottomLeftCorner", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0, 200)));


        // Thumb center locations
        public Point LeftVertex
        {
            get { return (Point)GetValue(LeftVertexProperty); }
            set { SetValue(LeftVertexProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LeftVertex.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LeftVertexProperty =
            DependencyProperty.Register("LeftVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0,266)));



        public Point MiddleVertex
        {
            get { return (Point)GetValue(MiddleVertexProperty); }
            set { SetValue(MiddleVertexProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MiddleVertex.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MiddleVertexProperty =
            DependencyProperty.Register("MiddleVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(100, 100)));




        public Point RightVertex
        {
            get { return (Point)GetValue(RightVertexProperty); }
            set { SetValue(RightVertexProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RightVertex.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RightVertexProperty =
            DependencyProperty.Register("RightVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(100, 50)));




        public ColorOpacityControl()
        {
            InitializeComponent();

            DataContext = this;
        }

        private void LeftThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
        {
            LeftThumb.Background = Brushes.Red;
        }

        private void LeftThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
        {
            LeftThumb.Background = Brushes.White;
        }

        private void LeftThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
        {
            //Move the Thumb to the mouse position during the drag operation
            var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
            var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
            if ((xadjust >= 0) && (yadjust >= 0))
            {
                // Compute new thumb location
                double X = Canvas.GetLeft(LeftThumb) + e.HorizontalChange;
                double Y = Canvas.GetTop(LeftThumb) + e.VerticalChange;

                // Move thumb
                Canvas.SetLeft(LeftThumb, X);
                Canvas.SetTop(LeftThumb, Y);

                // Compute center of thumb as vertex location
                LeftVertex = new Point(X + LeftThumb.Width, Y + LeftThumb.Height);
                NotifyPropertyChanged("LeftVertex");
            }
        }

        private void MiddleThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
        {
            MiddleThumb.Background = Brushes.Green;
        }

        private void MiddleThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
        {
            MiddleThumb.Background = Brushes.White;
        }

        private void MiddleThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
        {
            //Move the Thumb to the mouse position during the drag operation
            var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
            var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
            if ((xadjust >= 0) && (yadjust >= 0))
            {
                // Compute new thumb location
                double X = Canvas.GetLeft(MiddleThumb) + e.HorizontalChange;
                double Y = Canvas.GetTop(MiddleThumb) + e.VerticalChange;

                // Move thumb
                Canvas.SetLeft(MiddleThumb, X);
                Canvas.SetTop(MiddleThumb, Y);

                // Compute center of thumb as vertex location
                MiddleVertex = new Point(X + MiddleThumb.Width, Y + MiddleThumb.Height);
                NotifyPropertyChanged("MiddleVertex");
            }
        }

        private void RightThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
        {
            RightThumb.Background = Brushes.Yellow;
        }

        private void RightThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
        {
            RightThumb.Background = Brushes.White;
        }

        private void RightThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
        {
            //Move the Thumb to the mouse position during the drag operation
            var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
            var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
            if ((xadjust >= 0) && (yadjust >= 0))
            {
                // Compute new thumb location
                double X = Canvas.GetLeft(RightThumb) + e.HorizontalChange;
                double Y = Canvas.GetTop(RightThumb) + e.VerticalChange;

                // Move thumb
                Canvas.SetLeft(RightThumb, X);
                Canvas.SetTop(RightThumb, Y);

                // Compute center of thumb as vertex location
                RightVertex = new Point(X + ThumbRadius, Y + ThumbRadius);
                NotifyPropertyChanged("RightVertex");
            }
        }

        private void MyCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            // Adjust bottom left corners
            BottomLeftCorner = new Point(0, MyCanvas.ActualHeight);
            NotifyPropertyChanged("BottomLeftCorner");

            // Adjust botton right corner
            BottomRightCorner = new Point(MyCanvas.ActualWidth, MyCanvas.ActualHeight);
            NotifyPropertyChanged("BottomRightCorner");
        }

        private void InitializeVertices()
        {
            // Initialize bottom left corner
            BottomLeftCorner = new Point(ThumbRadius, MyCanvas.ActualHeight - ThumbRadius);
            NotifyPropertyChanged("BottomLeftCorner");

            // Initialize bottom right corner
            BottomRightCorner = new Point(MyCanvas.ActualWidth - ThumbRadius, MyCanvas.ActualHeight - ThumbRadius);
            NotifyPropertyChanged("BottomRightCorner");

            // Initialize right vertex
            RightVertex = new Point(MyCanvas.ActualWidth - ThumbRadius, ThumbRadius);
            NotifyPropertyChanged("RightVertex");

            // Initialize left vertex
            LeftVertex = BottomLeftCorner;
            NotifyPropertyChanged("LeftVertex");

            // Initialize middle vertex
            MiddleVertex = new Point(MyCanvas.ActualWidth * 0.5, MyCanvas.ActualHeight * 0.5);
            NotifyPropertyChanged("MiddleVertex");

            // Initialize Left Thumb
            Canvas.SetLeft(LeftThumb, LeftVertex.X - ThumbRadius);
            Canvas.SetTop(LeftThumb, LeftVertex.Y - ThumbRadius);

            // Initialize Right Thumb
            Canvas.SetLeft(RightThumb, RightVertex.X - ThumbRadius);
            Canvas.SetTop(RightThumb, RightVertex.Y - ThumbRadius);

            // Initialize Middle Thumb
            Canvas.SetLeft(MiddleThumb, MiddleVertex.X - ThumbRadius);
            Canvas.SetTop(MiddleThumb, MiddleVertex.Y - ThumbRadius);
        }


        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }


        #endregion

        private void MyCanvas_Loaded(object sender, RoutedEventArgs e)
        {
            InitializeVertices();
        }

    }
}

As you can see, I am setting both the vertices and the thumbs to the same position relative to the canvas. However, if you run the code, you can see that the initial triangle with the gradient fill is much smaller than it needs to be. I want the 3 thumbs to coincide with the 2 vertices and the midpoint.

Moreover, I notice that I have a Height and Width specified for the Path, which I am not sure if I need. If I make it larger to match the size of the canvas, the triangle will grow. Should I set Height and with to *?

I am new to WPF graphics, so any help would be appreciated.

1

There are 1 best solutions below

0
John On

Well.. after banging my head on the wall for a couple days, I finally figured out how to fix this issue. Hopefully this will save someone a lot of time and hassle down the road.

The problem was that I had the Path width/height set to the size of the parent window, and the Stretch property set to Fill.

Once I made the following change, the scaling came out correct.


    <Canvas Background="Black" x:Name="MyCanvas" SizeChanged="MyCanvas_SizeChanged" Loaded="MyCanvas_Loaded">
        <Path x:Name="MyPath" Stroke="Black" StrokeThickness="1" Height="{Binding MyCanvas.ActualHeight}" Width="{Binding MyCanvas.ActualWidth}" Stretch="None"  HorizontalAlignment="Left" VerticalAlignment="Top">