Home > Windows Development > Displaying the UAC Shield in WPF with an Adorner

Displaying the UAC Shield in WPF with an Adorner

Since creating an elevated managed COM object, I have, on odd occasions, given thought to how to display the UAC Shield Icon in a WPF application.  After some research failed to uncover any useful information, I embarked on the task of creating a manageable method for displaying the icon.  While a full general purpose solution might be nice, a button that can display the icon as needed, should be sufficient for most purposes and demonstrates a methodology to follow for other controls.

Since I limited my implementation to a button, I created the UACShieldButton derived from Button.  By deriving from Button, all of the click and commanding handling were already done.  I left the final decision to display the icon with the UACShieldButton class.  The caller simply indicates that it desires the shield to be displayed if all other conditions, elevation state and UAC enabled operating system, are correct.

The obvious items required are a method of the calling code to indicate the desire for the shield and the shield icon itself.  Since I wanted binding to work with these properties, they were implemented as dependency properties.  The DesiresShield dependency property represents the calling code’s desire to have the shield displayed.  The ShieldIcon dependency property is an ImageSource for the image to display.  This property will default to the system shield icon, System.Drawing.SystemIcons.Shield through the default value set in the UACShieldButton’s static constructor.

If you have been following along, you have noticed I keep indicating the code utilizing the UACShieldButton class indicates a desire to have the shield displayed.  By allowing the UACShieldButton class to make the final decision to display the shield, some complication is hidden from the caller and code is consolidated into one location.  The shield will be displayed if the three following conditions are true:  the shield is requested, the operating system’s major level is 6 or above (Vista and Server 2008 and higher) and the caller is not already elevated.  The test for elevation is the only one that is not entirely straight forward.  As it happens, the old Win32 API call IsUserAnAdmin supplies the needed information.  Even though the documentation indicates membership in the Administrator’s group, under UAC enabled systems, it indicates that the user is currently elevated to administrator status.

The other nicety provided is the automatic handling of different ToolTips depending on elevation state.  Generally, the non-elevated state indicates that the associated out-of-process COM server has not been created.  Since it is not created, any state information that it would supply is unavailable.  By providing for the automatic handling of the display to different ToolTips according to the elevation state, the UACShieldButton control automatically provides the user with the correct ToolTip.  For example, assume you have a Service Controller Interface COM server and Start and Stop buttons to stop or start a service.   Until such time as the COM server is created, the state of the service is unknown.  The ToolTipNotElevated version of the ToolTip could indicate the lack of state information along with the effect of clicking the button.  Once the COM server is created, the ToolTipElevated ToolTip would be displayed simply indicating the effect of clicking the button.

The only logic that the caller must provide is the setting of DesiresShield to false, once the elevation is provided for any button.  You might be wondering why?  The elevation done here is elevating an out-of-process COM object as opposed to the program elevation that the UACShieldButton class can detect.

The trick to easily displaying the shield is to use an Adorner.  The alternative is to provide an alternate control template for the button and having created an alternate control template once, I do not relish doing it again.  The UACShieldAdorner class derives from Adorner and manages all aspects of displaying the shield.  The only tricky part of this code is the sizing and positioning algorithm.  The shield is resized proportionally to be four pixels less than the hosting button’s height.  It is then placed two pixels below and to the right of the upper left corner of the button.  Since the adorner layer is the topmost layer, the shield can obscure the text or the image in the button.

While this solution is not an end-all solution to displaying UAC Shield Icons, it is a starting point to that can be easily adapted to other controls and positions algorithms.

The code is here

