Groups | Blog | Home
all groups > dotnet drawing api > october 2006 >

dotnet drawing api : Question on graphic objects disposing


pamela fluente
10/26/2006 4:08:12 AM

I have a question on object disposing.

On a form I have several controls that represent
document pages.

Each of these page has a Bitmap and a Graphics object associated.
When scrolling the document, pages are continuosly disposed of
and recreated on the fly. This is done dozen times for second.

If I declare the Bitmap and Graphics objects as Private within the
control,
do I have to dispose manually them when the control (ie the document
page)
is disposed? Or they are automatically disposed of ?

And what about they are declared Public. Does anything change?

Further, if I dispose the bitmap and the graphics based on that
bitmap, is the
disposing order important (I noticed sometimes I get exceptions due to
events still firing,
like mouse move when disposing)

-P
Patrick Steele
10/26/2006 2:03:44 PM
In article <1161860892.108149.138300@k70g2000cwa.googlegroups.com>,
pamelafluente@libero.it says...
[quoted text, click to view]

Any time you create an object that implements IDisposable, you should
Dispose of it when you're done. If you didn't create it (i.e. a
Graphics object is handed to you during a Paint event), then don't worry
about Disposing it.

You don't *have* to Dispose of anything yourself, but things work much
smoother if you do.

--
Patrick Steele
Bob Powell [MVP]
10/26/2006 9:55:24 PM
Disposing of and re-creating a bitmap while scrolling doesn't sound like a
good idea. Those are the sorts of things you want hanging around.

NEVER declare a field public! Its OOP not POO....

Don't keep Graphics object hanging about. Use 'em, dispose of 'em.

If an object implements IDisposable, dispose of it if you created it, leave
it for the caller to dispose of if it was passed to you.



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

Ramuseco Limited .NET consulting
http://www.ramuseco.com

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]

pamela fluente
10/27/2006 7:56:57 AM

Bob Powell [MVP] ha scritto:

[quoted text, click to view]

Thanks Patrick, Thanks Bob :)

The problem is the document can have hundred pages. I am currently
drawing only the pages which are visible to the user (they can be one
or dozens, depending on the zoom).

So far I could not see another solutions. Keeping in memory all the
pages bitmap would probably be impossible.

If you have a better strategy for doing this, please advise.

[quoted text, click to view]

right :)
[quoted text, click to view]

I guess there is no harm always disposing them. I was just wondering
if the graphics objects, associated with picture boxes on then
controls, get also disposed, when the control do...

-P
[quoted text, click to view]
pamela fluente
10/27/2006 2:45:08 PM
Thanks Joergen for all the brilliant ideas provided.

I will be working on them ! :)


Only one question. You mentioned the double buffering. I am working
with VS2005. I do not seem to see a difference when I use it. Is it
possible that it applied only to version 1.1. of the framework ? Or
perhaps it's because I am actually kind of doing it myself, painting on
an "invisible" bitmap and loading it as a picture box image only after
finished ?
I noticed a significant improvement by avoiding any refresh and simply
refreshing the pointer of the image, as this does not seem to cause any
refresh of possible objects layed out on the picture boxes (my pages).

-P

Joergen Bech ha scritto:

[quoted text, click to view]
Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/27/2006 6:51:06 PM

[quoted text, click to view]

Question is: Does it take significantly longer to render the pages
compared to saving/loading them from disk? In that case you might
consider caching the page "bitmaps" on disk. Only render pages when
new pages come into view. Read cached pages from disk when "old"
ones come (back) into view. Clear the cache if the zoom level is
changed.

But I wonder if you cannot just keep most of the pages in view anyway?

If the zoom level is high (like 100% or more), you can only see part
of a
single page. A single page should be easy to render in realtime,
requiring no caching at all. If the zoom level is low (such as 10%),
there is room for dozens of pages on the screen and though these
might take a bit longer to finish rendering, the resulting bitmaps are
so small that the price of caching them in memory would be very low.

My recommendation therefore would be to cache the most recent
<nn> number of pages visited in memory (put them in a queue) which
will fit in a buffer of roughly the size of 2-3 *screens* As new pages
are visited, the oldest ones in the queue are removed. So the number
of pages available in the queue would constantly depend on the
current zoom level. If the zoom level is changed, clear the queue
and start over.

