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.
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.