Canvas getRight method returns Nan - c#

I have a Rectangle that I am trying to drag and drop inside a Canvas. After the drag is complete I want to get the left, top, right and bottom coordinates. For that when I say Canvas.GetRight(dragableThumb) and Canvas.GetBottom(dragableThumb) it returns NaN for both the values.
So for the right and bottom I am saying something like this, but the problem is I get wrong values. Please help.
var right = this.Width - left - dragableThumb.ActualWidth;
var bottom = this.Height - top - dragableThumb.ActualHeight;
Here is the entire code.
xaml:
<Window.Resources>
<ControlTemplate x:Key="myDragableCT">
<Rectangle
x:Name="Arrow"
Height="94" RenderTransformOrigin="0.5,0.5" Width="128">
<Rectangle.Fill>
<SolidColorBrush Color="Green" />
</Rectangle.Fill>
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</ControlTemplate>
</Window.Resources>
<Canvas x:Name="DragableCanvas">
<Thumb x:Name="dragableThumb" DragDelta="DragableThumb_DragDelta" DragCompleted="DragableThumb_DragCompleted" Canvas.Left="331"
Canvas.Top="112" Template="{StaticResource myDragableCT}"></Thumb>
</Canvas>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DragableThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Canvas.SetLeft(dragableThumb, Canvas.GetLeft(dragableThumb) + e.HorizontalChange);
Canvas.SetTop(dragableThumb, Canvas.GetTop(dragableThumb) + e.VerticalChange);
}
private void DragableThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
var left = Canvas.GetLeft(dragableThumb);
var top = Canvas.GetTop(dragableThumb);
var right = this.Width - left - dragableThumb.ActualWidth;
var bottom = this.Height - top - dragableThumb.ActualHeight;
MessageBox.Show(string.Format("left:{0}, top:{1}, right:{2}, bottom:{3}", left, top, right, bottom));
}
}

Canvas.Left, Canvas.Top, Canvas.Right and Canvas.Bottom are layout properties instead of render properties. The four properties are used to limit the layout of the UIElements but not presents the layout states And this behavior is the same as the Width and Height properties.
<Grid>
<Button x:Name="DemoButton" />
</Grid>
See this code above, if we read the Width and Height properties, we'll find that they are both double.NaN. Only if we read the ActualWidth, ActualHeight or RenderSize properties we can get the correct layout states.
The same as the Canvas.Left, Canvas.Top, Canvas.Right and Canvas.Bottom properties. You can get the right and the bottom by using other methods, such as:
var right = Canvas.GetLeft(dragableThumb) + dragableThumb.ActualWidth;
var bottom = Canvas.GetTop(dragableThumb) + dragableThumb.ActualHeight;
If you want a method for all other situations you can try this code below:
var bounds = new Rect(
dragableThumb.TranslatePoint(default, DragableCanvas),
dragableThumb.TranslatePoint(new Point(dragableThumb.ActualWidth, dragableThumb.ActualHeight), DragableCanvas));
var right = bounds.Right;
var bottom = bounds.Bottom;
By the way, you spelled the wrong dragable. The correct spell is draggable.

It should be:
var right = left + dragableThumb.ActualWidth;
var bottom = top + dragableThumb.ActualHeight;

Related

Cover an image with black and reveal parts of the image using mouse

So I am creating a "Map Viewer" for a program I am working on. Basically I want to display a map and have it resize the image to fit in the grid. For this I am using Viewbox to hold the image and resize it. I was attempting to use this code to reveal the map but it does not center the circle on the mouse and it does not retain the revealed portions of the map (it ONLY reveals where the mouse is and not where it has been).
Here is the XAML:
<Viewbox x:Name="MapHolder" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="1" Grid.RowSpan="7" Margin="25,5,25,15">
<Image x:Name="SelectedMap" Source="/wizard_dungeon.jpg" Stretch="Uniform" MouseMove="SelectedMap_MouseMove" >
<Image.OpacityMask>
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Ellipse Width="400" Height="400" StrokeThickness="1" Fill="Black"/>
</VisualBrush.Visual>
<VisualBrush.RelativeTransform>
<TransformGroup>
<TranslateTransform x:Name="OpacityFilterTransform" X="1" Y="1"/>
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Image.OpacityMask>
</Image>
</Viewbox>
And the code-behind:
private void SelectedMap_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var height = MapHolder.ActualHeight;
var width = MapHolder.ActualWidth;
// with the position values, interpolate a TranslateTransform for the opacity mask
var transX = position.X / width;
var transY = position.Y / height;
OpacityFilterTransform.X = transX - 0.5;
OpacityFilterTransform.Y = transY - 0.5;
}
I want there to basically be a Image under a Black screen and I can erase the black screen to reveal the image in the areas I have erased.

