C# grotty graphics

  • Thread starter Thread starter rayreeves
  • Start date Start date
R

rayreeves

If I look for guidance on this topic I find simple examples like this:

public class Form2:Form {
public Form2() {
this.Text = "FORM2";
this.Size = new Size(450,400);
this.Paint += new PaintEventHandler(Draw_Graphics);
}

public void Draw_Graphics(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
System.Drawing.Bitmap bmap = new System.Drawing.Bitmap(500, 400);
for(int i = 0; i < 500; i++)
for(int j = 0; j < 50; j++)
bmap.SetPixel(i, j, Color.Red);
g.DrawImage(bmap,200,100);
}
};

static void Main()
{
Application.Run(new Form2());
}

This works as advertised but there is some sly sleight of hand going on
here.
Apparently the Form2 constructor raises a signal that is caught by the
handler Draw_Graphics().
What was the signal and who raised it?
I ask because if I replace the innocuous-looking statement
"Application.Run" by a simple call to the constructor it executes that but
no event is raised and the handler does not run.
What is the dark secret here? What does Application.Run() actually do?

Now I want to run a dialog box first and then perform the above, so I say:

Application.Run(new Form1())
Application.Exit();
Application.Run(new Form2())

No dice! I get Form1 and the Form2 constructor but no signal.

Ray Reeves
 
If I look for guidance on this topic I find simple examples like this:

public class Form2:Form {
public Form2() {
this.Text = "FORM2";
this.Size = new Size(450,400);
this.Paint += new PaintEventHandler(Draw_Graphics);
}

public void Draw_Graphics(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
System.Drawing.Bitmap bmap = new System.Drawing.Bitmap(500, 400);
for(int i = 0; i < 500; i++)
for(int j = 0; j < 50; j++)
bmap.SetPixel(i, j, Color.Red);
g.DrawImage(bmap,200,100);
}
};

static void Main()
{
Application.Run(new Form2());
}

This works as advertised but there is some sly sleight of hand going on
here.
Apparently the Form2 constructor raises a signal that is caught by the
handler Draw_Graphics().

No, the Windows message loop requests that the form is painted, and the
constructor to Form2 registers a handler for the paint event.
What was the signal and who raised it?
I ask because if I replace the innocuous-looking statement
"Application.Run" by a simple call to the constructor it executes that but
no event is raised and the handler does not run.
What is the dark secret here? What does Application.Run() actually do?

It starts a Windows message loop.
Now I want to run a dialog box first and then perform the above, so I say:

Application.Run(new Form1())
Application.Exit();
Application.Run(new Form2())

No dice! I get Form1 and the Form2 constructor but no signal.

Well, you don't want Application.Exit() in there - that will exit the
application (as the name suggests) unless you have any other threads
running.
 
rayreeves said:
If I look for guidance on this topic I find simple examples like this:

public class Form2:Form {
public Form2() {
this.Text = "FORM2";
this.Size = new Size(450,400);
this.Paint += new PaintEventHandler(Draw_Graphics);
}

public void Draw_Graphics(object sender,PaintEventArgs e)
{
Graphics g = e.Graphics;
System.Drawing.Bitmap bmap = new System.Drawing.Bitmap(500, 400);
for(int i = 0; i < 500; i++)
for(int j = 0; j < 50; j++)
bmap.SetPixel(i, j, Color.Red);
g.DrawImage(bmap,200,100);
}
};

static void Main()
{
Application.Run(new Form2());
}

This works as advertised but there is some sly sleight of hand going on
here.

Yes. It's Win32's windowing system.
Apparently the Form2 constructor raises a signal that is caught by the
handler Draw_Graphics().

No. Application.Run() contains a loop (called the "message loop" or
"message pump") which does basically two things in its body:

1) Retrieve a message from Windows (see e.g. GetMessage and PeekMessage
in MSDN docs)
2) Dispatch that message from Windows (see e.g. DispatchMessage in MSDN
docs)

Messages are things like WM_PAINT, which is a request to paint a window
(for example, the window might have been obscured by another window, and
is now visible). Messages contain several parameters: the message code
(WM_PAINT in this case), the window it applies to, and two parameters
whose meaning changes based on message kind (wParam and lParam).

Retrieving the message and dispatching the message are done by the
application so that it happens on a thread the application controls.

Dispatching the message sends it off to something called a window
procedure, which is a function pointer (a bit like a delegate)
associated with that window in the OS. When the window is first created,
the creator of the window gets a chance to specify this window procedure
(see CreateWindow in MSDN docs).