As for performance: I have written a print layout system which uses
something like a hierarchical element layout system where each element
adjusts to its parent depending on a set of rules. Features row- and
column spanning, etc. Even with several hundred page elements,
calculating the layout and rendering those elements (even with inter-
polation enabled) takes little more than 50 ms full screen, double-
buffered. I don't think there should be any problems using the scheme
I propose, but then again, I do not know what you are rendering or
how well optimized your rendering funtions are.

Note: I presume that you are rendering your pages to scale rather
than storing the bitmaps at full size and rescaling them when
displaying them on the screen :)

[quoted text, click to view]

Yes, as long as you do not keep any other references to the images
(I presume we are talking about images and not the Graphics object
handed to you in the paint events) around, the garbage collector will
eventually reclaim the space. If you are just using plain old
picturebox controls, just remove the picturebox from your controls
collection and let the framework do the rest.

I, for one, would not constantly add/remove pictureboxes from a
controls collection (if that is what you are talking about), but just
render (using double-buffering) all the pages on a custom user
control.

/Joergen Bech


Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/28/2006 12:00:00 AM

There is nothing .Net-ish about double buffering. It is just
a technique.

When I do all my drawing in a usercontrol myself, I add
this to the user control code:

---snip---
Public Sub New()

' This call is required by the Windows Form Designer.
InitializeComponent()

' Add any initialization after the InitializeComponent()
call.
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
SetStyle(ControlStyles.UserPaint, True)
SetStyle(ControlStyles.ResizeRedraw, True)
SetStyle(ControlStyles.OptimizedDoubleBuffer, True)

End Sub
---snip---

But instead of using this method, it is perfectly acceptable to
create a bitmap in memory the size of the client rectangle of
the user control (remember to destroy and recreate it if the
size of the client rectangle changes), paint everything on
that buffer image, then blast the whole thing to the screen
using DrawImageUnscaled. Remember to override the
OnPaintBackground (comment out the MyBase call) so the
system does not paint anything on the background since
you are overwriting the whole thing yourself anyway.

If rendering "dynamic" elements on an image background,
you might consider using triple-buffering, i.e. one buffer with
the image, another for the scratch buffer where you combine
the image and dynamic elements, and finally the last "buffer"
which is the destination. Remember: Only destroy and recreate
buffers if their sizes actually change.

To elaborate on the simple, system double buffer example
I mentioned at the top, I have written this quick test you can
try out. Just start a new VB.NET 2005 project and replace the
Form1 code with this:

---snip---
Option Explicit On
Option Strict On

Imports System
Public Class Form1
Const USEDOUBLEBUFFERING As Boolean = True

Public Sub New()

' This call is required by the Windows Form Designer.
InitializeComponent()

' Add any initialization after the InitializeComponent() call.
SetStyle(ControlStyles.ResizeRedraw, True)

If USEDOUBLEBUFFERING Then
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
SetStyle(ControlStyles.UserPaint, True)
SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
End If

End Sub

