Draw on a form by a separate thread - c#

I'm trying to build a multithreaded game where I have a separate thread for painting on the form which is not the main thread. this brings us to thread-safe technics which I've read many articls about, but I'm not really sure I got it correctly.
my problem is that I have a structure where every data object is painting it self on the form so I didn't figure out how to implement it.
this is a snippet of my working mono-thread code:
public partial class Form1 : Form
{
GameEngine Engine;
public Form1()
{
InitializeComponent();
Engine = new GameEngine();
}
protected override void OnPaint(PaintEventArgs e)
{
Engine.Draw(e.Graphics);
}
}
class GameEngine
{
Maze Map;
List<Player> Players;
public void Draw(Graphics graphics)
{
Map.Draw(graphics);
foreach (var p in Players)
{
p.Draw(graphics);
}
}
}
so please can anyone give me a hint or a link to good article helping me to learn how to separate the drawing on an another thread?.
[Edit]
I managed to implement what I intended to do
and this is how I coded it
protected override void OnPaint(PaintEventArgs e)
{
formGraphics = e.Graphics;
DisplayThread = new Thread(new ThreadStart(Draw));
DisplayThread.Start();
}
private void Draw()
{
if (this.InvokeRequired)
{
this.Invoke(new DrawDelegate(this.Draw));
}
else
{
Engine.Draw(formGraphics);
}
}
but I got an ArgumentException : Parameter is not valid
would you please point to the error in that code

I think you will need to draw to a Bitmap, then in the OnPaint Method, draw that bitmap to the window. I will demonstrate in a moment.
As Hans pointed out, in the OnPaint method you are setting
formGraphics = e.Graphics;
but at the end of the method e.Graphics is disposed, so you can't use it anymore, if your code got to
Engine.Draw(formGraphics);
you would get an exception.
So basically you need to have a global
Bitmap buffer = new Bitmap(this.Width, this.Height)
in your asynced thread you would invoke your drawing to that Bitmap you can use
Graphics g=Graphics.FromBitmap(buffer);//
To get a graphics object, but remember you have to
g.Dispose()
it or wrap it in a
using (Graphics g=Graphics.FromBitmap(buffer))
{
//do something here
}
I am going to play with it for a few moments and see if I can get you a working sample
EDIT Here's your working sample.
I started a new form and tossed a button on it. I changed the mainform backgroundimagelayout to none.
I think you need to be using .net 4.0 or better, if not using this, let me know I can change it to match your version... I think.
//you need this line to use the tasks
using System.Threading.Tasks;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void Draw()
{
Bitmap buffer;
buffer = new Bitmap(this.Width, this.Height);
//start an async task
Task.Factory.StartNew( () =>
{
using (Graphics g =Graphics.FromImage(buffer))
{
g.DrawRectangle(Pens.Red, 0, 0, 200, 400);
//do your drawing routines here
}
//invoke an action against the main thread to draw the buffer to the background image of the main form.
this.Invoke( new Action(() =>
{
this.BackgroundImage = buffer;
}));
});
}
private void button1_Click(object sender, EventArgs e)
{
//clicking this button starts the async draw method
Draw();
}
}
}

Related

C# - Keeping the values from functions

I want to call a function like Spawn() in this case, but without losing values from it after calling it (for example, I want entityPicture to still be available after I call Spawn(), so I can use it later on MainWindow_KeyDown() function). Same with X and other variables like that.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace EpicGame
{
public partial class MainWindow : Form
{
public MainWindow()
{
InitializeComponent();
}
public void MainWindow_Load(object sender, EventArgs e)
{
int EntityCount = 0;
Background.ImageLocation = "Background.jpg";
Background.SizeMode = PictureBoxSizeMode.AutoSize;
Spawn(600, 600, EntityCount, "Player 1.png"); EntityCount++;
}
// Controls
public void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.D) MoveRight();
entityPicture.Location = new Point(X, Y);
}
// Movement
public void MoveRight()
{
X++;
}
// Entity spawning
public void Spawn(int X, int Y, int ID, string Path)
{
PictureBox entityPicture = new PictureBox();
Image Entity = Image.FromFile(Path);
entityPicture.Image = Entity;
entityPicture.SizeMode = PictureBoxSizeMode.AutoSize;
entityPicture.Location = new Point(X, Y);
entityPicture.BackColor = Color.Transparent;
Controls.Add(entityPicture);
entityPicture.BringToFront();
}
private void Background_Click(object sender, EventArgs e)
{
}
}
}
You can declare it public in the form class like:
public partial class MainWindow : Form
{
PictureBox entityPicture = new PictureBox();
}
I don't know the type of entityPicture so just used PictureBox as example, you use the correct type.
Your root issue is one of scope. Have a look at this video, it walks through your exact issue: https://www.youtube.com/watch?v=NemPMKTxM7w
You can technically move entityPicture to the top of the class, however given this appears to be a WinForms window, I would add the PictureBox via the designer so it's only declared once. If you declare it at the class level and reinitialize it in Spawn (i.e. = new PictureBox()) repeatedly, you're going to create a memory leak.

