Groups | Blog | Home
all groups > dotnet drawing api > may 2005 >

dotnet drawing api : Invalidating a region and having only that region drawn


Andrew
5/26/2005 6:57:01 PM
Hello,

In my application I perform all the drawing operations from within the form's
OnPaint method (the form is ultimately a TabPage derivative) ie

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//the base class LayerView.OnPaint handles things like AutoScrollPosition
int s = 1; // temporary dummy scale value
m_building.DrawBuildingLayer(g,s,this.Name);
m_columns.DrawColumnsLayer(g,s,this.Name);
}

The drawing works well and smoothly handles operations such as scrolling.

It is my understanding that when Invalidate() is called ie with no
parameters, the entire client area of the form is added to the update region
and a paint message is sent that raises the Paint event which is handled by
the OnPaint handler. OK

When Invalidate(region) is called, I believe that only that region is added
to the form's update region resulting in only that region being redrawn at
the next Paint message.

Either way, calling Invalidate results in OnPaint to be executed.

When I call Invalidate() the entire client area is redrawn with both drawing
functions called and this is exactly the outcome that I want for that
scenario.

The problem is that when I only want to redraw a invalidated region of this
form,
the entire form is redrawn( which is causing objects to be appear
incorrectly).

ie When I call Invalidate(with a specific region) in order to only have that
region redrawn I do NOT intend for the likes of DrawBuildingLayer nor
DrawColumnsLayer
to be called. Obviously they are being called because of their presence in
OnPaint().

I cannot think of any other place where I could call these draw functions
other than OnPaint and I want to steer clear of CreateGraphics.

Are there any suggestions regarding a strategy of how I could call
Invalidate(region) and only have that region redrawn ?


Thanks

Andrew.
Andrew
5/29/2005 6:36:01 PM
Does anyone have any feedback regarding this post ?

[quoted text, click to view]
Bob Powell [MVP]
5/30/2005 11:40:55 AM
Painting works like this:

The system stores the results of invalidate calls internally. Invalidate
without parameters will mark a whole window for refreshing, invalidate with
parameters will add regions to the list of "dirty" areas maintained by the
system.

When the queue is empty and there is nothing else to do, the system checks
for dirty areas and issues a WM_PAINT message if any are found, otherwise it
issues a WM_ENTERIDLE message to kick-off idle time processing.

In all cases the result of the WM_PAINT message, for Windows Forms programs,
is to run the OnPaint override which in turn kicks of the Paint event.

The clipping region set in the OnPaint event arguments will be the sum of
all the regions or the whole window depending on whether Invalidate was
called without parameters.

It's up to your code to either keep a list of its own dirty rectangles /
regions OR to do output based on the bounds of the clipping region provided
by the paint event arguments. Whatever paint strategies you devise, such as
only drawing partial content or layers, is entirely up to you and needs to
be managed by you. The system doesn't care about reporting anything except
that there is an area of screen marked as being in need of a refresh.

--
Bob Powell [MVP]
Visual C#, System.Drawing

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.





[quoted text, click to view]

Andrew
5/30/2005 9:41:04 PM
Thanks,

I will need to rethink my drawing strategy paying closer attention to the
clipping region.


[quoted text, click to view]
Doug Forster
6/1/2005 12:00:00 AM
Hi Andrew,

I could be wrong but I get the feeling you haven't fully understood what Bob
was saying. You shouldn't be attempting to use clipping at all to manipulate
the logical content of a window. The job of a paint handler is *just* to
paint part or all of a window with the correct content of the moment as
ascertained from state maintained external to the whole painting paradigm.
You should remember that Windows itself can invalidate any part of your
window at any time. This is not specific to .NET but is simply how Windows
has always worked.

Cheers
Doug Forster

[quoted text, click to view]

Andrew
6/5/2005 8:25:01 PM
Doug,

Thanks for your comment.

Andrew.

[quoted text, click to view]
Doug Forster
6/8/2005 12:00:00 AM
Hi Andrew,

No doubt there are many approaches, but I would usually keep a bounds
rectangle with each logical object and simply test it for visibility within
the clip rectangle before drawing it. Windows clipping is fairly efficient
so I would just leave it to deal with the finer details. You could, with
some programming effort, subset your objects to the clip region (or
rectangle) and just draw those bits but I doubt that you would be better off
unless your objects are very large (*perhaps* it might be worth it for large
bitmaps). You can keep the clip rectangle smaller by forcing repaints as you
invalidate and invalidating as little as possible.
The only issues that I can think of that really matter are flicker and user
response time. Unless you actually have a problem with these its usually not
worth spending too much effort on clipping.

Cheers
Doug Forster

Andrew
6/8/2005 5:29:03 PM
Hi Doug and Bob,

Having 'returned' from another section of my project, I have now had a
better chance to digest the responses that have been provided on my original
post.

I now have a clearer understanding of how painting works, however my
original question still remains ie just where does one place one's drawing
code such that it does not get unncessarily called during the paint event
handling operation ?

I have placed my layer drawing routines in my TabPage derived OnPaint
handler because that drawing code requires a Graphics object and without
resorting to CreateGraphics() where else can I get a graphics object other
than from the PaintEventArgs object of OnPaint ??