Private Sub Form1_Paint(ByVal sender As Object, ByVal e As
System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim fnt As New Font("Arial", 200, FontStyle.Regular)
Dim sf As SizeF = e.Graphics.MeasureString("Test", fnt, -1,
System.Drawing.StringFormat.GenericDefault)
Dim loc As PointF = New PointF((Me.ClientRectangle.Width -
sf.Width) / 2, _
(Me.ClientRectangle.Height -
sf.Height) / 2)
e.Graphics.DrawString("Test", fnt, Brushes.Blue, loc)
End Sub

End Class
---snip---

Start the program. Try resizing the form. When USEDOUBLEBUFFERING
= True, there should be no flickering. When False, there is plenty of
flickering as the form is being cleared and repainted on-screen.

Note that although double buffering avoids *flickering*, you will
still notice some *tearing*. Flickering happens because areas are
cleared, then repainted in the visible space. Tearing happens because
the update is not in sync with the monitor, i.e. part of the image is
updated during one refresh and the rest of it during the next.

For most standard applications (not games), tearing is acceptable,
but flickering should be avoided. In my opinion, a flickering view
is a sign of incompetence and makes me question the quality of the
rest of program whenever I see it. I made a few video games many
years ago and yes, the first one did suffer from flickering (which was
not unusual at that time), but after that I made every effort to avoid
it. Something I have carried with me to application UI programming.

To avoid tearing in Windows apps today, the only way I know of is
to go with DirectX and take over the whole desktop, but that is hardly
relevant for anything but games (or Windows Vista with Aero enabled).

/Joergen Bech



On 27 Oct 2006 14:45:08 -0700, "pamela fluente"
[quoted text, click to view]
pamela fluente
10/28/2006 2:17:29 AM

Joergen Bech ha scritto:

[quoted text, click to view]

Thanks Joergen, very helpful.

Thanks for the beautiful example. Now I see what you meant.
Probably the fact that I never saw that flickering is due to the
fact that I am not working with the Paint event.

I am definining pages as controls containing a docked picture box
and within each page I define a graphics derived from the picture box
image.
These pages are rendered as "controls" of a scrollable panel
(representing the document).

Following the suggestions just provided I have just added some dispose
command on
the "page" (even though it is probably unnecessary (?))

Private Graphics As Graphics
Private Bitmap As Bitmap
....

Private Sub PageSurface_Disposed(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.Disposed

' here I dispose : Me.Graphics, Me.Bitmap

End Sub

My document layout is "bidimensional" (I have pages in a grid, like a
crosstab
of pages) and on the pages there are several object (like very large
tables or images:
a table can span even 10 pages or more bottom and 100 pages right)
that are split on the
pages. Every object is recomputed and split in portions on each page
and only the pages
that are currently visible to the user are drawn (or rebuilt if size
changed) each time.

In my strategy I do not need to DrawImageUnscaled: I simply refresh
the pointer to
the image and this causes the newly painted page to appear smootly...
Do you think I should make changes to this strategy? I really did NOT
"know" how to do it,
but this one is the result of many attempts I made to find the
"smoothest" result ...
I may be missing something important though.


[quoted text, click to view]

In the previous post you mentioned an application of yours where you
solve a similar problem. If it is possible and there is some kind of
downloadable
version or demo, I would like to take a look at the degree of
smoothness you have
achieved to have an idea of what we are talking about and the kind of
result I should aim to.

Thanks a lot,

-P

[quoted text, click to view]
pamela fluente
10/28/2006 7:07:48 AM

[quoted text, click to view]

Thanks a lot Joergen,

this is very helpful. It's really kind of you to provide such quality
help.

Actually based on your suggestion of using the drawimage function for
the pages, I am actually thinking to modify my current strategy. I am
thinking to replace the controls (which usually are relatively "heavy",
with directly drawn images). Actualy the images I already have, because
they are just the bitmap I currently load as image in the picture
boxes.

The only complication is that would change the coordinates of the mouse
events (I have a lot of mouse processing, dragging and so on, on the
pages). But I expect the effort could be worth. Drawing directly the
pages on a bitmap which represent the vievable portion of the document
should be much quicker than addin the page controls.

Another complication I can foresee is with scrolling. Imagine I have a
100 pages vertically and some othe 10 horizontally. If I place the
pages as picture boxes in a scrollable panel it's easy to scroll and to
dispose them when go out of scope. I am not sure I will be able to
handle this on a bitmap (representing the document). Hmmm, In this case
the bitmap would remain "still" and what change is just the portion of
document which is projected on to it...

-P
pamela fluente
10/28/2006 12:38:01 PM


Joergen Bech ha scritto:

....
[quoted text, click to view]


Thanks Joergen. Very very helpful.

I am going to rewrite completely my editor based on you enlighting
suggestions :)

-P




[quoted text, click to view]
Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/28/2006 12:58:02 PM
On Sat, 28 Oct 2006 09:24:00 +0200, Joergen Bech

[quoted text, click to view]

I think I have my terms mixed up a bit. Check
http://en.wikipedia.org/wiki/Triple_buffering
for a better definition.

Let's just call it "off-screen compositing using as many buffers
as necessary".

[quoted text, click to view]

Let me put that in more diplomatic terms before I start coming across
like something I'm not: Flickering detracts from my/the overall user
experience. I do not like it myself, hence I strive to eliminate it
from my own programs/controls.

/Joergen Bech


Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/28/2006 1:19:57 PM

[quoted text, click to view]

There is no right or wrong way to do things. "My" way requires more
work, but offers more control. "Your" way might be quicker to
implement.

If it looks right and feels right, it is right.



[quoted text, click to view]

Sorry. Would love to release my little print layout/preview/pdf export
framework, but it is a small part of a commercial product for a niche
market (one of those: if you are not in the market, you'll never hear
about it). It is not mine to give away, even though it is just a
generic component.

Just fire up Microsoft Word, fill a page with text and various
graphical elements, put it in landscape mode, do a print preview
wit view set to Whole Page, then resize the preview window.
Pretty fast update.

Again: If it looks ok, you are done.

I can, however, offer two more tips:

1. Use Process Explorer from SysInternals to check if the memory or
(gdi) handle count fluctuates wildly when you constantly resize
your preview window.

2. Use the MemoryWatch class I wrote for my own use (see below)
to check how much memory your buffers are eating up. You can
check MemoryTotal either after each refresh or in a timer event
(write the value to debug or the caption of your form).

I don't think you should be concerned with buffers taking up
20-30MB, but if the value sometimes shoots over 100MB or more,
you probably need to rethink your strategy, as PCs equipped with
lesser amounts of RAM might not provide the same experience
once they hit the ceiling.

/Joergen Bech

---snip---
Option Explicit On
Option Strict On

Imports System.Diagnostics

Public Class MemoryWatch
Private _Total As Long 'Total memory used at last call to
Start or CheckPoint
Private _Start As Long 'Memory used at the time of the
last call to Start
Private _Previous As Long 'Previous _Total - _Start value

Public Sub New()
Start()
End Sub

Public Sub Start(Optional ByVal forceFullCollection As Boolean
= True)
Me._Total = System.GC.GetTotalMemory(forceFullCollection)
Me._Start = Me._Total
Me._Previous = Me._Total
End Sub

Public Sub CheckPoint(Optional ByVal forceFullCollection As
Boolean = True)
Me._Previous = MemoryUsed(False)
Me._Total = System.GC.GetTotalMemory(forceFullCollection)
End Sub

Public ReadOnly Property MemoryUsed(Optional ByVal
SetCheckPoint As Boolean = False) As Long
'Returns the amount of extra memory used relative to
_Start at the last call to Start or CheckPoint
Get
If SetCheckPoint Then
CheckPoint()
End If
Return Me._Total - Me._Start
End Get
End Property

Public ReadOnly Property MemoryTotal(Optional ByVal
SetCheckPoint As Boolean = False) As Long
'Returns the total amount of memory used at the time Start
or CheckPoint was called
Get
If SetCheckPoint Then
CheckPoint()
End If
Return Me._Total
End Get
End Property

Public ReadOnly Property MemoryDelta(Optional ByVal
SetCheckPoint As Boolean = False) As Long
'Returns the difference in memory used between last call
to CheckPoint and the previous call
'to Start or CheckPoint.
Get
If SetCheckPoint Then
CheckPoint()
End If
Return MemoryUsed(False) - Me._Previous
End Get
End Property

End Class

---snip---

Joergen Bech <jbech<NOSPAM> NO[at]SPAM
10/28/2006 6:23:31 PM

[quoted text, click to view]

Just add a horizontal and vertical scrollbar to the usercontrol.
Recalculate their positions, sizes, max values, and visibility every
time the control is resized. Use current values as offsets when
processing subsequent mouse events. When calculating
whether or not to show either vertical or horizontal scrollbar,
remember that the appearance of one limits the space available
on the other axis, which suddenly might require that the other
is shown as well :)
....

