WPF: Connecting rectangles on canvas dynamically with path between - c#

This is my first time posting, so be easy on me...
There are definitely posts that show how to connect lines to blocks, but this one is slightly different. The grids are placed on the canvas dynamically. I want a line to connect the grid from where the button is pressed to the new grid that's placed on the canvas. However the below code doesn't work. I've struggled with this one for a very long time.
Solely by accident I discovered a Messagebox thrown in the button event handler will allow it to work. I figured this was from threads running at different times, but after messing with task.delay/thread.sleep/async/await I can't find the solution.
I'm using custom classes because this is a stripped down version of the larger program, I wanted to have similar functionality in my example to reflect possible errors but leave out the unnecessary pieces.
I'm using this as a last resort, thank you in advance for any help you can provide. First the CS code
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Grid1 g = myCanvas.CreateGrid();
ContentControl1 cc = myCanvas.CreateCC();
Button1 b = myCanvas.CreateButton1();
Grid1.SetColumn(cc, 0);
Grid1.SetRow(cc, 0);
Grid1.SetColumn(b, 1);
Grid1.SetRow(b, 1);
g.Children.Add(cc);
g.Children.Add(b);
Canvas1.SetLeft(g, 500);
Canvas1.SetTop(g, 5);
myCanvas.Children.Add(g);
}
}
public class Button1 : Button
{
protected override void OnClick()
{
Grid1 old_g = (Grid1)VisualTreeHelper.GetParent(this as DependencyObject);
Canvas1 cnv = (Canvas1)VisualTreeHelper.GetParent(old_g as DependencyObject);
Grid1 g = cnv.CreateGrid();
ContentControl1 cc = cnv.CreateCC();
Button1 b = cnv.CreateButton1();
Grid1.SetColumn(cc, 0);
Grid1.SetRow(cc, 0);
Grid1.SetColumn(b, 1);
Grid1.SetRow(b, 1);
g.Children.Add(cc);
g.Children.Add(b);
Canvas1.SetLeft(g, 500);
Canvas1.SetTop(g, cnv.Children.Count * 120);
cnv.Children.Add(g);
cnv.ConnectGrids(old_g, g);
}
}
public class Canvas1 : Canvas
{
public Grid1 CreateGrid()
{
Grid1 g = new Grid1() { Width = 100, Height = 20, Background = Brushes.White };
g.HorizontalAlignment = HorizontalAlignment.Center;
g.VerticalAlignment = VerticalAlignment.Center;
g.ShowGridLines = false;
ColumnDefinition colDef1 = new ColumnDefinition();
ColumnDefinition colDef2 = new ColumnDefinition() { Width = new GridLength(20) };
g.ColumnDefinitions.Add(colDef1);
g.ColumnDefinitions.Add(colDef2);
RowDefinition rowDef1 = new RowDefinition();
g.RowDefinitions.Add(rowDef1);
return g;
}
public ContentControl1 CreateCC()
{
ContentControl1 cc = new ContentControl1()
{
VerticalContentAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
BorderBrush = Brushes.BlueViolet,
};
return cc;
}
public Button1 CreateButton1()
{
Button1 b = new Button1()
{
VerticalContentAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
BorderBrush = Brushes.Red
};
return b;
}
public void ConnectGrids(Grid1 g1, Grid1 g2)
{
Canvas1 cnv = (Canvas1)VisualTreeHelper.GetParent(g1 as DependencyObject);
Transform transform1 = (Transform)g1.TransformToVisual(cnv as Visual);
Transform transform2 = (Transform)g2.TransformToVisual(cnv as Visual);
Point StartPoint1 = transform1.Transform(new Point(g1.Width, g1.Height / 2.0));
Point EndPoint1 = transform2.Transform(new Point(g2.Width, g2.Height / 2.0));
var lineGeometry = new LineGeometry()
{
StartPoint = StartPoint1,
EndPoint = EndPoint1
};
var path = new Path()
{
Data = lineGeometry,
Stroke = new SolidColorBrush(Colors.Black),
};
cnv.Children.Add(path);
}
}
public class ContentControl1 : ContentControl
{
}
public class Grid1 : Grid
{
}
}
then the xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WpfApplication1"
Title="MainWindow" Height="1000" Width="1000">
<DockPanel>
<Button DockPanel.Dock="Top" Width="100" Height="20" Content="Start" Click="Button_Click"/>
<ScrollViewer HorizontalScrollBarVisibility="Visible" Width="901" x:Name="_scrollViewer" Margin="0,5,0,25">
<local:Canvas1 Background="Gray" Height="1000" Width="1000" x:Name="myCanvas"/>
</ScrollViewer>
</DockPanel>
</Window>
How can I successfully connect the grid with the path?

