update graphics between debugger stepping

  • Thread starter Thread starter Ben Voigt
  • Start date Start date
B

Ben Voigt

Ok, while troubleshooting painting code, I am trying to see my canvas after
each step. My OnPaint handler ultimately updates the screen with:
pe.Graphics.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);

So I tried writing the following, and calling it from the watch window
and/or debug window:
internal void BlitNow()
{
using (Graphics g = CreateGraphics())
{
g.FillRectangle(Brushes.Red, new Rectangle(0, 0, 500, 500));
g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);
g.Flush();
}
}

Doesn't do a thing. Why? Tried entering this in the watch window:
g = CreateGraphics()

Inspect g, VisibleClipBounds is {X = 0.0 Y = 0.0 Width = 339.0 Height =
537.0}
g.FillRectangle(Brushes.Green, g.VisibleClipBounds)
g.Flush()

Nothing happens. I think at this point my entire control should be green.
Where is .NET buffering my output and what method do I call to force .NET to
show me immediately? I am not using the DoubleBuffered or
OptimizedDoubleBuffering control style flags, since I manage my own
off-screen buffer.
 
Ben said:
Ok, while troubleshooting painting code, I am trying to see my canvas
after each step. My OnPaint handler ultimately updates the screen
with: pe.Graphics.DrawImageUnscaled(bmpCanvas, -scrollOffset.X,
-scrollOffset.Y);
So I tried writing the following, and calling it from the watch window
and/or debug window:
internal void BlitNow()
{
using (Graphics g = CreateGraphics())
{
g.FillRectangle(Brushes.Red, new Rectangle(0, 0, 500, 500));
g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);
g.Flush();
}
}

Doesn't do a thing. Why? Tried entering this in the watch window:
g = CreateGraphics()

Inspect g, VisibleClipBounds is {X = 0.0 Y = 0.0 Width = 339.0 Height
= 537.0}
g.FillRectangle(Brushes.Green, g.VisibleClipBounds)
g.Flush()

Nothing happens. I think at this point my entire control should be
green. Where is .NET buffering my output and what method do I call to
force .NET to show me immediately? I am not using the DoubleBuffered
or OptimizedDoubleBuffering control style flags, since I manage my own
off-screen buffer.

I don't know if it's relevant to the issue you're encountering, but... one
very useful technique for debugging paint routines is to run your
application on a VPC on your workstation and use remote debugging to debug
it. This works best on a dual monitor system with the VPC maximized on one
monitor and visual studio maximized on the other. That way your application
and the debugger are running on different desktops and the debugger coming
into focus doesn't actually invalidate your application's window(s).

-cd
 
Hi Ben,

I am not sure if I understand you completely. Based on my understanding,
you use debugger to set a breakpoint in the paint handler of the
form/control. When you want to single step through each code statement in
the paint handler, you want to see the actual visual painting effect of
each code statement. If I have misunderstood you, please feel free to tell
me, thanks.

Yes, this is a by design behavior. Below is the detailed information
regarding what happens:
The painting handler code executes in the main GUI thread of .Net winform.
If you use debugger to set a breakpoint in the paint handler, the main GUI
thread is break by the debugger.
Once the WM_PAINT message is sent to your paint handler, the breakpoint is
hit, which the debugger is notified with the breakpoint. At this point, all
the graphics/control UI that is drawn by the executed code is displayed on
the screen. However, the debugger code will suspend the GUI thread and
bring the IDE/debugger to the front, this operation will immediately erase
all the graphics/control UI the paint handler just draws. These operations
are all too fast, so you may not see the graphics/control UI.

While you are single stepping through each code statement of the paint
handler, you will find the winform window as a white rectangle without any
graphics on it. This is the same reason. When Windows recognize that you
want to display the winform, it will dispatch WM_PAINT message to winform's
Painting handler, who takes the responsibility to paint the
graphics/control UI. However, at this time, the Painting handler's main GUI
is still being suspended by debugger, so we can not execute all the code in
the painting handler, the UI remains as a white rectangle.

So you can only see the .Net winform painting effect without breaking the
Painting handler. With debugging the Painting handler, you can only verify
the logic of your code with single stepping, the visual effect can only be
visible with running at full speed.

