7

I've been trying to display an image which has a transparent border as the background to a control.

Unfortunately, the transparent area creates a hole in the parent form as follows:

In the above image, the form has a red background which I'd hoped to see behind my control in the transparent areas.

The code I used is as follows:

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        if (this.Image != null)
        {
            Graphics g = Graphics.FromImage(this.Image);

            ImageAttributes attr = new ImageAttributes();

            //set the transparency based on the top left pixel
            attr.SetColorKey((this.Image as Bitmap).GetPixel(0, 0), (this.Image as Bitmap).GetPixel(0, 0));

            //draw the image using the image attributes.
            Rectangle dstRect = new Rectangle(0, 0, this.Image.Width, this.Image.Height);

            e.Graphics.DrawImage(this.Image, dstRect, 0, 0, this.Image.Width, this.Image.Height,
                GraphicsUnit.Pixel, attr);
        }
        else
        {
            base.OnPaint(e);
        }
    }

    protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
    {
        //base.OnPaintBackground(e);
    }

This class is inherited from a PictureBox because I needed a control which implements OnMouseMove and OnMouseUp Events.

I've been researching most of the day without success testing out different ideas but unfortunately most only work on the full framework and not .Net CF.

Any ideas would be much appreciated.

ctacke
  • 66,480
  • 18
  • 94
  • 155
Wayne Phipps
  • 2,019
  • 6
  • 26
  • 31

1 Answers1

6

Ah the joys of CF transparency. I could go on and on about it (and have in my blog and the Project Resistance code I did ages ago).

The gist is this. The child control has to paint it's areas, but first it has to call back up to it's parent (the Form in your case) and tell it to redraw it's background image everywhere except in the child's clipping region and then draw itself on top of that. If that sounds a bit confusing it's because it is.

For example, if you look at Project Resistance, a View (which is just a Control) draws a resistor and bands. It lies in a Form that has an image background, and that background needs to "show through" the transparent areas of the resistor:

enter image description here

So in the drawing code of the resistor it does this:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    try
    {
        RECT rect = new RECT(this.Bounds);

        // draw the blank
        Infrastructure.GraphicTools.DrawTransparentBitmap(e.Graphics, m_blankImage, Bounds, 
              new Rectangle(0, 0, m_blankImage.Width, m_blankImage.Height));

        if (m_bandsImage != null)
        {
            // draw the bands
            Infrastructure.GraphicTools.DrawTransparentBitmap(e.Graphics, m_bandsImage, Bounds, 
                 new Rectangle(0, 0, m_bandsImage.Width, m_bandsImage.Height));
        }
    }
    finally
    {
    }

    if (!Controller.TouchMode)
    {
        // TODO: draw in the selection arrow
        // Controller.SelectedBand
    }
}

Which is simple enough. The key is that it calls to it's base OnPaint, which does this:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    // this assumes we're in a workspace, on MainForm (the whole Parent.Parent thing)
    IBackgroundPaintProvider bgPaintProvider = Parent.Parent as IBackgroundPaintProvider;
    if (bgPaintProvider != null)
    {
        Rectangle rcPaint = e.ClipRectangle;
        // use the parent, since it's the workspace position in the Form we want, 
        // not our position in the workspace
        rcPaint.Offset(Parent.Left, Parent.Top);
        bgPaintProvider.PaintBackground(e.Graphics, e.ClipRectangle, rcPaint);
    }
}

You can see it's calling PaintBackground of the containing Form (it's Parent.Parent in this case becuse the Control is actually in a container called a Workspace - you wouldn't need to walk up twice in your case). That draws in the background image in the area you're currently seeing as the "hole"

public void PaintBackground(Graphics g, Rectangle targetRect, Rectangle sourceRect)
{
    g.DrawImage(m_bmBuffer, targetRect, sourceRect, GraphicsUnit.Pixel);
}
ctacke
  • 66,480
  • 18
  • 94
  • 155
  • Wow, thanks for that. An extremely helpful and detailed explanation. You've certainly spent some time on the subject. – Wayne Phipps Jul 01 '12 at 10:45
  • @ctacke I came up with a Transparency solution that is very close to yours, and it works in the designer as well as at runtime. I recently realized my solution will not work when nesting Container Controls because "Parent.Parent" thing doesn't work. I tried switching "Parent.Parent" to this.TopLevelControl, which works at runtime but not at design time. Where you ever able to get a solution for nesting transparent controls inside Container controls and still have the designer render your control transparent? – Scope Creep Nov 07 '13 at 15:48
  • I gave up even attempting to get designer support for my controls years ago. It just never felt that important to me and was always fragile. Sometimes it would work, other times it wouldn't and I found myself burning days of time not actually doing anything productive so I just never even bother with the designer any longer short of laying out the rectangles where the controls will go. – ctacke Nov 07 '13 at 16:52