.NET Forum / Windows Forms / WinForm General / October 2004
Why must I paint the form background for an owner-draw control?
|
|
Thread rating:  |
Tim Crews - 07 Oct 2004 05:47 GMT Hello:
I have subclassed Button to create an owner-drawn non-rectangular button.
I had assumed that the parent form's background color or image would have already been painted in the area to be occupied by the button, before the button's OnPaint handler was called. I could then draw over that existing background with the custom shape I desired for my button.
This is not what is happening. The region to be occupied by the button is blacked out. I am forced to write code that paints the parent form's background color or image into the button's area. This performs especially badly if the parent form has a BackgroundImage. For example, on a form that has 40 buttons, my 3GHz machine takes a couple of seconds to draw the form. If I change the buttons back to rectangular, or if I change the parent form to only have a BackgroundColor instead of a BackgroundImage, or if I remove the button subclass code that paints the parent form's background color/image in the button area, the performance goes back to normal (i.e. drawing the form in the blink of an eye.)
Is the entire form's background painted, and then the button areas blacked out, and then the button OnPaint event handlers called? If so, is there a way to simply skip the second step? It is a shame for part of the form to be drawn, then erased, only to be drawn back again. I tried overriding the button subclass background paint event, but this had no effect.
The only other possibility is that the form is partially painted, i.e. the regions corresponding to the child controls are not painted when the form background is painted. This would surprise me, since this would not seem to result in the best performance. But if this is really what is happening, then I guess I have no choice but to do as I am currently doing.
Thank you for any advice,
Tim Crews GECO, Inc.
Stoitcho Goutsev \(100\) [C# MVP] - 07 Oct 2004 14:53 GMT Hi Tim,
 Signature Stoitcho Goutsev (100) [C# MVP]
> Hello: > [quoted text clipped - 35 lines] > Tim Crews > GECO, Inc. Stoitcho Goutsev \(100\) [C# MVP] - 07 Oct 2004 15:37 GMT Hi Tim,
Here what happens. For optimization purposes noramlly windows (I'm talking about native Windows' windows and becuase WindForms are build over them it applies for WinForms as well) have styles WS_CLIPCHILDREN and WS_CLIPSIBLINGS, which emans that all area covered by the child widnows or sibling windows is clipped off the device context during WM_PAIN. In other words even if the form wants to draw the background for you this area under the button is clipped and nothing goes on the screen. That's why you get that black rectangle. However setting those styles for the parrent window is not enough because invalidation of a child control won't trigger the repaint of the parent. In order to do that the child window has to have WS_EX_TRANSPARENT.
Anyway, WindowsForms has nicer solution because it supports transparent colors for the control's background.
Just add those lines in your botton constructor and you will get the transparent background
SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); this.BackColor = Color.FromArgb(0,SystemColors.Control);
However it seems like the button doesn't use transparency for the border, though.
 Signature HTH Stoitcho Goutsev (100) [C# MVP]
> Hello: > [quoted text clipped - 35 lines] > Tim Crews > GECO, Inc. Tim Crews - 07 Oct 2004 17:43 GMT > Hi Tim, <snip>
> SetStyle(ControlStyles.UserPaint, true); > SetStyle(ControlStyles.SupportsTransparentBackColor, true); > this.BackColor = Color.FromArgb(0,SystemColors.Control); Stoitcho:
Thank you for your response. Unfortunately, this still isn't working for me. I still get a black background around the owner-drawn control.
You mentioned that at the native Windows level, this behavior interacts with the form's WS_CLIPCHILDREN style. Do I need to do something at the form level to accomplish this?
I notice that the documentation of the SupportsTransparentBackColor says that this is a "simulated" transparency. I wonder what this implies. If the Windows Forms framework still tries to make an intelligent decision as to where to paint the form background around the child control, this probably means I need to set the owner-drawn button's region so that Windows Forms knows where to paint. Since I currently don't modify the button's region, as far as Windows Forms is concerned, my button still occupies its entire bounding rectangle, so Windows Forms doesn't think it needs to do any painting of the form background.
Do you think this is my problem? For various reasons, it will be quite difficult to pre-compute the button's region before the button's OnPaint is called.
I should also note that my buttons do not use SystemColors.Control as their background color. All of my buttons are different colors, assigned at design time. I use the assigned BackColor as the face color of the button. So my constructor looks like this:
public FancyButton() : base() { // class member variables PenWidth = 1; ButtonPressed = false;
// Control styles SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); this.BackColor = Color.FromArgb(0,this.BackColor); }
You can see that I also added "AllPaintingInWmPaint", because of advice I have received elsewhere. This makes no difference to my problem, however.
My OnPaint handler does nothing except draw a rounded-corner rectangle, with a drop shadow under it. To repeat myself just in case I miscommunicated the first time: neither the button face nor the shadow of the button face occupies the entire bounding rectangle that was specified for the button at design time. So the area that is within the bounding rectangle but outside the owner-drawn button face / button shadow shape needs to have the form's background.
Here is how I am currently handling this problem in the button's OnPaint handler:
Rectangle ButtonRectangle = new Rectangle(0,0,Width,Height);
if (Parent.BackgroundImage == null) { SolidBrush SolBrush = new SolidBrush (Parent.BackColor); e.Graphics.FillRectangle (SolBrush,ButtonRectangle); } else { // Note: "Bounds" coordinates are relative to parent // form, while ButtonRectangle coordinates are relative // to the button. TextureBrush TexBrush = new TextureBrush (Parent.BackgroundImage, Bounds); e.Graphics.FillRectangle (TexBrush,ButtonRectangle); }
// Then draw the button face and shadow over this.
This is the code that is performing very badly if Parent.BackgroundImage != null.
Thank you for your time,
Tim Crews GECO, Inc.
Mick Doherty - 07 Oct 2004 18:27 GMT when overriding the button control you must also set ControlStyles.Opaque to false.
\\\ SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, false);
this.BackColor = Color.Transparent; ///
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>> Hi Tim, >> [quoted text clipped - 86 lines] > Tim Crews > GECO, Inc. Tim Crews - 08 Oct 2004 00:29 GMT > when overriding the button control you must also set ControlStyles.Opaque to > false. [quoted text clipped - 9 lines] > this.BackColor = Color.Transparent; > /// I sincerely thank everyone for taking the time to look at this.
I added Opaque as you suggested, and _still_ get black backgrounds. I also added the DoubleBuffer style as you suggested, although I had not originally planned to double buffer the drawing. Either way, black backgrounds around the buttons.
I'm going to go for broke and post all of the button subclass code.
To test this, create a form, give it a background image, create a button on the form, then change the declaration and creation of the Button to FancyButton. To see what the button should look like (i.e. without black background), uncomment the code segment starting with "if (Parent.BackgroundImage == null)". But the performance will be terrible if you do that.
Thank you for your assistance,
Tim Crews GECO, Inc.
####################################################
public class FancyButton : Button {
private int PenWidth; private int ShadowHeight; private int ButtonThickness; private bool ButtonPressed;
public FancyButton() : base() { PenWidth = 1; ButtonPressed = false;
// All of the following added per suggestions of newsgroup // respondents. SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.Opaque, true); this.BackColor = Color.FromArgb(0,this.BackColor); }
public void SpecialRoundRect (Rectangle rect, Color BaselineColor, System.Windows.Forms.PaintEventArgs e, bool Shade3D, bool LoOutline, bool HiOutline) {
rect.Inflate(-1*PenWidth,-1*PenWidth);
int CurveSize = Math.Min(rect.Width, rect.Height) / 2; int CurveHalfSize = CurveSize/2; int CurveHalfLeft = rect.Left + CurveHalfSize; int CurveHalfRight = rect.Right-CurveHalfSize; int CurveHalfTop = rect.Top + CurveHalfSize; int CurveHalfBottom = rect.Bottom-CurveHalfSize;
Rectangle TopLeft = new Rectangle (rect.Left,rect.Top,CurveSize,CurveSize); Rectangle TopRight = new Rectangle (rect.Right-CurveSize,rect.Top,CurveSize,CurveSize); Rectangle BottomLeft = new Rectangle (rect.Left,rect.Bottom-CurveSize,CurveSize,CurveSize); Rectangle BottomRight = new Rectangle (rect.Right-CurveSize, rect.Bottom-CurveSize, CurveSize,CurveSize);
int MiddleX = (rect.Right + rect.Left) / 2; int MiddleY = (rect.Top + rect.Bottom) / 2;
SolidBrush BaselineBrush, LoBrush, MedBrush, HiBrush; Pen BaselinePen, LoPen, MedPen, HiPen;
BaselineBrush = new SolidBrush(BaselineColor); BaselinePen = new Pen(BaselineBrush, PenWidth);
if (Shade3D) { // The low color should be a lot darker version of the // specified BaselineColor byte ShadowR, ShadowG, ShadowB; ShadowR = (byte)((int)BaselineColor.R*2/3); ShadowG = (byte)((int)BaselineColor.G*2/3); ShadowB = (byte)((int)BaselineColor.B*2/3); Color ShadowColor = Color.FromArgb (ShadowR, ShadowG, ShadowB); LoBrush = new SolidBrush(ShadowColor); LoPen = new Pen(LoBrush, PenWidth);
// The medium color should be a somewhat darker version of // the specified BaselineColor byte MediumR, MediumG, MediumB; MediumR = (byte)((int)BaselineColor.R*4/5); MediumG = (byte)((int)BaselineColor.G*4/5); MediumB = (byte)((int)BaselineColor.B*4/5); Color MediumColor = Color.FromArgb (MediumR, MediumG, MediumB); MedBrush = new SolidBrush(MediumColor); MedPen = new Pen(MedBrush, PenWidth);
// The high color should be a brighter version of the button // BaselineColor byte HighlightR, HighlightG, HighlightB; HighlightR = (byte)( (int)BaselineColor.R + ( (255-BaselineColor.R) *1/3 ) ); HighlightG = (byte)( (int)BaselineColor.G + ( (255-BaselineColor.G) *1/3 ) ); HighlightB = (byte)( (int)BaselineColor.B + ( (255-BaselineColor.B) *1/3 ) ); Color HighlightColor = Color.FromArgb (HighlightR, HighlightG, HighlightB); HiBrush = new SolidBrush(HighlightColor); HiPen = new Pen(HiBrush, PenWidth);
} else { LoBrush = BaselineBrush; LoPen = BaselinePen; MedBrush = BaselineBrush; MedPen = BaselinePen; HiBrush = BaselineBrush; HiPen = BaselinePen; }
Pen OutlinePen; if (LoOutline) OutlinePen = new Pen(Color.Black,PenWidth); else if (HiOutline) OutlinePen = new Pen(Color.White,PenWidth); else OutlinePen = new Pen (Color.Black,PenWidth); // unused, but prevents compiler warning
if (Shade3D) { GraphicsPath PathUpperLeft = new GraphicsPath(); PathUpperLeft.StartFigure(); PathUpperLeft.AddArc(TopLeft,180,90); PathUpperLeft.AddLine (CurveHalfLeft,rect.Top,MiddleX,MiddleY); PathUpperLeft.CloseFigure(); e.Graphics.FillPath(HiBrush,PathUpperLeft); e.Graphics.DrawPath(MedPen,PathUpperLeft);
GraphicsPath PathUpper = new GraphicsPath(); PathUpper.StartFigure(); PathUpper.AddLine (CurveHalfLeft,rect.Top,CurveHalfRight,rect.Top); PathUpper.AddLine(CurveHalfRight,rect.Top,MiddleX,MiddleY); PathUpper.CloseFigure(); e.Graphics.FillPath(HiBrush,PathUpper); e.Graphics.DrawPath(MedPen,PathUpper);
GraphicsPath PathUpperRight = new GraphicsPath(); PathUpperRight.StartFigure(); PathUpperRight.AddArc(TopRight,270,90); PathUpperRight.AddLine (rect.Right,CurveHalfTop,MiddleX,MiddleY); PathUpperRight.CloseFigure(); e.Graphics.FillPath(MedBrush,PathUpperRight); e.Graphics.DrawPath(MedPen,PathUpperRight);
GraphicsPath PathRight = new GraphicsPath(); PathRight.StartFigure(); PathRight.AddLine (rect.Right,CurveHalfTop,rect.Right,CurveHalfBottom); PathRight.AddLine (rect.Right,CurveHalfBottom,MiddleX,MiddleY); PathRight.CloseFigure(); e.Graphics.FillPath(LoBrush,PathRight); e.Graphics.DrawPath(MedPen,PathRight);
GraphicsPath PathLowerRight = new GraphicsPath(); PathLowerRight.StartFigure(); PathLowerRight.AddArc(BottomRight,0,90); PathLowerRight.AddLine (CurveHalfRight,rect.Bottom,MiddleX,MiddleY); PathLowerRight.CloseFigure(); e.Graphics.FillPath(LoBrush,PathLowerRight); e.Graphics.DrawPath(MedPen,PathLowerRight);
GraphicsPath PathLower = new GraphicsPath(); PathLower.StartFigure(); PathLower.AddLine (CurveHalfRight,rect.Bottom,CurveHalfLeft,rect.Bottom); PathLower.AddLine (CurveHalfLeft,rect.Bottom,MiddleX,MiddleY); PathLower.CloseFigure(); e.Graphics.FillPath(LoBrush,PathLower); e.Graphics.DrawPath(MedPen,PathLower);
GraphicsPath PathLowerLeft = new GraphicsPath(); PathLower.StartFigure(); PathLowerLeft.AddArc (BottomLeft,90,90); PathLowerLeft.AddLine (rect.Left,CurveHalfBottom,MiddleX,MiddleY); PathLowerLeft.CloseFigure(); e.Graphics.FillPath(MedBrush,PathLowerLeft); e.Graphics.DrawPath(MedPen,PathLowerLeft);
GraphicsPath PathLeft = new GraphicsPath(); PathLeft.StartFigure(); PathLeft.AddLine (rect.Left,CurveHalfBottom,rect.Left,CurveHalfTop); PathLeft.AddLine(rect.Left,CurveHalfTop,MiddleX,MiddleY); PathLeft.CloseFigure(); e.Graphics.FillPath(HiBrush,PathLeft); e.Graphics.DrawPath(MedPen,PathLeft);
}
GraphicsPath PathWhole = new GraphicsPath(); PathWhole.StartFigure(); PathWhole.AddArc(TopLeft,180,90); PathWhole.AddArc(TopRight,270,90); PathWhole.AddArc(BottomRight,0,90); PathWhole.AddArc(BottomLeft,90,90); PathWhole.CloseFigure();
if (!Shade3D) { e.Graphics.FillPath(BaselineBrush,PathWhole); }
if (LoOutline || HiOutline) { e.Graphics.DrawPath(OutlinePen,PathWhole); }
}
protected override void OnPaint (PaintEventArgs e) { if ((Width>50) && (Height>50)) { ShadowHeight=10; ButtonThickness=20; } else { ShadowHeight = 3; ButtonThickness = 7; }
Rectangle ButtonRectangle = new Rectangle(0,0,Width,Height);
// Start by filling in the background image of the parent form. // This is necessary so that the parts of the bounding rectangle // that are not actually occupied by any part of the button will // look like they show the form's background. // if (Parent.BackgroundImage == null) // { // SolidBrush SolBrush = new SolidBrush // (Parent.BackColor); // e.Graphics.FillRectangle (SolBrush,ButtonRectangle); // } // else // { // // Note: "Bounds" coordinates are relative to parent // // form, while ButtonRectangle coordinates are // // relative to the button. // TextureBrush TexBrush = new TextureBrush // (Parent.BackgroundImage, Bounds); // e.Graphics.FillRectangle (TexBrush,ButtonRectangle); // }
ButtonRectangle.Inflate (-1*(ShadowHeight/2),-1*(ShadowHeight/2));
// Move the button if it is pressed if (ButtonPressed) { ButtonRectangle.Offset(ShadowHeight/3,ShadowHeight/3); } else { ButtonRectangle.Offset (-1*(ShadowHeight/2),-1*(ShadowHeight/2)); }
// Draw button shadow Rectangle ShadowRectangle = e.ClipRectangle; ShadowRectangle.Inflate (-1*(ShadowHeight/2),-1*(ShadowHeight/2)); ShadowRectangle.Offset(ShadowHeight/2,ShadowHeight/2); SpecialRoundRect (ShadowRectangle,Color.Black,e,false,false,false);
// Draw button edges SpecialRoundRect (ButtonRectangle,this.BackColor,e,true,true,false);
// Draw button face ButtonRectangle.Inflate (-1*(ButtonThickness/2),-1*(ButtonThickness/2)); SpecialRoundRect (ButtonRectangle,this.BackColor,e,false,false,true);
// Draw the button text in the specified font and color if (this.Text.Length > 0) { StringFormat textFormat = new StringFormat(); textFormat.Alignment = StringAlignment.Center; textFormat.LineAlignment = StringAlignment.Center; e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), ButtonRectangle, textFormat); } }
protected override void OnMouseDown (System.Windows.Forms.MouseEventArgs e ) { ButtonPressed = true; this.Invalidate(); this.OnClick(e); }
protected override void OnMouseUp (System.Windows.Forms.MouseEventArgs e) { ButtonPressed = false; this.Invalidate(); }
protected override void OnClick(System.EventArgs e) { base.OnClick(e); } }
Mick Doherty - 08 Oct 2004 06:52 GMT The immediate problem I see here is that Opaque is true, it should be false.
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>> when overriding the button control you must also set ControlStyles.Opaque >> to [quoted text clipped - 364 lines] > } > } Mick Doherty - 08 Oct 2004 07:21 GMT By the way, if you want to see a minor bug in your button just follow these instructions.
Depress button, and while depressed push and release space. Now release the button and click anywhere outside the button.
I have some Button source on my site which you may like to use as a base for your Button. You'll find the above bug fixed as well as a couple of others. Just replace my Bevel style with your Fancy style.
http://dotnetrix.co.uk/buttons.html
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
> The immediate problem I see here is that Opaque is true, it should be > false. [quoted text clipped - 372 lines] > Checked by AVG anti-virus system (http://www.grisoft.com). > Version: 6.0.742 / Virus Database: 495 - Release Date: 19/08/2004 Tim Crews - 08 Oct 2004 17:06 GMT > By the way, if you want to see a minor bug in your button just follow these > instructions. [quoted text clipped - 7 lines] > > http://dotnetrix.co.uk/buttons.html Mike:
Your sample code is very helpful! Believe me, I spent a lot of time searching for a good Button subclass implementation, but for some reason I did not run across yours.
You do indeed account for a lot of cases that my class does not. Some of them do not apply in my environment (a kiosk that has no keyboard, with no run-time changes to any button properties); nevertheless, your design is well worth incorporating.
But the "DrawParentBackground" function in your class remains just as costly, if not more, than my own code segment. I think I will still be faced with the same problem, that is, performance. If I have 40 buttons, it will still take a LONG time to execute 40 copies of your DrawParentBackground. I will plug your class in and see how it performs a little later today...
[Why does he have a form with 40 buttons, you ask? It's a virtual keyboard, for a kiosk that has no keyboard, but unfortunately still has a few cases where the user needs to enter some text.]
Since your expertise has lead you to a design that still has to do the one thing I have been trying to avoid all along (explicitly filling in the parent background), it appears that I am stuck with this performance problem. (At least if I want to use Button as a parent class. Another respondent had an alternate suggestion, which I will address in another post.) I will have to find a different button style that is rectangular, or I will have to remove the parent form background image and stick with a solid background color. Either of these approaches will be difficult for the customer to accept.
Thank you again for your generosity with your time, including the time it took you to write the sample code on your web site. It has been very helpful.
Tim Crews GECO, Inc.
Tim Crews - 08 Oct 2004 16:56 GMT > The immediate problem I see here is that Opaque is true, it should be false. Doh! Yes, you are right. The new constructor reads as follows:
public FancyButton() : base() { PenWidth = 1; ButtonPressed = false; SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); //SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.Opaque, false); this.BackColor = Color.FromArgb(0,this.BackColor); }
Now, I do not get a black background, but I still get a background that is the color of the button face (i.e. this.BackColor) instead of the background that is inherited from the form (either Parent.BackColor or Parent.BackgroundImage).
The results are the same with or without the AllPaintingInWmPaint style. In face, I can also remove the SupportsTransparentBackColor style, and the this.BackColor assignment, and see exactly the same results.
Thank you for catching my bug. Surely I am close to a solution now?
Tim Crews GECO, Inc.
Mick Doherty - 08 Oct 2004 17:36 GMT How are you setting the FaceColor? Don't forget that in your constructor you added the line: \\\ this.BackColor = Color.FromArgb(0,this.BackColor); ///
If you change this then the button Background will not be Transparent.
modify your constructor as follows and add the new BackColor Property
\\\ public FancyButton() : base() { PenWidth = 1; ButtonPressed = false;
SetStyle(ControlStyles.UserPaint, true); SetStyle(|ControlStyles.SupportsTransparentBackColor, true); //extra style for IDE. SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.Opaque, false);
//this changed to base base.BackColor = Color.Transparent;
}
// a new BackColor property for your ButtonFace private Color faceColor = Control.DefaultBackColor;
[DefaultValue(typeof(Color),"Control")] public new Color BackColor { get { return faceColor; } set { faceColor = value; this.Invalidate(); } } ///
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>> The immediate problem I see here is that Opaque is true, it should be >> false. [quoted text clipped - 28 lines] > Tim Crews > GECO, Inc. Tim Crews - 08 Oct 2004 18:10 GMT > How are you setting the FaceColor? Mick:
The suggestions you made in that post did indeed get the button backgrounds looking the way they were supposed to look, without requiring any code in my control to explicitly paint the form background around my button.
I was then faced with... crushing disappointment. (OK, perhaps it is not so serious as that.) Now that the framework is doing the work of painting the form background around my button, the performance is just as bad as when I was doing it myself. My 40-button form still takes two seconds to display on my 3GHz machine. The processor that will be driving our kiosk is much slower than 3GHz. I have a notebook computer with similar performance to what we are expecting to see on our kiosk. On the notebook computer, it takes _eight_ seconds to display the 40-button form.
Several of my co-workers are convinced that something is wrong, and that it must be possible to draw these buttons faster. As an "existence proof", they show me their windows desktops, with background images, and then they select a couple dozen icons on their desktops, and move them around, demonstrating to me that somehow Windows is able to lay those icons (including their text labels) over a textured background without any performance penalty.
I don't know what to tell them, other than that we are up against a limitation of Windows Forms, which does not support _true_ transparency, but only simulates transparency through costly (in runtime) re-drawing of portions of the parent image.
I evaluated many criteria when our project team was selecting a development environment. If we had it to do over again, this performance issue might very well have changed our decision. I don't look forward to reporting this at our next project meeting.
Thank you again for your persistence with this issue.
Tim Crews GECO, Inc.
Frank Hileman - 08 Oct 2004 18:22 GMT Here is an example of transparent buttons with VG.net, drawing in the way I described. You can see they are very fast. You can create hundreds, no problem. The Lite download has source for that demo.
http://weblogs.asp.net/frank_hileman/archive/2004/05/10/129387.aspx
Really you just need an optimized graphics setup. It is possible to do it yourself without VG.net, but the VG.net run-time is free, and has a great designer.
Regards, Frank Hileman
check out VG.net: www.vgdotnet.com Animated vector graphics system Integrated Visual Studio .NET graphics editor
> > How are you setting the FaceColor? > [quoted text clipped - 34 lines] > Tim Crews > GECO, Inc. Mick Doherty - 08 Oct 2004 18:50 GMT Are your buttons sitting on a form that has a BackgroundImage? You'll find the same behaviour with 40 standard buttons.
I just placed 60 buttons on a form and it loaded immediately on my 2Ghz machine.
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>> How are you setting the FaceColor? > [quoted text clipped - 51 lines] > Tim Crews > GECO, Inc. Tim Crews - 08 Oct 2004 19:06 GMT > Are your buttons sitting on a form that has a BackgroundImage? > You'll find the same behaviour with 40 standard buttons. > > I just placed 60 buttons on a form and it loaded immediately on my 2Ghz > machine. Mick:
(I apologize for calling you Mike in an earlier post.)
My buttons are indeed sitting on a form that has a BackgroundImage.
By your second sentence, are you saying that even standard buttons will draw slowly over a form with a BackgroundImage? That's not what I'm seeing. If I change the FancyButtons in my form to System.Windows.Forms.Buttons, the form goes from taking two seconds to draw, to drawing in one or two tenths of a second. Standard buttons do not draw slowly over a form with a background image. But that also seems to be what you are saying in your third sentence.
With my FancyButtons, if I remove the BackgroundImage from the parent form, I am back to acceptable performance, i.e. the form displays in one or two tenths of a second. It's not so fast that I can't see the screen being drawn, but it's probably fast enough.
But the customer really did want that background image. Really, this is just not a limitation that I would have expected from Windows Forms.
Are you suspicious that I have an implementation problem that is causing the bad performance? I am still willing to plug your button code in and see how it does.
Tim Crews GECO, Inc.
Mick Doherty - 08 Oct 2004 21:27 GMT I didn't notice that you called me Mike, but it's OK that you did. I have been called much worse ;o)
I placed 60 fancyButtons on a form with no BackgroundImage and it displayed immediately.
I hadn't tested with standard buttons, but I have now and it's not quite as bad but there is still a delay in painting.
I haven't tried it, but you may get better results by setting the region instead of using transparency. Your rounded edges won't look so good though.
Your best bet would probably be to create a Button Component, rather than Control, which monitors it's Parent for paint and mouse events and calls it's own Draw and HitTest Methods.
Frank Hilemans VG.Net is excellent and you may wish to consider using that.
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>> Are your buttons sitting on a form that has a BackgroundImage? >> You'll find the same behaviour with 40 standard buttons. [quoted text clipped - 39 lines] > Tim Crews > GECO, Inc. Frank Hileman - 08 Oct 2004 22:11 GMT > Your best bet would probably be to create a Button Component, rather than > Control, which monitors it's Parent for paint and mouse events and calls > it's own Draw and HitTest Methods. You stated this more clearly. If you don't want to use VG.net there is an example somewhere by Shawn Burke called Lite Buttons, that has a very basic implementation of a Button component. I believe it came with a sample regarding code serialization of custom components. Look at the windowsforms.com articles section for Shawn Burke articles.
- Frank
Tim Crews - 08 Oct 2004 22:29 GMT <snip>
> I haven't tried it, but you may get better results by setting the region > instead of using transparency. Your rounded edges won't look so good though. It seems unlikely to me that setting the region will result in better performance. With the current implementation, the alpha channel of my owner- drawn button is being used to mask the form background around my button, right? This would seem to be a faster operation than dynamically drawing and filling a region based on a data structure containing the outlines of that region.
Tim Crews GECO, Inc.
Mick Doherty - 09 Oct 2004 00:01 GMT I just tried a basic shaped button and there is no performance gain. It's the initial loading of the button that is slow just as it is with the transparent background. So it looks like a Component based button (Lite Button) is the way to go.
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
> <snip> > [quoted text clipped - 13 lines] > Tim Crews > GECO, Inc. Mick Doherty - 14 Oct 2004 07:21 GMT 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
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>I didn't notice that you called me Mike, but it's OK that you did. I have >been called much worse ;o) [quoted text clipped - 66 lines] > Checked by AVG anti-virus system (http://www.grisoft.com). > Version: 6.0.742 / Virus Database: 495 - Release Date: 19/08/2004 Tommy Carlier - 08 Oct 2004 09:22 GMT I think the problem might be that you set ControlStyles.AllPaintingInWmPaint to true. Normally, when a control has to be painted, it first paints the background (OnPaintBackground), and then it paints the foreground (OnPaint). Setting the ControlStyle AllPaintingInWmPaint means that all the painting is done in OnPaint, and OnPaintBackground is ignored.
Tim Crews - 08 Oct 2004 16:52 GMT > I think the problem might be that you set > ControlStyles.AllPaintingInWmPaint to true. > Normally, when a control has to be painted, it first paints the > background (OnPaintBackground), and then it paints the foreground > (OnPaint). Setting the ControlStyle AllPaintingInWmPaint means that > all the painting is done in OnPaint, and OnPaintBackground is ignored. Once I took care of the bug (pointed out elsewhere) that I was setting Opaque to true instead of false, I got somewhat improved results. Now, instead of a black background for my button, I get a background that is the color of the face of the button. Still not what I want, though -- I want a background that is the color (or image) of the parent form.
From that point, it seems that AllPaintingInWmPaint has no effect at all. Either true or false, I still get a button background that is the color of the face of the button.
Tim Crews GECO, Inc.
Stoitcho Goutsev \(100\) [C# MVP] - 08 Oct 2004 15:31 GMT Hi Tim,
So here is how to fix the problem with the black background: 1. Take off those lines form the form constructor
SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.Opaque, true);
Let only UserPaint and SupprtTransparentBackColor stay.
2. As long as (you mentioned yourself) this is simulated transparency it means that the control class does some work to make this happen. In other words in the OnPaint method you need to call the base.OnPaint in order to let the control do it's work. Put that call as the first line in the OnPaint method (you don't want the default method to overdraw you fancy button, do you)
Here you go... the transparent backgroung. That was easy. However your troubles doesn't stop here. The default OnPaint draws some stuff that you don't want. Like (button 3d appearance, focus rectangle, Balck rectangle around the default button). Among this only the focused rectangle I managed to get rid of. I did that by overriding ShowFocusCues. In my override I return *false*. 3D appearnace you can kind of remove by setting the button's FlatStyle. Default-button black rectangle I removed by overriding NotifyDefault method and calling the base method with *false* as a parameter. It has side effect that the button cannot be default
Anyways, the result is far from perfect.
So my suggestion is to go with the Control as a base class. So, do what I told you erlier (stpes 1 and 2) and change the base class to Control. It works just fine. The problem here is that the FancyButton cannot be used where Button type is expected.
If you still want to use the button as a base class then take a look in the Contol.Region property. Setting the region of the control to match exactly your button's outline shape will make all that crappy adornments to be clipped off.
 Signature HTH Stoitcho Goutsev (100) [C# MVP]
>> when overriding the button control you must also set ControlStyles.Opaque >> to [quoted text clipped - 364 lines] > } > } Mick Doherty - 08 Oct 2004 17:00 GMT His problem stems from the fact that the button base Class has ControlStyles.Opaque set to true by default so his setting it to true makes no difference. This problem does not appear in a class based upon Control because in a Control/UserControl ControlStyles.Opaque is false by default. This is the only modification that is necessary to cure the black background.
OnPaintBackground() is called to draw the controls parent in any non opaque regions of the control. If the control is fully opaque then this method is not called, that is why there is a black background.
I have created a button derived from button and have not set the region of the control, doing so causes nasty jagged edges around curves. By not setting the region you can set the Graphics.SmoothingMode to High and see nice smooth edges around the button.
Without Doublebuffer, AllPaintingInWMPaint does not need to be set.
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
> Hi Tim, > [quoted text clipped - 404 lines] >> } >> } Tim Crews - 08 Oct 2004 17:23 GMT > His problem stems from the fact that the button base Class has > ControlStyles.Opaque set to true by default so his setting it to true makes > no difference. This problem does not appear in a class based upon Control > because in a Control/UserControl ControlStyles.Opaque is false by default. > This is the only modification that is necessary to cure the black > background. Wouldn't it be easier just to change Opaque to false in my constructor, rather than choosing a different base class that defaults it to false? Maybe I'm missing what you intended here. (I think Stoitcho recommended the different base class not only because of the Opaque property, but because I could then suppress some of the button adornments like 3d edges and focus indicators.) If I change the base class of my FancyButton to Control, of course I have lots of forms that _do_ expect FancyButton to have various Button-specific properties that are no longer available if I subclass it from Control.
> OnPaintBackground() is called to draw the controls parent in any non opaque > regions of the control. If the control is fully opaque then this method is > not called, that is why there is a black background. But if I change the Button style to non-opaque, my black background changes to the background color of the Button, instead of the background of the parent form.
> I have created a button derived from button and have not set the region of > the control, doing so causes nasty jagged edges around curves. By not > setting the region you can set the Graphics.SmoothingMode to High and see > nice smooth edges around the button. But I think not setting the region is the reason that I get the Button's background color instead of the form's background color around my button.
So, as you say, the only other approach is to explicitly fill in the entire background area with the parent form's color or image (as you do in DrawParentBackground in your example code) before drawing the button. And this gets me right back to my performance problem.
I was unaware of the issue with nasty jagged edges around curves. I was willing to try setting the region, but if the result is going to look terrible, I guess I will save myself the time I would have spent on this experiment.
> Without Doublebuffer, AllPaintingInWMPaint does not need to be set. Yes, I agree. I have removed it from my constructor.
Thank you again, everyone.
Tim Crews GECO, Inc.
Mick Doherty - 08 Oct 2004 17:50 GMT I was just explaining why you get the Black Background in Button and not in Control. There is no need to derive from Control, just set Opaque to false in the constructor of your Button class. You get the nasty button borders if you call the default OnPaint(). There is no need to call it.
Inherit from Control if you don't need Button behaviour, otherwise Inherit from Button and fix the problems as you encounter them.
My DrawParentBackground method was used mainly to show how to interrogate the poperty of another control if that property exists. You don't need it.
What color should be used for the Background if you are using the BackColor to change the buttonface?
See post above for solution.
 Signature Mick Doherty http://dotnetrix.co.uk/nothing.html
>> His problem stems from the fact that the button base Class has >> ControlStyles.Opaque set to true by default so his setting it to true [quoted text clipped - 61 lines] > Tim Crews > GECO, Inc. Stoitcho Goutsev \(100\) [C# MVP] - 08 Oct 2004 19:28 GMT Hi Tim,
As the other suggested I got this work simply by turning Opaque to false. However you need to set the transparent back color in order to get rid of the BackColor in the background. Don't worry about seting SupportTransparentBackColor. it looks like it is set by default for buttons. Don't call the base OnPaint and you get rid of the button adornments
In you constructor you hould have SetStyle(ControlStyles.Opaque, false); this.BackColor = Color.FromArgb(0,Color.Green);
When you have background image and the better part of the button is transparent this approach has some funky side effects when you move button arround.
So if I were you I'd go with the Button's region.
 Signature HTH Stoitcho Goutsev (100) [C# MVP]
>> His problem stems from the fact that the button base Class has >> ControlStyles.Opaque set to true by default so his setting it to true [quoted text clipped - 61 lines] > Tim Crews > GECO, Inc. Frank Hileman - 08 Oct 2004 18:12 GMT Windows forms controls don't support transparency well. Since buttons are so simple, you may wish to create a control that covers the entire form, and paint both the background and a vector graphics "button" on top of that. It will not be a true control, but will be easier to use.
We use this technique to create all kinds of "controls", not just buttons.
Regards, Frank Hileman
check out VG.net: www.vgdotnet.com Animated vector graphics system Integrated Visual Studio .NET graphics editor
> Hello: > [quoted text clipped - 35 lines] > Tim Crews > GECO, Inc.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|