Do you fail to see the g.FillRectangle filled rectangle on the screen
without breaking in debugger? Based on my review, it seems that you use
CreateGraphics() to create a new Graphics object for painting. Normally, we
should use the Graphics object provided by the paint handler. For example,
the code snippet below works well on my side:

protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Green, e.Graphics.VisibleClipBounds);
base.OnPaint (e);
}

If you still need help, please feel free to feedback more information and I
will work with you. Thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
Carl Daniel said:
I don't know if it's relevant to the issue you're encountering, but... one
very useful technique for debugging paint routines is to run your
application on a VPC on your workstation and use remote debugging to debug
it. This works best on a dual monitor system with the VPC maximized on one
monitor and visual studio maximized on the other. That way your
application and the debugger are running on different desktops and the
debugger coming into focus doesn't actually invalidate your application's
window(s).

I have dual monitors, so the form is not covered by the debugger, so it's
not being invalidated.
 
"Jeffrey Tan[MSFT]" said:
Hi Ben,

I am not sure if I understand you completely. Based on my understanding,
you use debugger to set a breakpoint in the paint handler of the
form/control. When you want to single step through each code statement in

And/or subfunctions called by the paint handler.
the paint handler, you want to see the actual visual painting effect of
each code statement. If I have misunderstood you, please feel free to tell
me, thanks.

Since I've allocated a memory bitmap, there is no visual effect of each
statement. My paint handler ends by flipping the offscreen canvas to the
screen with:
pe.Graphics.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);

So I tried to write a method BlitNow and call it from watch window or
immediate window to see the offscreen buffer being built up.
internal void BlitNow()
{
using (Graphics g = CreateGraphics())
{
g.FillRectangle(Brushes.Red, new Rectangle(0, 0, 500, 500));
g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);
g.Flush();
}
}

I also tried g.Flush(FlushIntention.Flush). Still nothing.

All my drawing code works on a Graphics for the bmpCanvas, so pe.Graphics
isn't in scope. In addition, some updates to bmpCanvas are performed during
OnLayout. So I used CreateGraphics() in BlitNow. I considered that without
calling Flush and/or Dispose on the Graphics associated with bmpCanvas, the
DrawImageUnscaled command might not work (because graphics commands could be
buffered and thus not apparent on bmpCanvas, and Win32 API won't let you
SelectObject a bitmap into two DCs at once). But I would still expect the
rectangle to appear... it didn't, even for cases when bmpCanvas.Height <
500 and Control.Height > 500, when no matter what image was in the bitmap it
could not have obscured the entire rectangle. Also, immediately after
recreating bmpCanvas, before creating a Graphics object for it, I got the
same behavior (probably was 100% transparent, but should have drawn with no
errors leaving me with the rectangle).
 
Hi Ben,

Thanks for your feedback!

Can you be specific about what effect you want to achieve? Can you provide
detailed description regarding this?

I suspect that you have some misunderstanding over the code snippet below:
internal void BlitNow()
{
using (Graphics g = CreateGraphics())
{
g.FillRectangle(Brushes.Red, new Rectangle(0, 0, 500, 500));
g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);
g.Flush();
}
}

In the code, CreateGraphics() method is a member method of Control class,
which will create a Graphics object for the control.(Graphics object
encapsulates a GDI+ drawing surface). So, all the following
g.FillRectangle, g.DrawImageUnscaled will take effect over this control
surface, not over bmpCanvas surface. Graphics object *g* has no relation
with bmpCanvas memory bitmap.

Regarding g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y),
GDI+ will read the memory bitmap bmpCanvas and write to the surface *g*
represents. So the result is drawn to the control surface, not bmpCanvas.

Is this the logic you want to complete?

If I still have misunderstood your key point, pleasse feel free to create a
little sample project with detailed steps to demonstrate what you want to
achieve and what the actual problem is.

Thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
"Jeffrey Tan[MSFT]" said:
Hi Ben,

Thanks for your feedback!

Can you be specific about what effect you want to achieve? Can you provide
detailed description regarding this?

