Transparent controls using modified PictureBox as container

T

Tim Crews

Hello:

I was the original poster for a previous thread entitled "Why must I paint the
form background for an owner-draw control?", which can be reviewed at the
following URL, if you recombine all of the lines:

http://groups.google.com/groups?
hl=en&lr=lang_en&safe=off&threadm=OpoxJYbsEHA.904%40TK2MSFTNGP11.phx.gbl&rnum=1
&prev=/groups%3Fhl%3Den%26lr%3Dlang_en%26safe%3Doff%26selm%3DOpoxJYbsEHA.904%
2540TK2MSFTNGP11.phx.gbl

I was having a performance problem with non-rectangular owner-drawn controls
placed on a form that had a BackgroundImage. Because the parent Form control
clips to the child windows before it paints its background image, each of the
owner-drawn controls is responsible for re-painting the portion of the form
background that it occupies. The computer that I will be running my program on
is taking 8 seconds to draw a form with 40 to 50 such owner-drawn controls.

Stoitcho Goutsev, Frank Hileman, and Mick Doherty had several helpful
suggestions. Eventually, I was able to configure the owner-drawn controls so
that my owner-draw code did not have to explicitly fill in the background
areas. Instead, the control's transparency color was used so that the Windows
Forms infrastructure took care of the background painting itself.
Unfortunately, the performance problem was just as bad that way as it was when
I explicitly painted the form background around the controls.

The last suggestion I received was from Mick Doherty:

-----------------------------------------

If this is still an issue I have a workaround that may be satisfactory.

Inherit from Picturebox and give it the ScrollableControlDesigner Attribute.
You can then Dock this to the fill the form and place the buttons in that
instead.

[System.ComponentModel.Designer(typeof
(System.Windows.Forms.Design.ScrollableControlDesigner))]
public class PictureBoxContainer : System.Windows.Forms.PictureBox
{
//Standard code ommitted...
}

note: you'll need a reference to System.Design.dll

------------------------------------------

Mick, If you are still around, I need a little more detail on some of the steps
here. I have made an attempt at what you suggested, but it looks to me like
the PictureBox control is _also_ clipping the child windows, so that I still
have to (either explicitly or implicitly) cause the PictureBox background to be
painted around the owner-drawn control.

Here's what I did:

1) Created a new form.
2) Editing the form in design mode, I created a PictureBox control that fills
the entire form. I assigned a BackgroundImage to the PictureBox. I did not
"dock" the PictureBox control. Did you mean this literally, and if so, how do
I do it?
3) Editing the code for the form, I augmented the form code to add a
PictureBoxContainer class as you specified in your example. As for the
"Standard code ommitted", all I added was a constructor that calls the base()
constructor. Was there any other standard code I should have written?
4) I edited the code for the PictureBox instance to make it a
PictureBoxContainer instance.
5) Back in form design mode, I created a Button in the middle of the form.
6) Back in code mode, I changed the Button instance to make it an instance of
my owner-drawn "FancyButton" class.
7) For that "FancyButton" instance, I assigned the PictureBoxContainer
instance as the Parent of the FancyButton. Was this the proper way to make the
PictureBoxContainer the container for the FancyButton?

8) I modified the FancyButton constructor so that Windows doesn't try to
simulate transparency (which is what's causing the performance problem). I did
this by commenting out three lines of code from the original constructor:

public FancyButton() : base()
{
PenWidth = 1;
ButtonPressed = false;
SetStyle(ControlStyles.UserPaint, true);
//SetStyle(ControlStyles.Opaque, false);
SetStyle(ControlStyles.ResizeRedraw, true);
//SetStyle (ControlStyles.SupportsTransparentBackColor, true);
//base.BackColor = Color.Transparent;
}


Anyway, the result of all of this is that at design time, the fancy button is
surrounded by the Form's background color, instead of by the PictureBox's
background image. At runtime, the fancy button is surrounded by black (not the
Form's background color), and the fancy button is not the same size/location as
it was in design mode. (I suppose the reason for the different location/size is
that I dynamically re-parent the button at run-time. There must be a design-
time way of accomplishing the re-parenting.) When the button is pressed, which
causes an animation of the button moving slightly down and to the right, an
image of the un-pressed button is left behind.

Mick, if you have any advice on how I can properly implement what you
suggested, I would be glad to have it. Given the difference in behavior
between design-time and run-time, I am not convinced I have actually
accomplished what you suggested.

But on the other hand, the behavior on the button press leads me to an
observation of a problem: On the initial draw of the form and its buttons,
maybe eventually I will be able to rely on the background image already being
present when the buttons are drawn, so that I don't have to re-draw it. But
when I animate the button press, it seems to me that there is no way around a
redraw of the form background image around that button as the button is moved.
Am I right about that? If so, I guess I have two options:

a) Re-design the button so that it gives some other visual indicator of the
pressing action, without actually moving the button.

b) Add code to the owner-drawn button class that knows the difference between
an initial draw and a button-animation draw. The performance hit on the
drawing of a single button is no big deal.

