Why does the WPF storyboard stops after a few cycles? - c#

I have a canvas where I draw rectangles and move them randomly with the help of a storyboard. After a few cycles the storyboard.completed event does not fire any more. Does anybody know? here is my xaml:
<Grid>
<Canvas Name="movingCanvas" Background="Green" Margin="0,29,0,0"></Canvas>
<TextBlock Height="23" Name="textBlock1" Text="TextBlock" Margin="528,0,0,538" />
</Grid>
And the code:
private Random random = new Random();
private Storyboard gameLoop = new Storyboard();
private int i = 0;
public Window3()
{
InitializeComponent();
this.gameLoop.Duration = TimeSpan.FromMilliseconds(100);
this.gameLoop.Completed += new EventHandler(this.gameLoop_Completed);
this.gameLoop.Begin();
}
private void gameLoop_Completed(object sender, EventArgs e)
{
this.addRectangle();
this.moveRectangle();
i++;
this.textBlock1.Text = i.ToString();
this.gameLoop.Begin();
}
private void addRectangle()
{
Rectangle rect = new Rectangle();
rect.Height = 100;
rect.Width = 100;
rect.Stroke = new SolidColorBrush(Colors.Black);
Canvas.SetLeft(rect, random.Next((int)this.Width));
Canvas.SetTop(rect, random.Next((int)this.Height));
this.movingCanvas.Children.Add(rect);
}
private void moveRectangle()
{
foreach (UIElement elm in this.movingCanvas.Children)
{
int moveLeft = random.Next(10);
int distance = random.Next(-10, 20);
if (moveLeft > 5)
{
Canvas.SetTop(elm, Canvas.GetTop(elm) + distance);
}
else
{
Canvas.SetLeft(elm, Canvas.GetLeft(elm) + distance);
}
}
}

The Completed event does also not occur whitout creating and moving rectangles:
private Storyboard gameLoop = new Storyboard();
private int i = 0;
public Window3()
{
InitializeComponent();
this.gameLoop.Duration = TimeSpan.FromMilliseconds(100);
this.gameLoop.Completed += new EventHandler(this.gameLoop_Completed);
this.gameLoop.Begin();
}
private void gameLoop_Completed(object sender, EventArgs e)
{
i++;
this.textBlock1.Text = i.ToString();
this.gameLoop.Begin();
}
If you add a animation to the storyboard, the storyboard not stops firing the event.
public Window3()
{
InitializeComponent();
this.gameLoop.Duration = TimeSpan.FromMilliseconds(100);
this.gameLoop.Completed += new EventHandler(this.gameLoop_Completed);
DoubleAnimation animation= new DoubleAnimation { From = 100, To = 101 };
ani.SetValue(Storyboard.TargetProperty, this);
ani.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
this.gameLoop.Children.Add(ani);
this.gameLoop.Begin();
}
Like Kshitij Mehta said above, i think use a timer instead the storyboard, but maybe you have a reason to use a storyboard....

Do you really mean to add a new rectangle on every iteration of the loop?
You'll very quickly get tens of thousands if not millions of rectangles which will take longer and longer to draw.

Related

UI block when I use a timer

I try to move a grid in my window with an animation, but the animation doesn't happen, so I put a timer so that you can see the animation little by little but it still dont work :/
Here is my code:
class Animation
{
private int x_dropGridToBottom;
private Grid grid_dropGridToBottom;
private System.Timers.Timer t;
public Animation()
{
t = new System.Timers.Timer();
t.Interval = 500;
}
private void TimerElapsed_DropGridToBottom(object sender, ElapsedEventArgs e)
{
while (x_dropGridToBottom > 0)
{
x_dropGridToBottom--;
Application.Current.Dispatcher.Invoke(() =>
{
grid_dropGridToBottom.Margin = new Thickness(0, x_dropGridToBottom, 0, x_dropGridToBottom);
});
}
if (x_dropGridToBottom == 0)
t.Stop();
}
internal void DropGridToBottom(Grid grid, Window window)
{
grid.Margin = new Thickness(0, window.Height, 0, window.Height);
// 0 x 0 x (x = window.height)
// x = -y (y=412)
// x (=) 0
grid_dropGridToBottom = grid;
x_dropGridToBottom = Convert.ToInt32(window.Height);
t.Elapsed += new ElapsedEventHandler(TimerElapsed_DropGridToBottom);
t.Start();
}
}
And here is the code when I press the button that is supposed to start the animation :
private void Button_Click(object sender, RoutedEventArgs e)
{
Animation anim = new Animation();
anim.DropGridToBottom(grid_2, this);
}
Somebody has an idea of why and how I can make my grid move little by little but not directly in one go (as it does nowadays).
Thank you

WPF draw border in container

I want to draw area inside a container. For this i thought about using custom control.
My problem is that Canvas does not take up all the given space it gets. How to force it to use all available space?
What i have done is inherited from Canvas and created border element inside it:
public class DrawableCanvas : Canvas
{
private Border border;
static DrawableCanvas()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawableCanvas), new FrameworkPropertyMetadata(typeof(DrawableCanvas)));
}
public DrawableCanvas()
{
this.border = new Border();
border.Background = new SolidColorBrush(Colors.Blue);
border.BorderThickness = new Thickness(1);
border.Width = 0;
border.Visibility = Visibility.Hidden;
border.Opacity = 0.3;
this.Children.Add(border);
this.MouseLeftButtonDown += DrawableCanvas_MouseLeftButtonDown;
this.MouseLeftButtonUp += DrawableCanvas_MouseLeftButtonUp;
this.MouseMove += DrawableCanvas_MouseMove;
}
private void DrawableCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Left mouse up");
// release mouse
Mouse.Capture(null);
border.Width = 0;
border.Height = 100;
startPosition = 0;
}
double startPosition = 0;
private void DrawableCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Left mouse down");
border.Visibility = Visibility.Visible;
// capture mouse
Mouse.Capture(this);
var point = e.GetPosition(this);
startPosition = point.X;
SetLeft(border, point.X);
}
private void DrawableCanvas_MouseMove(object sender, MouseEventArgs e)
{
if(this.IsMouseCaptured)
{
var point = e.GetPosition(this);
Debug.WriteLine("Mouse move");
// set the position to far left
SetLeft(border, Math.Min(startPosition, point.X));
// width is the difference between two points
border.Width = Math.Abs(startPosition - point.X);
Debug.WriteLine(Math.Min(startPosition, point.X));
Debug.WriteLine(border.Width);
}
}
}
And for view:
<DockPanel>
<local:DrawableCanvas>
<Rectangle Height="500" Width="500" Fill="Transparent" />
</local:DrawableCanvas>
</DockPanel>
I want something like this:
What I wanted to do is to create this control to enable selection on ui.
For this i needed a border/rectangle that will span only in width and use up all the height it gets
I ended up with this:
View:
<Window x:Class="Wpf.Test01.Stack_43281567"
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:Wpf.Test01.Controls"
mc:Ignorable="d"
Title="Stack_43281567" Height="300" Width="300">
<Grid>
<local:DrawableCanvas />
</Grid>
</Window>
DrawableCanvas.cs:
public class DrawableCanvas : Canvas
{
private Border border;
static DrawableCanvas()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawableCanvas), new FrameworkPropertyMetadata(typeof(DrawableCanvas)));
}
// when selection is done expose event to notify the user of selection change
public DrawableCanvas()
{
this.border = new Border();
border.Background = new SolidColorBrush(Colors.Blue);
border.BorderThickness = new Thickness(1);
border.Visibility = Visibility.Hidden;
border.Opacity = 0.3;
border.Height = this.ActualHeight;
this.Children.Add(border);
this.Background = new SolidColorBrush(Colors.Transparent);
this.MouseLeftButtonDown += DrawableCanvas_MouseLeftButtonDown;
this.MouseLeftButtonUp += DrawableCanvas_MouseLeftButtonUp;
this.MouseMove += DrawableCanvas_MouseMove;
this.SizeChanged += DrawableCanvas_SizeChanged;
}
private void DrawableCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
border.Height = e.NewSize.Height;
}
private void DrawableCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Left mouse up");
// release mouse
Mouse.Capture(null);
border.Width = 0;
startPosition = 0;
}
double startPosition = 0;
private void DrawableCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Left mouse down");
border.Visibility = Visibility.Visible;
// capture mouse
Mouse.Capture(this);
var point = e.GetPosition(this);
startPosition = point.X;
SetLeft(border, point.X);
}
private void DrawableCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (this.IsMouseCaptured)
{
var point = e.GetPosition(this);
Debug.WriteLine("Mouse move");
// set the position to far left
SetLeft(border, Math.Min(startPosition, point.X));
// width is the difference between two points
border.Width = Math.Abs(startPosition - point.X);
Debug.WriteLine(Math.Min(startPosition, point.X));
Debug.WriteLine(border.Width);
}
}
}

