Why can't I display a bitmap I create?

T

Tom P.

I'm trying to generate a bitmap in code and then display it on the
screen. If I use the Paint event of the window it shows up but when I
move the code to the proper place in my codebase (the game engine)
then the bitmap doesn't display. I'm using the standard Update,
Calculate, Render game engine.

I've tried letting the Render method generate a Graphics object.
I've tried passing a Graphics object to the Render method.
I've tried returning a Bitmap from the Render method.

None of that works. But if I move the Render method back to the Form
and trigger it from a Paint event, it works.
I've added Refresh() calls after the Render call.
I've tried setting the BackgroundImage...

I simply cannot get the stupid bitmap to display unless it is created
as a result of a Paint.
What am I doing wrong?
 
T

Tom P.

What "standard Update, Calculate, Render game engine"?

The one everybody tells you to use on this forum. I forgot the blog it
comes from but it uses the AppIdle event to push Update(), Calculate()
and Render() methods.
Generate a Graphics object how?

control.CreateGraphics... this.CreateGraphics.
Again, where do you get the Graphics object?

From the PaintEvent args.
What Bitmap?

The bitmap I created in the Render method.
No possible way to know, since you didn't bother to post any code, never
mind a concise-but-complete code example that reliably demonstrates the
problem.

However, most likely you are confused about how drawing to the screen
works in Forms applications.  Handling the Paint event or overriding
OnPaint() isn't optional; it's how you _have_ to do it.

There are some exceptions, but they involve much more complicated
scenarios (e.g. DirectX).  There's nothing in your post to suggest that
you're involved in something like that.

You can search this newsgroup for past discussions related to
Paint/OnPaint/rendering and using the Invalidate() method to prompt a
redraw operation.  If the messages you find don't clear things up for
you, you need to post a much more specific question, including a
concise-but-complete code example that reliably demonstrates the problem.

Pete

OK, thank you. That was the answer I was looking for.
Tom P.
 
T

Tom P.

The one everybody tells you to use on this forum. I forgot the blog it
comes from but it uses the AppIdle event to push Update(), Calculate()
and Render() methods.





control.CreateGraphics... this.CreateGraphics.





From the PaintEvent args.





The bitmap I created in the Render method.











OK, thank you. That was the answer I was looking for.
Tom P.

Hmmm, apparently that's not ALL I was looking for.

OK, I found the name of the guy that I stole the render loop (not
"game engine", sorry for that) it's Tom Miller.
(blog found here http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx)

Basically:
while (!NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0,
0, 0))
{
_Engine.Calculate();
_Engine.Update();
_Engine.Render();

//this.Invalidate(true);
this.Invalidate(this.ClientRectangle, true);
}

Engine.Render() currently simply loads a bitmap form disk and returns.

public void Render()
{
_displayBitmap = new Bitmap(@"C:\$hank\Projects\Nimals
\background.bmp");
return;
}

I expose the bitmap through a property (mostly because I wasn't having
any luck returning the object).

public Bitmap DisplayBitmap
{
get
{
return _displayBitmap;
}
}

Then during the OnPaint() I draw the image to the form.

protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(_nimalEngine.DisplayBitmap, 0, 0);

//base.OnPaint(e);
}

This provides me with a black background, not the bitmap I am
expecting.
If I comment out the render call and load the bitmap once, it
displays. If I generate the bitmap in code (which is the eventual
goal, to dynamically display the game graphics) and set that once
it'll display. But if I try to set the bitmap on every call to
Render() I get only the first black bitmap.

I've also noticed Invalidate() not setting off the OnPaint() event on
a regular basis. In fact I couldn't get it to set off the OnPaint()
event until I added the ClientRegion to the call. And then only once.

I have tried setting the style on the window to allow user paint:
this.SetStyle(ControlStyles.AllPaintingInWmPaint, false);
this.SetStyle(ControlStyles.UserPaint, false);

....but that doesn't change anything.

What can I do to dynamically generate a bitmap and display it in a
form?

Tom P.
 
T

Tom P.

First, you definitely didn't see "everybody tell you to use on this
forum" that design.  Second, that's being suggested in that blog for the
specific purpose of dealing with DirectX, which is the special-case
scenario I mentioned earlier and which doesn't seem to apply to your
scenario.

Well, you may have a different opinion but that doesn't change the
response I got when I asked. I may have asked longer ago than you
approve, but that's the response I got.
Personally, it's my opinion that code of that design is obsolete anyway.
  It's the kind of thing that people who learned game implementation
fifteen or twenty years ago, and who never took another look at what the
OS and frameworks offer today and how fast the hardware is, would use.

For people implementing programs where a design like that would have
been desirable a decade ago, IMHO today they should be putting the world
update code in a separate thread, and run screen updates using the
normal messaging API.  The context switch is inconsequential compared to
the cost of rendering and especially maintenance of the world state, and
the design can be made to fit the more normal OS API design much better
using threads.

Fair enough, I've seen several and was entertaining the notion of
changing the game loop I used except I've never been comfortable
enough with rendering to the screen to change it. I would probably use
a threaded engine that triggers an event when it needs to update the
display. But I'd like to be a little more comfortable with painting on
the screen before that.
Besides all that, IMHO one definitely should not be trying to use a
game-style rendering loop, and especially not in .NET, until they are
thoroughly familiar and competent with the basic event-driven rendering
model that .NET provides.

I'm not thoroughly familiar with the screen rendering in .NET that's
why I was asking such an open-ended question. I seriously don't
understand why Invalidate() doesn't repaint the screen every time it's
called.
For test code, maybe that's okay.  You definitely don't want to be
reading bitmaps from disk in a rendering loop in a real program.  And
especially if you don't dispose the old bitmap before loading a new one.

Well, yes. That's why I tried to make substantial reference to the
fact that, in real time, the display will be dynamically generated in
code.
Again, you haven't provided a concise-but-complete code example that
reliably reproduces the problem.  So it's still impossible to say what's
wrong with your code.

Then I'm missing what you are asking for. That's pretty much all of my
code.
using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Nimals
{
public partial class NimalMainForm : Form
{
GameEngine _nimalEngine;

private bool AppStillIdle
{
get
{
NativeMethods.Message msg;

return !NativeMethods.PeekMessage(out msg,
IntPtr.Zero, 0, 0, 0);
}
}

public void Application_Idle(object sender, EventArgs e)
{
while (AppStillIdle)
{
//Debug.WriteLine("AppIdle - Before");

_nimalEngine.Calculate();
_nimalEngine.Update();
_nimalEngine.Render();

//Debug.WriteLine("AppIdle - After");

this.Invalidate(this.ClientRectangle, true);
}
}

public NimalMainForm()
{
InitializeComponent();

_nimalEngine = new GameEngine(this.Width, this.Height);
}

protected override void OnPaint(PaintEventArgs e)
{
Graphics CurrentGraphics;
CurrentGraphics = this.CreateGraphics();


Debug.WriteLine("DrawImage");
e.Graphics.DrawImage(_nimalEngine.DisplayBitmap, 0, 0);

base.OnPaint(e);
}

}

internal class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct Message
{
public IntPtr hWnd;
public IntPtr msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}

[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr
hWnd,
uint messageFilterMin,
uint messageFilterMax,
uint flags);
public NativeMethods() { }
}
}

/**********************************************/
/*** GameEngine Class *******************/

using System;
using System.Diagnostics;
using System.Drawing;

namespace Nimals
{
public class GameEngine
{
FoodField _foodField;

Bitmap _displayBitmap;
Bitmap _workBitmap;

public Bitmap DisplayBitmap
{
get
{
return _workBitmap;// _displayBitmap;
}
}

public GameEngine(int Width, int Height)
{

_foodField = new FoodField(Width, Height, .5F);

_displayBitmap = _workBitmap = new Bitmap(Width, Height,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
}

public void Calculate()
{
//throw new NotImplementedException();
}

public void Update()
{
//throw new NotImplementedException();
}

public void Render()
{
LockUnlockBitsExample();
Debug.WriteLine("Render method");
}

private void LockUnlockBitsExample()
{
Rectangle rect;
System.Drawing.Imaging.BitmapData bmpData;
IntPtr ptr;
int bytes;
byte[] bgrValues;

// Lock the bitmap's bits.
rect = new Rectangle(0, 0, _displayBitmap.Width,
_displayBitmap.Height);

bmpData = _workBitmap.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite,
_workBitmap.PixelFormat);

// Get the address of the first line.
ptr = bmpData.Scan0;

// Declare an array to hold the bytes of the bitmap.
bytes = bmpData.Stride * _workBitmap.Height;
bgrValues = new byte[bytes];

// Copy the BGR values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr,
bgrValues, 0, bytes);

// Set every third value to 255. A 24bpp bitmap will look
red.
for (int counter = 0; counter < bgrValues.Length; counter
+= 3)
{
bgrValues[counter] = 255;
}

// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(bgrValues, 0,
ptr, bytes);

// Unlock the bits.
_workBitmap.UnlockBits(bmpData);

}

}
}




But your symptoms are typical for a program that has blocked the message
pump.  So you should be looking for reasons that your message pump
doesn't get to run.  Either because you haven't started it by calling
Application.Run(), or because the thread that calls Application.Run()
has somehow gotten stuck doing something else.  A third possibility is
that you've created the form or other controls on the wrong thread,
where there's no message pump, but since you haven't mentioned using
explicit threading at all, that's probably a more remote possibility.