I love that I solved this/hate that I have to answer my own dumb question...
Sooo simple. UpdateLayout(). The objects don't change location until it has been rendered, so the line was going to its old location.

Related

Fade in new image and simultaneously fade out old image WPF

I am trying to figure out how to have a new image fade in and simultaneously have the old image fade out.
I initially thought of using two double animations, one for fade in and another for fade out that on completion trigger each other, however that doesn't work for me since I need one image to fade in while the other is fading out and I don't know how to go about doing it.
What I attempted to do:
XAML:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Animation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="gridTest">
<StackPanel x:Name="stkHeaderBar" Grid.Row="0"
Orientation="Horizontal" FlowDirection="RightToLeft">
<Button x:Name="btnChangeImg" Content="Change Image"
Click="btnChangeImage_Click"/>
<Image x:Name="img"></Image>
</StackPanel>
</Grid>
Code behind:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Animation
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnChangeImage_Click(object sender, RoutedEventArgs e)
{
DoubleAnimation fadeIn = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(2)
};
DoubleAnimation fadeOut = new DoubleAnimation
{
From = 1,
Duration = TimeSpan.FromSeconds(2),
};
fadeOut.Completed += (o, e) =>
{
img.Source = new BitmapImage(new Uri(#"C:\images.jpg"));
img.BeginAnimation(OpacityProperty, fadeIn);
};
fadeIn.Completed += (o, e) =>
{
img.Source = new BitmapImage(new Uri(#"C:\images.jpg"));
img.BeginAnimation(OpacityProperty, fadeOut);
};
img.Source = new BitmapImage(new Uri(#"C:\images1.jpg"));
img.BeginAnimation(OpacityProperty, fadeIn);
}
}
}
You're over-complicating it, just put both images in a Grid so that they overlap:
<Grid>
<Image x:Name="img1" Stretch="UniformToFill"></Image>
<Image x:Name="img2" Stretch="UniformToFill"></Image>
</Grid>
The first image can have its opacity stay at 1.0 the whole time, and then you just animate the second image to fade in over top of it:
private void btnChangeImage_Click(object sender, RoutedEventArgs e)
{
DoubleAnimation fadeIn = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(2)
};
img1.Source = new BitmapImage(new Uri(#"C:\image1.jpg"));
img2.Source = new BitmapImage(new Uri(#"C:\image2.jpg"));
img2.BeginAnimation(OpacityProperty, fadeIn);
}
If you want to be thorough then you can use your fadeIn.Completed handler to remove img1 when the animation is complete, but I normally wouldn't worry about that unless it was a resource-critical application.

C# - WPF how to get object layer level [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
My goal is : Only allowed click to remove a rectangle if there is no rectangle in front of the target rectangle.
Maybe there is a solution to get a layer level from an object ? I have found nothing about this, im new to WPF. So if some can explain the solution or the way to think about this problem.
Code xaml.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WpfApp1
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DispatcherTimer gameTimer = new DispatcherTimer();
List<Rectangle> removeThis = new List<Rectangle>();
int posX;
int posY;
int width;
int rectangleVariation;
int height;
Random rand = new Random();
Brush brush;
public MainWindow()
{
InitializeComponent();
Random rand = new Random();
int nbObjects = rand.Next(15,30);
for (int i = 0; i < nbObjects; i++)
{
brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(1, 255), (byte)rand.Next(1, 255), (byte)rand.Next(1, 255)));
posX = rand.Next(15, 700);
posY = rand.Next(50, 250);
rectangleVariation = rand.Next(0, 2);
if (rectangleVariation == 0)
{
width = 200;
height = 50;
}
else
{
width = 50;
height = 200;
}
Rectangle rectangle = new Rectangle
{
Tag = "rectangle",
Height = height,
Width = width,
Stroke = Brushes.Black,
StrokeThickness = 1,
Fill = brush
};
Canvas.SetLeft(rectangle, posX);
Canvas.SetTop(rectangle, posY);
MyCanvas.Children.Add(rectangle);
}
}
private void ClickOnCanvas(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Rectangle)
{
Rectangle rectangle = (Rectangle)e.OriginalSource;
MyCanvas.Children.Remove(rectangle);
}
}
}
}
Code xaml file :
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Canvas Name="MyCanvas" MouseLeftButtonDown="ClickOnCanvas" Background="DarkGray">
</Canvas>
</Window>
You should first determine whether the clicked Rectangle intersects with any other Rectangle elements and if it does, you could determine whether it's in top of all of them by looking at the index in the Canvas's Children collection.
The Rect type has an IntersectsWith method that you can use. Something like this:
private void ClickOnCanvas(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Rectangle)
{
Rectangle clickedRectangle = (Rectangle)e.OriginalSource;
Rect clickedRect = new Rect()
{
Location = new Point(Canvas.GetLeft(clickedRectangle), Canvas.GetTop(clickedRectangle)),
Size = new Size(clickedRectangle.Width, clickedRectangle.Height)
};
for (int i = 0; i < MyCanvas.Children.Count; i++)
{
if (MyCanvas.Children[i] is Rectangle rectangle && rectangle != clickedRectangle)
{
Rect rect = new Rect()
{
Location = new Point(Canvas.GetLeft(rectangle), Canvas.GetTop(rectangle)),
Size = new Size(rectangle.Width, rectangle.Height)
};
if (clickedRect.IntersectsWith(rect) && MyCanvas.Children.IndexOf(clickedRectangle) < i)
return;
}
}
MyCanvas.Children.Remove(clickedRectangle);
}
}

