Another convenient behavior is to conditionally show a piece of UI based on the value of an enumeration property. This is especially useful for working with view models in an MVVM context, where enumerations are a common property type.

The input to this behavior is a value and a target value. The host is shown/hidden based on whether they match:

<TextBlock

Text="Access denied"

local:EnumVisibility.Value="{Binding UserType}"

local:EnumVisibility.TargetValue="Standard"

/>

We can specify multiple target values to compare against the Value property:

<TextBlock

Text="Access granted"

local:EnumVisibility.Value="{Binding UserType}"

local:EnumVisibility.TargetValue="Moderator, Administrator"

/>

We can also invert the translation using the WhenMatched and WhenNotMatched properties:

<TextBlock

Text="Access granted"

local:EnumVisibility.Value="{Binding UserType}"

local:EnumVisibility.TargetValue="Standard"

local:EnumVisibility.WhenMatched="Collapsed"

local:EnumVisibility.WhenNotMatched="Visible"

/>

An especially nice use is specifying user-friendly names for enumeration values (a traditionally thorny problem). We bind a bank of controls in the same space to the same property, knowing only one will show up at any given time. This gives us maximum flexibility in representing particular enumeration values:

<Grid>

<TextBlock

Text="Standard"

local:EnumVisibility.Value="{Binding UserType}"

local:EnumVisibility.TargetValue="Standard"

/>

<TextBlock

Text="Moderator"

local:EnumVisibility.Value="{Binding UserType}"

local:EnumVisibility.TargetValue="Moderator"

FontWeight="Bold"

/>

<StackPanel

local:EnumVisibility.Value="{Binding UserType}"

local:EnumVisibility.TargetValue="Administrator"

Orientation="Horizontal">

<TextBlock Text="Administrator" FontWeight="Bold" />

<Image Source="admin.jpg" />

</StackPanel>

</Grid>

This allows us to use bindings, resources, localization, and any other technique to portray user-friendly names.

EnumVisibility

We define the attached behavior as a static class:

publicstaticclassEnumVisibility

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:

publicstaticreadonlyDependencyProperty ValueProperty =

DependencyProperty.RegisterAttached(

"Value",

typeof(object),

typeof(EnumVisibility),

newPropertyMetadata(OnArgumentsChanged));

publicstaticobject GetValue(UIElement uiElement)

{

return uiElement.GetValue(ValueProperty);

}

publicstaticvoid SetValue(UIElement uiElement, object value)

{

uiElement.SetValue(ValueProperty, value);

}

Next, we register the TargetValue property, which is a simple string we will parse later:

publicstaticreadonlyDependencyProperty TargetValueProperty =

DependencyProperty.RegisterAttached(

"TargetValue",

typeof(string),

typeof(EnumVisibility),

newPropertyMetadata(OnArgumentsChanged));

publicstaticstring GetTargetValue(UIElement uiElement)

{

return (string) uiElement.GetValue(TargetValueProperty);

}

publicstaticvoid SetTargetValue(UIElement uiElement, string value)

{

uiElement.SetValue(TargetValueProperty, value);

}

Then, we register the WhenMatched property, giving it a default value of Visible:

The IBehavior implementation is similar to the other attached behaviors:

privatesealedclassEnumVisibilityBehavior : Behavior<UIElement>

{

privatereadonlyEnumCheck _enumCheck = newEnumCheck();

internal EnumVisibilityBehavior(DependencyObject host) : base(host)

{}

protectedoverridevoid Update(UIElement host)

{

_enumCheck.Update(GetValue(host), GetTargetValue(host));

host.Visibility = _enumCheck.IsMatch

? GetWhenMatched(host)

: GetWhenNotMatched(host);

}

}

The major difference is this behavior uses an object dedicated to checking the value against the target value. This cleans up the code considerably, as the matching logic is not trivial. It also allows us to reuse it in other enumeration-related behaviors.

Enumeration Checks

An enumeration check requires parsing of the target value(s) to the enumeration type. Rather than perform the parse every time, the EnumCheck type caches the parsed target values and only recalculates them when necessary. This stateful nature is why we update the _enumCheck field and then query its IsMatch property when updating the behavior.

The EnumCheck class has three properties:

publicobject Value { get; privateset; }

publicEnumTargetValue TargetValue { get; privateset; }

publicbool IsMatch { get; privateset; }

The interesting thing here is the TargetValue property: the EnumTargetValue class encapsulates the parsing and comparison of target value strings. We initialize it in the constructor to an empty instance:

public EnumCheck()

{

this.TargetValue = newEnumTargetValue();

}

The Update method, which we call from the behavior’s Update method above, is where we coordinate the match:

publicvoid Update(object value, string targetValue)

{

var valueChanged = value != this.Value;

var targetValueChanged = targetValue != this.TargetValue.Text;

if(valueChanged || targetValueChanged)

{

if(valueChanged)

{

this.Value = value;

}

this.TargetValue.Update(this.Value, targetValue);

this.IsMatch = this.TargetValue.Matches(this.Value);

}

}

We determine if either the value or target has changed and, if so, recalculate the target value and the match result. EnumTargetValue performs the core match logic.

Target Values

Using commas, a target value can actually be composed of multiple enumeration values. We represent this in the EnumTargetValue class by exposing a read-only wrapper of a list of parsed values:

privatereadonlyList<object> _parsedValues = newList<object>();

public EnumTargetValue()

{

this.ParsedValues = _parsedValues.AsReadOnly();

}

publicReadOnlyCollection<object> ParsedValues { get; privateset; }

publicstring Text { get; privateset; }

This list is the cache for the enumeration values we parse from the target value string. It is typed as object because we don’t have compile-time knowledge of the type of the bound enumeration – we wait to see the type of the value bound to the EnumVisibility.Value property and use that type to do the parsing.

(Later in this series, we will see why we make the parsed values publicly available.)

We also expose the source text, so we can check to see if it has changed in the beginning of the EnumCheck.Update method above. That method also calls the Update method on EnumTargetValue, which caches the text and repopulates the _parsedValues list:

publicvoid Update(object value, string text)

{

this.Text = text;

ParseValues(value);

}

In order to parse the text, we need the enumeration value against which it will be compared, which we use to determine the type to pass to the Enum.Parse method. By inferring the enumeration type like this, we avoid requiring developers to specify another attached property alongside Value and TargetValue.

The ParseValues method parses each token in a comma-separated string into an enumeration value of the same type as the bound value:

privatevoid ParseValues(object value)

{

_parsedValues.Clear();

if(value != null && value isEnum && !String.IsNullOrEmpty(this.Text))

{

var parsedValues =

from targetValue inthis.Text.Split(‘,’)

let trimmedTargetValue = targetValue.Trim()

where trimmedTargetValue.Length > 0

selectEnum.Parse(value.GetType(), trimmedTargetValue, false);

_parsedValues.AddRange(parsedValues);

}

}

First, we ensure the bound value is an enumeration and there is target value text to parse. Then, we tokenize the string by commas and perform a query over the resulting token. For each, we trim it, ensure it isn’t empty, and parse it to the enumeration type using a case-sensitive comparison. This gives us a sequence of enumeration values which we then add to the _parsedValues list.

Now that we have the set of parsed values, we have to compare it to the bound value. The EnumCheck.Update method above sets the EnumCheck.IsMatch property by calling the EnumTargetValue.Matches method:

publicbool Matches(object value)

{

return value == null || value.Equals("")

? String.IsNullOrEmpty(this.Text)

: _parsedValues.Contains(value);

}

A null or empty bound value matches a null or empty target value, neatly supporting nullable enumerations without actually having to know the enumeration type. If a value is provided, we determine if it exists in the set of parsed values. The Contains call will ultimately use the GetHashCode and Equals methods on the bound value and each parsed value, shouldering the bulk of the equality-checking work.

Sample Project

It allows you to set the value of the EnumVisibility.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

Enumeration matching is useful in many scenarios. The incarnation here, which manages the Visibility property, lays the foundation for enumeration-based behaviors.

Next time, we will use the EnumCheck class to manage the IsEnabled property.