Post a proper concise-but-complete code example if you really want some
reliable, precise help.

Pete

I have never had much luck understanding the rendering/paint events
and why, for example, when I use this.CreateGraphics() the graphics
object I get never seems to be able to draw to the screen. I've
overridden several of the paint events and have gone a long way toward
painting stuff that responds to paint events alone (the way a screen
looks or the way ListViewItems are painted). But when it comes to
dynamically generating images for display I can't seem to figure out
what I'm doing wrong.

Thus the open-ended post and search for some level of illumination. I
hope I haven't insulted or annoyed anyone. I've been programming long
enough to understand some rather complex issues (including the multi-
threaded suggestion you made earlier). But this eludes me.

Tom P.
 
T

Tom P.

Well, you may have a different opinion but that doesn't change the
response I got when I asked. I may have asked longer ago than you
approve, but that's the response I got.

Feel free to post a link to the archived thread in which "everybody"
told you to use that design.

I've been following this newsgroup for a very long time.  The design
you're attempting to implement is not standard practice for most .NET
applications.  I'm sure that you did not receive the advice you seem to
think you did — certainly not from "everybody" — but I'm at least
curious as to how you might have gotten the wrong impression.
[...]
I'm not thoroughly familiar with the screen rendering in .NET that's
why I was asking such an open-ended question. I seriously don't
understand why Invalidate() doesn't repaint the screen every time it's
called.

The Invalidate() method doesn't do any repainting at all.  It simply
marks an area of a control as in need of repainting.

If your program has a correct message pump loop running, and you haven't
elsewhere blocked the thread running that loop, then calling
Invalidate() _will_ always result in a paint message being pumped and
that will in turn result in the necessary redrawing (typically through
the OnPaint() method being called).

If it doesn't or you have, then it won't work.  Since you say your
program doesn't work, then obviously you've broken the message pumping
behavior somewhere, or you're not drawing correctly (more on that below).
[...]
Again, you haven't provided a concise-but-complete code example that
reliably reproduces the problem.  So it's still impossible to say what's
wrong with your code.
Then I'm missing what you are asking for. That's pretty much all of my
code.

Your most recent post includes more code.  But it's still not a proper
concise-but-complete code example.  Here are some links that should help
you understand not just what I mean by "concise-but-complete", but also
why it's so important to provide such a code example:

http://www.yoda.arachsys.com/csharp...10/08/29/writing-the-perf...http://sscce.org/

(Yes, I'm relying heavily on Jon Skeet's articles here…what can I say?
He's a smart guy, writes well, and on topics relevant to programming).
[...]
I have never had much luck understanding the rendering/paint events
and why, for example, when I use this.CreateGraphics() the graphics
object I get never seems to be able to draw to the screen.

It is definitely not correct to call CreateGraphics() in the OnPaint()
method.  In general, you shouldn't be using CreateGraphics() for drawing
at all, and in the OnPaint() method you should always be using the
Graphics object passed to you in the PaintEventArgs object.

In addition to that, it's a serious problem to not dispose of the
Graphics object that you created using CreateGraphics().  Underlying a
Graphics object is an unmanaged HDC, which depending on how it's created
may be a very limited resource (in the worst-case scenario, if .NET is
using the GetDC() function, there's something like five of them total).

Whether fixing your OnPaint() method will solve your problem, I don't
know.  The code you've posted is still neither concise nor complete.  It
may have other problems, in the parts you haven't posted.

Pete

The only thing that would be missing is the AppIdle event from
Program.cs
using System;
using System.Windows.Forms;

namespace Nimals
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
NimalMainForm NimalForm;

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

NimalForm = new NimalMainForm();
System.Windows.Forms.Application.Idle += new
EventHandler(NimalForm.Application_Idle);

Application.Run(new NimalMainForm());
}
}
}

That is literally every line of code I have. If the issue is that you
see something missing - than it's actually missing.
I created a new Windows Form project
I pasted the above in the Program.cs
I added the lines from before to the Form.cs
I created a new class and added the GameEngine code from above.

There is nothing more for me to be complete about. Now I'm at a rather
frustrated loss for where to go. You are telling me I am not posting
what you want but I don't have what you want but you won't tell me
what you want... I have no idea how to proceed if all you'll say is
"It's not complete." Believe it or not, the lack of what may or may
not be missing is not helping at all. Maybe if you made some slight
mention of what you think may be missing I can tell you if I have
written anything to do with that. Maybe you could make a suggestion as
to what I should have and I cant tell you that it's not there.

To simply contend that "it's not complete" is obtuse and frustrating.
I understand that not having access to the code base makes helping
difficul and I'm trying to do what I can, but the vagaries of your
demands are making really difficult.

Tom P.
 
T

Tom P.

