When using WPF, we have one further tool at our disposal to enable us to manipulate the built-in controls and avoid the need to create new ones. We are, of course, discussing Attached Properties, so let's extend an example that we started looking at in Chapter 4, Becoming Proficient with Data Binding.
In order to create a button that will enable us to set a second tooltip message to display when the control is disabled, we'll need to declare two Attached Properties. One will hold the disabled tooltip message and the other will be the previously mentioned read-only property that temporarily holds onto the original tooltip value. Let's look at our full ButtonProperties class now:
using System.Windows; using System.Windows.Controls; namespace CompanyName.ApplicationName.Views.Attached { public class ButtonProperties : DependencyObject { private static readonly DependencyPropertyKey originalToolTipPropertyKey = DependencyProperty.RegisterAttachedReadOnly("OriginalToolTip", typeof(string), typeof(ButtonProperties), new FrameworkPropertyMetadata(default(string))); public static readonly DependencyProperty OriginalToolTipProperty = originalToolTipPropertyKey.DependencyProperty; public static string GetOriginalToolTip( DependencyObject dependencyObject) { return (string)dependencyObject.GetValue(OriginalToolTipProperty); } public static DependencyProperty DisabledToolTipProperty = DependencyProperty.RegisterAttached("DisabledToolTip", typeof(string), typeof(ButtonProperties), new UIPropertyMetadata(string.Empty, OnDisabledToolTipChanged)); public static string GetDisabledToolTip( DependencyObject dependencyObject) { return (string)dependencyObject.GetValue( DisabledToolTipProperty); } public static void SetDisabledToolTip( DependencyObject dependencyObject, string value) { dependencyObject.SetValue(DisabledToolTipProperty, value); } private static void OnDisabledToolTipChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { Button button = dependencyObject as Button; ToolTipService.SetShowOnDisabled(button, true); if (e.OldValue == null && e.NewValue != null) button.IsEnabledChanged += Button_IsEnabledChanged; else if (e.OldValue != null && e.NewValue == null) button.IsEnabledChanged -= Button_IsEnabledChanged; } private static void Button_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) { Button button = sender as Button; if (GetOriginalToolTip(button) == null) button.SetValue(originalToolTipPropertyKey, button.ToolTip.ToString()); button.ToolTip = (bool)e.NewValue ? GetOriginalToolTip(button) : GetDisabledToolTip(button); } } }
As with all Attached Properties, we start with a class that extends the DependencyObject class. In this class, we first declare the read-only originalToolTipPropertyKey field using the RegisterAttachedReadOnly method and the OriginalToolTipProperty property and its associated CLR getter.
Next, we use the RegisterAttached method to register the DisabledToolTip property that will hold the value of the tooltip to be displayed when the control is disabled. We then see its CLR getter and setter methods and its all-important PropertyChangedCallback handling method.
In the OnDisabledToolTipChanged method, we first cast the dependencyObject input parameter to its actual type of Button. We then use it to set the ToolTipService.SetShowOnDisabled Attached Property to true, which is required because we want the button's tooltip to be displayed when the button is disabled. The default value is false, so our Attached Property would not work without this step.
Next, we determine whether we need to attach or detach the Button_IsEnabledChanged event-handling method depending on the NewValue and OldValue property values of the DependencyPropertyChangedEventArgs object. If the old value is null, then the property has not been set before and we need to attach the handler; if the new value is null, then we need to detach the handler.
In the Button_IsEnabledChanged event-handling method, we first cast the sender input parameter to the Button type. We then use it to access the OriginalToolTip property and if it is null, we set it with the current value from the control's normal ToolTip property. Note that we need to pass the originalToolTipPropertyKey field into the SetValue method, as it is a read-only property.
Finally, we utilize the e.NewValue property value to determine whether to set the original tooltip or the disabled tooltip into the control's normal ToolTip property. Therefore, if the control is enabled, the e.NewValue property value will be true and the original tooltip will be returned; if the button is disabled, the disabled tooltip will be displayed. We could use this Attached Property as follows:
<Button Content="Save" Attached:ButtonProperties.DisabledToolTip="You must correct validation errors before saving" ToolTip="Saves the user" />
As can be seen from this simple example, Attached Properties enable us to easily add new functionality to the existing suite of UI controls. This again highlights how versatile WPF is and demonstrates that we often have no need to create completely new controls.