views:

5167

answers:

3

I'm trying to create a transparent button in C# (.NET 3.5 SP1) to use in my WinForms application. I've tried everything to get the button to be transparent (it should show the gradient background underneath the button) but it's just not working.

Here is the code I'm using:

public class ImageButton : ButtonBase, IButtonControl
{
    public ImageButton()
    {
        this.SetStyle(
            ControlStyles.SupportsTransparentBackColor | 
            ControlStyles.OptimizedDoubleBuffer | 
            ControlStyles.AllPaintingInWmPaint | 
            ControlStyles.ResizeRedraw | 
            ControlStyles.UserPaint, true);
        this.BackColor = Color.Transparent;
    }

    protected override void OnPaint(PaintEventArgs pevent)
    {
        Graphics g = pevent.Graphics;
        g.FillRectangle(Brushes.Transparent, this.ClientRectangle);
        g.DrawRectangle(Pens.Black, this.ClientRectangle);
    }


    // rest of class here...

}

The problem is that the button seems to be grabbing random UI memory from somewhere and filling itself with some buffer from Visual Studio's UI (when in design mode). At runtime it's grabbing some zero'd buffer and is completely black.

My ultimate goal is to paint an image on an invisible button instead of the rectangle. The concept should stay the same however. When the user hovers over the button then a button-type shape is drawn.

Any ideas?

EDIT: Thanks everybody, the following worked for me:

public class ImageButton : Control, IButtonControl
{
    public ImageButton()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.Opaque, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        this.BackColor = Color.Transparent;

    }

    protected override void OnPaint(PaintEventArgs pevent)
    {
        Graphics g = pevent.Graphics;
        g.DrawRectangle(Pens.Black, this.ClientRectangle);
    }


    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        // don't call the base class
        //base.OnPaintBackground(pevent);
    }


    protected override CreateParams CreateParams
    {
        get
        {
            const int WS_EX_TRANSPARENT = 0x20;
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= WS_EX_TRANSPARENT;
            return cp;
        }
    }

    // rest of class here...
}
+1  A: 

I'm not sure ButtonBase supports transparency... have you checked that out?

I've written a number of transparent controls, but I have always inherited from Control or UserControl.

When you want to block out a control painting it's background - you should override OnPaintBackground instead of OnPaint and not call the base class.

Filling a rectangle with Brushes.Transparent is funny though - you're painting with an invisible color over what's aready there. Or to put it another way: it does nothing!

danbystrom
+5  A: 

WinForms (and underlying User32) does not support transparency at all. WinForms however can simulate transparency by using control style you provide - SupportsTransparentBackColor, but in this case all that "transparent" control does, it to allow drawing parent its background.

ButtonBase uses some windows styles that prevent working this mechanism. I see two solutions: one is to derive your control from Control (instead of ButtonBase), and second is to use Parent's DrawToBitmap to get background under your button, and then draw this image in OnPaint.

arbiter
+1 for great information @arbiter but some code example would have certainly gone a long way farther.
TheVillageIdiot
Something like this is what he had in mind but button rather than label. (i think)http://christian-helle.blogspot.com/2008/01/transparent-controls-in-netcf.html
TEEKAY
+1  A: 

In winforms there are some tricks to allow a control having its background correctly painted when using transparency. You can add this code to the OnPaint or OnPaintBackground to get the controls you have in the background being painted:

if (this.Parent != null)
{
 GraphicsContainer cstate = pevent.Graphics.BeginContainer();
 pevent.Graphics.TranslateTransform(-this.Left, -this.Top);
 Rectangle clip = pevent.ClipRectangle;
 clip.Offset(this.Left, this.Top);
 PaintEventArgs pe = new PaintEventArgs(pevent.Graphics, clip);

 //paint the container's bg
 InvokePaintBackground(this.Parent, pe);
 //paints the container fg
 InvokePaint(this.Parent, pe);
 //restores graphics to its original state
 pevent.Graphics.EndContainer(cstate);
}
else
  base.OnPaintBackground(pevent); // or base.OnPaint(pevent);...
jmservera
Thanks for this. It's really helpful.
rein