Title: Add a UAC shield to controls in C#
In Windows Vista and later versions of Windows, you are supposed to add a UAC (user access control) shield to buttons that launch processes that require privilege elevation. Unfortunately Microsoft hasn't made this easy to do.
Adding the shield to a button is awkward but possible. The AddShieldToButton method shown in the following code does it by sending the BCM_SETSHIELD message to the button. Note that the button must have FlatStyle prperty set to System and it must contain a text caption, even if the caption is a single space character.
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd,
uint Msg, int wParam, int lParam);
// Make the button display the UAC shield.
public static void AddShieldToButton(Button btn)
{
const Int32 BCM_SETSHIELD = 0x160C;
// Give the button the flat style and make it
// display the UAC shield.
btn.FlatStyle = System.Windows.Forms.FlatStyle.System;
SendMessage(btn.Handle, BCM_SETSHIELD, 0, 1);
}
Unfortunately Microsoft has provided no way to add the shield to anything other than a button so adding it to other things is more work. You could make an image of the shield and just add that where needed but the shield's appearance changes depending on system settings (such as regular or large font size) and might change in future releases of the operating system.
The GetUacShieldImage method shown in the following code gets an image of the system's shield and returns it in a bitmap so you can add it to menu items, picture boxes, and other controls as appropriate.
// Return a bitmap containing the UAC shield.
private static Bitmap shield_bm = null;
public static Bitmap GetUacShieldImage()
{
if (shield_bm != null) return shield_bm;
const int WID = 50;
const int HGT = 50;
const int MARGIN = 4;
// Make the button. For some reason, it must
// have text or the UAC shield won't appear.
Button btn = new Button();
btn.Text = " ";
btn.Size = new Size(WID, HGT);
AddShieldToButton(btn);
// Draw the button onto a bitmap.
Bitmap bm = new Bitmap(WID, HGT);
btn.Refresh();
btn.DrawToBitmap(bm, new Rectangle(0, 0, WID, HGT));
// Find the part containing the shield.
int min_x = WID, max_x = 0, min_y = HGT, max_y = 0;
// Fill on the left.
for (int y = MARGIN; y < HGT - MARGIN; y++)
{
// Get the leftmost pixel's color.
Color target_color = bm.GetPixel(MARGIN, y);
// Fill in with this color as long as we see the target.
for (int x = MARGIN; x < WID - MARGIN; x++)
{
// See if this pixel is part of the shield.
if (bm.GetPixel(x, y).Equals(target_color))
{
// It's not part of the shield.
// Clear the pixel.
bm.SetPixel(x, y, Color.Transparent);
}
else
{
// It's part of the shield.
if (min_y > y) min_y = y;
if (min_x > x) min_x = x;
if (max_y < y) max_y = y;
if (max_x < x) max_x = x;
}
}
}
// Clip out the shield part.
int shield_wid = max_x - min_x + 1;
int shield_hgt = max_y - min_y + 1;
shield_bm = new Bitmap(shield_wid, shield_hgt);
Graphics shield_gr = Graphics.FromImage(shield_bm);
shield_gr.DrawImage(bm, 0, 0,
new Rectangle(min_x, min_y, shield_wid, shield_hgt),
GraphicsUnit.Pixel);
// Return the shield.
return shield_bm;
}
This method makes a button with a single space as its text and uses the AddShieldToButton method to give it the UAC shield. It then calls the button's DrawToBitmap method to make it draw itself onto a bitmap.
Next the code digs through the bitmap to see where the shield image is. It skips the very edges where the button's borders are and discovers the background color for each horizontal row of pixels in the bitmap. As it traverses the image, it records the largest and smallest X and Y coordinates that do not have those colors. That is where the shield is. The code also converts the background pixels to the transparent color.
Finally the code copies the shield into a new bitmap that is sized to fit and returns the new bitmap. The background pixels are transparent so you can place the shield over a non-matching background.
The following code shows how the main program adds UAC shields to a Button, MenuItem, and PictureBox.
private void Form1_Load(object sender, EventArgs e)
{
// Add the shield to a button.
UacStuff.AddShieldToButton(btnClickMe);
// Add the shield to a menu item.
mnuFileFormatHardDrive.ImageScaling =
ToolStripItemImageScaling.None;
mnuFileFormatHardDrive.Image = UacStuff.GetUacShieldImage();
// Add the shield to a PictureBox and
// move a LinkLabel next to it.
picShield.Image = UacStuff.GetUacShieldImage();
int y = ClientSize.Height - 8 - picShield.Height;
llblDangerous.Location = new Point(
btnClickMe.Right - llblDangerous.Width, y);
picShield.Location = new Point(
llblDangerous.Left - picShield.Width, y);
}
Most of this code is straightforward. The only non-obvious step is the final statement, which moves a LinkLabel so it sits next to a PictureBox displaying the UAC shield. (The LinkLabel itself cannot hold an image so it can't display its own shield.)
Download the example to experiment with it and to see additional details.
|