Advertisements
Categories: Windows Development Tags: , , ,
  1. sevenalive
    2010/10/10 at 20:19

    Here is my revised version that solves a few issues I discovered using it.

    namespace System.Windows.Controls
    {
    using System.ComponentModel;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;

    ///
    /// Provides a WPF button that displays a UAC Shield icon when required
    ///
    public sealed class UacButton : Button, INotifyPropertyChanged
    {
    #region Constants and Fields

    ///
    /// Dependency Property – Specifies the text to display on the button
    ///
    private static readonly DependencyProperty ButtonTextProperty = DependencyProperty.Register(
    “ButtonText”,
    typeof(string),
    typeof(UacButton),
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender, OnButtonTextChanged));

    ///
    /// Dependency Property – Indicates if the UAC Shield is desired on the button
    ///
    private static readonly DependencyProperty IsShieldNeededProperty = DependencyProperty.Register(
    “IsShieldNeeded”,
    typeof(bool),
    typeof(UacButton),
    new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender, OnIsShieldNeededChanged));

    ///
    /// The UAC shield
    ///
    private static readonly BitmapImage Shield = new BitmapImage(new Uri(@”pack://application:,,,/System.Windows;component/Images/Shield.png”, UriKind.Absolute));

    ///
    /// The disabled shield image
    ///
    private static readonly BitmapImage ShieldDisabled =
    new BitmapImage(new Uri(@”pack://application:,,,/System.Windows;component/Images/ShieldDisabled.png”, UriKind.Absolute));

    ///
    /// Dependency Property – The shield icon to display
    ///
    private static readonly DependencyProperty ShieldIconProperty;

    ///
    /// Indicates if the Uac shield is needed
    ///
    private static readonly bool ShieldNeeded;

    ///
    /// The text for the button
    ///
    private static readonly string Text = string.Empty;

    #endregion

    #region Constructors and Destructors

    ///
    /// Initializes static members of the class.
    ///
    static UacButton()
    {
    ShieldIconProperty = DependencyProperty.Register(
    “ShieldIcon”,
    typeof(ImageSource),
    typeof(Button),
    new FrameworkPropertyMetadata(Shield, FrameworkPropertyMetadataOptions.AffectsRender, OnShieldIconChanged));

    ButtonTextProperty = DependencyProperty.Register(
    “ButtonText”, typeof(string), typeof(Button), new FrameworkPropertyMetadata(Text, FrameworkPropertyMetadataOptions.AffectsRender, OnButtonTextChanged));

    if (Environment.OSVersion.Version.Major >= 6)
    {
    // Vista or higher
    ShieldNeeded = true; // Should check if user is already an admin and set to false if the program is running under admin permissions.
    }
    }

    ///
    /// Initializes a new instance of the class.
    ///
    public UacButton()
    {
    this.Loaded += this.OnLoaded;
    this.IsEnabledChanged += (o, args) => this.ChangeUacIcon(args);
    var stackPanel = new StackPanel
    {
    Orientation = Orientation.Horizontal
    };

    var imgShield = new Image
    {
    Source = this.IsEnabled ? Shield : ShieldDisabled,
    Stretch = Stretch.None,
    Margin = new Thickness(0, 0, 5, 0)
    };
    stackPanel.Children.Add(imgShield);

    var textBlock = new TextBlock
    {
    Text = Text,
    VerticalAlignment = VerticalAlignment.Center
    };
    stackPanel.Children.Add(textBlock);
    this.Content = stackPanel;
    }

    #endregion

    #region Events

    ///
    /// Occurs when a property has changed
    ///
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    #region Properties

    ///
    /// Gets or sets the text to display on the button
    ///
    public string ButtonText
    {
    get
    {
    return (string)this.GetValue(ButtonTextProperty);
    }

    set
    {
    this.SetValue(ButtonTextProperty, value);
    }
    }

    ///
    /// Gets a value indicating whether the shield is desired and the OS supports elevation
    ///
    public bool IsShieldDisplayed
    {
    get
    {
    return ShieldNeeded && this.IsShieldNeeded;
    }
    }

    ///
    /// Gets or sets a value indicating whether the caller desires the to be displayed. This is a dependency property.
    ///
    ///
    /// A Boolean that indicates if the caller wants the displayed
    ///
    ///
    /// This is only an indication of desire. If the operating system does not support UAC or the user is already
    /// elevated, any request to display is ignored.
    ///
    public bool IsShieldNeeded
    {
    get
    {
    return (bool)this.GetValue(IsShieldNeededProperty);
    }

    set
    {
    this.SetValue(IsShieldNeededProperty, value);
    }
    }

    ///
    /// Gets or sets the icon so show when elevation is required. This is a dependency property.
    ///
    /// An that represents a graphic to be displayed
    public ImageSource ShieldIcon
    {
    get
    {
    return (ImageSource)this.GetValue(ShieldIconProperty);
    }

    set
    {
    this.SetValue(ShieldIconProperty, value);
    }
    }

    ///
    /// Gets or sets shown when elevation has been preformed
    ///
    ///
    /// A string that is used as the when elevation is complete
    ///
    public object ToolTipElevated { get; set; }

    ///
    /// Gets or sets shown when elevation has not been preformed
    ///
    /// A string that is used as the when elevation is required
    public object ToolTipNotElevated { get; set; }

    #endregion

    #region Methods

    ///
    /// Handles a change to the property
    ///
    ///
    /// The dependency object
    ///
    ///
    /// The instance containing the event data.
    ///
    private static void OnButtonTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
    var me = (UacButton)obj;
    var stackPanel = (StackPanel)me.Content;
    if (stackPanel != null)
    {
    var textBlock = (TextBlock)stackPanel.Children[1];
    if (textBlock != null)
    {
    textBlock.Text = e.NewValue.ToString();
    }
    }

    me.ToolTip = me.GetToolTip();
    me.OnPropertyChanged(“ButtonText”);
    }

    ///
    /// Handles a change to the property
    ///
    ///
    /// The dependency object
    ///
    ///
    /// The instance containing the event data.
    ///
    ///
    /// Adds or removes the UACShieldAdorner as appropriate
    ///
    private static void OnIsShieldNeededChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
    var showShield = (bool)e.NewValue && ShieldNeeded;
    var me = (UacButton)obj;
    var sp = (StackPanel)me.Content;
    var imageShield = (Image)sp.Children[0];
    if (imageShield != null)
    {
    imageShield.Visibility = showShield ? Visibility.Visible : Visibility.Collapsed;
    }

    me.ToolTip = me.GetToolTip();
    me.OnPropertyChanged(“IsShieldNeeded”);
    }

    ///
    /// Handles a change to the property
    ///
    ///
    /// The dependency object
    ///
    ///
    /// The instance containing the event data.
    ///
    private static void OnShieldIconChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
    var me = (UacButton)obj;
    var sp = (StackPanel)me.Content;
    var imageShield = (Image)sp.Children[0];
    if (imageShield != null)
    {
    imageShield.Source = (ImageSource)e.NewValue;
    }

    me.ToolTip = me.GetToolTip();
    me.OnPropertyChanged(“ShieldIcon”);
    }

    ///
    /// Changes the UAC icon
    ///
    ///
    /// The instance containing the event data.
    ///
    private void ChangeUacIcon(DependencyPropertyChangedEventArgs e)
    {
    if (!this.IsShieldDisplayed)
    {
    return;
    }

    if ((bool)e.NewValue)
    {
    this.ShieldIcon = Shield;
    }
    else
    {
    this.ShieldIcon = ShieldDisabled;
    }
    }

    ///
    /// Returns current “actual”
    ///
    ///
    /// If both and are ,
    /// is returned.
    /// Otherwise or is returned
    /// based on
    ///
    ///
    ///
    private object GetToolTip()
    {
    if (this.ToolTipElevated == null && this.ToolTipNotElevated == null)
    {
    return this.ToolTip;
    }

    return this.IsShieldNeeded ? this.ToolTipNotElevated : this.ToolTipElevated;
    }

    ///
    /// Called when the control is loaded
    ///
    ///
    /// The sender.
    ///
    ///
    /// The instance containing the event data.
    ///
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
    this.ToolTip = this.GetToolTip();
    }

    ///
    /// When a property has changed, call the Event
    ///
    ///
    /// The property name that has changed
    ///
    private void OnPropertyChanged(string name)
    {
    var handler = this.PropertyChanged;

    if (handler != null)
    {
    handler(this, new PropertyChangedEventArgs(name));
    }
    }

    #endregion
    }
    }

    • SOHO Technology
      2011/05/04 at 20:43

      SevenAlive,
      Would you be so kind as to tell me what issues you discovered and fixed?

  2. 2013/08/02 at 00:55

    Hi there, just wanted to tell you, I liked this blog post.
    It was inspiring. Keep on posting!

  1. 2013/06/03 at 13:55

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: