2
votes

I'm trying to make a splash screen for my application. I have an .PNG image which has some partially transparent parts.

I've made the form borderless and disabled the controlbox. However, background color is causing problems.

First I placed the image in a picturebox. Then I made the form background transparent this way:

this.BackColor = Color.Magenta;
this.TransparencyKey = Color.Magenta;

It sort of worked. The fully transparent parts of the screen are transparent, but magenta can be seen in the partially transparent parts.

Then I tried putting the image as a background image of the form. I tried using this code to make the background of the form transparent:

private void Form1_Load(object sender, EventArgs e)
{
   this.SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor, true);
   this.BackColor = System.Drawing.Color.Transparent;
}

It didn't work either. The background still isn't transparent. What can I do?

3
this.BackColor = Color.Transparent;?geedubb
Doesn't work. The background is now light grey.Kestis
Background is now shoving black.Kestis

3 Answers

3
votes

Why magenta? Try using White. I set the back color White and transparency key White. In the image below, the random box with a picture is my splash.

Results

2
votes

TransparencyKey will set the form to be fully opaque, except pixels of the exact color specified. AFAIK it does this by actually defining the WindowRegion i.e. window shape based on pixels matching the colour - those pixels are no longer part of your window and can be clicked through.

Sounds like your PNG has an alpha layer i.e. transparency levels 0..255, which are blended over your background colour (magenta) and after that only the pure magenta pixels become transparent.

You could try:

  • Pick a colour e.g. Magenta not used in your splash and put that in your splash PNG in place of the fully transparent areas as opaque pixels
  • Capture a copy of the desktop bitmap under your window before it shows, and put this in a PictureBox under your splash.

The partially transparent areas of the splash would then blend with the background image, the fully transparent areas will match TransparencyKey and be transparent.

But you will get visual artefacts if the rest of the desktop changes while the splash is showing, if your splash screen moves etc.

edit: Seems there's an easier solution here without using TransparencyKey

2
votes

You can accomplish it with layered windows and UpdateLayeredWindow() Api:

protected override CreateParams CreateParams
{
    get
    {
        // Add the layered extended style (WS_EX_LAYERED) to this window
        CreateParams createParam = base.CreateParams;
        createParam.ExStyle = (createParam.ExStyle | APIHelp.WS_EX_LAYERED);
        return createParam;
    }
}

And in your form load event:

IntPtr memDc, hBmp, hOldBmp;

private void Form1_Load(object sender, EventArgs e)
{
    APIHelp.BLENDFUNCTION blend;

    //Only works with a 32bpp bitmap
    blend.BlendOp = APIHelp.AC_SRC_OVER;
    //Always 0
    blend.BlendFlags = 0;
    //Set to 255 for per-pixel alpha values
    blend.SourceConstantAlpha = 255;
    //Only works when the bitmap contains an alpha channel
    blend.AlphaFormat = APIHelp.AC_SRC_ALPHA;

    IntPtr screenDc;

    screenDc = APIHelp.GetDC(IntPtr.Zero);

    Bitmap bmp;

    using (bmp = (Bitmap)Bitmap.FromFile(@"C:\.......png")) //the image must be the same size as your form
    {
        memDc = APIHelp.CreateCompatibleDC(screenDc);
        hBmp = bmp.GetHbitmap(Color.FromArgb(0));
        hOldBmp = APIHelp.SelectObject(memDc, hBmp); //memDc is a device context that contains our image
    }

    APIHelp.DeleteDC(screenDc);

    APIHelp.Size newSize;
    APIHelp.Point newLocation;
    APIHelp.Point sourceLocation;

    newLocation.x = this.Location.X;
    newLocation.y = this.Location.Y;

    sourceLocation.x = 0;
    sourceLocation.y = 0;

    newSize.cx = this.Width;
    newSize.cy = this.Height;

    APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, ref newLocation, ref newSize, memDc, ref sourceLocation, 
           0, ref blend, APIHelp.ULW_ALPHA);
}

Class APIHelp:

class APIHelp
{
    public const Int32 WS_EX_LAYERED = 524288;
    public const Int32 ULW_ALPHA = 2;
    public const byte AC_SRC_OVER = 0;
    public const byte AC_SRC_ALPHA = 1;

    [StructLayout(LayoutKind.Sequential)] 
    public struct Point
    {
        public Int32 x;
        public Int32 y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Size
    {
        public Int32 cx;
        public Int32 cy;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ARGB
    {
        public byte Blue;
        public byte Green;
        public byte Red;
        public byte Alpha;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct BLENDFUNCTION
    {
        public Byte BlendOp;
        public Byte BlendFlags;
        public Byte SourceConstantAlpha;
        public Byte AlphaFormat;
    }

    [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
       ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pptSrc, uint crKey,
       [In] ref BLENDFUNCTION pblend, uint dwFlags);


    [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", SetLastError = true)]
    public static extern IntPtr CreateCompatibleDC([In] IntPtr hdc);

    [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
    public static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj);

    [DllImport("user32.dll")]
    public static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
    public static extern bool DeleteDC([In] IntPtr hdc);

    [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool DeleteObject([In] IntPtr hObject);
}

When the form closes release the resources:

APIHelp.SelectObject(memDc, hOldBmp);
APIHelp.DeleteObject(hBmp);
APIHelp.DeleteDC(memDc);