The window procedure for .NET controls is Control.WndProc. Basically,
different descendants of Control have 'case' statements that switch on
the message type and call different methods based on the message.

In Control.WndProc, there's an entry in this 'case' statement that calls
a private method called WmPaint, which in turn calls
PaintWithErrorHandling, which itself calls OnPaint, which finally calls
the 'Paint' event if it is non-null.

I'm leaving out many details, but that's the gist of it.
Now I want to run a dialog box first and then perform the above, so I say:

Application.Run(new Form1())

This sets up a message loop and keeps going until the application exits
(with Application.Exit) or the main form (the argument) is closed.
Application.Exit();

Because there's no loop to exit, this does nothing. The documentation
for this method says:

"Informs all message pumps that they must terminate, and then closes all
application windows after the messages have been processed."

Because there's no message pump active (Application.Run() is not
running), and there's no forms on screen at this point, the method won't
do anything.
Application.Run(new Form2())

This shows a second form, in a new loop.

-- Barry
 
Jon Skeet said:
Well, you don't want Application.Exit() in there - that will exit the
application (as the name suggests) unless you have any other threads
running.

Doh! Ignore this bit, and read Barry's answer instead, which is much
more sensible.
 
Barry Kelly said:
"

This shows a second form, in a new loop.

-- Barry

Thanks for the comprehensive and detailed response.
Unfortunately, the second form Form2 does not appear, unless it is the only
form. It would be nice to know if a Paint event actually occurred and who
caught it.

I don't even really want to paint on a Form but it's not apparent to me that
I can just create a window. I would also like to SetPixel directly in the
window without using BitMaps so that I can watch the picture grow, but that
doesn't seem possible either.

Ray Reeves
 
rayreeves said:
Thanks for the comprehensive and detailed response.
Unfortunately, the second form Form2 does not appear, unless it is the only
form. It would be nice to know if a Paint event actually occurred and who
caught it.

The second form should appear after you close the first. It does in this
simple test program:

---8<---
using System;
using System.Windows.Forms;

class App
{
public static void Main()
{
Application.Run(new Form());
Application.Exit();
Application.Run(new Form());
}
}
--->8---
I don't even really want to paint on a Form but it's not apparent to me that
I can just create a window. I would also like to SetPixel directly in the
window without using BitMaps so that I can watch the picture grow, but that
doesn't seem possible either.

Windows doesn't use a "retained mode" graphics infrastructure.
Everything is lost if the window is obscured in any way. You need to
draw to a bitmap, and keep the bitmap around. For example:

---8<---
using System;
using System.Drawing;
using System.Windows.Forms;

class MyForm : Form
{
Bitmap _image = new Bitmap(500, 500);
Button _testButton = new Button();
Random r = new Random();

public MyForm()
{
Paint += MyPaintEvent;
using (Graphics g = Graphics.FromImage(_image))
g.FillRectangle(Brushes.White, 0, 0, 500, 500);
ClientSize = new Size(500, 500);
_testButton.Parent = this;
_testButton.Text = "Click Here";
_testButton.Click += MyClickEvent;
DoubleBuffered = true;
}

void MyClickEvent(object sender, EventArgs e)
{
// Random color for RGB fields, and 'OR' in 255 for Alpha.
Color c = Color.FromArgb(r.Next(0x1000000) | (0xFF << 24));
using (Graphics g = Graphics.FromImage(_image))
{
using (Pen p = new Pen(c))
g.DrawLine(p, r.Next(500), r.Next(500),
r.Next(500), r.Next(500));
}
_image.SetPixel(r.Next(500), r.Next(500), c);
// Invalidate() needed to get windows to send paint message
// to repaint form.
Invalidate();
}

void MyPaintEvent(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(_image, 0, 0);
}

public static void Main()
{
Application.Run(new MyForm());
}
}
--->8---

-- Barry
 
I do exactly that in my code but don't succeed. Somehow my Dialog Form is
intercepting the Paint event, if there is one. Its true that a simpler Form1
does work properly for me..I'll have to try whitteling down my Dialog bit by
bit.
I am very much obliged for your kind attention and detailed help.

Ray Reeves
 
What I have found is that if the Form1 Dialog Box contains plain buttons
then when they are pressed, even if they do nothing, the Paint signal to
Form2 is pre-empted! Closing the Form1 window is the only way to get to
Form2.
Grotty!

Ray Reeves
 
Back
Top