I have a ScrollViewer with Image in it. I attached listener to MouseWheel event so I could zoom in/out the image. The code looks like this:
private void ImageMouseWheel(object sender, MouseWheelEventArgs e)
{
Matrix matrix = image.LayoutTransform.Value;
if (e.Delta > 0)
matrix.ScaleAt(1.5, 1.5, e.GetPosition(image).X, e.GetPosition(image).Y);
else
matrix.ScaleAt(1.0 / 1.5, 1.0 / 1.5, e.GetPosition(image).X, e.GetPosition(image).Y);
image.LayoutTransform = new MatrixTransform(matrix);
}
It pretty much works, but it has odd quirk -- when the image is so zoomed out that is at state "fit to parent" visually I cannot it zoom out more (visually), but the matrix is scaled still.
This is bad in sense that any computation after this point is wrong, and also it has strange effect in UI, say user starts from "fit to parent" zoom:
zoom out --> visually no change, internally matrix is zoomed out
zoom in --> surprise, visually no change, internally matrix is zoomed in
zoom in --> visually zoom in, matrix is also changed (OK)
Because of those problems I would like either to continue zooming out visually in order to keep both things (internal and visual) in sync.
How to achieve this?
Xaml:
<DockPanel>
<ScrollViewer Name="imageScrollViewer" Grid.Column="1" MouseWheel="ImageMouseWheel"
CanContentScroll="true" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Image MouseWheel="ImageMouseWheel" Name="image" Source="{Binding Path=Bitmap}"
MouseMove="Image_MouseMove"></Image>
</ScrollViewer>
</DockPanel>
Update: Originally I asked how to continue scaling visually or stop internally, but I found out that the last step of zooming out, when the image hits the state "fit to parent" is already out of sync internally-visually. For example position of the mouse is not correctly decoded above such image when moving the mouse around. Only when I zoom in a little the positions are correct. So the only way I see is to scale it visually correctly.
Hmm, I am tempted to delete this embarrassing question, but on the other hand maybe someone will have the same problem in future.
The problem is image is zoomed out, but later the dock panel zoom it in back. The image does not know about it so the math goes astray. The solution is to add some container which does not do additional scaling on itself, like stack panel.
Thus the solution lies in xaml:
<DockPanel>
<StackPanel>
...
</StackPanel>
</DockPanel>
Related
Summary
I am confused about how exactly to spawn multiple instances of the same control and then apply an animation to them.
Here's a example of what I'm trying to achieve (watch the click effects)
Current Approach
I have 2 "predefined" controls (TextLabel and Rectangle) which respectively serve the "+1" text and the image.
On click, I set the position of those to the mouse and begin the animation, which then animates them relative to the cursor position into some direction and then fades (like what's shown in the gif)
This is a good and functional approach, but it leaves something to be desired:
How to make more than one of these pairs appear and animate at once?
My problem
This is not trivial. Cloning the controls and then applying the same animation to them as the "original, dummy" ones causes issues with Z-Ordering and conflicts between Storyboards.
This "cloning" approach also seems very naive and inefficient.
So...
How to replicate that wonderful, cookie click effect with the text and small cookie image bouncing from the cursor... With more than one "pair" of those effects at once?
Thanks in advance.
I would simply generate an image with the particles once and for all. You create two instances of that image and you rotate them in opposite directions. The images of course are transparent where there is no particle. The cookie image can be put on top of this by a simple animation.
This has been solved by superimposing a non-hittestable Canvas control with a transparent Background on top of the entire Window.
Window.xaml, pseudocode
<Window>
<Grid>
<Canvas IsHitTestVisible="False"/>
<(Rest of content)/>
</Grid>
</Window>
Then it's a matter of storing all active animated cookies and drawing them.
Window.xaml.cs, pseudocode
internal class CookieCanvas : Canvas {
private Vector2[] _cookiePositions;
protected override void OnRender(DrawingContext drawingContext) {
foreach(var cookiePosition in _cookiePositions)
{
drawingContext.DrawImage(someCachedCookieImage,
new Rect(cookiePosition.X, cookiePosition.Y, img.PixelWidth, img.PixelHeight)
);
}
}
}
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>
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>
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,
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.