WPF Canvas Fill

I currently have a WPF windows with a Canvas is 600 x 400. Is it possible to scale or automatically zoom in so that the lines take up as much as the 600x600 as possible?
<Border>
<Canvas x:Name="cMap" Width="600" Height="400">
<Line X1="5" Y1="5" X2 ="10" Y2="10" StrokeThickness="2" Stroke="Black"/>
<Line X1="10" Y1="10" X2 ="15" Y2="25" StrokeThickness="2" Stroke="Black"/>
</Canvas>
</Border>
My intention will be to add lines programmatically via code instead of XAML.
Thanks.
Not sure what is your exact usecase, but you could probably benefit by using ViewBox:
<Border>
<Viewbox Stretch="Uniform">
<Canvas x:Name="cMap" Width="15" Height="25">
<Canvas.LayoutTransform>
<ScaleTransform />
</Canvas.LayoutTransform>
<Line X1="5" Y1="5" X2 ="10" Y2="10" StrokeThickness="2" Stroke="Black"/>
<Line X1="10" Y1="10" X2 ="15" Y2="25" StrokeThickness="2" Stroke="Black"/>
</Canvas>
</Viewbox>
</Border>
Hope this helps you!
To draw lines in code you shoud do something like this:
Line line = new Line();
Thickness thickness = new Thickness(101,-11,362,250);
line.Margin = thickness;
line.Visibility = System.Windows.Visibility.Visible;
line.StrokeThickness = 4;
line.Stroke = System.Windows.Media.Brushes.Black;
line.X1 = 10;
line.X2 = 40;
line.Y1 = 70;
line.Y2 = 70;
and don't forget to add:
myCanvas.Children.Add(line);
to put those line in some place
from: Drawing lines in code using C# and WPF
To resize your canvas please read this:
Canvas is the only panel element that has no inherent layout
characteristics. A Canvas has default Height and Width properties of
zero, unless it is the child of an element that automatically sizes
its child elements. Child elements of a Canvas are never resized, they
are just positioned at their designated coordinates. This provides
flexibility for situations in which inherent sizing constraints or
alignment are not needed or wanted. For cases in which you want child
content to be automatically resized and aligned, it is usually best to
use a Grid element.
So as a solution you would make it inside a GRID or using the following code:
public class CanvasAutoSize : Canvas
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
{
base.MeasureOverride(constraint);
double width = base
.InternalChildren
.OfType<UIElement>()
.Max(i => i.DesiredSize.Width + (double)i.GetValue(Canvas.LeftProperty));
double height = base
.InternalChildren
.OfType<UIElement>()
.Max(i => i.DesiredSize.Height + (double)i.GetValue(Canvas.TopProperty));
return new Size(width, height);
}
}
at your XAML:
<local:CanvasAutoSize VerticalAlignment="Top" HorizontalAlignment="Left"></local:CanvasAutoSize>
from: WPF: How to make canvas auto-resize?

How to apply Render Transform to mouse position

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

How to rotate an image within a StackPanel or Grid in Silverlight