I suspect that you have some misunderstanding over the code snippet below:
internal void BlitNow()
{
using (Graphics g = CreateGraphics())
{
g.FillRectangle(Brushes.Red, new Rectangle(0, 0, 500, 500));
g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);
g.Flush();
}
}

In the code, CreateGraphics() method is a member method of Control class,
which will create a Graphics object for the control.(Graphics object
encapsulates a GDI+ drawing surface). So, all the following
g.FillRectangle, g.DrawImageUnscaled will take effect over this control
surface, not over bmpCanvas surface. Graphics object *g* has no relation
with bmpCanvas memory bitmap.

Regarding
g.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y),
GDI+ will read the memory bitmap bmpCanvas and write to the surface *g*
represents. So the result is drawn to the control surface, not bmpCanvas.

That's my intention.

Is this the logic you want to complete?

I have declared a new OnPaint, such that a user can subclass my control and
override OnPaint, or else wire up the Paint event handler, but I provide
some simple default painting code otherwise:
new public event PaintEventHandler Paint;

new protected virtual void OnPaint(PaintEventArgs subpe)

{

if (Paint != null)

{

Paint(this, subpe);

}

else

{

LinearGradientBrush background = new LinearGradientBrush(new Point(0, 0),
new Point(size), Color.Gray, Color.LightGray);

subpe.Graphics.FillRectangle(background, subpe.ClipRectangle);

}

}








I have created a Graphics for bmpCanvas:
using (Graphics subg = Graphics.FromImage(bmpCanvas))





And passed subg to my subclass's OnPaint:

PaintEventArgs subpe = new PaintEventArgs(subg, shiftedClip);

if (invalid != null)

{

subg.Clip = invalid;

//global::System.Diagnostics.Debug.WriteLine("invalid bounded by " +
invalid.GetBounds(pe.Graphics).ToString());

}

OnPaint(subpe);



And copied some part of the resulting image to the screen:
pe.Graphics.DrawImageUnscaled(bmpCanvas, -scrollOffset.X, -scrollOffset.Y);



I'm trying to debug code in my OnLayout method (e.g. called by
OnFontChanged) that paints into the offscreen buffer bmpCanvas, by using the
immediate window to call BlitNow as I step through the code. Actually
OnLayout substitutes bmpCanvas with another larger/smaller bitmap, whose
size is based on content, not window area). But I rearranged things so that
this substitution takes place before the draw and blit code; the new
bmpCanvas is the Bitmap I'm trying to observe.

This does not show me any progression of states as I build up the offscreen
bitmap. I considered that perhaps the Graphics for bmpCanvas isn't flushed,
or perhaps the limitation of SelectObject -- one DC per HBITMAP -- prevents
DrawImageUnscaled from using a Bitmap while I have a Graphics object for it.
So I added the FillRectangle line, which covers an area larger than
bmpCanvas but smaller than my control, so I ought to see it regardless of
what bmpCanvas contains. But FillRectangle doesn't even show anything.

I've not set the DoubleBuffer or OptimizedDoubleBuffer control bits. I've
flushed that Graphics g = CreateGraphics. Where else could my bits be
getting held up so I can't see them onscreen? I've not overridden
CreateGraphics (yet).
 
Hi Ben,

Thanks for your feedback!

Yes, I understand your purpose better now. Based on my understanding, there
are many places in your code that draw onto the memory bmpCanvas. Then you
want to see the final effect of bmpCanvas, so you draw the bmpCanvas onto
the control surface in OnPaint method.

However, there are too much context information in your application, and
without reproducing this problem it is hard for us to find out the root
cause. Is it possible for you to create a little sample project to
demonstrate this problem? This will be helpful and efficient for us to find
out the root cause.

With the current information, my recommendation is:
1. Do not draw the bmpCanvas to control surface, but use bmpCanvas.Save()
method to save the memory bitmap to the disk file. Then you may view it
from disk to verify the visual effect for testing purpose.
2. I'm not sure why you have to call BlitNow from Watch window. I recommend
you embed the BlitNow code in the OnLayout method, this will help us to
isolate other IDE issue.

Anyway, a sample reproduce project is still the most efficient way to
demonstrate the problem, I will wait for your sample project to give it a
deeper review and understanding.

Thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
Back
Top