An easier solution might be to set the size of your user control
to encompass the pages you need, place that single user control
on a scrollable panel, thereby getting the scrollbars for free.
Only paint on the part of the user control which is visible.

In this case you should do any buffering yourself. Do NOT use
SetStyle(...) to let the system handle the buffering, as it will
create a buffer the size of the control.

Try out the code below. Start a new VB.NET project, add
a new UserControl named "HugeControl" with the code
shown below. Set Form1.AutoScroll = True. Add an instance
of HugeControl to Form1 and set its size to 30.000 x 30.000.

Yes, a 30.000 x 30.000 pixel control with double-buffered
rendering without taking up more memory for the buffer than
the size of the screen (make sure you read the comments).

/Joergen Bech

---snip---
Option Explicit On
Option Strict On

Public Class HugeControl
Private buffer As Bitmap

Public Sub New()

' This call is required by the Windows Form Designer.
InitializeComponent()

' ' Add any initialization after the
InitializeComponent() call.

'NO! Do NOT use the system's double buffering. A buffer
matching the
' full size of the control would be created, even if we are
only
' going to show a small portion of it.
' If you enable this code, memory will disappear and the
app might
' even crash.
' SetStyle(ControlStyles.AllPaintingInWmPaint, True)
' SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
' SetStyle(ControlStyles.ResizeRedraw, True)
' SetStyle(ControlStyles.UserPaint, True)