Throws an exception when cropping an image if window is maximized WPF

I'm very new to WPF and I'm trying to create a picture cropping application. The program has a rectangle that can be dragged to crop the picture (like in Microsoft Paint). The way its set up is that both image and canvas which holds the rectangle are children of grid container. Grid has events for mousemove, mouseleftbuttondown and up to calculate the cropped image. My program is cropping the image fine but the problem occurs when the window is maximized which throws an exception error below:
An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll
Additional information: Value does not fall within the expected range.
This exception is caused by a line in my Go_Click method:
BitmapSource bs = new CroppedBitmap(LoadedImage.Source as BitmapSource, rcFrom);
I stepped into the program and found out that at first it was caused by setting the width and height of Image (LoadedImage.Width and LoadedImage.Height) to auto or NAN. This makes the value NAN which mess up the calculation and throws of the exception. I changed it to ActualWidth (LoadedImage.ActualWidth and ActualHeight) and I only got it work partially since it still throws an exception whenever I crop the image while window is maximized.
Below is source code for my project.
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<GroupBox Header="Loaded Image:" Grid.Column="0">
<Grid x:Name="LoadedImage" MouseLeftButtonDown="image1_MouseLeftButtonDown" MouseMove="image1_MouseMove" MouseLeftButtonUp="image1_MouseLeftButtonUp" ShowGridLines="True">
<Image x:Name="loaded" Margin="10" Stretch="Fill"/>
<Canvas x:Name="BackPanel" Margin="10">
<Rectangle x:Name="selectionRectangle" Stroke="LightBlue" Fill="#220000FF" Visibility="Collapsed"/>
</Canvas>
</Grid>
</GroupBox>
<StackPanel Grid.Column="1" x:Name="MyPanel" HorizontalAlignment="Left" Orientation="Vertical" Height="340" Width="262">
<GroupBox x:Name="Box2" Header="Preview Box:">
<Image x:Name="PreviewImage" MaxWidth="240" MaxHeight="240" Stretch="Fill" MinWidth="240" MinHeight="240"/>
</GroupBox>
<StackPanel Height="61" Orientation="Horizontal" HorizontalAlignment="Center">
<Button MinWidth="93" Height="32" Click="Import_Click">Import</Button>
<Button Name="Crop" MinWidth="93" Height="32" Margin="10,0,0,0" Click="Go_Click">Crop</Button>
</StackPanel>
</StackPanel>
</Grid>
And this is the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.IO;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool isDragging = false;
private Point anchorPoint = new Point();
public MainWindow()
{
InitializeComponent();
}
//Import Button event handler which opens an open file dialog
//to choose an image file. If the return value from ShowDialog
//is true (user choose image and click's OK)it will store the image to file,
//else it would exit open file dialog.
private void Go_Click(object sender, RoutedEventArgs e)
{
if (loaded.Source != null)
{
Rect rect1 = new Rect(Canvas.GetLeft(selectionRectangle), Canvas.GetTop(selectionRectangle), selectionRectangle.Width, selectionRectangle.Height);
Int32Rect rcFrom = new Int32Rect();
rcFrom.X = (int)((rect1.X) * (loaded.Source.Width) / `enter code here`(loaded.ActualWidth));
rcFrom.Y = (int)((rect1.Y) * (loaded.Source.Height) / (loaded.ActualHeight));
rcFrom.Width = (int)((rect1.Width) * (loaded.Source.Width) / (loaded.ActualWidth));
rcFrom.Height = (int)((rect1.Height) * (loaded.Source.Height) / (loaded.ActualHeight));
BitmapSource bs = new CroppedBitmap(loaded.Source as BitmapSource, rcFrom);
PreviewImage.Source = bs;
}
}
private void image1_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
double x = e.GetPosition(BackPanel).X;
double y = e.GetPosition(BackPanel).Y;
selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);
if (selectionRectangle.Visibility != Visibility.Visible)
selectionRectangle.Visibility = Visibility.Visible;
}
}
private void image1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isDragging == false)
{
anchorPoint.X = e.GetPosition(BackPanel).X;
anchorPoint.Y = e.GetPosition(BackPanel).Y;
isDragging = true;
}
}
private void image1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
isDragging = false;
if (selectionRectangle.Width > 0)
{
Crop.Visibility = System.Windows.Visibility.Visible;
Crop.IsEnabled = true;
}
if (selectionRectangle.Visibility != Visibility.Visible)
selectionRectangle.Visibility = Visibility.Visible;
}
}
private void RestRect()
{
selectionRectangle.Visibility = Visibility.Collapsed;
isDragging = false;
}
private void Import_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.FileName = "Picture";
dlg.Filter = "All Pictures (*.jpeg)|*.jpg";
if (dlg.ShowDialog() == true)
{
loaded.Source = new BitmapImage(new Uri(dlg.FileName));
}
}
}
}
Like I said i'm new to programming WPF and I apologize if the way i have my code written may not be the best. But any advise would help on how I should handle this issue.
Thanks in advance.
You need to get LoadedImage position relative to GridLoadedImage, and use it when creating crop:
private void Go_Click(object sender, RoutedEventArgs e)
{
if (LoadedImage.Source != null)
{
var imagePosition = loaded.TransformToAncestor(LoadedImage).Transform(new Point(0, 0));
Rect rect1 = new Rect(Math.Max(Canvas.GetLeft(selectionRectangle) - imagePosition.X, 0), Canvas.GetTop(selectionRectangle), selectionRectangle.Width, selectionRectangle.Height);
Int32Rect rcFrom = new Int32Rect();
rcFrom.X = (int)((rect1.X) * (LoadedImage.Source.Width) / (LoadedImage.ActualWidth));
rcFrom.Y = (int)((rect1.Y) * (LoadedImage.Source.Height) / (LoadedImage.ActualHeight));
rcFrom.Width = (int)((rect1.Width) * (LoadedImage.Source.Width) / (LoadedImage.ActualWidth));
rcFrom.Height = (int)((rect1.Height) * (LoadedImage.Source.Height) / (LoadedImage.ActualHeight));
BitmapSource bs = new CroppedBitmap(LoadedImage.Source as BitmapSource, rcFrom);
PreviewImage.Source = bs;
}
}

Creating WriteableBitmap form chart issue

I have a problem when trying to create writeable bitmap form Silverlight toolkit Graph.
When using textBlock, everything is fine, but after trying to use Chart, generated bitmap is empty :( .
var data = new List<Point>(100);
for (int i = 0; i < 100; i++)
{
data.Add(new Point(i, Math.Sin(i * Math.PI / 50)));
}
Chart chart_ = new Chart()
{
Name = "Chart",
Width = 512,
Height = 512
};
LineSeries line = new LineSeries()
{
Name = "Line",
Title = "test",
IndependentValuePath = "X",
DependentValuePath = "Y",
ItemsSource = data
};
chart_.Series.Add(line);
This code creates chart with sinusoid in it. Then Im trying to create bitmap from it.
LayoutRoot.Children.Add(chart_); // I tried to add chart_ to visual tree, It doesn't help
//creates bitmap
ScaleTransform t = new ScaleTransform() { ScaleX = 1.0, ScaleY = 1.0 };
//bitmap = new WriteableBitmap(chart_, t); Tried it also with this way
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, t);
texture = new Texture2D(GraphicsDeviceManager.Current.GraphicsDevice, bitmap.PixelWidth, bitmap.PixelHeight, false, SurfaceFormat.Color);
bitmap.CopyTo(texture);
All this code creates Empty Bitmap.But when I use TextBlock or some primitives like Ellipse, everything works. Im sure, that code generating chart is fine, cause chart is generated fine in Silverlight control.
EDIT:
I tried to create bitmap this way, but it dont help.
chart_.InvalidateMeasure();
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, null);
bitmap.Invalidate();
EDIT 2:
I don't want graph to be in visual tree. I just need generate image of it an than use it in XNA part of my application.
Just a couple of small changes, but I suspect the big change is manually adding a reference to System.Windows.Controls after using NuGet to add the Charting package.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Controls.DataVisualization.Charting;
using System.Windows.Media.Imaging;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
Chart chart_;
public MainPage()
{
InitializeComponent();
var data = new List<Point>(100);
for (int i = 0; i < 100; i++)
{
data.Add(new Point(i, Math.Sin(i * Math.PI / 50)));
}
chart_ = new Chart()
{
Name = "Chart",
Width = 512,
Height = 512
};
LineSeries line = new LineSeries()
{
Name = "Line",
Title = "test",
IndependentValuePath = "X",
DependentValuePath = "Y",
ItemsSource = data
};
chart_.Series.Add(line);
LayoutRoot.Children.Add(chart_); // I tried to add chart_ to visual tree, It doesn't help
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//creates bitmap
WriteableBitmap bitmap;
ScaleTransform t = new ScaleTransform() { ScaleX = 1.0, ScaleY = 1.0 };
//bitmap = new WriteableBitmap(chart_, t); Tried it also with this way
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, t);
//texture = new Texture2D(GraphicsDeviceManager.Current.GraphicsDevice, bitmap.PixelWidth, bitmap.PixelHeight, false, SurfaceFormat.Color);
//bitmap.CopyTo(texture);
image1.Source = bitmap;
}
}
}
And
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<Image Height="150" HorizontalAlignment="Left" Margin="97,109,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="200" />
</Grid>
</UserControl>
That should get you going...
Don't forget to delete all of the attempted workarounds - I don't think any of them are necessary. The only code I did not use from your example had to do with the textures - I didn't know what to do with that and it wasn't the problem you were having anyways...
David
I believe that by the time you are creating WriteableBitmap object & calling Render method, Chart has not be rendered yet. You can check this by moving these lines of code to some other event like Button_Click etc. Have a look at below codes as it is creating the WriteableBitmap and then passes it to image control as source....
XAML Code.....
<UserControl x:Class="TryBitmap.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="25" Width="100" Content="Capture" Click="Button_Click"/>
<Grid x:Name="grdGraphs" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="img" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Grid>
Code Behind....
public partial class MainPage : UserControl
{
private Chart chart_;
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var data = new List<Point>(100);
for (int i = 0; i < 100; i++)
{
data.Add(new Point(i, Math.Sin(i * Math.PI / 50)));
}
chart_ = new Chart()
{
Name = "Chart",
Width = 512,
Height = 512
};
LineSeries line = new LineSeries()
{
Name = "Line",
Title = "test",
IndependentValuePath = "X",
DependentValuePath = "Y",
ItemsSource = data
};
chart_.Series.Add(line);
chart_.SetValue(Grid.ColumnProperty, 0);
grdGraphs.Children.Add(chart_);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var bitmap = new WriteableBitmap((int)(chart_.RenderSize.Width), (int)(chart_.RenderSize.Height));
bitmap.Render(chart_, new MatrixTransform());
bitmap.Invalidate();
img.Source = bitmap;
}
}
I'm afraid this is only going to be half an answer. I can solve your immediate problem but I'm afraid you'll just end up with another one I haven't managed to solve.
Creating the bitmap after a call to Dispatcher.BeginInvoke should ensure that your bitmap isn't completely blank, i.e.:
// do stuff with chart_ ...
Dispatcher.BeginInvoke(() =>
{
bitmap = new WriteableBitmap(512, 512);
bitmap.Render(chart_, null);
bitmap.Invalidate();
// At this point, the bitmap shouldn't be blank.
});
However, the results of this likely to be less than satisfactory. I ran your code and I found that the LineSeries was missing from the chart image, although the rest of the chart was there. This remained true even after I set all Durations of the various animations in the ControlTemplate for the LineSeries to 0 and additionally set the following properties on the chart:
chart_.AnimationSequence = AnimationSequence.Simultaneous;
chart_.TransitionDuration = new TimeSpan(0);
I tried wrapping the WriteableBitmap operations in further calls to Dispatcher.BeginInvoke(), and after doing this the data points started to appear more clearly. However, I can't believe that an approach like this is the right solution to the problem.

