Reputation: 466
My understanding of invalidate is that it adds a flag to a region saying that the region needs updating, and then when there's nothing else to do, the region gets updated. When that happens, everything in the region gets deleted and then redrawn by the subscribers to the paint-event, as defined in the constructor of the forms-class.
But when I click on the reversi board of my program (a windows forms application), the reversi piece gets drawn where I clicked, yet the indicators for what is a valid move (drawn by DrawEllipse) stay on the screen, even though they're drawn in the same method that the piece gets drawn in. The valid move indicators do disappear when I drag the window off screen and back.
What am I doing wrong?
For completeness, my code is here (the most important comments are in english, some of the comments are still dutch):
void tekenGrid(object obj, PaintEventArgs pea)
{
int vakjeBreedte = speelbordPanel.Width / Math.Max(r, k);
int speelstukDiameter = vakjeBreedte - 6;
Pen mijnPen = new Pen(Color.Black, 2);
Graphics gr = speelbordGraphics; //we use the graphics object specifically made for the playing board
gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
for (int rij = 0; rij < r; rij++) //lets make the grid, in this for-loop
{
for (int kolom = 0; kolom < k; kolom++)
{
{
int x = vakjeBreedte * kolom + 3; //x,y coordinaat van de vierhoek waar de cirkel in zit
int y = vakjeBreedte * rij + 3;
gr.DrawRectangle(mijnPen, vakjeBreedte * kolom, vakjeBreedte * rij, vakjeBreedte, vakjeBreedte); //gives the playing board black rectangles around every position
if (speelbord[kolom, rij] == speelstuk.Blauw) //draw the blue pieces
{
gr.FillEllipse(Brushes.Blue, x, y, speelstukDiameter, speelstukDiameter);
}
else if (speelbord[kolom, rij] == speelstuk.Rood) //draw the red pieces
{
gr.FillEllipse(Brushes.Red, x, y, speelstukDiameter, speelstukDiameter);
}
if (laatHintsZien == true) //the legal move function indicator is here
{
if (checkLegaalheid(kolom, rij))
{
gr.DrawEllipse(mijnPen, x, y, speelstukDiameter, speelstukDiameter);
}
}
}
}
}
}
Upvotes: 1
Views: 1137
Reputation: 941665
everything in the region gets deleted
That is probably the biggest misconception that produces problems like this. You can't "delete" a pixel on the screen. It is always there and has a particular color. If it has the wrong color then its your job to give it another one.
The paint cycle in Windows proceeds as follows:
Your window needs to be painted whenever its content is out of date. That's done explicitly by calling Invalidate(), your case, or when the window manager can figure it out for itself, like when you drag the window off-screen and back or when the user resizes the window. On old Windows versions before Aero, also when another window is moved across yours.
First thing the OS does is create a "device context" for the window, it directs anything you paint to the window surface. The Graphics class is the .NET wrapper for the device context. At this point you are supposed to go "uh-oh", that device context of course doesn't match the one you created earlier and stored in your speelbordGraphics
variable. In particular, its ClipBounds property can be wrong, that happens when the window size changed.
Next it determines what part of the window needs to be repainted, exposed by PaintEventArgs.ClipRectangle property. You are supposed to go "uh-oh" again here. Anything you draw outside of that clip rectangle doesn't actually make it to the window surface. It is an optimization, one that cannot work in your code.
If you set the DoubleBuffered property to true then Winforms creates another device context, it directs paints to an in-memory bitmap so everything you draw doesn't go straight to the screen but to the bitmap. It helps suppress visible painting artifacts, usually called "flicker". Something you will really like for your game board. You are supposed to exclaim "uh-oh" again here, what you paint through speelbordGraphics
goes the screen surface, not that bitmap.
Next, the OnPaintBackground() method of your form runs. If you did not override it then the pixels in the clip rectangle are set to the BackColor color with Graphics.FillRectangle(). This is the closest analogy to "everything in the region gets deleted". Yet another reason to go "uh-oh", your speelbordGraphics
allows you to slam pixels to the screen directly but without painting the background. The side-effect of doing that is you won't see pixels being set back to the BackColor as expected. It looks like they don't get "erased". A strong match for what you observe.
Next, the OnPaint() method of your form runs. It fires the Paint event, no doubt your tekenGrid
method runs.
If the DoubleBuffered property is set to true then Winforms now copies the bitmap to the screen surface with a fast bitblt. Another "uh-oh" here, that overpaints anything you drew through speelbordGraphics
. This is pretty easy to notice, you'll see what you draw for just a fraction of a second, looks like a massive case of flicker. The exact opposite of what DoubleBuffered is supposed to accomplish :)
Collecting all the "uh-oh"s in the previous bullets, they all have a single cause. They are all caused by using speelbordGraphics
. You called it 'handy' but it isn't handy at all, it causes bugs. And worse, it is inefficient. A device context is very cheap to create, well less than a microsecond, but expensive to keep around. Those device contexts mentioned in the bullets get created anyway, your variable does not optimize them away. And it occupies memory from a heap that all processes on the same desktop allocate from. The heap size is limited, it can only store 65535 graphics objects for appcompat reasons.
So the fix is very simple, just delete the variable. There is no way to get it wrong now. And the compiler will tell you where you did it wrong, it will force you to use the Graphics object you got from e.Graphics in your Paint event handler. You'll inevitably discover and fix your bug.
Upvotes: 4