Binding to X Y coordinates of element on WPF Canvas - c#

I have a Canvas with 2 "dots" drawn on it. See this (simplified) code:
<Canvas>
<Ellipse />
<Ellipse />
<Canvas.RenderTransform>
<RotateTransform x:Name="rotateEllipse" />
</Canvas.RenderTransform>
</Canvas>
As you can see, I want to rotate the canvas using the given RotateTransform.
Next, I want to put a TextBlock near to each Ellipse (a label). However, I don't want to include this TextBlock into the Canvas because it will then rotate also. I want the text to remain horizontal.
Any idea how to solve this in an elegant way?

Something like this, should work for you
<TextBlock RenderTransform="{Binding RelativeSource={RelativeSource AncestorType=Canvas},
Path=RenderTransform.Inverse}"/>
Assign to text box transformation matrix an inverse of the transformation matrix of the Canvas.

Good question! And I'm going to guess, so please take this answer with a pinch of salt.
I believe you are trying to place text annotations next to ellipses on a rotated canvas, but these annotations need to remain horizontal. Two things you could try:
Firstly, given the XY point that you know of each ellipse from Canvas.GetTop/GetLeft, you could find its new rotated XY location by applying the RotateTransform to the ellipse location, using the formula U = M*V, where U is the output point, V is the input point (XY location of ellipse) and M is the Rotation Matrix.
Secondly, you could place a second canvas over the first (assuming they are both in a grid, the second canvas is at higher Z-index and is the same size as the underlying canvas). Call it an annotation layer. Your annotations (text labels) can appear at the new transformed locations and unrotated using this approach.
You'd have to do this in code of course, not Xaml, although you might find a binding friendly approach by creating a value converter on the TextBlock that bound to the source RotateTransform/Ellipse and did this operation for you.
Another approach would be to take the .Inverse of the RotateTransform and apply that to the textblocks, however you may still need to translate to get to the new location. Either way I think it demands some experimentation.
Best regards,

Related

Trying to animate such that the other rectangle below it moves upward?

<StackPanel x:Name="rootStackPanel" Background="{ThemeResource SystemControlAcrylicWindowBrush}" Padding="0,48">
<Rectangle x:Name="sampleRectangle" Width="200" Height="300" Fill="DeepPink" DoubleTapped="Rectangle_DoubleTapped" RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<CompositeTransform TranslateY="-100"/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="otherRectangle" Width="200" Height="200" Fill="Cyan"/>
</StackPanel>
I want to achieve a dynamic look, where, when the pink rectangle is moved upward, the blue rectangle takes up the left over space. Such that it is always touching the pink rectangle.
I have tried manipulating offsets and scale properties provided by visual layer but the actual height is not affected by any of the property, as a result the other rectangle just stays in the original position. Now I am trying to achieve that effect using storyboards animation. But as you can see, the translation property (and the scale property as well) doesn't effect the actual container of the control somehow but rather makes the content in it move to whatever translation.
So, what properties do I need to manipulate to achieve the effect where the other rectangle inside the stackpanel moves dynamically with the changes to the first rectangle?
I know adjusting width or height property would result in what I am trying to achieve but for more complex scenarios where there might be a textbox rather than simple rectangle that is being animated, there is sort of a weird animation of the placeholder text(shrinking of text), which is something I don't want!
So, what properties do I need to manipulate to achieve the effect where the other rectangle inside the stackpanel moves dynamically with the changes to the first rectangle?
You could use Microsoft.Toolkit.Uwp.UI.Animations.Behaviors to realize this feature directly. Before animate your rectangle, you need to get the absolute position of the each rectangle like the follow.
var scgt = sampleRectangle.TransformToVisual(Window.Current.Content);
Point screenCoords = scgt.TransformPoint(new Point(0, 0));
var ddv = otherRectangle.TransformToVisual(Window.Current.Content);
Point Res = ddv.TransformPoint(new Point(0, 0));
If you want pink rectangle to move upward, you could Offset offsetY value
sampleRectangle.Offset(offsetX: 0, offsetY: -(float)screenCoords.Y - (float)sampleRectangle.Height, duration: 2500).Start();
And then animate otherRectangle like the follow
otherRectangle.Offset(offsetX: -(float)Res.X, offsetY: -(float)Res.Y).Scale(scaleX: 2, scaleY: 2).Start();
You need keep scaleX equal with scaleY when you scale TextBox.Otherwise, the TextBox will be deformed.