My application has five such TabPage derived classes which exist in a
TabControl. Clicking on any TabPage draws that 'view' by calling specific
drawlayer routines such as the one I originally described. Because OnPaint is
called quite often (such as just after window creation, window resizing(not
relevant here), window refocus; this was another reason for placing my
drawlayer routines in OnPaint becasue it effectivley displays an initial
number of graphical objects. When such initial detail is displayed, the stage
is set on that view for users to alter the appearance, add delete to the
initial content. And as the designer, I want the 'behind the scenes' drawing
operation to be as efficient as possible.

Take one such example where this initial view portrays a large rectangle
with ruler lines and dimension numbers - that represent a plan view of the
building outer. Now within this exist say half a dozen pairs of small
rectangles that represent the structural supports of the building outer. A
user selects one of these support rectangles which changes both their color
(and that of their dimensions/rulers) from gray to blue ie the select color.
The user can then reposition that support rectangle pair.

Behind the scenes: A. for the object selecting operation, I invalidate two
rectangles that represent the rectangle pair and its ruler/dimension situated
above that pair.
B. For the resizing code, after data
validation, I invalidate the entire client area because many other support
rectangles could also be moved.

The problem is that with operation A. I believe that the clip region of
PaintEventArgs contains both of my invalidated regions and hence I would only
expect those parts of the view to be redrawn - however because my drawlayer
code is situated in OnPaint (for the reasons mentioned above) the entire
initial structure is redrawn. It looks fine however I only want the
invalidated sections to be redrawn.

I am now writing small graphical objects which will represent locks (that
will act to lock the position of any selected support frame pair) and I
certainly do not want to have to redraw the entire initial structure when the
user simply clicks to change to graphical appearance of one of these lock
objects.

So considering all of the above, do you have any suggestions regarding how
to handle the objectives of initial display and fine detail update ?

Many thanks

Andrew.

[quoted text, click to view]
Bob Powell [MVP]
6/10/2005 12:00:00 AM
Invalidation schemes that work well often take notice of the intersection of
the clipping region with the objects drawn.

It's neccesary to draw all of any object that impinges upon that region but
the unused pixels are suppressed by the clipper.


--
Bob Powell [MVP]
Visual C#, System.Drawing

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.





[quoted text, click to view]

Andrew
6/10/2005 1:27:03 AM
Thanks Doug,

As mentioned, my drawlayer code is situated in my OnPaint handler.

With no attention paid to double buffering, I am getting what *appears* to
be good drawing performance regardless of whether my code is invalidating the
entire screen or a rectangle. There is a slight flicker which I will fix with
double buffering.

When I invalidate a rectangle, YES it appears that only that rectangle is
being redrawn (due to a slight flicker only in that rectangle) however my
debug code indicates that every graphical object is indeed being executed !!??

Of course this is the case due to the location of my drawing code - but
where else can it go.

Can you explain this apparent contradiction, which seems to make a mockery
of the entire invalidation approach ?

I have seen a simplistic example in the .NET notes where a rcDraw rectangle
is generated in OnMouseDown/OnMouseUp handlers which is drawn in the OnPaint
handler. In trying to relate this to my situation, I do not explicity draw an
invalidated rectangle. Yes I am invalidating rectangles and Yes I assume that
they are accumulating in the clipping region, but again, I do not actually
mention any such rectangles in the draw layer routines.

I do have a bounds rectangle for each graphical object, however each object
is stored in a SortedList derived class, the contents of which are being
sequentially called by the draw layer routines.

It is curious that when I invalidate a region, whilst only the object in
that region appears to be drawn, the draw code for every object is actually
being called, but produces absolutely no flicker (just as if it were not
being redrawn at all) ??

I am trying to achieve a situation where : When I invalidate a rectangle,
not only does it appear that only the contents of the rectangle is being
redrawn, ONLY the draw code for the objects in that rectangle is executed.

Thanks

Andrew
[quoted text, click to view]
Doug Forster
6/12/2005 12:00:00 AM
Hi Andrew,

[quoted text, click to view]

As it should be

[quoted text, click to view]

Yes, as I tried to say, you have to explicitly manage this yourself

[quoted text, click to view]

Its not a contradiction, it is simply how Windows works. I think your
expectations are incorrect.

[quoted text, click to view]

So all you have to do is test for intersection with the clip rectangle and
not draw the object if no intersection

[quoted text, click to view]

Again, this is how Windows clipping works. Although you execute the motions
of drawing, Windows ensures only pixels in the invalid region are actually
drawn.

[quoted text, click to view]

Once again, YOU have to deal with this. There is no underlying magic (apart
from clipping) that does it for you.

Cheers
Doug Forster

Andrew
6/12/2005 3:04:02 AM
Thanks Bob,

Yes, even as I was writing my posts, I was doubting the effectiveness of my
drawing strategy.

I am going to rethink my approach, reflecting upon the comments that have
been provided (thanks).

Due to the pressures of this project I have to get on and implement more of
the graphics functionality. However as I proceed I will be scrutinising just
how I go about drawing any items on the screen.

I will let you know how things progress.

Regards

Andrew.
Tamworth N.S.W,
Australia.

[quoted text, click to view]
Andrew
6/13/2005 7:09:01 PM
Thanks Doug,

A couple of days ago, after rethinking my drawing approach and reflecting on
the comments that were provided, I devised an approach that exactly coincides
with your last advise.

I am in the middle of implementing the changes now and (as you and Bob have
been implying) yes the concept of invalidating and drawing rectangles is
quite straight forward.

So far the required changes to my drawing code are minimal.

I am soon to test the revised drawing strategy and will let you know how
things go.


Regards

Andrew.


[quoted text, click to view]
Andrew
6/15/2005 10:14:04 PM
Hi Bob and Doug,

My drawing strategy is now working fine !

I am only drawing a graphical object if it lies within the PaintEvent's
ClipRectangle.

Yes I can see that even if accumulate several dirty rectangles via several
calls to Invalidate(rectangle) - Windows handles the situation very
efficiently by sending ONE WM_PAINT message. Only the draw code for the
objects that exist within the Invalidated region(s) is executed.

The drawing performance is great (and I am confident of correcting the
slight flicker
with double buffering).

Thanks for your help.

Regards

Andrew.
Australia.

[quoted text, click to view]
AddThis Social Bookmark Button