WPF: Animating TranslateTransform from code

I have a WPF canvas on which I'm dynamically creating objects from code. These objects are being transformed by setting the RenderTransform property, and an animation needs to be applied one of those transforms. Currently, I can't get properties of any transform to animate (although no exception gets raised and the animation appears to run - the completed event gets raised).
In addition, if the animation system is stressed, sometimes the Storyboard.Completed event is never raised.
All the examples I've come accross animate the transforms from XAML. MSDN documentation suggests that the x:Name property of a transform must be set for it to be animatable, but I haven't found a working way to set it from code.
Any ideas?
Here's the full code listing that reproduces the problem:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace AnimationCompletedTest {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
Canvas panel;
public MainWindow() {
InitializeComponent();
MouseDown += DoDynamicAnimation;
Content = panel = new Canvas();
}
void DoDynamicAnimation(object sender, MouseButtonEventArgs args) {
for (int i = 0; i < 12; ++i) {
var e = new Ellipse {
Width = 16,
Height = 16,
Fill = SystemColors.HighlightBrush
};
Canvas.SetLeft(e, Mouse.GetPosition(this).X);
Canvas.SetTop(e, Mouse.GetPosition(this).Y);
var tg = new TransformGroup();
var translation = new TranslateTransform(30, 0);
tg.Children.Add(translation);
tg.Children.Add(new RotateTransform(i * 30));
e.RenderTransform = tg;
panel.Children.Add(e);
var s = new Storyboard();
Storyboard.SetTarget(s, translation);
Storyboard.SetTargetProperty(s, new PropertyPath(TranslateTransform.XProperty));
s.Children.Add(
new DoubleAnimation(3, 100, new Duration(new TimeSpan(0, 0, 0, 1, 0))) {
EasingFunction = new PowerEase {EasingMode = EasingMode.EaseOut}
});
s.Completed +=
(sndr, evtArgs) => {
Debug.WriteLine("Animation {0} completed {1}", s.GetHashCode(), Stopwatch.GetTimestamp());
panel.Children.Remove(e);
};
Debug.WriteLine("Animation {0} started {1}", s.GetHashCode(), Stopwatch.GetTimestamp());
s.Begin();
}
}
[STAThread]
public static void Main() {
var app = new Application();
app.Run(new MainWindow());
}
}
}
Leave out the Storyboard:
var T = new TranslateTransform(40, 0);
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 0));
DoubleAnimation anim = new DoubleAnimation(30, duration);
T.BeginAnimation(TranslateTransform.YProperty, anim);
(small fix for syntax)
Seems that after a bit of Googling I solved the problem myself. Many thanks to MSDN documentation and a post in MSDN forums by Antares19.
In summary:
For a Freezable object (like TranslateTransform) to be targetable by an Storyboard, it must have a registered name. This can be done by calling FrameworkElement.RegisterName(..).
I added the Storyboard object to the ResourceDictionary of the same Framework element to which I registered the TranslateTransform. This can be done by calling ResourceDictionary.Add(..)
Here's the updated code, which now animates nicely, and registers/deregisters the added resources:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace AnimationCompletedTest {
public partial class MainWindow : Window {
Canvas panel;
public MainWindow() {
InitializeComponent();
MouseDown += DoDynamicAnimation;
Content = panel = new Canvas();
}
void DoDynamicAnimation(object sender, MouseButtonEventArgs args) {
for (int i = 0; i < 12; ++i) {
var e = new Ellipse { Width = 16, Height = 16, Fill = SystemColors.HighlightBrush };
Canvas.SetLeft(e, Mouse.GetPosition(this).X);
Canvas.SetTop(e, Mouse.GetPosition(this).Y);
var tg = new TransformGroup();
var translation = new TranslateTransform(30, 0);
var translationName = "myTranslation" + translation.GetHashCode();
RegisterName(translationName, translation);
tg.Children.Add(translation);
tg.Children.Add(new RotateTransform(i * 30));
e.RenderTransform = tg;
panel.Children.Add(e);
var anim = new DoubleAnimation(3, 100, new Duration(new TimeSpan(0, 0, 0, 1, 0))) {
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseOut }
};
var s = new Storyboard();
Storyboard.SetTargetName(s, translationName);
Storyboard.SetTargetProperty(s, new PropertyPath(TranslateTransform.YProperty));
var storyboardName = "s" + s.GetHashCode();
Resources.Add(storyboardName, s);
s.Children.Add(anim);
s.Completed +=
(sndr, evtArgs) => {
panel.Children.Remove(e);
Resources.Remove(storyboardName);
UnregisterName(translationName);
};
s.Begin();
}
}
[STAThread]
public static void Main() {
var app = new Application();
app.Run(new MainWindow());
}
}
}
I have a Solution using XAML / C# Combo. In your XAML file define:
<UserControl.RenderTransform>
<TranslateTransform x:Name="panelTrans" Y="0"></TranslateTransform>
</UserControl.RenderTransform>
This will give you the ability to do the following in the C# code:
Storyboard.SetTargetName(mFlyInDA, "panelTrans");
Storyboard.SetTargetProperty(mFlyInDA, new PropertyPath("Y"));
The rest is business as usual. Create a DoubleAnimation, set its properties, add it as a child to your Storyboard, call the Begin function on the story board.
No need to register the transform or add the storyboard to the resource collection if the target is the FrameworkElement you want to animate. You can use Blend to generate the PropertyPath syntax.
frameworkElement.RenderTransform = new TransformGroup
{
Children =
{
new ScaleTransform(),
new SkewTransform(),
new RotateTransform(),
translate
}
};
translate.X = 100.0;
var animation = new DoubleAnimation(0.0, TimeSpan.FromSeconds(1));
var sb = new Storyboard();
sb.Children.Add(animation);
Storyboard.SetTarget(animation, frameworkElement);
Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"));
sb.Begin();

Categories

Resources