Updating WPF GUI Every 2 Seconds (C#) - 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.

Related

Accessing control from another thread safely

I'm creating a program that draws points in a bitmap in memory (using system.drawing) and displays it in a PictureBox control, this way :
private void button_startGen_Click(object sender, EventArgs e) {
continueTask_generate = true;
Task task = Task.Run(() => {
while(continueTask_generate) {
for (int i = 0; i < Iterations; i++) {
gm.GenerateNextPoint();
}
UpdateBitmap();
Thread.Sleep(Time);
}
});
}
private void UpdateBitmap() {
pictureBox_bitmap.Image = gm.bitmap;
}
The way this works is that, when you press the button "start generation", it'll start generating points, and you'll see that generation in the PictureBox. It generates i points, and then updates the pictureBox, waits for t milliseconds, and does the process again, until you press a "stop" button.
Now, as I'm updating the pictureBox control from a different thread than the main one, I'll get those pesky "InvalidOperationException". I'm a noob in threading. I've tried both solutions in this page but none of them worked for me.
(For reference, this is my 'updated' UpdateBitmap() method for the first solution, which it didn't work:
private delegate void UpdateBitmapDelegate();
private void UpdateBitmap() {
if(pictureBox_bitmap.InvokeRequired) {
UpdateBitmapDelegate deg = new UpdateBitmapDelegate(UpdateBitmap);
pictureBox_bitmap.Invoke(deg);
}
else {
pictureBox_bitmap.Image = gm.bitmap;
}
}
Any help would be appreciated, as I'm making this program mainly to learn this kind of thing.
Edit:
I'm not sure if the content of gm.GenerateNextPoint() is relevant, but I'll post it too. GenerateNextPoint() calls this method:
private void DrawPoint(Point point, int radius, Color color) {
SolidBrush brush = new SolidBrush(color);
using (Graphics g = Graphics.FromImage(bitmap)) {
if (radius > 0 && radius < 3) {
g.FillRectangle(brush, point.X, point.Y, radius, radius);
}
else if (radius > 1) {
int radiusOffset = (int)(radius / 2f);
g.FillEllipse(brush, point.X - radiusOffset, point.Y - radiusOffset, radius, radius);
}
}
}
This method also interacts with gm.bitmap, as does UpdateBitmap().
Pls try this:
Add this to your function on the other thread-
if (InvokeRequired)
{
this.Invoke(new MethodInvoker(delegate
{ //your working code here }));
}
else
{
//the same code here
}

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 assign an arrow key to a button in C#?

right now I'm making a game and a character in it to move the player. I'm just a beginner about programming.
There are 8 buttons, and each button goes to a direction. For example, this my program for
private void btnUp_Click(object sender, EventArgs e)
{
//move up
y = y - 1;
MovePlayer();
UpdateLabelLocation();
}
public void MovePlayer()
{
picPlayer.Location = new Point(x, y);
}
public void UpdateLabelLocation()
{
lblLocation.Text = "Location: (" + x + ", " + y + ")";
}
I want to make it move when I press up, down, left or right keys. Also, if possible I want to make it so that when I press right and up at the same time, it triggers this:
private void btnRightUp_Click(object sender, EventArgs e)
{
//move player
y = y - 1;
x = x + 1;
MovePlayer();
UpdateLabelLocation();
}
I appreciate the help.
What you're essentially doing is creating your own simple game engine. As commentors have noted, you're better off using an existing game engine such as Unity. It's far easier and more liberating than going about it with WinForms.
That said, if you really want to continue with this, I strongly suggest you move the code in the Click handlers to a method. This reduces code duplication. i.e.
private void DownButton_Click(...)
{
MovePlayer(0, 1);
}
private void UpButton_Click(...)
{
MovePlayer(0, -1);
}
public void MovePlayer(float xStep, float yStep)
{
x += xStep;
y += yStep;
MovePlayer();
UpdateLabelLocation();
}
To move down and left you'd call MovePlayer(-1, 1);
To move up and right you'd call MovePlayer(1, -1);
Next you'll need to respond to KeyPress events. i.e.
public void Form_KeyPress(object sender, KeyPressEventArgs args)
{
switch (args.KeyChar) {
case 'a': // Left
args.Handled = true;
MovePlayer(-1, 0);
break;
case 'd': // Right
args.Handled = true;
MovePlayer(1, 0);
break;
case 'w': // Up
args.Handled = true;
MovePlayer(0, -1);
break;
case 's': // Down
args.Handled = true;
MovePlayer(0, 1);
break;
}
}
Note that if you have another control that accepts keyboard input(such as a TextBox), it will intercept the key press. To get around this, use KeyPreview to force the window to preview the input first. args.Handled = true prevents the event from routing to child controls after your code.
Unfortunately WinForms doesn't record multiple keys pressed at the same time, so using KeyPress alone isn't enough to handle corner movement. You can work around this by hooking onto KeyDown and KeyUp, but it's more trouble than it's worth.
Here's a more robust solution. Bear in mind the following isn't thread safe, so if you plan on introducing other threads you'll need to use appropriate locking.
HashSet<KeyCode> state = new HashSet<KeyCode>();
float speed = 120; // 120 pixels/second.
private void Form_KeyDown(object sender, KeyEventArgs args)
{
var key = args.KeyCode;
state.Add(key);
// Fire pressed when a key was up.
if (!state.Contains(key)) {
state.Add(key);
OnKeyPressed(key);
}
}
private void Form_KeyUp(object sender, KeyEventArgs args)
{
var key = args.KeyCode;
state.Remove(key);
// Fire release when a key was down.
if (state.Contains(key)) {
state.Remove(key);
OnKeyReleased(key);
}
}
// Runs when key was up, but pressed just now.
private void OnKeyPressed(KeyCode key)
{
// Trigger key-based actions.
}
// Runs when key was down, but released just now.
private void OnReleased(KeyCode key)
{
// Trigger key-based actions, but on release instead of press.
}
private bool IsDown(KeyCode key)
{
return state.Contains(key);
}
// Trigger this periodically, at least 20 times a second(ideally 60).
// An option to get you started is to use a windows timer, but
// eventually you'll want to use high precision timing instead.
private void Update()
{
var deltaTime = // Calculate the seconds that have passed since the last update.
// Describing it is out of the scope of this answer, but see the links below.
// Determine horizontal direction. Holding both
// A & D down cancels movement on the x-axis.
var directionX = 0;
if (IsDown(KeyCode.A)) {
directionX--;
}
if (IsDown(KeyCode.D)) {
directionX++;
}
// Determine vertical direction. Holding both
// W & S down cancels movement on the y-axis.
var directionY = 0;
if (IsDown(KeyCode.W)) {
directionY--;
}
if (IsDown(KeyCode.S)) {
directionY++;
}
// directionX & directionY should be normalized, but
// I leave that as an exercise for the reader.
var movement = speed * deltaTime;
var offsetX = directionX * movement;
var offsetY = directionY * movement;
MovePlayer(offsetX, offsetY);
}
As you can see, there's quite a bit involved. If you want more fine-grained timing, look into this article. Eventually you'll want to transition to a game loop, but that's yet another topic out of the scope of this answer.

Struggling to get new graphics to appear with each tick of the timer

Sorry if this sounds a bit of a dim question - I'm completely new to this. I'm trying to get a new ellipse to appear on each tick of a timer. So far, I have:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public partial class Form1 : Form
{
private Bitmap DrawingArea;
int numberofcircles = 0;
int[] narrary = new int[30];
int newcircle;
Random rnd = new Random();
public Form1()
{
InitializeComponent();
Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
numberofcircles = numberofcircles + 1;
newcircle = (rnd.Next(15) * 6) + 76;
narrary[numberofcircles] = newcircle;
Invalidate();
timer1.Start();
}
private void Form1_Load(object sender, EventArgs e)
{
DrawingArea = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Pen pen = new Pen(Color.Black);
using (var canvas = Graphics.FromImage(DrawingArea))
{ canvas.Clear(Color.Transparent);
canvas.DrawLine(pen, 100, 100, 700, 100);
for (int i = 1; i <= numberofcircles; i++)
{
canvas.DrawEllipse(pen, 180 + (30 * i), narrary[i], 8, 6);
}
}
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Pen pen = new Pen(Color.Red);
for (int i = 1; i <= numberofcircles; i++)
{
canvas.DrawEllipse(pen, 180 + (30 * i), narrary[i], 8, 6);
}
e.Graphics.DrawImage(DrawingArea, 0, 0, DrawingArea.Width, DrawingArea.Height);
}
private void timer1_Tick(object sender, EventArgs e)
{
numberofcircles = numberofcircles + 1;
newcircle = (rnd.Next(15) * 6) + 76;
narrary[numberofcircles] = newcircle;
for (int i = 1; i <= numberofcircles; i++)
{
canvas.DrawEllipse(pen, 180 + (30 * i), narrary[i], 8, 6);
}
Invalidate();
}
}
}
"canvas" and "pen" references are flagging up as errors in the Form1_Paint and timer1_Tick sections ("The name 'canvas' does not exist in the current context"). I'm sure I must be referencing them wrong, but I'm afraid I don't have the basic C# knowledge to be able to sort this out!
I'd be very grateful for any help.
There are a number of concepts it looks like you need explaining.
First, as noted in the comments, you need to pay attention to "scoping". In C# (and most other languages), variables have a well-defined scope that prevent them from being visible except where they are relevant. The variable you're having trouble with are "local variables", and are valid only in the method in which they are declared.
If you want those variables to be visible to other methods, they need to be declared somewhere that they are visible to all methods. For example, you could declare them as instance variables.
But in this case, that would be the wrong thing to do. Another concept you seem to have trouble with is how drawing in a Winforms program works. You should only draw to the screen in the Paint event handler. There are a couple of ways to approach this:
Keep track of the data that is the basis of what you're drawing, and then redraw everything any time the Paint event is raised.
Maintain an off-screen bitmap, drawing new data to it as needed, and then draw this bitmap to the screen when the Paint event is raised.
Either way, you cause the Paint event to be raised by calling Invalidate().
Here is an example of the first approach, based on your original code:
public partial class Form1 : Form
{
const int maxCircleCount = 30;
List<int> circles = new List<int>();
Random rnd = new Random();
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private void button1_Click(object sender, EventArgs e)
{
// Arguably, you only need to start the timer and can skip these first two lines
circles.Add(rnd.Next(15) * 6 + 76);
Invalidate();
timer1.Start();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 100, 100, 700, 100);
for (int i = 0; i < circles.Count; i++)
{
e.Graphics.DrawEllipse(Pens.Red, 180 + (30 * i), circles[i], 8, 6);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (circles.Count < maxCircleCount)
{
circles.Add(rnd.Next(15) * 6 + 76);
Invalidate();
}
else
{
timer1.Stop();
}
}
}
The above will draw 30 circles after you click the button, one per timer tick, and then stop the timer. Since in your code example, when the Load event handler is called you don't have any circles yet, it wasn't clear to me what your intent with that code was. So I did not bother to draw any black circles.
Other changes include:
Setting DoubleBuffered to true, to avoid flickering when Invalidate() is called.
Using the stock Pens objects instead of creating them (note that your code, which did create new Pen objects, should have disposed the Pen objects it created…that it did not was also a bug).
Use a List<int> for the circles. This encapsulates the storage and the count in a single object.
Note that an improvement I didn't bother to make would be to consolidate the circles.Add() and Invalidate() calls into a separate method that can be called by any place it needs to be.
Hopefully the above gets you back on track. There are lots of other questions on Stack Overflow discussing the various nuances of how to draw in a Winforms program, whether from raw data or by caching to an off-screen bitmap. You should be able to use those posts to refine your techniques.
See also e.g. Force Form To Redraw? and How do I call paint event?, which include some answers that elaborate on the basic "call Invalidate()" concept.

Slowing down animation in C#

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.

Categories