Using the OnPaint() method

I am using this library to generate QRcode into a WinForm application, but I don't really know how to take use of the OnPaint() method.
So I have this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
QrEncoder encoder = new QrEncoder(ErrorCorrectionLevel.M);
QrCode qrCode;
encoder.TryEncode("link to some website", out qrCode);
new GraphicsRenderer(new FixedCodeSize(200, QuietZoneModules.Two))
.Draw(e.Graphics, qrCode.Matrix);
base.OnPaint(e);
}
private void Form1_Load(object sender, EventArgs e)
{
this.Invalidate();
}
}
I have a simple pictureBox in the form and I just want to generate the QRcode image in there (if it is possible to generate it in a picturebox).
If you're putting your image in a picturebox and you're only producing your image once, then you don't need to worry about the paint method (you're not doing an animation etc, it's just a QR code)
Just do this in your form load (or where ever you produce your image)
mypicturebox.Image = qrCodeImage;
Update - additional code to facilitate your library
var bmp = new Bitmap(200, 200);
using (var g = Graphics.FromImage(bmp))
{
new GraphicsRenderer(
new FixedCodeSize(200, QuietZoneModules.Two)).Draw(g, qrCode.Matrix);
}
pictureBox1.Image = bmp;
This is what I eventually did:
public partial class Form1 : Form
{
public event PaintEventHandler Paint;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox_Paint);
this.Controls.Add(pictureBox1);
}
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
QrEncoder encoder = new QrEncoder(ErrorCorrectionLevel.M);
QrCode qrCode;
encoder.TryEncode("www.abix.dk", out qrCode);
new GraphicsRenderer(
new FixedCodeSize(200, QuietZoneModules.Two)).Draw(e.Graphics, qrCode.Matrix);
}
}

Syncing object passed to thread with timer