How can I transform a LineSeries in a WPF OxyPlot Plot?

I have an OxyPlot graph, and I want to transform one of the LineSeries in this graph. I'd like to apply both rotation and translation transformations, but I can't get any kind of transformation to actually take place (I'm not including the model that holds the MyItems property, but it's MVVM and working correctly).
<oxy:Plot>
<oxy:Plot.Axes>
<oxy:LinearAxis Position="Bottom" Title="X"/>
<oxy:LinearAxis Position="Left" Title="Y"/>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:LineSeries ItemsSource={Binding Path=MyItems}/>
</oxy:Plot.Series>
</oxy:Plot>
So far, I've tried playing with the LayoutTransform property in the LineSeries, like so (the X and Y values are just arbitrary for testing if I can get it to move at all):
<oxy:LineSeries ItemsSource={Binding Path=MyItems}>
<oxy:LineSeries.LayoutTransform>
<TranslateTransform X="1" Y="2"/>
</oxy:LineSeries.LayoutTransform>
</oxy:LineSeries>
However, nothing happens - the series displays exactly the same way with and without the LayoutTransform property. I've read through the documentation, and I can't find any other properties that even appear to reference transformation, and I can't find any guide on how to actually use the LayoutTransform property. Does anyone know how to achieve this, short of manually recalculating each and every point in MyItems in the code-behind?
I'm willing to create a transformation object in the code-behind, but I would much rather work with the XAML directly because I'll be using binding.
Note: The points in MyItems all have both X and Y coordinates within 0 and 1, so the problem isn't that the move is too slight for me to see.

How to change orientation of one component in windows phone 8 page with locked orientation

I'm working on Windows Phone 8/8.1 C#/XAML .NET 4.5 Application and I'd like to know how to change orientation of just one control/item on page (rotate it 90 degrees).
I have a webBrowser on my portrait page (that stays locked on that orientation) and the webbrowser needs to be in landscape orientation (and does not rotate).
How can I set the webbrowser to be rotated 90 degrees and stay that way?
So, I've figured one way to do it on my own.
I'm going to put the answer as community wiki so that anyone who comes later can edit and add more options to do this.
The rotation transformation
One of the options is to rotate the element.
This is done by rotation transformation (answer combined from this ans this question).
It can be done in code behind:
//Create a transformation
RotateTransform rt = new RotateTransform();
//and set the rotation angle
rt.Angle = 90; //number of degrees to rotate clockwise
//for counterclockwise rotation use negative number
//default rotation is around top left corner of the control,
//but you sometimes want to rotate around the center of the control
//to do that, you need to set the RenderTransFormOrigin
//of the item you're going to rotate
//I did not test this approach, maybe You're going to need to use actual coordinates
//so this bit is for information purposes only
controlToRotate.RenderTransformOrigin = new Point(0.5,0.5);
//the name of the control is controlToRotate in this instance
controlToRotate.RenderTransform = rt;
Or in XAML:
the browser in this instance is taken from my own code and everything is set so that the item is rotated and takes the full place assigned to it
the browser is in the grid, positioned dynamically, the grid is named so that I can gain access to it simply
the browser needs to have width and height specified, in this case I'm taking it from the width and height of the grid, otherwise it is set automatically somehow and the result is pretty small somehow
The vertical and horizontal alignments are set to center so that the resulting rotation is centered too, otherwise it is not (in dynamic layout)
Code:
<Grid x:Name="ContentPanel">
<phone:WebBrowser x:Name="controlToRotate"
VerticalAlignment="Center"
HorizontalAlignment="Center"
RenderTransformOrigin=".5, .5"
Width="{Binding ElementName=ContentPanel, Path=ActualHeight}"
Height="{Binding ElementName=ContentPanel, Path=ActualWidth}">
<phone:WebBrowser.RenderTransform>
<RotateTransform Angle="90"/>
</phone:WebBrowser.RenderTransform>
</phone:WebBrowser>
</Grid>

Drawing a fading grid in WPF

I'm trying to draw a graph-paper like grid as the background of a Canvas. This grid is different from most explanations of how to do this that I've found because the canvas can be scaled to implement zooming. What I want to do is have a series of scales of grid lines, i.e. at every 10^n units. Then, the grid lines should fade out as they become close together due to zooming. In other words if n is large, the lines associated with that grid should be darker/heavier weight than those for a smaller n.
This was easy to do in WinForms, I implemented it by overriding OnPaint and defining the color of the line to be a function of the distance to the next grid line. Lines far apart were given a heavier weight than lines close together.
I have not figured out how to do this in WPF. I can sort of get this behavior by creating a line that has a StrokeThickness according to the spacing of the grid lines, but this only works for a small range of StrokeThickness and scaling values. It would work if it were possible to define a line as having a very heavy weight, but still a small StrokeThickness.
Even doing this via implementing a custom control with OnRender is difficult because I have not found a reliable way to get the scale of the control while rendering it (the ScaleTransform is part of one of the parent controls, not the immediate parent).
Any thoughts on how to accomplish this goal would be much appreciated!
I solved this by NOT adding the grid to the canvas but by stacking the canvas on top of another control that contains the grid:
<Grid>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="DrawingLayer" />
</Grid>
When zooming events occur I simply redraw the GridLayer.
This allowed me to only draw the lines that are needed, to draw them exactly how I want them and, in my case very important because I had potentially a gazillion grid lines, I did not need to draw the lines any longer/taller than needed. This way I conserved a lot of CPU time.
Another thing to note is that I implemented my own zoom code. I did not use a RenderTransform or a ViewBox because I wanted the line to stay at the same width. All I did was keep track of the coordinates of the top left corner to support panning and the zoomlevel. As soon as one of these changes I redraw the canvases. I wrote two functions: one transforms a coordinate on the Canvas to a graph coordinate and the other one does the reverse. The first method allows me to translate cursor coordinates to graph coordinates and the second one will turn the coordinates of the graph into points that can be used to draw on the canvas.
Untested code and making a lot of assumptions about the orientation of axis:
Point Graph2Canvas(Point graphPoint)
{
var canvasPoint = new Point(graphPoint);
canvasPoint.X *= zoomLevel;
canvasPoint.Y *= zoomLevel;
canvasPoint.X -= topLeft.X;
canvasPoint.Y -= topLeft.Y;
return canvasPoint;
}
This can be optimized and the truth is I created more functions that do the same thing for collections of points.
Extra:
I ended up with a far more complex setup that looked a bit like this:
<Grid>
<Canvas x:Name="BackgroundLayer"/>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="AxisLayer"/>
<Canvas x:Name="DrawingLayer" />
<Canvas x:Name="SelectionBoxLayer"/>
<Canvas x:Name="CursorLayer"/>
</Grid>

How do you get the REAL position of objects in silverlight?

How do you get the REAL position of objects in silverlight?
I have a header image centered on the screen. When I make the browser window smaller, obviously, the header's left side goes off the screen. Finding out the actual position is good to know if you want to position objects on top of the image.
I capture the Content_Resized and I run a little test:
if (App.Current.Host.Content.ActualWidth > header.Width)
{
TEST = Canvas.GetLeft(header);
}
else
{
TEST = Canvas.GetLeft(header);
}
TEST always returns zero.
EDIT: header sits on a grid instead of a canvas. "Well, there is your problem..." So a better question might be this. How would I get the margins of an image sitting on a grid?
I probably should just answer the question but how to find the position of an element relative to another is probably something that has been answered before (by myself and others) here and elsewhere on the tinternet.
However if your goal is to place an item over an image then place the image in a Grid and then add the item as child of the Grid. That way you assign the relative position over the image as the margin of the item and let Silverlight's layout system do the rest.
As a general rule if you feel that you need to write code to move stuff about when the size of things change then unless you are writing a custom panel or something you're probably not using Silverlight layout system properly.
Edit:
Try this experiment:-
<Grid x:Name="LayoutRoot">
<Grid x:Name="headerContainer" Margin="50, 60, 0, 0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image Source="YourLargeImage" />
<Image Source="YourSmallerImage" HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
</Grid>
Now try changing the inner grid's Margin to move its position around the screen. Note the smaller image always remains at the top center of the large image.
I got it working.
First of all, these images are on a grid, not a canvas. But switching the grid to a canvas caused lots of other problems one of which is that I could not have the header image centered like before.
The solution was to change the margin of the smaller image sitting on top of the larger header image when the content resized like this:
blankbarimage.Margin = new Thickness((App.Current.Host.Content.ActualWidth - header.Width) / 2, 0, 0, 0);
and, by the way, you create a content resized method like this:
App.Current.Host.Content.Resized += new EventHandler(Content_Resized);
So, to answer my own question, the way you get the REAL position of object in silverlight is (if they are on a grid) by looking at their margin settings.

Categories