Creating semi-transparent panels/controls. Is there a foolproof way?

5

I am trying to create a semi-transparent control derived from System.Windows.Forms.Panel. [Edit: Basically what I want to achieve is this]:

enter image description here

I have gone through numerous web articles as well as SO questions and have come up with this:

class SeeThroughPanel : Panel
{
    public SeeThroughPanel()
    {
    }

    protected override CreateParams CreateParams {
        get {
            var cp = base.CreateParams;
            cp.ExStyle |= 0x00000020;
            return cp;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        //base.OnPaint(e);
        e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, 0, 0, 0)), this.ClientRectangle);
    }
}

Which results this (Note that this code too does not make it really semi-transparent. The button is displayed in full color, unlike in above image):

Custom control shows transparency as expected.

However, as soon as I do something that causes repaint of any other controls within the bounds of the semi-transparent panel, it is overdrawn (In this case, I have scrolled the underlying control):

Custom control incorrectly repainted.

So my question is, how can I avoid this? There has to be some way. After all, web browsers handle this kind of rendering all the time.

c#
.net
custom-controls
asked on Stack Overflow Jul 6, 2014 by sampathsris • edited Jul 6, 2014 by leppie

2 Answers

4

The transparency feature of the Windows Forms control leaves much to be desired and is a blatant fudge. The control is not really transparent, it just pretends to be by looking at the background of it's parent control and copying the appropriate portion of the image or background onto it's own surface during the OnPaintBackground method.

This means that a "transparent" control placed on top of another on the same parent will in fact obscure the other child controls. Figure 1 shows this effect in action.

enter image description here

The panel control to the right of the form obscures the PictureBox control and shows only the background of the parent.

In order to make a truly transparent control we need to do a couple of things. Firstly, it's necessary to change the behaviour of the window by giving it a WS_EX_TRANSPARENT style. This is accomplished by overriding the CreateParams property so that the correct window style is included when the control is instantiated. The listing below shows this property override.

protected override CreateParams CreateParams
{
  get
  {
    CreateParams cp=base.CreateParams;
    cp.ExStyle|=0x00000020; //WS_EX_TRANSPARENT
    return cp;
  }
}

The second thing we need to do is to invalidate the parent of the control, not the control itself, whenever we need to update the graphics. This ensures that whatever is behind the control gets painted before we need to do our own graphics output. To do this, a routine such as that shown in the following listing is needed.

protected void InvalidateEx()
{
  if(Parent==null)
    return;
  Rectangle rc=new Rectangle(this.Location,this.Size);
  Parent.Invalidate(rc,true);
}

Finally, we need to ensure that the background draw routine does not mess up the recently repainted parent-form content by stubbing out the OnPaintBackground method.

protected override void OnPaintBackground(PaintEventArgs pevent)
{
  //do not allow the background to be painted 
}

Now the control is ready for the rest of its modification. It is important to note at this point that transparent controls are not suitable for double buffering using the standard SetStyle method. The memory bitmap which is provided to your code has an opaque background and does not allow the carefully retained parent pixels to show through.

To complete this article, a simple control that does nothing but paint moving ellipses on its surface is shown in the following listing.

using System;
using System.Drawing;
using System.Windows.Forms;

internal class SeeThroughPanel : Panel
{

    public SeeThroughPanel()
    {
    }

    protected void TickHandler(object sender, EventArgs e)
    {
        this.InvalidateEx();
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

            cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT

            return cp;
        }
    }

    protected void InvalidateEx()
    {
        if (Parent == null)
        {
            return;
        }

        Rectangle rc = new Rectangle(this.Location, this.Size);

        Parent.Invalidate(rc, true);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {

    }

    private Random r = new Random();

    protected override void OnPaint(PaintEventArgs e)
    {
        
        e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(128, 0, 0, 0)), this.ClientRectangle);
    }
}

enter image description here

answered on Stack Overflow Jul 6, 2014 by Parimal Raj • edited Jun 21, 2020 by Samuel Liew
4

I have done a few tests now.

You are mixiing several problems into one, so let's take them apart:

  • No WebBrowser is written in C# using Winforms. So this is no reason why this must be possible.

  • Your button almost certainly is not in the Form's Controls collection. You need to either script it to be or use a little UI trick (*) to place it over another Control without adding it to it.

  • When you scroll you will see whatever the scrolling control report as its surface.

Here are two screenshots:

This is after startup:

screenshot1

..and this is after I scrolled a little to the right:

screenshot1

I have use this code to make sure the Z-order is right:

button1.Parent = this;
panel1.BringToFront();
button1.BringToFront();
seeThroughPanel1.BringToFront();

Note how the the space of the button is spared; this shows how the old surface is being used.

To work around this you would have to get at the current Form surfacse (maybe by Control.DrawToBitmap) and then use the right part of that to paint your 'semi-transparent' panel. Of course it would have to hide before you capture the form's current surface.

Of all terrible ideas I have had, this seems to be one of the worst..

* The trick is to move it over the container with the keyboard, not the mouse; with this trick it just moves without chnging its parent container. Also useful for placing a Control on top of the tabs of a TabControl... But I was too lazy for that, so I coded it..

answered on Stack Overflow Jul 6, 2014 by TaW • edited Jul 6, 2014 by TaW

User contributions licensed under CC BY-SA 3.0