Thank you to anyone who has any advice or input.

Tim Crews
GECO, Inc.
 
M

Mick Doherty

Hello Tim,

Add a New userControl to your project, call it PicturBoxContainer.
Change the Inheritance from userControl to PictureBox.
You will now have the Standard code (constructor, Dispose(), etc..).
Add the ScrollableControl attributes to the class.
Build the project.
You now have a PictureBox control which acts like a Panel at DesignTime.

Right click on the toolbox and select add/remove items...
browse to your projects executable and select it.
You should now be able to select PictureBoxContainer and Fancybutton from
the Toolbox and drop them directly onto your form instead of modifying the
code in the InitializeComponent() method.

Don't set the BackgroundImage of the control, set the Image property, that
is the one that's optimized and is the reason for Inheriting from
PictureBox.

You don't need to Dock the Picturebox, I just suggested you do so that you
don't need to set the forms BackgroundImage, since you'll be adding all
controls to the Picturebox instead of the Form.

There will still be a slight delay, but it is hugely improved. The
alternative is to create LiteButtons, which will be a fair amount of work.

Your button code was fine, except for an error in the graphics path for the
Shadow. This caused failure of the button whilst repainting. One of your
variables used Math.Min(arg,arg)/2; which sometimes returned a negative
value. Other than that I didn't find any issues with the button and don't
see how you could improve performance by modifying it, other than adding the
ControlStyles.DoubleBuffer attribute which you did not use.

Nice looking button by the way.

--
Mick Doherty
http://dotnetrix.co.uk/nothing.html


Tim Crews said:
Hello:

I was the original poster for a previous thread entitled "Why must I paint
the
form background for an owner-draw control?", which can be reviewed at the
following URL, if you recombine all of the lines:

http://groups.google.com/groups?
hl=en&lr=lang_en&safe=off&threadm=OpoxJYbsEHA.904%40TK2MSFTNGP11.phx.gbl&rnum=1
&prev=/groups%3Fhl%3Den%26lr%3Dlang_en%26safe%3Doff%26selm%3DOpoxJYbsEHA.904%
2540TK2MSFTNGP11.phx.gbl

I was having a performance problem with non-rectangular owner-drawn
controls
placed on a form that had a BackgroundImage. Because the parent Form
control
clips to the child windows before it paints its background image, each of
the
owner-drawn controls is responsible for re-painting the portion of the
form
background that it occupies. The computer that I will be running my
program on
is taking 8 seconds to draw a form with 40 to 50 such owner-drawn
controls.

Stoitcho Goutsev, Frank Hileman, and Mick Doherty had several helpful
suggestions. Eventually, I was able to configure the owner-drawn controls
so
that my owner-draw code did not have to explicitly fill in the background
areas. Instead, the control's transparency color was used so that the
Windows
Forms infrastructure took care of the background painting itself.
Unfortunately, the performance problem was just as bad that way as it was
when
I explicitly painted the form background around the controls.

The last suggestion I received was from Mick Doherty:

-----------------------------------------

If this is still an issue I have a workaround that may be satisfactory.

Inherit from Picturebox and give it the ScrollableControlDesigner
Attribute.
You can then Dock this to the fill the form and place the buttons in that
instead.

[System.ComponentModel.Designer(typeof
(System.Windows.Forms.Design.ScrollableControlDesigner))]
public class PictureBoxContainer : System.Windows.Forms.PictureBox
{
//Standard code ommitted...
}

note: you'll need a reference to System.Design.dll

------------------------------------------

Mick, If you are still around, I need a little more detail on some of the
steps
here. I have made an attempt at what you suggested, but it looks to me
like
the PictureBox control is _also_ clipping the child windows, so that I
still
have to (either explicitly or implicitly) cause the PictureBox background
to be
painted around the owner-drawn control.

Here's what I did:

1) Created a new form.
2) Editing the form in design mode, I created a PictureBox control that
fills
the entire form. I assigned a BackgroundImage to the PictureBox. I did
not
"dock" the PictureBox control. Did you mean this literally, and if so,
how do
I do it?
3) Editing the code for the form, I augmented the form code to add a
PictureBoxContainer class as you specified in your example. As for the
"Standard code ommitted", all I added was a constructor that calls the
base()
constructor. Was there any other standard code I should have written?
4) I edited the code for the PictureBox instance to make it a
PictureBoxContainer instance.
5) Back in form design mode, I created a Button in the middle of the
form.
6) Back in code mode, I changed the Button instance to make it an
instance of
my owner-drawn "FancyButton" class.
7) For that "FancyButton" instance, I assigned the PictureBoxContainer
instance as the Parent of the FancyButton. Was this the proper way to
make the
PictureBoxContainer the container for the FancyButton?

8) I modified the FancyButton constructor so that Windows doesn't try to
simulate transparency (which is what's causing the performance problem).
I did
this by commenting out three lines of code from the original constructor:

public FancyButton() : base()
{
PenWidth = 1;
ButtonPressed = false;
SetStyle(ControlStyles.UserPaint, true);
//SetStyle(ControlStyles.Opaque, false);
SetStyle(ControlStyles.ResizeRedraw, true);
//SetStyle (ControlStyles.SupportsTransparentBackColor, true);
//base.BackColor = Color.Transparent;
}


Anyway, the result of all of this is that at design time, the fancy button
is
surrounded by the Form's background color, instead of by the PictureBox's
background image. At runtime, the fancy button is surrounded by black
(not the
Form's background color), and the fancy button is not the same
size/location as
it was in design mode. (I suppose the reason for the different
location/size is
that I dynamically re-parent the button at run-time. There must be a
design-
time way of accomplishing the re-parenting.) When the button is pressed,
which
causes an animation of the button moving slightly down and to the right,
an
image of the un-pressed button is left behind.

Mick, if you have any advice on how I can properly implement what you
suggested, I would be glad to have it. Given the difference in behavior
between design-time and run-time, I am not convinced I have actually
accomplished what you suggested.

But on the other hand, the behavior on the button press leads me to an
observation of a problem: On the initial draw of the form and its
buttons,
maybe eventually I will be able to rely on the background image already
being
present when the buttons are drawn, so that I don't have to re-draw it.
But
when I animate the button press, it seems to me that there is no way
around a
redraw of the form background image around that button as the button is
moved.
Am I right about that? If so, I guess I have two options:

a) Re-design the button so that it gives some other visual indicator of
the
pressing action, without actually moving the button.

b) Add code to the owner-drawn button class that knows the difference
between
an initial draw and a button-animation draw. The performance hit on the
drawing of a single button is no big deal.

Thank you to anyone who has any advice or input.

Tim Crews
GECO, Inc.
 
T

Tim Crews

Hello Tim,
There will still be a slight delay, but it is hugely improved. The
alternative is to create LiteButtons, which will be a fair amount of work.

Your button code was fine, except for an error in the graphics path for the
Shadow. This caused failure of the button whilst repainting. One of your
variables used Math.Min(arg,arg)/2; which sometimes returned a negative
value. Other than that I didn't find any issues with the button and don't
see how you could improve performance by modifying it, other than adding the
ControlStyles.DoubleBuffer attribute which you did not use.

Nice looking button by the way.


Mick:

I have followed your instructions. Thank you for the tips on how to properly
work with user controls. That does make things a lot cleaner. (I was often
pestered by VS.NET backing out my manual changes to the form-designer code.)

I can confirm that this implementation does improve performance. The draw time
for a 50-button display goes down from 7 seconds to 3 seconds. Unfortunately,
this is still not even close to fast enough. I really need it to draw in much
less than half a second. (Even on my 3GHz machine, I would say that the
current draw time of about 1 second is unacceptable.)

Furthermore, even with the PictureBox container approach, I am only able to get
the owner-draw buttons properly surrounded by the background image if I set
them to be transparent so that Windows Forms does its "simulated transparency"
trick, just as I was doing with the original buttons. I originally assumed
that the advantage of the PictureBox container was that it wouldn't clip
children when painting its image. This is apparently not the case. The
PictureBox control does still clip children, and Windows Forms still has to
paint in the background image in the transparent regions of the children. In
your latest message, you make explicit that the performance improvement comes
from an optimization of the background painting algorithm in PictureBox.

OK, so moving on: I guess now I get to investigate the LiteButtons approach.
I searched the linked site for a Shawn Burke LiteButtons article, but couldn't
find it. But before I even try it, I want to be sure that this approach really
will be working around the child clipping problem, not just by filling in the
clipped regions after the fact, but by avoiding the clipping altogether. If
LiteButtons still has to fill in the clipped regions, then I am suspicious that
I will not ever see any better performance than I'm seeing now.

