I am having trouble when trying to obtain the actual width of a transformed ( rotated ) element in WPF.
I have a rectangle containing an image, in a Canvas. When I rotate the rectangle, I would like to get the rendered width to center it in my window, but I always get the base Width, no matter how I try.
I tried using Width, ActualWidth, RenderSize.Width : exact same result.
I tried using a LayoutTransform and RenderTransform, same.
I really don't get what I'm doing wrong, everything I've read about the subject suggest that I should get the rendered width of the whole bounding box using ActualWidth, and not the default width of my rectangle/image.
Here is my code :
Setting up the scaleTransform :
private ScaleTransform scaleT = new ScaleTransform();
private RotateTransform rotateT = new RotateTransform();
private TransformGroup tGroup = new TransformGroup();
//Adding renderTransform
scaleT.ScaleX = 1;
scaleT.ScaleY = 1;
rotateT.Angle = 0;
tGroup.Children.Add(rotateT);
tGroup.Children.Add(scaleT);
imageContainer.RenderTransform = tGroup;
And the rotation :
private void rotateWindow(string direction)
{
double prevWidth = this.Width;
double prevHeight = this.Height;
this.Width = prevHeight;
this.Height = prevWidth;
imageContainer.RenderTransformOrigin = new Point(0.5, 0.5);
if (direction == "Right")
{
rotateT.Angle += 30;
}
if (direction == "Left")
{
rotateT.Angle -= 30;
}
adaptWindowToGrid();
}
Thanks !
Related
I post this before and it was remove for being a duplicate. It is not. My problem is different then what that other people is doing. He is not doing zoom nor pan, and does not have a boarder.
I am using Stretch="Fill" to place my entire picture in the borders of an Image box. I am using a Border so that I can do Zoom and Pan. I am using the Canvas to draw rectangles around giving click areas. I want to map the left mouse click coordinates of the Canvas with zoom and pan back to the original image. here is my XAML code :
`
<Border x:Name="VideoPlayerBorder" ClipToBounds="True" Background="Gray" >
<Canvas x:Name="CanvasGridScreen" MouseLeftButtonDown="VideoPlayerSource_OnMouseLeftButtonDown" >
<Image x:Name="VideoPlayerSource" Opacity="1" RenderTransformOrigin="0.5,0.5" MouseLeftButtonUp="VideoPlayerSource_OnMouseLeftButtonUp" MouseWheel="VideoPlayerSource_OnMouseWheel" MouseMove="VideoPlayerSource_OnMouseMove" Width="{Binding Path=ActualWidth, ElementName=CanvasGridScreen}" Height="{Binding Path=ActualHeight, ElementName=CanvasGridScreen}" Stretch="Fill" >
</Image>
</Canvas>
`
here is my C# code:
`private void VideoPlayerSource_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
VideoPlayerSource.CaptureMouse();
var tt = (TranslateTransform)((TransformGroup)VideoPlayerSource.RenderTransform).Children.First(tr => tr is TranslateTransform);
start = e.GetPosition(VideoPlayerBorder);
origin = new Point(tt.X, tt.Y);
_stIR = start;
_stIR2 = start;
addRemoveItems(sender, e);
}
private void addRemoveItems(object sender, MouseButtonEventArgs e)
{
// this is the event that will check if we clicked on a rectangle or if we clicked on the canvas
// if we clicked on a rectangle then it will do the following
if (e.OriginalSource is Rectangle)
{
// if the click source is a rectangle then we will create a new rectangle
// and link it to the rectangle that sent the click event
Rectangle activeRec = (Rectangle)e.OriginalSource; // create the link between the sender rectangle
CanvasGridScreen.Children.Remove(activeRec); // find the rectangle and remove it from the canvas
}
// if we clicked on the canvas then we do the following
else
{
// generate a random colour and save it inside the custom brush variable
Custombrush = new SolidColorBrush(Color.FromRgb((byte)r.Next(1, 255),
(byte)r.Next(1, 255), (byte)r.Next(1, 233)));
// create a re rectangle and give it the following properties
// height and width 50 pixels
// border thickness 3 pixels, fill colour set to the custom brush created above
// border colour set to black
Rectangle newRec = new Rectangle
{
Width = 50,
Height = 50,
StrokeThickness = 3,
Fill = Custombrush,
Stroke = Brushes.Black
};
// once the rectangle is set we need to give a X and Y position for the new object
// we will calculate the mouse click location and add it there
Canvas.SetLeft(newRec, Mouse.GetPosition(CanvasGridScreen).X); // set the left position of rectangle to mouse X
Canvas.SetTop(newRec, Mouse.GetPosition(CanvasGridScreen).Y); // set the top position of rectangle to mouse Y
CanvasGridScreen.Children.Add(newRec); // add the new rectangle to the canvas
}
}
private void VideoPlayerSource_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
TransformGroup transformGroup = (TransformGroup)VideoPlayerSource.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
double zoom = e.Delta > 0 ? .2 : -.2;
double transformScaleX = Math.Round((transform.ScaleX + zoom), 2);
double transformScaleY = Math.Round((transform.ScaleY + zoom), 2);
if (transformScaleX <= 8.2 && transformScaleX >= 1)
{
transform.ScaleX = Math.Round(transform.ScaleX + zoom, 2);
transform.ScaleY = Math.Round(transform.ScaleY + zoom, 2);
zoomFactor2 = zoomFactor2 + zoom;
zoomFactor = zoomFactor2;
}
}
void PanMethod(MouseEventArgs e)
{
var tt = (TranslateTransform)((TransformGroup)VideoPlayerSource.RenderTransform).Children.First(tr => tr is TranslateTransform);
Vector v = start - e.GetPosition(VideoPlayerBorder);
if (zoomFactor > 1.0)
{
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
}
is there a function that would give me this information ? is there a way of using TransformGroup or ScaleTransform to return the actual location in the picture that was clicked? again the Image with possible zoom and/or pan
Check out: https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.visual.transformtovisual
The right way to translate coordinates back to the original pre-transforms control is to use the TransformToVisual helper. It's probably a good idea to do that regardless since transforms could be applied higher up in the stack.
In your case you want to call:
GeneralTransform transform = CanvasGridScreen.TransformToVisual(VideoPlayerSource);
Point normalizedPoint = transform.Transform(new Point(0, 0));
Background: I am currently busy with showing position of a Vehicle on a Zoomable Canvas based on the Position (X,Y) and Orientation (for Rotation). I use Rectangle for visualizing the vehicle. Everything works well but I got a bit greedy and now I want to replace the Rectangle with Top View Picture of the Vehicle, so it looks that the vehicle itself is moving instead a Rectangle.
Code Below:
private void PaintLocationVehicle(VehicleClass vc)
{
IEnumerable<Rectangle> collection = vc.ZoomableCanvas.Children.OfType<Rectangle>().Where(x => x.Name == _vehicleobjectname);
List<Rectangle> listE = collection.ToList<Rectangle>();
for (int e = 0; e < listE.Count; e++)
vc.ZoomableCanvas.Children.Remove(listE[e]);
// Assign X and Y Position from Vehicle
double drawingX = vc.gCurrentX * GlobalVar.DrawingQ;
double drawingY = vc.gCurrentY * GlobalVar.DrawingQ;
// Scale Length and Width of Vehicle
double tractorWidthScaled = vc.tractorWidth * GlobalVar.DrawingQ;
double tractorLengthScaled = vc.tractorLength * GlobalVar.DrawingQ;
// Get Drawing Location
double _locationX = drawingX - (tractorLengthScaled / 2);
double _locationY = drawingY - ((tractorWidthScaled / 2));
RotateTransform rotation = new RotateTransform();
// Angle in 10th of a Degree
rotation.Angle = vc.gCurrentTheeta/10 ;
double i = 0;
//paint the node
Rectangle _rectangle = new Rectangle();
_rectangle.Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString(vc.VehicleColor == "" ? "Black" : vc.VehicleColor));
_rectangle.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(vc.VehicleColor == "" ? "Black" : vc.VehicleColor));
i += 0;
_rectangle.Width = tractorLengthScaled ;
_rectangle.Height = tractorWidthScaled;
rotation.CenterX = _rectangle.Width / 2;
rotation.CenterY = _rectangle.Height / 2;
_rectangle.RenderTransform = rotation;
Canvas.SetTop(_rectangle, _locationY + i);
Canvas.SetLeft(_rectangle, _locationX + i);
_rectangle.SetValue(ZoomableCanvas.ZIndexProperty, 2);
string _tooltipmsg = "Canvas: " + vc.ZoomableCanvas.Name;
// Assign ToolTip Values for User
_tooltipmsg += "\nX: " + vc.gCurrentX;
_tooltipmsg += "\nY: " + vc.gCurrentY;
_rectangle.ToolTip = _tooltipmsg;
_rectangle.Name = _vehicleobjectname;
//add to the canvas
vc.ZoomableCanvas.Children.Add(_rectangle);
}
Note: VehicleClass holds all the Values for a certain Vehicle. DrawingQ holds the transformation scale from Reality to Zoomable Canvas.
So the issues I forsee:
How to append the Size of a Jpeg file to get the size same as
Rectangle?
What kind of Shape object shall I use? Please
suggest.
If i undrestand you correctly. you wanted to show an image of the vechicle inside the rectangle. in order to do that you can use
ImageBrush and assign to the Rectangle Fill property
something like this
Rectangle rect = new Rectangle();
rect.Width = 100;
rect.Height = 100;
ImageBrush img = new ImageBrush();
BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
bmp.UriSource = new Uri("vehicle image path");
bmp.EndInit();
img.ImageSource = bmp;
rect.Fill = img;
I hope that helps
On MouseDownEvent I set upper left corner of Ellipse I'm trying to draw.
public MyCircle(Point location)
{
ellipseObject = new Ellipse
{
Stroke = Brushes.Black,
StrokeThickness = 2,
Margin = new Thickness(location.X, location.Y, 0, 0)
};
}
Then on MouseMoveEvent I update Width and Height properties and it works fine as long as I don't move mouse above or/and to the left of my Ellipse upper left corner, in that case I'm getting exception that these properties can't be negative (which of course makes perfect sense).
public void Draw(Point location)
{
if (ellipseObject != null)
{
ellipseObject.Width = location.X - ellipseObject.Margin.Left;
ellipseObject.Height = location.Y - ellipseObject.Margin.Top;
}
}
The problem doesn't exist with drawing lines:
public void Draw(Point location)
{
lineObject.X2 = location.X;
lineObject.Y2 = location.Y;
}
I know it's trivial, but I'm completely stuck on this. How should I handle drawing Ellipses?
I had this EXACT problem when trying to create a crop tool. Problem is that you need to create if statements for when the cursor goes negative X or negative Y from your starting point. For starters, you'll need to have a global Point that you mark as your 'start' point. Also specify a global current point position that we'll talk about in a minute.
public Point startingPoint;
public Point currentPoint;
Then, make sure you have an onMouseDown event on whatever control you are trying to put the ellipse on.
private void control_MouseDown(object sender, MouseEventArgs e)
{
startingPoint.X = e.X;
startingPoint.Y = e.Y;
}
Then, you need to create if statements in your MouseMove event to check with point (current mouse position, or starting point) has a lower X/Y value
private void control_MouseMove(object sender, MouseEventArgs e)
{
//The below point is what we'll draw the ellipse with.
Point ellipsePoint;
Ellipse ellipseObject = new Ellipse();
currentPoint.X = e.X;
currentPoint.Y = e.Y;
//Then we need to get the proper width/height;
if (currentPoint.X >= startingPoint.X)
{
ellipsePoint.X = startingPoint.X;
ellipseObject.Width = currentPoint.X - startingPoint.X;
}
else
{
ellipsePoint.X = currentPoint.X;
ellipseObject.Width = startingPoint.X - currentPoint.X;
}
if (currentPoint.Y >= startingPoint.Y)
{
ellipsePoint.Y = startingPoint.Y;
ellipseObject.Height = currentPoint.Y - startingPoint.Y;
}
else
{
ellipsePoint.Y = currentPoint.Y;
ellipseObject.Height = startingPoint.Y - currentPoint.Y;
}
ellipseObject.Stroke = Brushes.Black;
ellipseObject.StrokeThickness = 2;
ellipseObject.Margin = new Thickness(ellipsePoint.X, ellipsePoint.Y, 0, 0);
}
Hope this helps!
Save the origin point separately and set the X and Y properties of the Ellipse's Margin to the mouse position and the Width and Height to the distances between the mouse and origin point.
Untested:
public MyCircle(Point location)
{
ellipseObject = new Ellipse
{
Stroke = Brushes.Black,
StrokeThickness = 2,
Margin = new Thickness(location.X, location.Y, 0, 0)
Tag = new Point(location.X, location.Y)
};
}
public void Draw(Point location)
{
if (ellipseObject != null)
{
Point o = (Point)ellipseObject.Tag;
double x = Math.Min(location.X, o.Left);
double y = Math.Min(location.Y, o.Top);
double width = Math.Abs(Math.Max(location.X, o.Left) - x);
double height = Math.Abs(Math.Max(location.Y, o.Top) - y);
ellipseObject.Margin.X = x;
ellipseObject.Margin.Y = y;
ellipseObject.Width = width;
ellipseObject.Height = height;
}
}
I have a Canvas in my WPF application. I am adding the Rectangle on button click. The Width is Fixed but, Height is value entered by user in TextBox/GridCell.
When i add rectangle on Canvas with specifying Height. it adds the rectangle but, it doesnt appear one after other. Any idea?
In .xaml.cs:
int width=200;
Reactangle rect;
static int val=0;
Protected void Add()
{
rect = new Rectangle();
rect.Stroke = Brushes.Red;
rect.StrokeThickness = 1;
rect.Height = Convert.ToInt32(txtheight.Text);
rect.Width = width;
Canvas.SetLeft(rect,100);
Canvas.SetTop(rect,rect.Height);
rect.Tag = val;
canvasboard.Children.Add(rect);
val=val+1;
}
This adds Rectangle but not Exactly one after other on canvas.
<Canvas Name="canvasboard" Background="White" Margin="2">
</Canvas>
<TextBox Name="txtheight" Width="150"/>
Note: I cant use WrapPanel or StackPanel for this form. and want to make changes in existing code.
Help Appreciated!
If all your adding is sequential elements vertically into this Canvas, you can do it without having to add a new variable in class scope as well.
private void Add() {
rect = new Rectangle {
Stroke = Brushes.Red,
StrokeThickness = 1,
Height = Convert.ToDouble(txtheight.Text),
Width = width
};
Canvas.SetLeft(rect, 100);
double canvasTop = 0.0;
if (canvasboard.Children.Count > 0) {
var lastChildIndex = canvasboard.Children.Count - 1;
var lastChild = canvasboard.Children[lastChildIndex] as FrameworkElement;
if (lastChild != null)
canvasTop = Canvas.GetTop(lastChild) + lastChild.Height + 1;
}
Canvas.SetTop(rect, canvasTop);
rect.Tag = val++;
canvasboard.Children.Add(rect);
}
Try storing a local variable that maintains the combined height of all Rectangles:
private double _top = 0;
protected void Add()
{
var rect = new Rectangle();
rect.Stroke = Brushes.Red;
rect.StrokeThickness = 1;
rect.Height = double.Parse(txtheight.Text);
rect.Width = 20;
Canvas.SetLeft(rect, 100);
Canvas.SetTop(rect, _top);
_top += rect.Height;
rect.Tag = val;
canvasboard.Children.Add(rect);
val = val + 1;
}
I'm trying to draw a row of rectangles across my Canvas. When I run the following code, I only get one rectangle, even though my canvas element says it has 12 children.
Dimensions is a class with 2 integer properties, Height and Width. The canvas I am drawing this is on 400px by 600px.
Dimensions windowDimensions = new Dimensions()
{
Width = (int)cvsGameWindow.Width,
Height = (int)cvsGameWindow.Height
};
//init rectangles
for (int i = 0; i < windowDimensions.Width; i+=50)
{
Rectangle rect = new Rectangle(); //create the rectangle
rect.StrokeThickness = 1; //border to 1 stroke thick
rect.Stroke = _blackBrush; //border color to black
rect.Width = 50;
rect.Height = 50;
rect.Name = "box" + i.ToString();
Canvas.SetLeft(rect,i * 50);
_rectangles.Add(rect);
}
foreach (var rect in _rectangles)
{
cvsGameWindow.Children.Add(rect);
}
and the private members declared at the top of my code:
private SolidColorBrush _blackBrush = new SolidColorBrush(Colors.Black);
private SolidColorBrush _redBrush = new SolidColorBrush(Colors.Red);
private SolidColorBrush _greenBrush = new SolidColorBrush(Colors.Green);
private SolidColorBrush _blueBrush = new SolidColorBrush(Colors.Blue);
private List<Rectangle> _rectangles = new List<Rectangle>();
This is the culprit:
Canvas.SetLeft(rect,i * 50);
On the first loop, with i=0, you're setting Canvas.Left = 0; Since your for loop is doing i+=50, on the second loop i will be 50, so you'll be setting Canvas.Left = 2500. You said your Canvas is 400x600, so your rectangles are off-screen.
The simplest fix: use Canvas.SetLeft(rect, i) - since i is increasing in increments of 50.