Introduction
Since I created alpha-blended bitmap buttons for MFC, I thought I would try the same thing in a .NET Windows Forms environment. Even though the concepts behind the coding were very similar, I found implementing the details to be a bit more challenging. I therefore thought it might benefit others to learn some of the pitfalls and workarounds that are required to make this work.
I did not create a very sophisticated demo project for this example. There are simply a number of buttons on the bitmapped background window for display examples, and only the refresh buttons and exit button actually do something. The refresh buttons will change the background bitmap (2 in all) so the transparent buttons have another look.
Creating the Bitmaps
The bitmaps for this button must contain four images, laid out horizontally within the bitmap. The four states represent Normal, Selected, Hover, and Disable, in that order. The four images must be the same size. I used 48x48 round buttons and 75x48 pill buttons for this demo.
The button bitmaps are 32-bit-per-pixel (32bpp) RGB images with an alpha channel that determines transparency. I created these images in Photoshop a bit different than the MFC button images. The tricky part is the alpha channel, because Photoshop is not always very clear about how to get this just right. The steps I took were to first create a new alpha channel by selecting the Channels tab in the Layers-Channels-Paths window, and then select the tiny "Create New Channel" button at the bottom. Once the Alpha Channel is created, I double-clicked this channel to open the Options dialog, and then changed the Opacity to 0% with a Black or White Color and a Masked Area selection. Next, I turned off the RGB channel views (deselect the little eye next to each one), and left the Alpha channel visible. The display should be either black or white at this point depending on the mask color. I then selected the RGB channels by using Ctrl-click on the RGB channel. This should select part or all of the actual button layers. Finally, I painted the opaque areas with a black or white brush to get the level of transparency I want. The display usually looks something like this:
The images must be saved as 32-bit ARGB, so the Alpha layer must be preserved in the Save dialog, and remember that there can only be one Alpha layer.
Loading the Bitmaps from a Resource DLL
One of the most frustrating things I learned about alpha channel bitmaps in GDI+ is that they don't load properly. No matter what I tried, the alpha-channel was always lost, and the bitmaps were converted back to 32bppRGB. I finally found a solution to this problem when I stumbled across a demo by Steve McMahon on vbaccelerator.com. Steve's demo provided a good workaround by loading the bitmaps from a resource DLL and doing some manipulation to keep the alpha data in tact.
public static Bitmap DibToBitmap(
IntPtr hDib
)
{
BITMAP tBM = new BITMAP();
GetObjectBitmap(hDib, Marshal.SizeOf(tBM), ref tBM);
Bitmap bm = new Bitmap(tBM.bmWidth, tBM.bmHeight);
if (tBM.bmBitsPixel == 32)
{
bm = bm.Clone(new Rectangle(0, 0, tBM.bmWidth, tBM.bmHeight),
PixelFormat.Format32bppArgb);
BitmapData destData = bm.LockBits(
new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
int destWidth = destData.Stride;
IntPtr destScan0 = destData.Scan0;
unsafe
{
byte * pDest = (byte *) (void *) destScan0;
pDest += ((bm.Width * 4) * (bm.Height - 1));
byte * pSrc = (byte *) (void *) tBM.bmBits;
for (int y = 0; y < bm.Height; ++y)
{
for (int x = 0; x < bm.Width; ++x)
{
pDest[0] = pSrc[0];
pDest[1] = pSrc[1];
pDest[2] = pSrc[2];
pDest[3] = pSrc[3];
pDest += 4;
pSrc += 4;
}
pDest -= (bm.Width * 8);
}
}
bm.UnlockBits(destData);
}
else
{
IntPtr hWndDesktop = GetDesktopWindow();
IntPtr hDCComp = GetDC(hWndDesktop);
IntPtr hDCSrc = CreateCompatibleDC(hDCComp);
ReleaseDC(hWndDesktop, hDCComp);
IntPtr hBmpOld = SelectObject(hDCSrc, hDib);
Graphics gfx = Graphics.FromImage(bm);
IntPtr hDCDest = gfx.GetHdc();
BitBlt(hDCDest, 0, 0, tBM.bmWidth, tBM.bmHeight, hDCSrc, 0, 0, SRCCOPY);
gfx.ReleaseHdc(hDCDest);
SelectObject(hDCSrc, hBmpOld);
DeleteDC(hDCSrc);
}
return bm;
}
Using Steve's idea, I created an MFC DLL, loaded it with all the 32bppARGB bitmaps, and used his ResourceLibrary
and ImageUtility
classes to create GDI+ Bitmap
objects.
string ResourceFileName = "TransButtonResources.dll";
if (File.Exists(ResourceFileName))
{
using (ResourceLibrary lib = new ResourceLibrary())
{
lib.Filename = ResourceFileName;
if (!lib.Handle.Equals(IntPtr.Zero))
{
bgImage1 = LoadBitmapResource(lib, 2000);
bgImage2 = LoadBitmapResource(lib, 2006);
purpleButton = LoadBitmapResource(lib, 2001);
redButton = LoadBitmapResource(lib, 2002);
whiteButton = LoadBitmapResource(lib, 2003);
openButton = LoadBitmapResource(lib, 2004);
saveButton = LoadBitmapResource(lib, 2005);
}
}
}
The Button Class
My TransButton
class here is nothing very fancy. I actually borrowed the control state code from the XP button example. I added functions to set the bitmap and region, and painted the correct image from a horizontal offset within the 4-state bitmap.
if (ButtonImages != null)
{
Rectangle destRect = new Rectangle( 0, 0,
pea.ClipRectangle.Width, pea.ClipRectangle.Height);
Rectangle srcRect = new Rectangle( xOffset, yOffset,
pea.ClipRectangle.Width, pea.ClipRectangle.Height);
GraphicsUnit units = GraphicsUnit.Pixel;
pea.Graphics.DrawImage(ButtonImages, destRect, srcRect, units);
}
The very nice thing about GDI+ is that I no longer have to do pixel-by-pixel alpha-blending.
Using the Code
If you create a new project and use the classes here, remember that there is unmanaged code within these classes, so your project settings must be changed to allow unsafe code.
Follow these steps to create and display your transparent buttons:
- Create the buttons with 32-bit ARGB format.
- Create a resource DLL and import the bitmaps.
- Move the resource DLL to your .NET project directory, or provide a reference path.
- Create buttons on your .NET project's Form that have transparent background color.
- Change the buttons class type to
TransButton.TransButton
.
- Extract the bitmaps from DLL using
ResourceLibrary
class code.
- Set the Bitmap and Region of buttons after
InitializeComponent
.
That's about it.