Last active
November 19, 2020 05:10
-
-
Save RupertAvery/cb77e1f0fa26eee45f9d5fca372d2cde to your computer and use it in GitHub Desktop.
Drawing flicker-free on Windows forms with threaded loop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| using System.ComponentModel; | |
| using System.Windows.Forms; | |
| using System.Drawing; | |
| using System.Windows.Input; | |
| using System.Threading; | |
| namespace SpriteTest | |
| { | |
| // A Sprite class that encapsulates the image and position | |
| // and abstracts movement | |
| class Sprite : IDisposable { | |
| public int X { get; private set; } | |
| public int Y { get; private set; } | |
| public Bitmap Image { get; private set; } | |
| public Sprite(string path) { | |
| Image = new Bitmap(path); | |
| } | |
| public void MoveX(int direction) { | |
| X += direction; | |
| } | |
| public void MoveY(int direction) { | |
| Y += direction; | |
| } | |
| // There's a more complete pattern for disposing | |
| public void Dispose() { | |
| Image.Dispose(); | |
| } | |
| } | |
| // A subclassed Form that stores our Sprites and runs the game loop | |
| class GameForm : Form { | |
| private PictureBox Canvas; | |
| private Sprite _sprite; | |
| // used to check if we're done | |
| private bool _running; | |
| // a worker thread, just runs whatever method we pass it on another thread, so we don't block the main UI thread | |
| private Thread _worker; | |
| public GameForm(){ | |
| InitializeControls(); | |
| LoadAssets(); | |
| _running = true; | |
| // Start the main loop thread | |
| _worker = new Thread(Execute); | |
| _worker.Start(); | |
| } | |
| private void InitializeControls() { | |
| Text = "Sprite Test"; | |
| Width = 512; | |
| Height = 512; | |
| // Handle key events | |
| KeyDown += HandleInputs; | |
| // Handle when the user closes the form | |
| Closing += Shutdown; | |
| Canvas = new PictureBox(); | |
| Canvas.Location = new System.Drawing.Point(0, 0); | |
| Canvas.Size = new System.Drawing.Size(512, 512); | |
| // Hanlde the Control's Paint event | |
| Canvas.Paint += Redraw; | |
| Controls.Add(Canvas); | |
| } | |
| private void LoadAssets() { | |
| _sprite = new Sprite("img.png"); | |
| } | |
| private void HandleInputs(object sender, KeyEventArgs args) | |
| { | |
| switch(args.KeyCode) | |
| { | |
| case Keys.Up: | |
| _sprite.MoveY(-1); | |
| break; | |
| case Keys.Down: | |
| _sprite.MoveY(1); | |
| break; | |
| case Keys.Left: | |
| _sprite.MoveX(-1); | |
| break; | |
| case Keys.Right: | |
| _sprite.MoveX(1); | |
| break; | |
| } | |
| } | |
| // The Canvas Paint handler, draw sprites and stuff | |
| private void Redraw(object sender, PaintEventArgs args) | |
| { | |
| var graphics = args.Graphics; | |
| var rectangle = new System.Drawing.Rectangle(0, 0, 512, 512); | |
| graphics.FillRectangle(new SolidBrush(Color.White), rectangle); | |
| graphics.DrawImage(_sprite.Image, _sprite.X, _sprite.Y); | |
| } | |
| private void Shutdown(object sender, CancelEventArgs e) { | |
| // signal to the thread that we're done | |
| _running = false; | |
| // Clean up event handlers. Not really needed but should always cleanup | |
| KeyDown -= HandleInputs; | |
| Canvas.Paint -= Redraw; | |
| Closing -= Shutdown; | |
| } | |
| // The main loop. It should be a method that has the signature void (object) | |
| private void Execute(object sender){ | |
| Console.WriteLine("Starting thread"); | |
| // Loops forever until someone sets _running to false... | |
| while(_running) { | |
| // Update objects each frame then redraw | |
| Canvas.Invalidate(); | |
| } | |
| Console.WriteLine("Thread exited"); | |
| } | |
| // overrider the Form's Dispose method | |
| protected override void Dispose(bool disposing) | |
| { | |
| if (disposing) | |
| { | |
| // cleanup any IDisposables | |
| _sprite.Dispose(); | |
| Canvas.Dispose(); | |
| } | |
| base.Dispose(disposing); | |
| } | |
| } | |
| class Program | |
| { | |
| static void Main(string[] args) | |
| { | |
| Application.Run(new GameForm()); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment