To learn about animation and UI, I'm making a basic WPF/C# application where a user selects the number of vehicles to display then those vehicles (ie images of different vehicles) appear in the canvas and move around.
The WPF is very simple:
<Grid>
<Canvas x:Name="MenuTabCanvas" Visibility="Visible">
<Label x:Name="AnimateDemo" Content="Animate!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="104" Background="#25A0DA" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Cursor="Hand" MouseDown="AnimateGrid" Canvas.Left="640" Canvas.Top="87"/>
<Canvas x:Name="AnimationCanvas" Canvas.Top="150" Canvas.Left="224" Width="814" Height="489">
</Canvas>
</Grid>
I started with a fade animation using the method described in this post and it works fine. Then I tried the translatetransform as shown below leaving the fade code in comments, and the images appear but nothing moves:
private void AnimateGrid(object sender, EventArgs e)
{
int NumberOfVehicles = 5;
var sb = new Storyboard();
for (int i = 0; i < NumberOfVehicles; i++)
{
//create & add the images to our target canvas
Image Img = getRandomVehicleImage(); //returns image of vehicle
AnimationCanvas.Children.Add(Img);
Canvas.SetTop(Img, 30 + 60 * i); //position image w/in canvas
Canvas.SetLeft(Img, 30 + 80 * i);
//add an animation
DoubleAnimation myAnimation = new DoubleAnimation()
{
// From = 0,
To = 150,
Duration = TimeSpan.FromSeconds(2),
};
Storyboard.SetTarget(myAnimation, Img);
// Storyboard.SetTargetProperty(myAnimation, new PropertyPath(Button.OpacityProperty));
Storyboard.SetTargetProperty(myAnimation, new PropertyPath(TranslateTransform.XProperty));
sb.Children.Add(myAnimation);
}
sb.Begin();
}
I have been able to get it to work with TranslateTransform.BeginAnimation but I would prefer to use the storyboard here.
Why does the translate transform behave differently than the opacity animation and what do I need to do to get it to function as intended?
By default, there is no TranslateTransform applied to a UIElement. So if you want to move an Image, you first have to set its RenderTransform property to a TranslateTransform, and then set the TargetProperty to the correct property path:
Img.RenderTransform = new TranslateTransform();
...
Storyboard.SetTargetProperty(myAnimation, new PropertyPath("RenderTransform.X"));
Alternatively, you could directly animate the TranslateTransform, even without a Storyboard:
var transform = new TranslateTransform();
Img.RenderTransform = transform;
transform.BeginAnimation(TranslateTransform.XProperty, myAnimation);
Related
I have a small button that should adjust vertically and horizontally. I Would like to use Storyboard and manipulate the X and Y under transform.
And I would like to do it code-behind.
The button moves to the correct position, but no animation is active.
private void AnimatePlayerMovement(Player player)
{
//This will hold hour animation
Piece.RenderTransform = new CompositeTransform();
//New storyboard
Storyboard storyboard = new Storyboard();
//New DoubleAnimation - Y
DoubleAnimation translateYAnimation = new DoubleAnimation();
translateYAnimation.From = this.Path[player.from, 1];
translateYAnimation.To = this.Path[player.to, 1];
translateYAnimation.EasingFunction = new ExponentialEase();
translateYAnimation.EasingFunction.EasingMode = EasingMode.EaseOut;
translateYAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
Storyboard.SetTarget(translateYAnimation, Piece);
Storyboard.SetTargetProperty(translateYAnimation,
"(UIElement.RenderTransform).(CompositeTransform.TranslateY)");
storyboard.Children.Add(translateYAnimation);
//New DoubleAnimation - X
DoubleAnimation translateXAnimation = new DoubleAnimation();
translateXAnimation.From = this.Path[player.from, 0];
translateXAnimation.To = this.Path[player.to, 0];
translateXAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
Storyboard.SetTarget(translateXAnimation, Piece);
Storyboard.SetTargetProperty(translateXAnimation,
"(UIElement.RenderTransform).(CompositeTransform.TranslateX)");
storyboard.Children.Add(translateXAnimation);
storyboard.Begin();
}
And the button in xaml
<Button x:Name="Piece" Height="50" Width="50" HorizontalAlignment="Left"
Margin="115,400,0,0" VerticalAlignment="Top" Click="Button_Click"
RenderTransformOrigin="0.5,0.5">
<Button.Template>
<ControlTemplate>
<Image Source="Assets/piece.png"/>
</ControlTemplate>
</Button.Template>
</Button>
I have tested your code and the animation works properly. You can find the working repo on GitHub. As the code is almost 1:1 copy of your sample, except I hardcoded the From and To property values of the animations. Thus I think the problem you are seeing is that this.Path[player.from, 0] and this.Path[player.to, 0] values are the same (or very similar). This way the button jumps to the this.Path[player.from, 0] location and stays there, not moving further.
I have a series of rectangles in which the user can add images to, by dragging the images in.
The image is then scaled down in proportion and the rectangle is then filled with an ImageBrush.
I need for the user to be able to manipulate the image within the rectangle to fit their needs. Like any photo collage app does.
My question is: How can I show the full, unmasked image on top of the rectangle so that the user can manipulate it to their needs? I don't know where to start with this one.
private async void Mask_Drop(object sender, DragEventArgs e)
{
Rectangle maskSq = e.OriginalSource as Rectangle;
var maskW = maskSq.Width.ToSingle();
var maskH = maskSq.Height.ToSingle();
double maskX = Canvas.GetLeft(maskSq);
double maskY = Canvas.GetTop(maskSq);
// Image sizes for bounding to mask
float boundH = Convert.ToSingle(size.Height);
float boundW = Convert.ToSingle(size.Width);
maskSq.Fill = new ImageBrush
{
ImageSource = new BitmapImage(new Uri("ms-appdata:///local/" + SelectedImage.Name, UriKind.Absolute)),
Stretch = Stretch.UniformToFill
};
}
private void Tap_Collage(object sender, TappedRoutedEventArgs e)
{
//Gets the full image from ImageBrush
ImageBrush brush = (ImageBrush)(((Rectangle)sender).Fill);
Rectangle rect = sender as Rectangle;
//Mask sure rectangle does not drag, just the image brush
rect.CanDrag = false;
rect.StrokeThickness = 6;
//Drag Image Functionality
rect.ManipulationDelta += ImageManipulation.Resize_ImageEdit;
ImageManipulation.ImageEdit_Drag = new TranslateTransform();
brush.Transform = ImageManipulation.ImageEdit_Drag;
//Zoom Image Functionality
ImageManipulation.ImageEdit_Zoom = new ScaleTransform();
brush.RelativeTransform = ImageManipulation.ImageEdit_Zoom;
}
Class
public static class ImageManipulation
{
public static TranslateTransform ImageEdit_Drag;
public static ScaleTransform ImageEdit_Zoom;
public static RotateTransform ImageEdit_Rotate;
public static void Resize_ImageEdit(object sender, ManipulationDeltaRoutedEventArgs e)
{
ImageEdit_Drag.X += e.Delta.Translation.X;
ImageEdit_Drag.Y += e.Delta.Translation.Y;
ImageEdit_Zoom.ScaleX *= e.Delta.Scale;
ImageEdit_Zoom.ScaleY *= e.Delta.Scale;
if (ImageEdit_Zoom.ScaleX < 1.0)
{
ImageEdit_Zoom.ScaleX = 1.0;
ImageEdit_Zoom.ScaleY = 1.0;
}
ImageEdit_Rotate.Angle += e.Delta.Rotation;
}
}
XAML
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:CollageGrid">
<Rectangle Width="{Binding CollageW}" Height="{Binding CollageH}" AllowDrop="True" CanDrag="True" Fill="Transparent"
Drop="Mask_Drop"
DragOver="Mask_DragOver"
ManipulationMode="TranslateX, TranslateY" Stroke="Black" StrokeThickness="2" DragEnter="Mask_DragEnter" DragLeave="Mask_DragLeave" Tapped="Tap_Collage">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding CollageX}" Y="{Binding CollageY}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
Example of what I'm looking to acheive:
How it looks currently:
You can try to replace the Rectangle with the Polyline to draw the Rectangle so that you can access the Image which is on the bottom of the Rectangle.
<Grid>
<Image Source="Assets/image1.jpg" Width="800"
Height="400" Tapped="Image_Tapped" />
<Polyline Stroke="Black" HorizontalAlignment="Center"
VerticalAlignment="Center"
StrokeThickness="4" Tapped="Polyline_Tapped"
Points="0,0,0,200,200,200,200,0,0,0" />
</Grid>
---Update---
UniformToFill will cause the image is resized to fill the destination dimensions while it preserves its native aspect ratio. If the aspect ratio of the destination rectangle differs from the source, the source content is clipped to fit in the destination dimensions. So it is not suitable for your requirement. You should manually scale the Image to make image fit one of your Rectangle's border and keep other part out of the Rectangle.
It seems you put the image as the Rectangle's background image brush, there are no other place to display the image out of the Rectangle. So I think, we may take a considerition for a new scenario.
As my pervious reply, using Image control to display the image and Polylines to draw a Rectangle above the Image so that we can operate the Image which is below the Rectangle using the manipulation events to fit the rectangle, meanwhile we can also use the community toolkit BackdropBlurBrush on the xaml layout to Blur the outer image.
Im creating UWP app which displays collection of MapIcons on map. MapIcon images are dynamically created from XAML and rendered to bitmap. MapIcon's are updated every few second. There can be around 200 MapIcon on map.
The problem is that my MapIcon update code gets somehow out of sync, MapIcon images are not correct for spesific MapIcon/plane. E.g. some planes get previous plane image etc.
Im storing my MapIcons to separate _planesDictionary (Dictionary<string, MapIcon>), the idea is to speed up the update process as I hope it is more faster to get map icon from dictionary than from mapControl MapElements list?
My code:
// This is called every few seconds with new planes collection
public async void AddAndUpdatePlanes(IReadOnlyCollection<IRealtimePlane> planes)
{
foreach (var plane in planes)
{
MapIcon foundMapIcon = null;
if(!_planesDictionary.TryGetValue(plane.PlaneId, out foundMapIcon))
{
var planeIcon = new MapIcon
{
Location = new Geopoint(plane.Location),
NormalizedAnchorPoint = new Point(0.5, 0.5),
ZIndex = 10,
CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible,
Image = await GetMapIconImage(GetPlaneMarContainer(plane))
};
MapControl.MapElements.Add(planeIcon);
_planesDictionary.Add(plane.PlaneId, planeIcon);
}
else if(foundMapIcon != null)
{
// Update all field to get all markers updated?
foundMapIcon.Location = new Geopoint(plane.Location);
foundMapIcon.NormalizedAnchorPoint = new Point(0.5, 0.5);
foundMapIcon.ZIndex = 10;
foundMapIcon.CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible;
foundMapIcon.Image = await GetMapIconImage(GetPlaneMarContainer(plane));
}
}
}
// Get XAML element (grid) which is used to render MapIcon image
private Grid GetPlaneMarContainer(IRealtimePlane plane)
{
PlaneImage.RenderTransformOrigin = new Point(0.5, 0.5);
PlaneImage.RenderTransform = new RotateTransform
{
Angle = plane.Angle
};
PlaneImageText.Text = plane.NumberCode;
return PlaneMarkerContainer;
}
// Convert UIElement MapIcon image source
public async Task<IRandomAccessStreamReference> GetMapIconImage(UIElement uielement)
{
var rtb = new RenderTargetBitmap();
await rtb.RenderAsync(uielement);
var dpi = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi;
var pixels = (await rtb.GetPixelsAsync()).ToArray();
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)rtb.PixelWidth, (uint)rtb.PixelHeight, dpi, dpi, pixels);
await encoder.FlushAsync();
stream.Seek(0);
return RandomAccessStreamReference.CreateFromStream(stream.AsStream().AsRandomAccessStream());
}
This is the XAML which is used to render MapIcon images (this is behind the MapControl).
<Grid x:Name="PlaneMarkerContainer" Width="36" Height="36" Background="Transparent">
<Image x:Name="PlaneImage" Width="36" Height="36" VerticalAlignment="Center" HorizontalAlignment="Center" Source="ms-appx:///Assets/Icons/Plane.png" />
<TextBlock x:Name="PlaneImageText" Text="" FontSize="10" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
More finding:
With my PC everything works fine if I have less than 100 MapIcons. With Lumia 950XL, even 50 MapIcon updates is too much. MapIcons (planes) gets wrong images.
UPDATE
This code works completely fine. The issue was in my GetPlaneMarContainer() which had small functionality to pic correct grid. This code is not shown in my example. Somehow I managed to mess up this functionality and it lead my code to not work correctly.
I have 2 buttons created in code behind ,I am trying to draw a line that connects them by getting the button coordinates, but it doesn't work the way that I wanted , I am new to this , am I doing this the right way?
I searched internet for line connecting 2 controls, but I just want a simple one that make use of coordinates to connect them .
Here are my codes :
Button btn1 = new Button();
btn1.Content = "Hello";
btn1.Width = 150;
btn1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
wrapPanel1.Children.Add(btn1);
btn1.PreviewMouseDown += new MouseButtonEventHandler(btn1_MouseDown);
Button btn2 = new Button();
btn2.Content = "World";
btn2.Width = 150;
btn2.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn2.PreviewMouseDown += new MouseButtonEventHandler(btn1_MouseDown);
wrapPanel1.Children.Add(btn2);
private void ShowLocation(ContentControl element)
{
var location = element.PointToScreen(new Point(0, 0));
MessageBox.Show(string.Format(
"{2}'s location is ({0}, {1})",
location.X,
location.Y,
element.Content));
Line redLine = new Line();
redLine.X1 = location.X;
redLine.Y1 = location.Y;
redLine.X2 = 323;
redLine.Y2 = 167;
//Create a red Brush
SolidColorBrush redBrush = new SolidColorBrush();
redBrush.Color = Colors.Red;
//Set Line's width and color
redLine.StrokeThickness = 4;
redLine.Stroke = redBrush;
// Add line to the Grid.
wrapPanel1.Children.Add(redLine);
}
private void btn1_MouseDown(object sender, RoutedEventArgs e)
{
var element = sender as ContentControl;
if (element != null)
{
ShowLocation(element);
}
}
As you notice the X2 and Y2 coordinates I just random giving it a number but the X1 and Y1 is suppose to be drawn from the button but it is not.
The line is appearing somewhere else away from the button, but the coords I use are the coords of the button.
Also, I have to dynamically create many buttons from db, This is just testing out yet I am stuck here.
__EDIT____
trying out with 1 button , i realise when maximize and minimize , the coords are different , this is how it looks like , the button seems to increase its height everytime i click on it to generate the line .
i want the line to start from the end of the button width at the side , if its not possible , it will be good for the button to overlap the line but is that possible?
The main problem is that WPF mostly uses layout-based positioning (Grid, DockPanel, etc), instead of Windows Forms, which is coordinate-based. This means, that real location of left top element's corner depends from layout control being used and its current settings (and yes, this usually blows up your mind, if you are very new to WPF).
If you want to manage position of elements by coordinates in WPF, then you should use Canvas (you can easily translate this sample from XAML to C#):
<Canvas>
<Button Canvas.Left="10" Canvas.Top="10" Width="20" Height="20" Content="A"/>
<Button Canvas.Left="50" Canvas.Top="50" Width="20" Height="20" Content="B"/>
<Line Canvas.Left="30" Canvas.Top="30" X2="20" Y2="20" StrokeThickness="4" Stroke="Red"/>
</Canvas>
I have a Canvas in my MainWindow and I draw a line there. When it draws over the width/height of my Canvas, the drawing continues in my MainWindow. Is there a mistake in my code or is that normal?
<Canvas x:Name="coordinateSystem" HorizontalAlignment="Right" Height="580" Margin="0,10,283,0" VerticalAlignment="Top" Width="1024" Cursor="Cross" UseLayoutRounding="False"/>
Here is my function I call everytime when I get a new coordinate for my line:
// xOld, yOld and t are static
// t represents the time
private void drawPoly(double value)
{
t++;
Point pOne = new Point(xOld, yOld);
Point pTwo = new Point(t, value);
GeometryGroup lineGroup = new GeometryGroup();
LineGeometry connectorGeometry = new LineGeometry();
connectorGeometry.StartPoint = pOne;
connectorGeometry.EndPoint = pTwo;
lineGroup.Children.Add(connectorGeometry);
System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
path.Data = lineGroup;
path.StrokeThickness = 1;
path.Stroke = path.Fill = Brushes.Red;
coordinateSystem.Children.Add(path);
xOld = t;
yOld = value;
}
thx
PS: Is there a way to save all drawn points? I want later resize my canvas (zoom out/zoom in) or if the time going to big move my painted line in my canvas and then I need to draw all points again.
Canvases do not clip child elements. If you want to stop the child elements from being drawn outside of the Canvas, you'll need to set ClipToBounds to true or set the Clip of the Canvas.