'We are being lazy here by just once and for all creating a
buffer
'the size of the primary screen. In the case that the primary
screen
'is smaller than a secondary screen and we are displaying the
application
'on the secondary screen, this is a really, really bad idea.
buffer = New Bitmap(Screen.PrimaryScreen.Bounds.Width, _
Screen.PrimaryScreen.Bounds.Height, _

System.Drawing.Imaging.PixelFormat.Format32bppPArgb)

End Sub

Private Sub HugeControl_Paint(ByVal sender As Object, ByVal e As
System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim count As Integer = 0

Dim offsetX As Integer = -e.ClipRectangle.X
Dim offsetY As Integer = -e.ClipRectangle.Y

Dim gBuffer As Graphics = Graphics.FromImage(buffer)

gBuffer.FillRectangle(New SolidBrush(Me.BackColor), New
Rectangle(0, 0, e.ClipRectangle.Width, e.ClipRectangle.Height))

For x As Integer = 0 To Me.ClientRectangle.Width Step 64
Dim x255 As Integer = CType((x / Me.ClientRectangle.Width)
* 255, Integer)
For y As Integer = 0 To Me.ClientRectangle.Height Step 64
Dim y255 As Integer = CType((y /
Me.ClientRectangle.Height) * 255, Integer)
Dim clr As Color = Color.FromArgb(x255, x255, y255)
Dim destrect As New Rectangle(x, y, 64, 64)
If e.ClipRectangle.IntersectsWith(destrect) Then
Dim bufferrect As New Rectangle(x + offsetX, y +
offsetY, 64, 64)
gBuffer.FillEllipse(New SolidBrush(clr),
bufferrect)
count = count + 1
End If
Next
Next

e.Graphics.DrawImageUnscaled(buffer, e.ClipRectangle)

gBuffer.Dispose()

Debug.WriteLine("Count: " & count.ToString)

End Sub

Protected Overrides Sub OnPaintBackground(ByVal e As
System.Windows.Forms.PaintEventArgs)
'Do nothing here. Do not call the base event handler.
End Sub

End Class
---snip---


pamela fluente
10/30/2006 1:05:09 AM

Joergen Bech ha scritto:


[quoted text, click to view]


Hello Joergen,

I wanted just let you know I have rewritten the code for layout
visualization.
I have removed the controls (pages) layed out on a scrolling panel.

As you suggested, I have used a viewport with a couple of scrollbars
(vertical and horizontal).
Only the pages visible are plotted on the viewport and their origin
(the "starting point" of layout) is shifted using the bars to give the
impression of scrolling. I draw the pages on a bitmap (buffer) which is
loaded into the viewport after finishing drawing.

The final result is much smoother and faster. I have not measured it,
but it's probably more
than 10 times faster.

I still have to refine some details (there is something strange going
on with the horizontal bar)
scrolling but I am quite happy with that . Thanks :)

-P
AddThis Social Bookmark Button