Tom said:
[...]
To simply contend that "it's not complete" is obtuse and frustrating.

If you think that's all I've done, you haven't been reading my posts.
I've offered quite a bit of advice, even in spite of the lack of a good
code example, _and_ I've been clear about what I consider to be a good
code example.
I understand that not having access to the code base makes helping
difficul and I'm trying to do what I can, but the vagaries of your
demands are making really difficult.

I provided four different links describing exactly what I'm looking for.
  Perhaps if you're getting frustrated, you might try reading those
articles.

You _still_ haven't posted something that is complete, and yet at the
same time it's clear that if and when you eventually get around to
posting the rest of your code, so that the whole thing can be compiled
without adding anything else, it still won't be _concise_.  That is, you
have included plenty of things in the code you've posted so far that I'm
sure are not strictly required to reproduce the issue you're asking about..

Pete

Then I must thank you for your efforts and move on. Unfortunately, it
is becoming increasingly apparent that it is too difficult to get help
from you. The links you asked me to read admit to being bossy and one-
sided. If you are requiring me to re-write my code to get help, I
think I'll pass.

Again, thanks for the effort but I'll just hit it with a stick until
it works.
Tom P.
 
T

Tom P.

Okay, your choice.  So long as you understand that this isn't just about
getting help from me.  It's about getting help from anyone, especially
on a volunteer, unpaid basis.

If you get a better offer, you should definitely take it.  I doubt you
will though, until you embrace the ideals described in those articles.

Pete

For what it's worth, I think I found my problem.

In the above Program.cs you'll see that I instantiate a NimalMainForm
and set the Application.Idle event handler... then I Run() a new
object instance that has nothing to do with the one that I setup.

That's where my messagepump is getting hosed (I'll bet).

Tom P.
 
M

mp

Jeff Johnson said:
This is not a (Web) forum. It is a USENET newsgroup. You may be using a
Web interface to access posts (eww) but that doesn't make it a forum.



And there's your problem. http://bobpowell.net/picturebox.htm

Jeff,
dumb newby question...how do I get the project on that site to compile in
vcsExpress?
I read that article, created new windows forms application in csExpress,
copied and pasted Listing1 into Form1.cs(deleting the autogenerated code)
and get the following (first of many) error:
Error 1 Program '<Path to project>\WindowsFormsApplication1.exe' has more
than one entry point defined: 'WindowsFormsApplication1.Program.Main()'.
Compile with /main to specify the type that contains the entry point.

when I pasted the code listing into Form1.cs the ide created a Program.cs
and put some of the code in there...now there are two sub Mains...one in
Form1.cs and one in Program.cs....can i just delete Program.cs??? well I did
that and now the error is: Error 1
'WindowsFormsApplication1.Form1.Dispose(bool)': no suitable method found to
override


so i deleted that project and created an empty project, then added a form
and pasted listing 1 into Form1.cs
now the error is:
Error 1 'Project1.Form1.Dispose(bool)': no suitable method found to override

no clue what i'm doing wrong.
thanks
mark
 
J

Jeff Johnson

Jeff,
dumb newby question...how do I get the project on that site to compile in
vcsExpress?
I read that article, created new windows forms application in csExpress,
copied and pasted Listing1 into Form1.cs(deleting the autogenerated code)
and get the following (first of many) error:
Error 1 Program '<Path to project>\WindowsFormsApplication1.exe' has more
than one entry point defined: 'WindowsFormsApplication1.Program.Main()'.
Compile with /main to specify the type that contains the entry point.

when I pasted the code listing into Form1.cs the ide created a Program.cs
and put some of the code in there...now there are two sub Mains...one in
Form1.cs and one in Program.cs....can i just delete Program.cs??? well I
did that and now the error is: Error 1
'WindowsFormsApplication1.Form1.Dispose(bool)': no suitable method found
to override


so i deleted that project and created an empty project, then added a form
and pasted listing 1 into Form1.cs
now the error is:
Error 1 'Project1.Form1.Dispose(bool)': no suitable method found to
override

no clue what i'm doing wrong.

Well, stop with the "empty project" thing, for one. Create a new Windows
Forms application and then inject the code into it. But let's take a step
back. What "project on that site" are you referring to? Could you post the
URL to it?
 
M

mp

Jeff Johnson said:
Well, stop with the "empty project" thing, for one. Create a new Windows
Forms application and then inject the code into it. But let's take a step
back. What "project on that site" are you referring to?

sorry...the url you provided in your post to which I was responding...

on that page is a "listing 1"
that's what I pasted into Form1.cs in a new Windows
Forms application (before i tried the empty one and adding a form manually)

Could you post the
URL to it?

sure <g>
http://bobpowell.net/picturebox.htm

thanks for any insight...(maybe not all that code should go in form1?)
mark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top