I have an image control sits inside a Grid control. I already have a button to enable zoom-in to this image. After zoom-in, the Horizontal/vertical scroll bars are displayed. And then I rotate the image contained grid, the image and the grid scroll bar are messed up. How should I incorporate both zoom-in and rotate for the image control? The following are the code that I am using in my project.
The image control zoom-in code I used (x is the image control):
if ((x as Image) != null) { x.Height = x.Height * 1.3; x.Width = x.Width * 1.3; }
The rotation code I used (x is the image control):
if ((x as Image) != null)
{
RotateTransform rotate = new RotateTransform(); rotate.Angle = rotateAngle;
rotate.CenterX = x.Width / 2;
rotate.CenterY = x.Height / 2;
x.RenderTransform = rotate;
};
The XAML is:
<ScrollViewer x:Name="scrollViewer" Height="480" Width="615"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ScrollViewer.Content>
<Grid x:Name="ImageGrid">
<StackPanel x:Name="ImageStackPanel">
<Image Source="..." VerticalAlignment="Center" Width="220" Height="170" ></Image>
</StackPanel>
</Grid>
</ScrollViewer.Content>
</ScrollViewer>
Does anybody have any existing code snippet that I can borrow to resolve this trick?
I think you need to use TransformGroup to use more than one transform at the time:
ScaleTransform myScaleTransform = new ScaleTransform();
myScaleTransform.ScaleY = 3;
RotateTransform myRotateTransform = new RotateTransform();
myRotateTransform.Angle = 45;
// Create a TransformGroup to contain the transforms
// and add the transforms to it.
TransformGroup myTransformGroup = new TransformGroup();
myTransformGroup.Children.Add(myScaleTransform);
myTransformGroup.Children.Add(myRotateTransform);
// Associate the transforms to the image.
x.RenderTransform = myTransformGroup;
This may work for your needs:
<Image x:Name="image" Source="myImageSource" Stretch="Uniform"
HorizontalAlignment="Center" VerticalAlignment="Center"
RenderTransformOrigin="0.5, 0.5">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="Rotate"/>
<ScaleTransform x:Name="Scale" />
</TransformGroup>
</Image.RenderTransform>
</Image>
code behind:
Rotate.Angle = 45;
Scale = 0.25;
You may be missing the LayoutTransformer from the Silverlight Toolkit, and the AnimationMediator from one of the Toolkit developers.
With the LayoutTransformer you can set its content to anything, not just images, and apply any transformation with it, and as opposed to the usual RenderTransform, it will affect layout and actual sizes.
I have a similar scenario and I use it like this:
<Grid>
<fs:AnimationMediator x:Name="RotateMediator" LayoutTransformer="{Binding ElementName=LayoutTransformer}" AnimationValue="{Binding Angle, ElementName=RotateTransform, Mode=TwoWay}" />
<fs:AnimationMediator x:Name="ScaleXMediator" LayoutTransformer="{Binding ElementName=LayoutTransformer}" AnimationValue="{Binding ScaleX, ElementName=ScaleTransform, Mode=TwoWay}" />
<fs:AnimationMediator x:Name="ScaleYMediator" LayoutTransformer="{Binding ElementName=LayoutTransformer}" AnimationValue="{Binding ScaleY, ElementName=ScaleTransform, Mode=TwoWay}" />
<tkt:LayoutTransformer x:Name="LayoutTransformer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<tkt:LayoutTransformer.LayoutTransform>
<TransformGroup>
<RotateTransform x:Name="RotateTransform" />
<ScaleTransform x:Name="ScaleTransform" />
</TransformGroup>
</tkt:LayoutTransformer.LayoutTransform>
<Image x:Name="MyImage" Source="mysource.png" Width="600" Height="800" />
</tkt:LayoutTransformer>
</Grid>
Because of the lack of MultiBinding you'd probably additionally have to manually handle the input value (from Slider controls etc) changed events and then set the AnimationValues of RotateMediator etc accordingly.

Rotate image (control) in Silverlight with Transform

I'm trying to rotate an image in Silverlight and can't seem to get it right. I've tried a few different ways so far and can't find the answer.
<Image Opacity=".5" x:Name="compassImg" Source="compass.png">
<Image.RenderTransform>
<RotateTransform x:Name="compassRotator"></RotateTransform>
</Image.RenderTransform>
</Image>
+
void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
{
Dispatcher.BeginInvoke(() =>
{
compassRotator.Angle = e.SensorReading.TrueHeading;
});
}
and
<Image Opacity=".5" x:Name="compassImg" Source="compass.png"></Image>
+
void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
{
Dispatcher.BeginInvoke(() =>
{
compassImg.RenderTransform = new CompositeTransform()
{
CenterX = 0.5,
CenterY = 0.5,
Rotation = e.SensorReading.TrueHeading
};
//OR (variations with 0.5 and width / 2 for both composite and rotate
compassImg.RenderTransform = new RotateTransform()
{
CenterX = compassImg.Width / 2,
CenterY = compassImg.Height / 2,
Angle = e.SensorReading.TrueHeading
};
});
}
It rotates, but it always rotates around 0/0. What am I doing wrong?
I looked up MSDN, and the second form is correct. http://msdn.microsoft.com/en-us/library/system.windows.media.rotatetransform.centerx.aspx (It is the coordinates, not fraction).
However, if you put a breakpoint where you apply the transform, you may find that Width is NaN. This is because width wasn't set. What you want is the ActualWidth.
One good way for exploration of transforms is to paste the following snippet into your XAML and experiment away.
<StackPanel HorizontalAlignment="Left">
<TextBlock>Center X</TextBlock>
<Slider
Name="RTX" Minimum="0.0" Maximum="116" />
<TextBlock>Center Y</TextBlock>
<Slider
Name="RTY" Minimum="0.0" Maximum="800"/>
<TextBlock>Angle</TextBlock>
<Slider
Name="Angle" Minimum="0.0" Maximum="360" />
</StackPanel>
<Image Source="{Binding ImagePath}" Name="image1">
<Image.RenderTransform>
<RotateTransform Angle="{Binding ElementName=Angle,Path=Value}"
CenterX="{Binding ElementName=RTX, Path=Value}"
CenterY="{Binding ElementName=RTY, Path=Value}"/>
</Image.RenderTransform>
</Image>
You need to set the RenderTransformOrigin Property to "0.5, 0.5", this will rotate the element around its centre.

Categories

Resources