I previously made a simple ball and paddle program all running in one form. Now I am trying to have the ball run on a separate thread, but don't quite understand conceptually how to have the ball run a separate thread while keeping the rest of my program the same.
Originally the timer would make the ball move, so I thought I can insert that move line into my ballUpdate class. But now my question is if there is a way to make the thread work my current timer implemented.
This is what my form1 looks like
public partial class Form1 : Form
{
Graphics paper;
Paddle paddle = new Paddle();
Ball ball = new Ball();
Thread myThread;
BallUpdate ballUpdate = new BallUpdate();
public Form1()
{
InitializeComponent();
myThread = new Thread(() => ballUpdate.ballMotion(ball));
myThread.Start();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
paper = e.Graphics;
paddle.drawPaddle(paper);
ball.drawBall(paper);
}
private void timer1_Tick(object sender, EventArgs e)
{
//ball.moveBall();
ball.collide();
ball.hitPaddle(paddle.PaddleRec);
this.Invalidate();
}
I thought this might get something going, but the ball doesn't move at all.
class BallUpdate
{
public BallUpdate(){}
public void ballMotion(Ball x)
{
while(true)
{
x.moveBall();
Thread.Sleep(30);
}
}
}
You need to initialize your ballUpdate and start your myThread. Only the ball's logic should be on a separate thread.
public partial class Form1 : Form
{
Graphics paper;
Paddle paddle = new Paddle();
Ball ball = new Ball();
Thread myThread;
BallUpdate ballUpdate = new BallUpdate();
public Form1()
{
InitializeComponent();
myThread = new Thread(() => ballUpdate.ballMotion(ball));
myThread.Start();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
paper = e.Graphics;
paddle.drawPaddle(paper);
ball.drawBall(paper);
}
private void timer1_Tick(object sender, EventArgs e)
{
//ball.moveBall();
ball.collide();
ball.hitPaddle(paddle.PaddleRec);
this.Invalidate();
}
In BallUpdate change if(true) to while(true) to prevent the thread from exiting. Incorporate delay so your ball doesn't go flying.
class BallUpdate
{
public BallUpdate(){}
public void ballMotion(Ball x)
{
while(true)
{
x.moveBall();
Thread.Sleep(10000);
}
}
}
I'd recommend building a thread method that loops over the following:
Calculate the Timespan in milliseconds since the last calculation
Move all the objects accordingly
Collision checking and resolving
Invoke the display method to render the frame
Return to Step 1
Please do not create a new thread in each frame, that will have abysmal performance. Also, do not manipulate graphics objects in a thread.

Programmatically put an image into the form which is not active?

I want programmatically put an image into the form which is not active.
Image myImage = new Image();
gfx = Form.ActiveForm.CreateGraphics();
gfx.DrawImage(myImage, 0,0);
It's will be working perfectly only if the form is active, but it's make no sense when the form is not active, just returns me an error:
In an instance of an object reference not set to an object.
how to handle the form which is not active in my application and put a picture to it?
upd 1
I did instance, and turned on DoubleBuffered property (true) but nothing happens:
Form1 form = new Form1();
gfx = form.CreateGraphics();
gfx.DrawImage(bmp, 0, 0);
upd 2 More Source, it's my added class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
namespace testimg
{
class doimg
{
public void picture()
{
// some staffs to get a picture, so it's in bmp object now.
gfx = this. // watch picture below
gfx.DrawImage(bmp, 0, 0);
// I tried to use PictureBox, but it's the same issue (I can't handle it on a form)
PictureBox pb = new PictureBox();
pb.CreateGraphics();
pb.DrawToBitmap(bmp,pb.ClientRectangle);
}
}
}
and a picture for the Arif Eqbal solution: no graph methods for this (pic)
upd 3
The full source what I have (timer is on a 10 sec)
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace testimg
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
doimg pic = new doimg();
pic.picture();
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
}
}
doimg.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
namespace testimg
{
class doimg
{
public void picture()
{
// some staffs to get a picture, so it's in bmp object now.
Bitmap bmp = new Bitmap(200, 200, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics gfx = Graphics.FromImage(bmp);
gfx = Form1.ActiveForm.CreateGraphics(); // works well with active form
gfx.DrawImage(bmp, 0, 0);
}
}
}
and whole archive with project http://www.filedropper.com/testimg
as you can see all running well while the program is active. Still need help.
On your Form2, try to set it up like this:
public partial class Form2 : Form {
public Image MyImage { get; set; }
public Form2() {
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e) {
if (MyImage != null) {
e.Graphics.DrawImage(MyImage, 0, 0);
}
base.OnPaint(e);
}
}
Then from your active form, you can call the other form to draw itself:
public partial class Form1 : Form {
Form2 f2 = new Form2();
public Form1() {
InitializeComponent();
f2.Show();
}
private void button1_Click(object sender, EventArgs e) {
f2.MyImage = myImage;
f2.Invalidate();
}
}
Part of the problem is that when you are setting your bitmap the Form is not updating, expecially if it is minimized or doesn't have focus. You also have the issue that since it is not being set in the Paint Event Handler you have no retention of the Image. Try something like this, Note the Invalidate call and the Timer was set for 10,000 ms. This is a variation of #LarsTech's answer if you are working with multiple Forms his is the way to go.
public partial class Form1 : Form
{
Bitmap myImage;
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
myImage = new Bitmap("Your Image Name Here");
Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (myImage != null)
e.Graphics.DrawImage(myImage,0,0);
}
}
Modifed my Example to work with the OP's structure:
My original example does work when the form does not have focus. Your main problem with the error is using Form1.ActiveForm when it is not the active form, it will return null which is causing your error. Use the Forms Paint Event and Invalidate the Form to insure that the Image is painted.
Form1.cs
namespace testimg
{
public partial class Form1 : Form
{
Bitmap myImage;
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
myImage = doimg.picture();
Invalidate();
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (myImage != null)
e.Graphics.DrawImage(myImage,0,0);
}
}
}
doimg.cs
namespace testimg
{
static class doimg
{
static Color[] clr = new Color[] { Color.Red, Color.Blue, Color.Black, Color.Violet, Color.Wheat };
static int count = 0;
static public Bitmap picture()
{
// some staffs to get a picture, so it's in bmp object now.
Bitmap bmp = new Bitmap(200, 200, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// Added some drawing to bitmap to test functionality
Graphics gfx = Graphics.FromImage(bmp);
gfx.FillEllipse(new SolidBrush(clr[count]),new Rectangle(0,0,199,199));
gfx.Dispose();
if (count >= 4)
count = 0;
else
count += 1;
return bmp;
}
}
}
You have to create an instance of your form.. regardless of whether it's visible and active.
Example:
Image myImage = new Image(); // load image
MyForm form = new MyForm();
gfx = form.CreateGraphics();
gfx.DrawImage(myImage, 0, 0);
Then you will be able to call form.Show() when you want to show the form.
(Note, you'll probably also have to set DoubleBackBuffer on the form to true.
I think I can make a sense of it...is it that your code to Get the instance of the form and draw on it run on a timer? And your application has just one form? And you run into issues when the Application is not Active?
If Yes then replace the line
Form.ActiveForm.CreateGraphics();
with
this.CreateGraphics();
Form.ActiveForm gives the reference of the currently Active Form for the Application, if you write this code on say a Button click you are bound to get the name of the Form on which the Button was clicked. However, if the code is on a timer it might give the instance of the Active form or Null if the entire application is not Active.
As you have mentioned in one of the comments that you do not have any other form, it appears that your application has just one form and this code is running on the only form in the app so this.CreateGraphics should suffice. However, if your application has more than one form then Form.ActiveForm is a legitimate code but you need to take care of the scenario when no form is active i.e. the application itself is not active.
EDIT:
I think you need to pass the instance of the Form on which you want to draw to this class. Make a properry and set the property value to the instance of the form, you might pass it in the constructor as well...using Form.ActiveForm was not a bad idea here but for 2 reasons, tomorrow if you had more than one form in your app it will draw randomly on whicheever is active, second as in your current case if the app is not active you will not get hold of the instance...since you have only one form in your app I suppose you would be creating the instance of this Class on the form so may be you can add a property
public Form ParentForm { get; set; }
in the class and set it as
myClassObject.ParentForm = this;
on the form when you create an instance of this class and then say
ParentForm.CreateGraphics
in the class

Making a Custom Drawing Method available throughout entire application

I've added a custom border to the labels in one of my application forms as follows:
private void ColorMe(PaintEventArgs e)
{
Color myColor = Color.FromArgb(104, 195, 198);
Pen myPen = new Pen(myColor, 1);
e.Graphics.DrawRectangle(myPen,
e.ClipRectangle.Left,
e.ClipRectangle.Top,
e.ClipRectangle.Width - 1,
e.ClipRectangle.Height - 1);
base.OnPaint(e);
}
private void lblDisbs_Paint(object sender, PaintEventArgs e)
{
ColorMe(e);
}
Which works nicely. All I have to do it put ColorMe(e) in the Paint Event of each label.
However I want to use this method on all forms throughout the whole application. I tried putting my ColorMe() method in a class to call it from multiple forms that way, but it does not work, saying that 'base has no OnPaint event'.
How should I make this method available throughout the whole application?
Create class LabelWithBorder derive it from Label, override the OnPaint method.
public class LabelWithBorder : Label {
protected override void OnPaint(PaintEventArgs e) {
ColorMe(e);
}
}
Replace all WinForms labels in your app with your label.
You probably shouldn't use the ClipRectangle for drawing in this case, since it would produce malformed rectangles on your control.
If not using Karel Frajtak's solution, which is cleaner, you can try making a static class and then you can call it from any form:
internal static class LabelBorder {
public static void ColorMe(Rectangle r, PaintEventArgs e) {
r.Inflate(-1, -1);
using (Pen p = new Pen(Color.FromArgb(104, 195, 198), 1))
e.Graphics.DrawRectangle(p, r);
}
}
Example:
public Form1() {
InitializeComponent();
label1.Paint += label_Painter;
label2.Paint += label_Painter;
}
void label_Painter(object sender, PaintEventArgs e) {
LabelBorder.ColorMe(((Label)sender).ClientRectangle, e);
}

Categories

Resources