I currently have a canvas that I load an image on in my program, and I would like to be able to right-click on a certain point on that image and click zoom in to zoom in on that point.
One solution I found was:
<Canvas>
<Canvas.RenderTransform>
<ScaleTransform ScaleX="2" ScaleY="2">
</Canvas.RenderTransform>
</Canvas>
This works however it doesn't zoom in to the specific coordinates but rather just zooms in to the top-left of the image. How would I implement coordinate zooming? zooming in on specific coordinates?
Using three separate transforms. One to move the canvas so that the click point is the origin. The second transform to scale and the third to put the click point back at the center again
<Canvas.RenderTransform>
<TransformGroup>
<TranslateTransform X="0" Y="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
<TranslateTransform X="0" Y="0" />
</TransformGroup>
</Canvas.RenderTransform>
private void Image_MouseDown(object sender, MouseButtonEventArgs e)
{
Point p = e.GetPosition(this);
var transformGroup = ImageCanvas.RenderTransform as TransformGroup;
var moveToOriginTransform = transformGroup.Children[0] as TranslateTransform;
var scaleTransform = transformGroup.Children[1] as ScaleTransform;
var moveBackTransform = transformGroup.Children[2] as TranslateTransform;
moveToOriginTransform.X = -p.X;
moveToOriginTransform.Y = -p.Y;
scaleTransform.ScaleX += 1;
scaleTransform.ScaleY += 1;
moveBackTransform.X = p.X;
moveBackTransform.Y = p.Y;
}
Related
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.
I am adding a line in code in WPF between two ellipses which works fine. I now need to add a triangle as an arrow head indicator at the top and bottom of my line. My problem is that I do not know the mathematical equation to ensure that the triangle is exactly over the top of my line and facing the correct direction. I have attached a image showing the issue, any help would be greatly received.
Given a Line element, you would calculate the angle of its direction (clockwise from north) by
var dx = line.X2 - line.X1;
var dy = line.Y2 - line.Y1;
var angle = 180 * (1 - Math.Atan2(dx, dy) / Math.PI);
or
var angle = Vector.AngleBetween(new Vector(0, -1), new Vector(dx, dy));
You could use that angle for the Angle property of a RotateTransform.
An example:
<Line x:Name="line" X1="100" Y1="100" X2="200" Y2="200"
Stroke="Black" StrokeThickness="3"/>
<Path Fill="Red">
<Path.Data>
<PathGeometry Figures="M0,10 L10,30 -10,30Z">
<PathGeometry.Transform>
<TransformGroup>
<RotateTransform x:Name="rotation"/>
<TranslateTransform
X="{Binding ElementName=line, Path=X2}"
Y="{Binding ElementName=line, Path=Y2}"/>
</TransformGroup>
</PathGeometry.Transform>
</PathGeometry>
</Path.Data>
</Path>
with this code behind:
rotation.Angle = angle;
I was currently working on a behavior class for dragging UIElements with the mouse. I had made something like this before but I was trying to make it so that the UIElement is not depending on having a certain type of parent (besides a window).
The problem I was facing was when I tried to translate the coordinates of both the mouse and the UIElement(AssociatedObject) relative to the window, which resulted in a offset shown down below.
Trying to add offsets to "fix" the current offset feels like a cheat and it would make creating the rest of the movement behavior a lot more complicated.
currently I am using a grid with multiple elements inside for testing the dragBehaviorComponentClass.
(I filled the grid in with lightBlue to act as an indicator, although it is slightly overlapped by other shapes)
I am currently only trying to attach the 0,0 position of the uiElement to the point of the mouse, I am not working on any offsets yet to further improve dragging.
Code
public class MouseDragBehaviourComponent : Behavior<UIElement>
{
private TranslateTransform transform = new TranslateTransform();
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
AssociatedObject.PreviewMouseMove += AssociatedObject_PreviewMouseMove;
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
}
private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
AssociatedObject.ReleaseMouseCapture();
}
private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point currentPosition = AssociatedObject.TranslatePoint(new Point(0, 0), Window.GetWindow(AssociatedObject));
AssociatedObject.RenderTransform = transform;
AssociatedObject.CaptureMouse();
}
private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (AssociatedObject.IsMouseCaptured)
{
Point mousePosition = e.GetPosition(Window.GetWindow(AssociatedObject));
transform.X = mousePosition.X;
transform.Y = mousePosition.Y;
}
}
xaml
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:UISet}">
<Border >
<e:Interaction.Behaviors>
<bt:MouseDragBehaviourComponent/>
</e:Interaction.Behaviors>
<Grid Background="LightBlue">
<StackPanel>
<Path MouseLeftButtonDown="geometry_MouseLeftButtonDown" Name="geometry" Fill="Blue">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry RadiusX="2" RadiusY="2" Rect="-5,-50,100,50" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry RadiusX="2" RadiusY="2" Rect="5,-40,90,40" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
<Path MouseLeftButtonDown="geometry_MouseLeftButtonDown" Fill="Red">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="5,6"/>
<LineSegment Point="10,0"/>
<LineSegment Point="0,0"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</StackPanel>
</Grid>
The way your code is set up:
transform.X = mousePosition.X;
transform.Y = mousePosition.Y;
gets the mouse position inside of the thing your moving. This causes the transform to move away from the mouse whatever that distance is. To fix that, you need to get the mouse position of the container of the thing you're moving which will act as the main point, and also get the mouse position of the cursor inside of the thing you're moving. This will act as the offset. So in all, the code will look something similar to:
Point mouseContainerLocation = Mouse.GetPosition(container);
Point mouseObjectLocation = Mouse.GetPosition(object);
transform.X = mouseContainerLocation.X - mouseObjectLocation.X;
transform.Y = mouseContainerLocation.Y - mouseObjectLocation.Y;
I did something similar in an application I made and this worked for me
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.
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.