I am trying to think of the best way to scale two panels proportionally.
If I have a grid that contains two canvases, both stacked horizontally next to each other, I want canvas (A) to scale to the size of canvas (B), proportionally, though.
So, essentially, if canvas (B) increases in size, canvas (A) decreases, and if canvas (A) increases, canvas (B) decreases.
I'm thinking of using a converter to do this, but wanted to know if anyone had any good ideas.
Below is a link that demonstrates the desired behavior. Refer to the pan/zoom control in the lower right corner of the screen. That control represents a preview of the main screen. If you press on the zoom button within the pan/zoom control, the main screen zooms in, and the rectangular "pan" area in the pan/zoom control decreases in size.
http://quince.infragistics.com/#/Search/ViewPattern$pattern=Button+Groups/PatternExamples$guid=289a497a-6632-455a-87b6-74ee70c2d3be
Thanks!
Chris
A converter is probably be the best way to go. You could also use RenderTransform.ScaleX/ScaleY instead of adjusting the Height/Width of the canvas.
Here's an example of binding to a property. Not sure if it'd be better then a converter.
<Canvas Background="Blue">
<Canvas x:Name="canvas1" ClipToBounds="True" Background="Red" Width="100" Height="100">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ElementName=slider, Path=Value}" ScaleY="{Binding ElementName=slider, Path=Value}"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" ClipToBounds="True" Background="Green" Grid.Column="2" Height="100" Width="100" Canvas.Left="200">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ScaleValue2}" ScaleY="{Binding ScaleValue2}"/>
</Canvas.RenderTransform>
</Canvas>
<Slider x:Name="slider" Canvas.Top="200" Width="200" Value="{Binding Path=ScaleValue, Mode=TwoWay}" Maximum="2"></Slider>
</Canvas>
Code:
public partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private double scaleValue = 1;
public double ScaleValue
{
get
{
return scaleValue;
}
set
{
scaleValue = value;
NotifyPropertyChanged("ScaleValue");
NotifyPropertyChanged("ScaleValue2");
}
}
public double ScaleValue2
{
get
{
return slider.Maximum - ScaleValue;
}
}
}
Related
I'm trying to render a simple image by binding a PathFigureCollection defined in Code-Behind to the Figures property of a corresponding UI element. PropertyChanged is appearing as null in the debugger and the figures I'm trying to render are not appearing.
This is my first time implementing data binding so I'm guessing the issue lies with my understanding of it. Most similar issues I've found were solved by setting the DataContext variable or setting Source instead of Path in the XAML. I implemented those solutions and they don't solve my problem.
<Window x:Class="DrawingSandBox.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:DrawingSandBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="754">
<Grid Margin="0,0,0,0">
<Image HorizontalAlignment="Left" Height="400" Margin="10,10,10,10" VerticalAlignment="Bottom" Width="700" RenderTransformOrigin="0.5,0.5">
<mage.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Pen>
<Pen Thickness="11" Brush="Black"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="{Binding Path=Frame, UpdateSourceTrigger=PropertyChanged}" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
namespace DrawingSandBox
{
public partial class MainWindow : Window
{
private static readonly CurveBuilder curve = new CurveBuilder();
public MainWindow()
{
InitializeComponent();
DataContext = curve;
}
}
public class CurveBuilder : INotifyPropertyChanged
{
private PointCollection points;
private PolyBezierSegment seg;
private PathFigureCollection frame;
public event PropertyChangedEventHandler PropertyChanged;
public PathFigure Figure;
public PathFigureCollection Frame
{
get
{
return frame;
}
set
{
if (value != frame)
{
frame = value;
NotifyPropertyChanged("Frame");
}
}
}
public CurveBuilder()
{
points = new PointCollection { new Point(20, 20), new Point(40, 40) };
seg = new PolyBezierSegment(points, true);
Figure = new PathFigure(new Point(50, 50), new PathSegmentCollection { seg }, false);
Frame = new PathFigureCollection { Figure };
}
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
As it stands, this code merely displays a blank page.
You need at least 3 points for the PolyBezierSegment to show up.
According to the documentation:
A cubic Bezier curve is defined by four points: a start point, an end point and two
control points. A PolyBezierSegment specifies one or more cubic Bezier curves by
setting the Points property to a collection of points. For every three points in the
collection, the first and second points specify the two control points of the curve
and the third point specifies the end point.
The binding is correct. Add one more Point to your PointCollection or use a PolyQuadraticBezierSegment or QuadraticBezierSegment.
So I am working on a project which requires me to do some hardware simulations. Let's say I have a belt filled with sensors. Just like this one.
When the obj reaches the sensor it should trigger the sensor which in this case the state of the sensor becomes False (otherwise True). I am working with PLC so when it's false or true I write a bit to the PLC. I have this part covered though. Just need help triggering the sensor(s).
Just like so
Notice the color of the first sensor from the right side turns gray which means it detected an object so now the sensor is false (turned off in other words). Just as the obj passes by the sensor returns to its default state which is true
To do this I had the following in mind.
Something with collision when the sensor detects an object closing by
Instead of a straight beam I could have a region and when the object lands in this region the sensor triggers.
I could work with timers (I have tried this) but it gives me the feeling that I am actually cheating. So It's not very preferable.
I am working with WPF and C#. Would someone please show me some working sample code I could built upon that.
Thanks for your time!
Update #1 Based on the comment below
I have classes of the following with proberties.
Sensor (position, size)
The object which travels along the belt. (position, size)
The simulator I am working on should view both the behavior and visual representation. Just like the diagrams. For testing purposes I have now a simple eclipse. As soon as the object closes by the color of the eclipse changes. It changes back as soon as the object passses by. Doing this in timer.
The objects traveling are always in a shape of a rectangular, but may differ in sizes. The user is able to choose between 2 sort of objects. One is as big as what the diagram now shows, and the other is little bigger which might cause to trigger sensor earlier.
You need HitTesting.
And then you need to set a DependencyProperty something like CollisionDetected to True.
Then apply a DataTrigger to your Beam xaml to change its Style/BackgroundColor etc.
<uc:BeamControl>
<uc:BeamControl.Style>
<Style TargetType="BeamControl">
<Style.Triggers>
<DataTrigger Binding="{Binding CollisionDetected}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</uc:BeamControl.Style>
</uc:BeamControl>
*********************** New Addition with code************************
In this working solution I have used an Attached Property. I have kept everything simple to focus on core issue.
Window2.xaml
<Window x:Class="WpfAnimation.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAnimation"
Title="Window2" Height="300" Width="900">
<Canvas>
<Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}" Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="100" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">
</Path>
<Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}" Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="200" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">
</Path>
<Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}" Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="300" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">
</Path>
<Path local:Window2.MovingObjectPos="{Binding (Canvas.Left), ElementName=ElpObj}" Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Canvas.Left="400" Canvas.Top="50" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z">
</Path>
<Ellipse x:Name="ElpObj" Fill="Pink" Stroke="Black" HorizontalAlignment="Right" VerticalAlignment="Top" Width="57" Height="51" Canvas.Left="701" Canvas.Top="115">
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" To="50" By="-2.0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
</Canvas>
</Window>
Window2.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfAnimation
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
}
public static double GetMovingObjectPos(DependencyObject obj)
{
return (double)obj.GetValue(MovingObjectPosProperty);
}
public static void SetMovingObjectPos(DependencyObject obj, double value)
{
obj.SetValue(MovingObjectPosProperty, value);
}
// Using a DependencyProperty as the backing store for MovingObject. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MovingObjectPosProperty =
DependencyProperty.RegisterAttached("MovingObjectPos", typeof(double), typeof(Window2), new PropertyMetadata(0.0, new PropertyChangedCallback(MovingObjectPosChanged)));
private static void MovingObjectPosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double leftOfMovingObject = (double) e.NewValue ;
Path beam = (Path) d;
System.Diagnostics.Debug.WriteLine("Left = " + e.NewValue.ToString());
double leftOfBeam = Canvas.GetLeft(beam);
double widthOfBeam = 20.0;
if (leftOfMovingObject > leftOfBeam && leftOfMovingObject < leftOfBeam + widthOfBeam)
{
System.Diagnostics.Debug.WriteLine("Hit >>>>> = " + e.NewValue.ToString());
beam.Fill = Brushes.Gray;
}
}
}
}
I have refined my old answer using Task, and considering unlimited moving objects.
It uses actual HitTesting.
Create a UserControl for Beam object. This is needed to initiate Task immediately after Loading. It contains a DP for the containing Panel.
BeamControl.xaml
<UserControl x:Class="WpfAnimation.BeamControl" ...
Height="300" Width="39">
<Path Fill="Red" Stretch="Fill" Stroke="Black" HorizontalAlignment="Left" Width="39" Data="M19.5,0.5 L19.5,147.5 M0.5,148.5 L38.5,148.5 L38.5,169.5 L0.5,169.5 z"/>
</UserControl>
BeamControl.xaml.cs
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfAnimation
{
/// <summary>
/// Interaction logic for BeamControl.xaml
/// </summary>
public partial class BeamControl : UserControl
{
Path _beamPath;
public Panel Sensor
{
get { return (Panel)GetValue(SensorProperty); }
set { SetValue(SensorProperty, value); }
}
// Using a DependencyProperty as the backing store for Sensor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SensorProperty =
DependencyProperty.Register("Sensor", typeof(Panel), typeof(BeamControl), new PropertyMetadata(null));
private double left;
private double top;
public BeamControl()
{
InitializeComponent();
this.Loaded += BeamControl_Loaded;
}
void BeamControl_Loaded(object sender, RoutedEventArgs e)
{
_beamPath = this.Content as Path;
left = Canvas.GetLeft(this);
top = Canvas.GetTop(this);
Task.Factory.StartNew(() =>
{
while (true)
{
_doDetection();
}
});
}
void _doDetection()
{
// 200 is the height of the beam, you can change it
for (double i = top; i < 200; i = i + 1)
{
System.Diagnostics.Debug.WriteLine(i.ToString());
Point pt = new Point(left + 20.0, i);
this.Dispatcher.Invoke(() =>
{
VisualTreeHelper.HitTest(Sensor,
new HitTestFilterCallback((o) =>
{
if (o is Ellipse)
{
_beamPath.Fill = Brushes.DarkKhaki;
System.Diagnostics.Debug.WriteLine("Detected ! " + o.GetType().ToString());
System.Diagnostics.Debug.WriteLine(pt.ToString());
return HitTestFilterBehavior.Stop;
}
else
System.Diagnostics.Debug.WriteLine(o.GetType().ToString());
return HitTestFilterBehavior.Continue;
}),
new HitTestResultCallback((result) => { return HitTestResultBehavior.Continue; }),
new PointHitTestParameters(pt));
});
}
}
}
}
MainWindow.xaml
<Window x:Class="WpfAnimation.MainWindow"
...
Title="Window5" Height="303.383" Width="622.556">
<Canvas>
<local:BeamControl Canvas.Left="200" Canvas.Top="50" Sensor="{Binding RelativeSource={RelativeSource AncestorType=Canvas, Mode=FindAncestor}}"/>
<local:BeamControl Canvas.Left="300" Canvas.Top="50" Sensor="{Binding RelativeSource={RelativeSource AncestorType=Canvas, Mode=FindAncestor}}"/>
<Ellipse x:Name="ElpObj" Fill="Pink" Stroke="Black" HorizontalAlignment="Right" VerticalAlignment="Top" Width="57" Height="51" Canvas.Left="548" Canvas.Top="114">
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" To="50" By="-0.5" Duration="0:0:20"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
</Canvas>
</Window>
Run the application Without Debugging.
I'm not sure if you intend to follow MVVM, but either case you can probably try doing this way:
For the visual part, you can use a Canvas, but any other Panels would probably work. Canvas would allow you to use Canvas.Left and stuff like that, and all your positioning would be probably based on System.Windows.Point values, instead of relative positioning, anyway.
The rest of the things can be drawn with one of the System.Window.Shapes.Shape subclasses. The two bars probably Rectangle, for beam would be Line, and for sensor probably an Ellipse or a Rectangle.
The beam objects should have Visibility bound to some property in your ViewModel (or simply code behind if not using MVVM). The Stroke property of the beams and Fill property of the sensors should be bound as well.
I am not sure how sensor size would affect anything, probably it could mean the beam could be very wide? If it does affect, bind the Width and Height as well. Also remember to bind beam's StrokeThickness if sensor size matters.
For the object, you would use Rectangle again. Again, do the necessary binding. The new binding would be Canvas.Left and Canvas.Top, so that you know the position.
Now we need to animate the object. Since your have the position through binding, you can change the position in the source property - the binding engine will do the render changes for you. You can use a timer for the movement of the object.
Now we need to find collision. One way is to calculate it code behind, which would be done every time the object position changes. There is another way to do it, because you are using Canvas. Read this SO post. All you need to do is to convert the underlying Geometry instance for the object and beams (through myobject.RenderedGeometry.FillContainsWithDetail(beam)).
There are certainly many other ways to do this. I just hope I managed to provide a direction to start your project.
I am building an application like a noteBook in Landscape.
I have two canvas next to each other and I'm changing the Background inside them.
Every time I write on the two canvas and move to another 2 backgrounds, I need to save what I wrote on the two canvas and reload it once I move to these backgrounds once again, and clear them. And do the same for all the backgrounds.
<Grid x:Name="RenderedGrid" Width="1140" Height="770">
<ScrollViewer x:Name="DScrollViewer" MaxZoomFactor="2.0" MinZoomFactor="1.0" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ZoomMode="Disabled">
<StackPanel Orientation="Horizontal">
<Canvas x:Name="inkCanvas0" HorizontalAlignment="Left" Width="570">
<Canvas.Background>
<ImageBrush x:Name="DImage0" ImageSource="{Binding}"/>
</Canvas.Background>
</Canvas>
<Canvas x:Name="inkCanvas1" HorizontalAlignment="Right" Width="570">
<Canvas.Background>
<ImageBrush x:Name="DImage1" ImageSource="{Binding}"/>
</Canvas.Background>
</Canvas>
</StackPanel>
</ScrollViewer>
</Grid>
I need to bind the Canvas in a way to this class instead of inkCanvas0 & inkCanvas1:
public class MyCanvas
{
public Canvas inkCanvas { get; set; }
}
List<MyCanvas> CanvasList = new List<MyCanvas>();
This is how I am creating Empty Canvas and storing them in CanvasList:
for (int i = 0; i < 24; i++)
{
Canvas canvas = new Canvas();
canvas.Name = "inkCanvas" + i;
MyCanvas mc = new MyCanvas();
mc.inkCanvas = canvas;
CanvasList.Add(mc);
}
I need to Bind these Canvas to the its corresponding page (inkCanvas0 to page0)
but I'm getting an exception in Xaml when I try to bind the Name of the Canvas.
<Canvas x:Name="{Binding}" HorizontalAlignment="Left" Width="570">
Exception: Catastrophic failure The name already exists in the tree.
How can I solve this? Is my logic of creating and loading the Canvas correct?
I'm making a ploting application. Because I want same behaviour as math plots I apply following transformation to canvas with data points:
<UserControl.Resources>
<TransformGroup x:Key="CanvasTransform">
<TranslateTransform X="30" Y="30"/>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5" />
</TransformGroup>
</UserControl.Resources>
Here is the transformation used:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransformOrigin>
<Point X="0.5" Y="0.5"/>
</Canvas.RenderTransformOrigin>
<Canvas.RenderTransform>
<Binding Source="{StaticResource CanvasTransform}"/>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
So far so good. The problem is with adding points to plot. Because the mouse click event is returning position in window coordinates it's no use. The point is added at wrong location, because it's transformed after adding.
e.g. Canvas is 400 units high. I click in top left corner mouse location is [X=10, Y=10] this point is added to the plot and rendered. The render transform then use the [10,10] point and calculates it's new position: [X=40,Y=360] (window coordinates).
It means that I click in top corner and the point appears in bottom corner. Which is in fact correct behavior.
My question is, how to apply the render transform manually before storing the point, so the point will appear under mouse.
So far I have tried following:
var trans = Resources["CanvasTransform"] as TransformGroup;
var mouse = e.GetPosition(this); // mouse position relative to canvas
var newPoint = trans.Transform(mouse);
But after this transformation newPoint has following coordinates [40,-39]. Again I know why is the result as it is. The origin of the transform is [0,0] and the translation is 29 probably due to rounding error.
Now I can take this new point and manualy change the values - subtract 30 from X coord and then add Canvas.ActualHeight to Y coordinate, which will fix the position.
But then what's the point?
My question is: is it possible to apply the RenderTransform in same fashion as the rendere is doing it, to avoid fiddling with coordinates manualy?
CenterX=".5" CenterY=".5" in ScaleTransform is unnecessary. All it does is adds a tiny translate transform (half-pixel).
To get source position from transformed position, you need to use inverse transformation (Inverse property of Transform). This is where X-30 error comes from.
To change transformation origin, you need to, first, subtract half canvas size, then transform, then add half canvas size.
var origin = new Point(lstItems.ActualWidth / 2, lstItems.ActualHeight / 2);
var transform = ((TransformGroup)Resources["CanvasTransform"]).Clone();
transform.Children.Insert(0, new TranslateTransform(-origin.X, -origin.Y));
transform.Children.Add(new TranslateTransform(origin.X, origin.Y));
_transform = transform.Inverse;
Complete sample:
MainWindow.xaml
<Window x:Class="So21501609WpfMouseRenderTransform.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Control.Resources>
<TransformGroup x:Key="CanvasTransform">
<TranslateTransform X="30" Y="30"/>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5"/>
</TransformGroup>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="SkyBlue"/>
</Style>
</Control.Resources>
<ItemsControl x:Name="lstItems" MouseDown="LstItems_OnMouseDown" Width="400" Height="400" Background="Transparent">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransformOrigin>
<Point X="0.5" Y="0.5"/>
</Canvas.RenderTransformOrigin>
<Canvas.RenderTransform>
<Binding Source="{StaticResource CanvasTransform}"/>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<TextBlock Canvas.Left="10" Canvas.Top="10" Text="10 10"/>
<TextBlock Canvas.Left="10" Canvas.Top="300" Text="10 300"/>
<TextBlock Canvas.Left="300" Canvas.Top="300" Text="300 300"/>
<TextBlock Canvas.Left="300" Canvas.Top="10" Text="300 10"/>
</ItemsControl.Items>
</ItemsControl>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace So21501609WpfMouseRenderTransform
{
public partial class MainWindow
{
private GeneralTransform _transform;
public MainWindow ()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded (object sender, RoutedEventArgs routedEventArgs)
{
var origin = new Point(lstItems.ActualWidth / 2, lstItems.ActualHeight / 2);
var transform = ((TransformGroup)Resources["CanvasTransform"]).Clone();
transform.Children.Insert(0, new TranslateTransform(-origin.X, -origin.Y));
transform.Children.Add(new TranslateTransform(origin.X, origin.Y));
_transform = transform.Inverse;
}
private void LstItems_OnMouseDown (object sender, MouseButtonEventArgs e)
{
Point pos = _transform.Transform(e.GetPosition(lstItems));
var item = new TextBlock { Text = pos.ToString() };
Canvas.SetLeft(item, pos.X);
Canvas.SetTop(item, pos.Y);
lstItems.Items.Add(item);
}
}
}
I have two images, as explained in the following XAML code:
<Window x:Class="TestApplicationGestureKinect.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="768" Width="1024" ScrollViewer.VerticalScrollBarVisibility="Disabled" MinWidth="1024" MaxWidth="1024" MinHeight="768" MaxHeight="768">
<Grid Background="Black">
<Image x:Name="img1" HorizontalAlignment="Left" Margin="47,82,0,0" VerticalAlignment="Top" Source="photos/01.jpg" Height="200" RenderTransformOrigin="0.5,0.5" >
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="9.577"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<Image x:Name="cursorRight" HorizontalAlignment="Left" Margin="757,133,0,0" Width="48" Height="48" VerticalAlignment="Top" Source="cursors/right_open.png" />
</Grid>
</Window>
And the following image shows how this appear:
I need a way to test, from C# code, if the image called cursorRight is onto the area covered by the image called img1, after the transformation.
How could I do? I thought about some consideration on the bounding boxes of the two images, but while for the cursorRight image, could be acceptable to consider the bounding box, this doesn't seem to be a good choice for the other image......
EDIT: The following images show four examples of how I want to do:
cursor on the image:
cursor not on the image:
SOLUTION: The following code is what I used in order to solve the above problem. I considered the bounding box of the cursor rather than the exact shape of it.
private bool isOn(Image img1, Image img2)
{
if (img1 == null || img1.Visibility != System.Windows.Visibility.Visible)
{
return false;
}
double img1_topLeft_X = img1.Margin.Left;
double img1_topLeft_Y = img1.Margin.Top;
double img1_bottomRight_X = img1_topLeft_X + img1.Width;
double img1_bottomRight_Y = img1_topLeft_Y + img1.Height;
Point img1_topLeft = new Point(img1_topLeft_X, img1_topLeft_Y);
Point img1_bottomRight = new Point(img1_bottomRight_X, img1_bottomRight_Y);
HitTestResult result_topLeft = VisualTreeHelper.HitTest(img2.Parent as Grid, img1_topLeft);
HitTestResult result_bottomRight = VisualTreeHelper.HitTest(img2.Parent as Grid, img1_bottomRight);
if (result_topLeft != null && result_bottomRight != null)
{
if (result_topLeft.VisualHit.GetType() == typeof(Image) && result_bottomRight.VisualHit.GetType() == typeof(Image) &&
(result_topLeft.VisualHit as Image).Name.Equals(img2.Name) && (result_bottomRight.VisualHit as Image).Name.Equals(img2.Name))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
However, in this way the cursor is ONTO the image only if the bounding box of it is TOTALLY onto the image. It's not exactly what I needed, but since it works pretty well, I decided to use this method.
An option not quite would be to use the VisualTreeHelper.HitTest(...) methods to check if points overlapped each other. You can read more about it works here.
Use bounding rectangle for your cursor and a "bounding polygon" that defines the area of your larger image and then use polygon intersection algorithm (one explanation here) to solve your problem.