I see your comment that Math.Min can return a negative value. Since the two
values being compared are button Width and Height, I don't see how this
happens. Are you saying that Width and Height can be negative? I know that I
do deflate the rectangle slightly before taking the min. However, I never
create buttons small enough for that to create a negative Width or Height. If
I did, I guess I would need another fallback that would display some sort of
useful error message and exit. Nothing useful can be drawn in that case.

But in your favor, I must admit that I am constantly pestered by error messages
popping up in form design mode, telling me "Invalid Property Value". I never
know when it's going to happen, but when it does happen, I get one instance of
the message for each owner-drawn button on my form, and each of those owner-
drawn buttons is displayed as a white rectangle with a red X through it. So,
clearly I do have a code bug in there somewhere. Even when this happens, I can
build the application and get no run-time errors.

Thank you for the compliment on the button appearance. Alas, I can't even take
the credit for that. My job was just to make a button that looked like a
button from one of our customer's previous applications, and to put it on a
graphical background that the customer had used before. I never suspected that
my choice of .NET and Windows Forms would be in any way a limiting factor in
accomplishing this goal. My previous experience with owner-drawn buttons in
other frameworks made me think this would be a slam-dunk.

I really appreciate all of the help you have given me.

Tim Crews,
GECO, Inc.
 
T

Tim Crews

Hello Tim,

There will still be a slight delay, but it is hugely improved. The
alternative is to create LiteButtons, which will be a fair amount of work.


Mick (or whoever else is interested):

As Stoitcho Goutsev noted in the original thread, this whole problem is caused
by the WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles in the parent form, "which
emans that all area covered by the child widnows or
sibling windows is clipped off the device context during WM_PAIN".

I have to ask, is there no way around this issue that simply involves modifying
the window style for the parent form, or if nothing else, overriding the Form
class OnPaint event handler to avoid the child window clipping?

Sorry if that's a stupid question.

Tim Crews
 
F

Frank Hileman

Hello Tim,

I think the problem may be the drawing code. I just created some different
Lite buttons. Depending on the type of fills, shadows, etc, some were very
fast, some not so fast. Here is a set of some pretty fast ones. It
demonstrates it can be done with good drawing code. I use a large bitmap for
the background. It is only 100 buttons. The only real delay is in resizing;
the shadows have to be redrawn into caches then, and that is the slowest
part (path gradients).

http://www.vgdotnet.com/support/LiteButtons.zip

I included the VG.net runtime and an exe. If you want to open the
Picture-derived classes in a visual designer, you will need to install the
VG.net Lite designer. All the Pictures were drawn in the designer.

Regards,
Frank Hileman

check out VG.net: www.vgdotnet.com
Animated vector graphics system
Integrated Visual Studio .NET graphics editor
 
T

Tim Crews

Hello Tim,

I think the problem may be the drawing code.

<snip>

Frank:

I think I can eliminate the drawing code in my owner-draw button as the culprit
by removing the BackgroundImage property from the parent form, so that the
owner-drawn buttons are drawn against a solid background. Under those
conditions, all 50 buttons on my form draw instantl (at least, faster than I
can measure).

I can also eliminate the painting of the BackgroundImage across the parent form
as the culprit, since I can draw 50 normal buttons (rectangular shaped) over a
form with a BackgroundImage with excellent performance.

So, I conclude that the performance problem is caused specifically by the
function of copying portions of the parent form (or parent PictureBox,
depending on the implementation) BackgroundImage over the transparent portions
of the child controls.

This was especially easy to tell when I was not taking advantage of the Windows
Forms pseudo-transparency, and my owner-draw button code explicitly filled in
the background region before drawing the button shape:

// Excerpt from FancyButton.OnPaint:
//
// if (Parent.BackgroundImage == null)
// {
// SolidBrush SolBrush = new SolidBrush (Parent.BackColor);
// e.Graphics.FillRectangle (SolBrush,ButtonRectangle);
// }
// else
// {
// TextureBrush TexBrush = new TextureBrush
// (Parent.BackgroundImage, Bounds);
// e.Graphics.FillRectangle (TexBrush,ButtonRectangle);
// }

If I included the above block of code, I got bad performance. If I commented
out the block, I got great performance (instant draw of 50 buttons), but of
course I got wrong-colored rectangular boxes around my pretty round, shadowed
buttons.

Tim Crews
GECO, Inc.
 
M

Mick Doherty