UWP app .cs file inheritance static variable from xaml.cs

I have created a PlayPage.xaml, PlayPage.xaml.cs and Game.cs file.
The PlayPage.xaml.cs has two variable, windowWidth and windowHeight.
I would like access two public static variable from Game.cs.
Game.cs:
namespace UwpApp
{
class Game
{
static Rectangle rectangle;
PlayPage pg = new PlayPage();
//Create a rectangle
public Rectangle draw()
{
rectangle = new Rectangle();
rectangle.Width = 70;
rectangle.Height = 70;
rectangle.Fill = new SolidColorBrush(Colors.Green);
Canvas.SetLeft(rectangle, randPos());
Canvas.SetTop(rectangle, randPos());
return rectangle;
}
//Create a random X and Y position
private Int32 randPos()
{
Random rnd = new Random();
Debug.WriteLine(pg.windowWidth);
return rnd.Next(0 , (int)pg.windowWidth);
}
}
}
PlayPage.xaml.cs:
namespace UwpApp
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public partial class PlayPage : Page
{
DispatcherTimer timer;
Rectangle rect;
bool isTapped;
public static double windowWidth, windowHeight;
Game game = new Game();
public PlayPage()
{
this.InitializeComponent();
startCounter.Visibility = Visibility.Collapsed;
isTapped = false;
}
private void Load_Variable(object sender, RoutedEventArgs e)
{
windowWidth = canvas.ActualWidth;
windowHeight = canvas.ActualHeight;
}
//Counter animation. (Number opacity, fall)
private void counterAnimation()
{
startCounter.Visibility = Visibility.Visible;
//Set Counter Horizontal Align
double left = (canvas.ActualWidth - startCounter.ActualWidth) / 2;
Canvas.SetLeft(startCounter, left);
Storyboard storyboard = new Storyboard();
DoubleAnimation opacityAnim = new DoubleAnimation();
DoubleAnimation fallAnim = new DoubleAnimation();
opacityAnim.From = 1;
opacityAnim.To = 0;
opacityAnim.Duration = new Duration(TimeSpan.FromSeconds(1));
opacityAnim.AutoReverse = false;
opacityAnim.RepeatBehavior = new RepeatBehavior(3);
Storyboard.SetTarget(opacityAnim, this.startCounter);
Storyboard.SetTargetProperty(opacityAnim, "(UIElement.Opacity)");
fallAnim.From = -115;
fallAnim.To = canvas.ActualHeight / 2;
fallAnim.Duration = new Duration(TimeSpan.FromSeconds(1));
fallAnim.AutoReverse = false;
fallAnim.RepeatBehavior = new RepeatBehavior(3);
Storyboard.SetTarget(fallAnim, this.startCounter);
Storyboard.SetTargetProperty(fallAnim, "(Canvas.Top)");
storyboard.Children.Add(opacityAnim);
storyboard.Children.Add(fallAnim);
storyboard.Begin();
}
//Countdown Timer
private void countDown()
{
timer = new DispatcherTimer();
timer.Tick += startCounter_CountDown;
timer.Interval = new TimeSpan(0, 0, 1);
timer.Start();
}
//Change Countdown value
private void startCounter_CountDown(object sender, object e)
{
int counterNum = int.Parse(startCounter.Text);
counterNum -= 1;
startCounter.Text = counterNum.ToString();
if (counterNum == 1)
{
timer.Stop();
StartedGame();
}
}
//Tap or Click to start the game
private void TapToStart(object sender, TappedRoutedEventArgs e)
{
if(!isTapped)
{
isTapped = true;
counterAnimation();
countDown();
this.TapToStartText.Visibility = Visibility.Collapsed;
}
}
//Create rectangles
private void StartedGame()
{
rect = game.draw();
canvas.Children.Add(game.draw());
Debug.WriteLine(windowWidth.ToString());
}
}
}
One more thing: I get an error this line: PlayPage pg = new PlayPage();
Error (pic)
The error in your picture is due to infinite recursion, as it says. The constructor for Game instantiates a new PlayPage. The constructor for PlayPage instantiates a new Game. Which instantiates a new PlayPage. Which instantiates a new Game. and on and on.
Static members are accessed by class name, not by instance. Like so:
PlayPage.windowWidth

C# one timer for multiple shapes

For a school project I need to make a small game where multiple Ellipses move.
The last one that gets made with my method to make multiple at the same time moves with the timer I make.
How do you make one timer for all the Ellipses.
class EnemyTeam
{
private Ellipse enemy;
private Canvas canvas;
private double xChange = 50, yChange = 50;
public DispatcherTimer enemyTimer;
private char direction = '0';
private Thickness enemyThickness;
public EnemyTeam(Canvas canvas, double startPosition, SolidColorBrush playerBrush)
{
this.canvas = canvas;
DrawTeam(canvas, 40, playerBrush);
enemyTimer.Interval = TimeSpan.FromMilliseconds(100);
enemyTimer.Start();
}
private void DrawBall(SolidColorBrush brush, Canvas canvas,double x,double y)
{
enemy = new Ellipse();
enemy.Stroke = brush;
enemy.Fill = brush;
enemy.Height = 30;
enemy.Width = 30;
enemy.Margin = new Thickness(x,y, 0, 0);
enemyTimer = new DispatcherTimer();
enemyThickness = enemy.Margin;
canvas.Children.Add(enemy);
enemyTimer.Tick += enemyTimer_Tick;
}
void enemyTimer_Tick(object sender, EventArgs e)
{
if (enemyThickness.Left >= canvas.Width - 100)
{
GoDown();
direction = '1';
}
if (enemyThickness.Left <= 0 + 20)
{
GoDown();
direction = '0';
}
MoveTeam(enemy);
}
private void MoveTeam(Ellipse enemy)
{
enemyThickness = enemy.Margin;
if (direction == '1')
{
enemyThickness.Left -= xChange;
}
if (direction == '0')
{
enemyThickness.Left += xChange;
}
enemy.Margin = enemyThickness;
}
private void GoDown()
{
enemyThickness.Top += yChange;
enemy.Margin = enemyThickness;
}
}
Instead of initializing and assigning event handler in DrawBall method, do that in constructor of EnemyTeam class. This is will give you on timer per EnemyTeam object.
Declare enemyTimer as a static field:
class EnemyTeam
{
private static enemyTimer = new DispatcherTimer();
...
The keyword static will make the field shared for the class.
You're making multiple timers and throwing them away. See this line:
enemyTimer = new DispatcherTimer();
Every time you call that, you're making a new timer and throwing away the previous copy that enemyTimer held a reference to. Because enemyTimer.Start() is called after DrawTeam, it's called only on the last-created timer. None of the other timers get started.
But even if the other timers got started, you'd still only see one Ellipse move, because in enemyTimer_Tick you only ever make changes to enemy, which is a class member variable that points to the last Ellipse created.
I would suggest that you only use one timer, that you save all the Ellipses you create in a list for later use, and that in enemyTimer_Tick you update all of those Ellipses by iterating through the list.
EDIT: Here is a copy of your code, reworked a bit to show you what I mean. I don't really understand what you're trying to do with MoveTeam and the enemyThickness variable, so I didn't mess with that stuff. That is to say, this isn't a complete working solution, just an example of the changes I'm suggesting.
using System.Collections.Generic;
class EnemyTeam
{
private List<Ellipse> enemies = new List<Ellipse>();
private Canvas canvas;
private double xChange = 50, yChange = 50;
public DispatcherTimer enemyTimer;
private char direction = '0';
private Thickness enemyThickness;
public EnemyTeam(Canvas canvas, double startPosition, SolidColorBrush playerBrush)
{
this.canvas = canvas;
DrawTeam(canvas, 40, playerBrush);
enemyTimer = new DispatcherTimer();
enemyTimer.Interval = TimeSpan.FromMilliseconds(100);
enemyTimer.Tick += enemyTimer_Tick;
enemyTimer.Start();
}
private void DrawBall(SolidColorBrush brush, Canvas canvas,double x,double y)
{
enemy = new Ellipse();
enemy.Stroke = brush;
enemy.Fill = brush;
enemy.Height = 30;
enemy.Width = 30;
enemy.Margin = new Thickness(x,y, 0, 0);
enemyThickness = enemy.Margin; // what is this supposed to do?
canvas.Children.Add(enemy);
enemies.Add(enemy);
}
void enemyTimer_Tick(object sender, EventArgs e)
{
foreach (Ellipse enemy in enemies)
{
if (enemyThickness.Left >= canvas.Width - 100)
{
GoDown();
direction = '1';
}
if (enemyThickness.Left <= 0 + 20)
{
GoDown();
direction = '0';
}
MoveTeam(enemy);
}
}
private void MoveTeam(Ellipse enemy)
{
enemyThickness = enemy.Margin;
if (direction == '1')
{
enemyThickness.Left -= xChange;
}
if (direction == '0')
{
enemyThickness.Left += xChange;
}
enemy.Margin = enemyThickness;
}
private void GoDown()
{
enemyThickness.Top += yChange;
enemy.Margin = enemyThickness;
}
}

System.InvalidOperationException in C# WPF

I want to move a rectangle in WPF application using the following code. However, I am getting the following error:
System.InvalidOperationException: Cannot use a DependencyObject that belongs to a different thread
I looked at other problems in stackoverflow but nothing worked.
public partial class MainWindow : Window
{
private Rectangle rect;
int count = 1;
Timer timer;
public MainWindow()
{
InitializeComponent();
Rectangle movedRectangle = new Rectangle();
movedRectangle.Width = 200;
movedRectangle.Height = 50;
movedRectangle.Fill = Brushes.Blue;
movedRectangle.Opacity = 0.5;
TranslateTransform translateTransform1 = new TranslateTransform(50, 20);
movedRectangle.RenderTransform = translateTransform1;
this.can.Children.Add(movedRectangle);
this.rect = movedRectangle;
timer = new Timer(500);
timer.Elapsed += OnTimedEvent;
timer.Enabled = true;
}
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
count++;
TranslateTransform translateTransform1 = new TranslateTransform(50 + count * 2, 20);
this.rect.Dispatcher.Invoke(new Action(()=>
rect.RenderTransform = translateTransform1));
//this.can.UpdateLayout();
this.can.Dispatcher.Invoke(new Action(()=>
this.can.UpdateLayout()
));
}
I would suggest you to use DispatcherTimer than a normal timer.
Please see the below solution. enjoy.
Note: for DispatcherTimer you will need to add the assembly reference for System.Windows.Threading
public partial class MainWindow : Window
{
private Rectangle rect;
int count = 1;
private DispatcherTimer timer = null;
public MainWindow()
{
InitializeComponent();
Rectangle movedRectangle = new Rectangle();
movedRectangle.Width = 200;
movedRectangle.Height = 50;
movedRectangle.Fill = Brushes.Blue;
movedRectangle.Opacity = 0.5;
TranslateTransform translateTransform1 = new TranslateTransform(50, 20);
movedRectangle.RenderTransform = translateTransform1;
this.can.Children.Add(movedRectangle);
this.rect = movedRectangle;
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
timer.Tick += timer_Tick;
timer.Start();
timer.IsEnabled = true;
}
void timer_Tick(object sender, EventArgs e)
{
count++;
TranslateTransform translateTransform1 = new TranslateTransform(50 + count * 2, 20);
Dispatcher.BeginInvoke(new Action<TranslateTransform>(delegate(TranslateTransform t1)
{
rect.RenderTransform = t1;
this.can.UpdateLayout();
}), System.Windows.Threading.DispatcherPriority.Render, translateTransform1);
}
}
You are constructing a TranslateTransform (which is a DependencyObject) outside the UI thread. Easy fix:
this.rect.Dispatcher.Invoke(new Action(
() =>
{
TranslateTransform translateTransform1 = new TranslateTransform(50 + count * 2, 20);
rect.RenderTransform = translateTransform1;
}));
Arguably a better fix: use a DispatcherTimer instead and get rid of all your Dispatcher.Invoke calls.

Categories

Resources