Skip to content

Instantly share code, notes, and snippets.

@RupertAvery
Last active November 19, 2020 05:10
Show Gist options
  • Select an option

  • Save RupertAvery/cb77e1f0fa26eee45f9d5fca372d2cde to your computer and use it in GitHub Desktop.

Select an option

Save RupertAvery/cb77e1f0fa26eee45f9d5fca372d2cde to your computer and use it in GitHub Desktop.
Drawing flicker-free on Windows forms with threaded loop
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