UI refresh looping a Dispatcher.BeginInvoke - c#

I'm trying to do a System.Windows.Shapes.Rectangle rotation in Y axis simulating a card rotation, showing all the route.
The problem is that the UI only refreshes at the end.
Simplified version of code
Call of method
for (i=0; i<=180; i++)
{
int j = i;
Dispatcher.BeginInvoke(new a_dispatcher(() => {
print_animation_of_card(card, i);
}),
null);
}
Method
private void print_animation_of_card(System.Windows.Shapes.Rectangle card)
{
...
System.Windows.Media.PlaneProjection p =
card.Projection as System.Windows.Media.PlaneProjection;
p.RotationY = i;
card.Projection = p;
}
I have also tried to put the FOR into the method, with same result...
How can I do to show all the rotation movement of the System.Windows.Shapes.Rectangle?

I believe the problem is that you are using BeginInvoke() instead of Invoke. This does not wait for one angle of rotation to complete before the next one is called, so the calls are all stacked on top of eachother.
Try it with Dispatcher.Invoke() or put the whole loop inside one Dispatcher.BeginInvoke() and see if the results are more to your satisfaction.

Finally I have done it with a BackgroundWorker, thanks #Jay for the answers.
Call of method
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (object sender, DoWorkEventArgs e) =>
{
print_animation_of_card(card);
};
bw.RunWorkerAsync();
Method
private void print_animation_of_card(System.Windows.Shapes.Rectangle card)
{
for (int i = 0; i <= 180; i++)
{
Thread.Sleep(3);
Dispatcher.BeginInvoke(new a_dispatcher(() =>
{
//same code as topic method code
}),null);
}
}

Related

C# - Animate a Winforms Control

I'm trying to develop some sort of "animation" for a control in winforms that will run in a new thread than the main one.
So the code I used for the animation is the one I leave you below (a label control that scrolls up pixel by pixel every few seconds until it reaches 0 pixels):
private void LabelAnimation(int amount)
{
this.Invoke((MethodInvoker)delegate
{
int currentX = Label.Location.X;
Label.Text = amount.ToString();
for (int h = 1; h < 7; h++)
{
int subtractHeight = h;
int currentY = Label.Location.Y;
Label.Location = new Point(currentX, (currentY - subtractHeight));
Thread.Sleep(200);
}
});
}
And the method in which the new thread is created:
private void ExecuteAnimation()
{
Thread t = new Thread(() => LabelAnimation(100));
t.Start();
}
The problem is that in itself it works but on a graphic level it sucks, I mean, instead of moving the entire control, the text string remained in the same position while the rectangle of the label moved in the indicated direction, covering its own string.
Use System.Timers.Timer.
It`s recommended to use a timer instead of sleeping the thread.
Here is one way to achieve this:
private System.Timers.Timer timer;
private int amount = 100;
private void ExecuteAnimation()
{
new Thread(() =>
{
// Set DoubleBuffered to true for smoother animation
this.DoubleBuffered = true;
if (timer == null)
{
timer = new System.Timers.Timer();
timer.Interval = 100;
timer.Elapsed += timer_tick;
}
timer.Start();
}).Start();
}
private void timer_tick(object sender, EventArgs e)
{
this.Invoke(new Action(() =>
{
int currentX = label1.Location.X;
label1.Text = amount.ToString();
if (label1.Location.Y > 0)
{
label1.Location = new Point(currentX, label1.Location.Y - 1);
}
}));
}
OUTPUT:
In addition to Jonathans fine answer, you might also be interested in this "Component Animator":
https://www.codeproject.com/Articles/548769/Animator-for-WinForms
If it could be useful to someone else, I finally solved the problem by mixing the #Jonathan Applebaum code and mine, like so:
private void LabelAnimation(int amount, int moveUp)
{
int currentX = 0;
this.Invoke(new Action(() =>
{
currentX = Label.Location.X;
Label.Text = amount.ToString();
}));
for (int h = 1; h < moveUp; h++)
{
this.Invoke(new Action(() =>
{
int currentY = Label.Location.Y;
Label.Location = new Point(currentX, (currentY - h));
}));
Thread.Sleep(150);
}
}
The thread execution always remains the same:
private void ExecuteAnimation()
{
Thread t = new Thread(() => LabelAnimation(100, 7));
t.Start();
}
In this way I make only the gets or sets related to the Label control run on the main thread while everything else, including the Thread.Sleep(150), in the secondary thread so as to avoid the Form getting stuck. In this way everything runs smoothly for me.

Blank components in a Form while updating it from a thread

I have made a Form that moves across the screen to the left, but all of the components on the Form are blank.
I put the code for the movement in comments and everything was fine, so the problem is in my movement code, but I don't know what the problem is.
System.Threading.Thread thread;
private void Form1_Load(object sender, EventArgs e)
{
thread = new System.Threading.Thread(loop);
thread.Start();
}
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This should make the form move to the left, however the components on the Form are blank.
Let's look at the loop() method:
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This code immediately invokes a delegate back on the main UI thread. The delegate runs a while(true) loop that never exits. As soon as the loop begins to execute, the UI thread is completely hosed with no hope of ever responding to other event messages, including paint events.
Try this instead:
void loop()
{
int x = 0;
int MoveRate = 1;
while(true)
{
x += MoveRate;
this.BeginInvoke((Action)delegate () { this.Location = new Point(x, 150); });
System.Threading.Thread.Sleep(16);
}
}
It's all the same code (except for the stuff that wasn't doing anything), but now arranged so the delegate is invoked inside the loop. The UI thread is only blocked for a brief time, and then control returns back to the loop thread, which will Sleep for a little while before bothering the UI again. The invoke even simple enough I was able to rewrite it as single line.
Notice I also increased the sleep time, because that still gives you 60 frames per second.
An Async variation of the same process, to test something different.
(The main reason why your thread wasn't working as expected has already been explained. If you use a thread and then invoke the UI thread in a close loop, it's more or less like not having your code run in a different thread at all: a Form doesn't have time to update itself or its controls).
This method add a termination to the scrolling procedure, when the Form is scrolled outside the current Screen bounds. When this condition is met, the while loop is exited and the Task ends, moving the Form in the center of the screen.
The Task is started in the Shown event. I think it's more appropriate than the Load event (the Form is ready to be presented, here).
Note that neither this Task or the Thread add any check on the Form.FormClosing event, to cancel the asynchronous proc: if the Form is closed while the scrolling is performed, you will most likely have a exception (the Form has been disposed, thus no more handle).
private async Task Scroller(int ScreenWidth)
{
int x = 0;
int MoveRate = 2;
while (true)
{
x += MoveRate;
this.BeginInvoke(new MethodInvoker(() => { this.Location = new Point(x, 150);}));
await Task.Delay(10);
if (x > ScreenWidth) break;
};
}
private async void Form_Shown(object sender, EventArgs e)
{
int ScreenWidth = Screen.FromHandle(this.Handle).Bounds.Width;
await this.Scroller(ScreenWidth);
this.Location = new Point((ScreenWidth - this.Width) / 2 , 150);
}

How to create and add controls in run time with BackgroundWorker

I don't know how to implement a method with a separate thread using the BackgroundWorker in WinForms.
I want this method (after every click on a button) to perform:
create ProgressBar (each new one under the previous one)
create Bitmap and BackgroundWorker
set color of every pixel in that Bitmap in the separate thread using BackgroundWorker
display a precentage progress on the ProgressBar
after completing: create a new form with bitmap on the background
after completing: remove the ProgressBar
My code:
List<BackgroundWorker> Workers;
List<ProgressBar> Progress;
int OperationsCount = 0;
private void ShowProgress(int n, int percent)
{
Progress[n].Value = percent;
}
private void Blend(object sender, DoWorkEventArgs e)
{
Bitmap BlendedImage = ... // creates a bitmap
for (int i = 0; i < BlendedImage.Width; i++)
{
for (int j = 0; j < BlendedImage.Height; j++)
{
... //changing colour of every pixel
}
this.Invoke(new Action(()=>ShowProgress((int)e.Argument, (int)(100 * (double)(i/BlendedImage.Width)))));
}
Form BlendedImage_Form = new Form();
BlendedImage_Form.Size = new Size(BlendedImage.Width, BlendedImage.Height);
BlendedImage_Form.BackgroundImage = BlendedImage;
BlendedImage_Form.BackgroundImageLayout = ImageLayout.Stretch;
this.Invoke(new Action(() => BlendedImage_Form.Show()));
}
private void PerformBlending_Button_Click(object sender, EventArgs e)
{
int n = OperationsCount++;
Progress.Add(new ProgressBar());
Progress[n].Size = ...
Progress[n].Location = ...
Progress[n].Maximum = 100;
this.Controls.Add(Progress[n]);
Workers.Add(new BackgroundWorker());
Workers[n].DoWork += new DoWorkEventHandler(Blend);
Workers[n].RunWorkerCompleted += (object _sender, RunWorkerCompletedEventArgs _e) =>
{
//OperationsCount--;
//Progress[n].Dispose();
//this.Controls.Remove(Progress[n]);
//Progress.RemoveAt(n);
};
Workers[n].RunWorkerAsync(n);
}
When I click the button only once then everything seems to be good but when I click the button two times then program:
creates the first ProgressBar which shows progress correctly and the new form and bitmap are displayed also correctly
creates the second ProgressBar but it doesn't show the progress at all and no form and no bitmap is displayed.
PS I'd rather use BackgroundWorker than other tools.
As per your comment here is the solution
public void DoSomething()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(()=> DoSomething()));
}
else
{
// update the ui from here, no worries
}
}
In this code, I am modifying the object on main thread. If the calls made from non-UI thread this will goes in InvokeRequired.
// From this code you given in comment
https://pastebin.com/45jQXCt9
Please try with making instance inside invoke. It should work.

Updating WPF GUI Every 2 Seconds (C#)

I'm doing an 8 Puzzle solver that ultimately stores each node (int[] of elements 0-8) in the path to put the blocks in order in a stack. I have a WPF GUI that displays an int[,]
foreach (var node in stack)
{
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
Thread.Sleep(500);
}
The GUI displays the initial board, and then after I click solve, the final (in order) board is displayed correctly. What I want to do is display each node on the board for some amount of time, ultimately arriving at the in-order board. With Thread.Sleep, the GUI will simply pause for the set amount of time before displaying the final node. Any ideas as to why it this code wouldn't display the board at each node every 500ms?
For reference, here's an example output from Console.Write for the nodes:
4,2,3,6,1,0,7,5,8
4,2,0,6,1,3,7,5,8
4,0,2,6,1,3,7,5,8
4,1,2,6,0,3,7,5,8
4,1,2,0,6,3,7,5,8
0,1,2,4,6,3,7,5,8
1,0,2,4,6,3,7,5,8
1,2,0,4,6,3,7,5,8
1,2,3,4,6,0,7,5,8
1,2,3,4,0,6,7,5,8
1,2,3,4,5,6,7,0,8
1,2,3,4,5,6,7,8,0
Edit:
Since my original answer was downvoted for using a Thread instead of a Timer, here is an example using a timer.
The code for using a Thread was just shorter and I wanted to give him a solution quickly.
Also, using a Thread instead of a timer meant he didn't need to pass parameters differently or restructure his loop.
This is why it is a good idea to discuss pros/cons of alternate solutions instead of simply insisting that there is only one right way.
Use the timer_Tick function to update the position.
You might notice that this complicates the original code since you will have to pass parameters differently and restructure your loop.
public partial class Form1 : Form
{
private Point pos = new Point(1,1);
private float[] vel = new float[2];
private Size bounds = new Size(20,20);
private Timer ticky = new Timer(); //System.Windows.Forms.Timer
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ticky.Interval = 20;
ticky.Tick += ticky_Tick;
vel[0] = 4; vel[1] = 0;
ticky.Start();
}
void ticky_Tick(object sender, EventArgs e)
{
updatePosition();
//This tells our form to repaint itself (and call the OnPaint method)
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(Color.LightBlue), new Rectangle(pos, bounds));
}
private void updatePosition()
{
pos = new Point(pos.X + (int)vel[0], pos.Y + (int)vel[1]);
vel[1] += .5f; //Apply some gravity
if (pos.X + bounds.Width > this.ClientSize.Width)
{
vel[0] *= -1;
pos.X = this.ClientSize.Width - bounds.Width;
}
else if (pos.X < 0)
{
vel[0] *= -1;
pos.X = 0;
}
if (pos.Y + bounds.Height > this.ClientSize.Height)
{
vel[1] *= -.90f; //Lose some velocity when bouncing off the ground
pos.Y = this.ClientSize.Height - bounds.Height;
}
else if (pos.Y < 0)
{
vel[1] *= -1;
pos.Y = 0;
}
}
}
Results:
You can use timers to do all sorts of delayed form drawing:
Original Solution:
//Create a separate thread so that the GUI thread doesn't sleep through updates:
using System.Threading;
new Thread(() => {
foreach (var node in stack)
{
//The invoke only needs to be used when updating GUI Elements
this.Invoke((MethodInvoker)delegate() {
//Everything inside of this Invoke runs on the GUI Thread
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
});
Thread.Sleep(500);
}
}).Start();
Solution in 2022:
await Task.Delay(500);
Things really are better these days.

The calling thread cannot access this object because a different thread owns it

So I am making a simple brick breaking game in c#/wpf. I am running into an issue using timers, I feel like it is probably a simple fix but here is whats happening. Whenever t_Elapsed is fired it attempts to call Update() but when it does its like OMG Im not in the right thread so I cant do that sir. How do I invoke the method from the Game from the proper thread? (And yes I know the code is ugly and has magic numbers but I just kinda chugged it out without putting a lot of effort in. And yes I have zero experience programming games)
public partial class Game : Grid
{
public bool running;
public Paddle p;
public Ball b;
Timer t;
public Game()
{
Width = 500;
Height = 400;
t = new Timer(20);
p = new Paddle();
b = new Ball();
for (int i = 15; i < 300; i += 15)
{
for (int j = 15; j < 455; j += 30)
{
Brick br = new Brick();
br.Margin = new Thickness(j, i, j + 30, i + 15);
Children.Add(br);
}
}
Children.Add(p);
Children.Add(b);
p.Focus();
t.AutoReset = true;
t.Start();
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (running)
{
Update();
}
}
void Update()
{
b.Update(); //Error here when Update is called from t_Elapsed event
}
void Begin()
{
running = true;
b.Initiate();
}
}
You should use the DispatcherTimer object instead, it will ensure that the timer events are published to the correct thread.
Timer elapsed events fire on a thread from the thread pool (http://www.albahari.com/threading/part3.aspx#_Timers) and not on the UI thread. Your best approach is to invoke the control's dispatcher through a call like this:
yourControl.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal
, new System.Windows.Threading.DispatcherOperationCallback(delegate
{
// update your control here
return null;
}), null);
The calling thread cannot access this object because a different thread owns it
this.Dispatcher.Invoke((Action)(() =>
{
...// your code here.
}));

Categories