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.
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.
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.
I have a class project that uses Windows Forms to create a GUI that controls a second form. The second form is a DrawingForm with a bitmap. Using a backgroundworker, I am drawing random, continuous Bezier curves all over the bitmap. It is a simple program, so it is able to draw them quickly, hundreds per second. I would like to add a slider bar that would allow me to control how fast the lines paint. In other words, I dont want to set up each curve to be drawn on a timer, which would cause it to appear to stop and start hundreds of times a second. I have exhausted myself searching google, any tips on how to do this would be awesome. Thanks!
Edit: Here is a code snippet. This code is in my class for my drawing form. Its constructor is called from my main GUI/user control class.
// this is the code executed by the background thread
// it can run continously without hanging the user interface thread
// except that it draws to a bitmap (with the bMapDC) instead of to the form
private void backgroundWorkerDrawing_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100000000; i++)
{
if (scribbleOn == true)
{
curveColor = changeColor(curveColor);
Pen pen = new Pen(curveColor, penThickness);
if (i == 0) // initial curve should start in center, the rest of the points will be random
{
lastX = GUI.rand.Next(0, bMapWidth); //used to store the x coordinate where the curve ends
lastY = GUI.rand.Next(0, bMapHeight); //used to store the y coordinate where the curve ends
bMapDC.DrawBezier(pen, initialX, initialY, GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight),
GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight), lastX, lastY);
}
if (i > 0) // used for all curves after the first one.
{
int tempX = GUI.rand.Next(0, bMapWidth); //used to store the x coordinate where the curve ends
int tempY = GUI.rand.Next(0, bMapHeight); //used to store the y coordinate where the curve ends
bMapDC.DrawBezier(pen, lastX, lastY, GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight),
GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight), tempX, tempY);
lastX = tempX; // sets the last x coordinate of the last curve for next loop
lastY = tempY; // sets the last y coordinate of the last curve for next loop
}
pen.Dispose(); // free up resources from the pen object
}
else i = 0;
}
}
// timer event handler causes the form to be repreatedly invalidated
// This causes the paint event handler to keep going off,
// which causes the bMap that is continously being drawn to
// by the background thread to be continously redisplayed in the form.
// We will see other ways to do this that may be better.
private void timerInvalidate_Tick(object sender, EventArgs e)
{
Invalidate();
}
private void DrawingForm_Shown(object sender, EventArgs e)
{
lock (bMap)
{
bMapHeight = bMap.Height; // set the vars that keep track of the size of the bMap
bMapWidth = bMap.Width;
initialX = bMapWidth / 2; // start the curve at the center of the bMap
initialY = bMapHeight / 2;
bMapDC = Graphics.FromImage(bMap); // setup the DC (device context) to allow drawing to the bMap)
backgroundWorkerDrawing.RunWorkerAsync(); // start the background thread
timerInvalidate.Enabled = true; // start the timer that will cause periodic Invalidates
}
}
You can make thread and use sleep
private Thread SimulaciaArts;
public Animation(){
public SpleepValue { get; set;}
SimulaciaArts = new Thread(new ThreadStart(simuluj));
}
public void simuluj(){
//anything
Thread.Sleep(SleepValue);
}
and in gui you must use delegate
delegate void Invoker();
private void setSpeed()
{
if (this.InvokeRequired)
{
this.BeginInvoke(new Invoker(setSpeed));
return;
}
Simulation.SleepValue=Speed;
}
Hope it is good.
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.
}));