Attached Behaviors Part 5: EnumIsEnabled
- Attached Behaviors Part 1: BooleanVisibility
- Attached Behaviors Part 2: Framework
- Attached Behaviors Part 3: NullVisibility
- Attached Behaviors Part 4: EnumVisibility
- Attached Behaviors Part 5: EnumIsEnabled
- Attached Behaviors Part 6: EnumGroup
- Attached Behaviors Part 7: EnumSelector
Last time, we created a small framework for matching enumeration values. With this very handy technique in our arsenal, we can start interpreting enumeration properties in various ways.
Scenario
A common situation is the need to enable and disable related sets of controls. For example, we might disable credit card fields if a user chooses the PayPal option on a checkout form (and vice versa). In these cases, we can use enumeration matching to manage the UIElement.IsEnabled property:
<StackPanel>
<local:PayPalOptions
local:EnumIsEnabled.Value="{Binding PaymentType}"
local:EnumIsEnabled.TargetValue="PayPal"
/>
<local:CreditCardOptions
local:EnumIsEnabled.Value="{Binding PaymentType}"
local:EnumIsEnabled.TargetValue="CreditCard"
/>
</StackPanel>
Both of the payment options are governed by the PaymentType property. They are mutually exclusive because only one target value will match at a time.
Like with other enumeration-based behaviors, we can invert the results of the match using the WhenMatched and WhenNotMatched properties:
<local:CreditCardOptions
local:EnumIsEnabled.Value="{Binding PaymentType}"
local:EnumIsEnabled.TargetValue="CreditCard"
local:EnumIsEnabled.WhenMatched="False"
local:EnumIsEnabled.WhenNotMatched="True"
/>
EnumIsEnabled
The naming for this concept is a little fuzzy. My first thought, EnumEnabler, uses a term most commonly associated with addictive behavior. Scratch. EnumEnabled is closer, but it sounds like we are enabling something about enumerations, not setting the IsEnabled property. I ultimately decided the format EnumPropertyName is more discoverable than using an arbitrary term to complete the identifier. Naming suggestions are welcome in the comments.
The process of defining an attached behavior should be very familiar by now. We start by declaring a static class:
public static class EnumIsEnabled
Next, we register the attached properties which govern the behavior. First is the Value property, which can contain any enumeration value and thus needs to be typed as object. We also create static accessors to facilitate XAML usage:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(object),
typeof(EnumIsEnabled),
new PropertyMetadata(OnArgumentsChanged));
public static object GetValue(UIElement uiElement)
{
return uiElement.GetValue(ValueProperty);
}
public static void SetValue(UIElement uiElement, object value)
{
uiElement.SetValue(ValueProperty, value);
}
Next, we register the TargetValue property, which is a simple string we will parse later:
public static readonly DependencyProperty TargetValueProperty =
DependencyProperty.RegisterAttached(
"TargetValue",
typeof(string),
typeof(EnumIsEnabled),
new PropertyMetadata(OnArgumentsChanged));
public static string GetTargetValue(UIElement uiElement)
{
return (string) uiElement.GetValue(TargetValueProperty);
}
public static void SetTargetValue(UIElement uiElement, string value)
{
uiElement.SetValue(TargetValueProperty, value);
}
Then, we register the WhenMatched property, giving it a default value of true:
public static readonly DependencyProperty WhenMatchedProperty =
DependencyProperty.RegisterAttached(
"WhenMatched",
typeof(bool),
typeof(EnumIsEnabled),
new FrameworkPropertyMetadata(true, OnArgumentsChanged));
public static bool GetWhenMatched(UIElement uiElement)
{
return (bool) uiElement.GetValue(WhenMatchedProperty);
}
public static void SetWhenMatched(UIElement uiElement, bool value)
{
uiElement.SetValue(WhenMatchedProperty, value);
}
Finally, we register the WhenNotMatched property, giving it a default value of false:
public static readonly DependencyProperty WhenNotMatchedProperty =
DependencyProperty.RegisterAttached(
"WhenNotMatched",
typeof(bool),
typeof(EnumIsEnabled),
new FrameworkPropertyMetadata(false, OnArgumentsChanged));
public static bool GetWhenNotMatched(UIElement uiElement)
{
return (bool) uiElement.GetValue(WhenNotMatchedProperty);
}
public static void SetWhenNotMatched(UIElement uiElement, bool value)
{
uiElement.SetValue(WhenNotMatchedProperty, value);
}
Behavior
As in the previous installments in this series, we declare the behavior, telling it how to create instances for individual host objects:
private static readonly AttachedBehavior Behavior =
AttachedBehavior.Register(host => new EnumIsEnabledBehavior(host));
Now, we update it whenever any of the above dependency properties changes:
private static void OnArgumentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Behavior.Update(d);
}
The behavior implementation looks almost exactly like the one for EnumVisibility:
private sealed class EnumIsEnabledBehavior : Behavior<UIElement>
{
private readonly EnumCheck _enumCheck = new EnumCheck();
internal EnumIsEnabledBehavior(DependencyObject host) : base(host)
{}
protected override void Update(UIElement host)
{
_enumCheck.Update(GetValue(host), GetTargetValue(host));
host.IsEnabled = _enumCheck.IsMatch
? GetWhenMatched(host)
: GetWhenNotMatched(host);
}
}
We once again leverage the EnumCheck class to manage the parsing, caching, and matching of target values.
Sample Project
This WPF application shows EnumIsEnabled in action:
Attached Behaviors Part 5 EnumIsEnabled.zip
It allows you to set the value of the EnumIsEnabled.Value attached property on three different text blocks. The first shows when Value matches TargetValue. The second shows when Value matches a comma-separated TargetValue. The third uses the WhenMatched and WhenNotMatched attached properties to show when Value does not match TargetValue instead.
Summary
Except for the reference to the IsEnabled property, we didn’t really write anything new for this attached behavior. We only have to write code at all because the Value, TargetValue, WhenMatched, and WhenNotMatched properties must be scoped to a specific class in the XAML. This, along with the fact that WhenMatched and WhenNotMatched can have different types, is why we redeclare the properties on each new behavior.
Next time, we will use a group of radio buttons to allow the user to select enumeration values.