You can remove the styles but it doesn't make any performance difference.
\\\
const int WS_CLIPCHILDREN = 0x2000000;
const int WS_CLIPSIBLINGS = 0x4000000;

protected override CreateParams CreateParams
{
get
{
CreateParams CP = base.CreateParams;
CP.Style -= WS_CLIPCHILDREN | WS_CLIPSIBLINGS ;
return CP;
}
}
///
 
F

Frank Hileman

Hello Tim,

Here is the article we were looking for. The sample is called ButtonArray,
not LiteButton. The concept is similar to the VG.net buttons I built. All
VG.net graphical components are "lite" components in this sense.

http://msdn.microsoft.com/library/d...n-us/dndotnet/html/custcodegen.asp?frame=true

Using this approach your problems are solved, I think. We get great
performance by drawing everything into one buffer, and transparency works as
it should.

Frank
 
T

Tim Crews

Hello Tim,

Here is the article we were looking for. The sample is called ButtonArray,
not LiteButton. The concept is similar to the VG.net buttons I built. All
VG.net graphical components are "lite" components in this sense.

http://msdn.microsoft.com/library/d...n-us/dndotnet/html/custcodegen.asp?frame=true

Using this approach your problems are solved, I think. We get great
performance by drawing everything into one buffer, and transparency works as
it should.

Frank

Thank you Frank.

I have downloaded the code and I will study it today.

Tim Crews
GECO, Inc.
 
T

Tim Crews

Hello Tim,

Here is the article we were looking for. The sample is called ButtonArray,
not LiteButton. The concept is similar to the VG.net buttons I built. All
VG.net graphical components are "lite" components in this sense.

http://msdn.microsoft.com/library/d...n-us/dndotnet/html/custcodegen.asp?frame=true

Using this approach your problems are solved, I think. We get great
performance by drawing everything into one buffer, and transparency works as
it should.

Frank

Frank:

I have read the article, downloaded the code, and studied it. I was surprised
to find that this code does _not_ take ownership of drawing the entire form. A
regular System.Windows.Forms.Form is used, and the ButtonArray is contained on
that form. I am willing to try it (especially if I am able to get your
executable to run and see its good performance with my own eyes), but I don't
understand how this gets around the background image painting performance
problem. Then again, maybe you still don't believe me that it is the
background image painting that is the source of my performance problem.

Thank you again for finding this for me.

Tim Crews
GECO, Inc.
 
T

Tim Crews



Frank:

I'm not having any luck getting this file. I would definitely like to try it.
In Mozilla Firefox, I get "NOT FOUND: The requested URL
/support/LiteButtons.zip was not found on this server. Additionally, a 404 Not
Found error was encountered while trying to use an ErrorDocument to handle the
request." I get similar results from Internet Explorer.

Are there any unusual instructions for fetching this file?

Tim Crews
GECO, Inc.
 
M

Mick Doherty

I don't
understand how this gets around the background image painting performance
problem.

The buttons do not exist as seperate windows. They are simply drawn directly
onto the form so the transparent parts do not need drawing. There is only
one paint event for the form and all of the buttons.
With your existing button there is a paint event for the form every time a
button is drawn (60 buttons on form load means 60 paint events for the form)
and a paintbackground event. On top of that each button is painted twice,
once for the background followed by once for the button. So at form load
there are 240 events for 60 fancyButtons, compared to 2 events for 60
liteButtons.
 
T

Tim Crews

The buttons do not exist as seperate windows. They are simply drawn directly
onto the form so the transparent parts do not need drawing. There is only
one paint event for the form and all of the buttons.
With your existing button there is a paint event for the form every time a
button is drawn (60 buttons on form load means 60 paint events for the form)
and a paintbackground event. On top of that each button is painted twice,
once for the background followed by once for the button. So at form load
there are 240 events for 60 fancyButtons, compared to 2 events for 60
liteButtons.

Mick and Frank:

OK, I have seen for myself that the ButtonArray example does indeed paint
buttons over a background much faster. I have also seen Frank's VG.NET buttons
display as quickly as anyone could wish. And I understand why, too. So, I
guess it's time for me to get to work implementing something like it (or buying
VG.NET). I think I could probably code up the painting support on my own,
since my particular application only has buttons and text labels, without any
other kinds of controls. However, the VS.NET designer support seems a little
more challenging. I will have to think hard before I decide to implement that,
but I wouldn't expect my other team members to do without it, either.

Thank you all very much for your help. You have been very generous with your
time. I particularly appreciate your persistence. That's what finally got me
through.

Tim Crews
GECO, Inc.
 

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