Archive

Posts Tagged ‘WPF’

Attached Behaviors Part 7: EnumSelector

April 28th, 2011 No comments

Last time, we explored the concept of a user making a choice between various enumeration values. We implemented the choice as a set of radio buttons, where each represents a different value and all are bound to the same property. Radio buttons are one of many controls which allow the user to select from various choices. There is a family of controls, such as ComboBox and ListBox, which inherit their selection capabilities from a common primitive, the Selector control. In this installment, we will write an attached behavior to select an enumeration value from any implementation of Selector.

Scenario

Selectors have two important elements: a collection of child objects, and a currently selected object out of that collection. This mimics a choice between enumeration values quite nicely. For example, a checkout form might offer multiple payment methods, such a credit card or PayPal, and allow the user to choose with a combo box. Here, we can use enumeration matching to synchronize a property with the selected item:

<ComboBox local:EnumSelector.SelectedValue="{Binding PaymentType, Mode=TwoWay}">

  <ComboBoxItem Content="Credit card" local:EnumSelector.ItemValue="CreditCard" />

  <ComboBoxItem Content="PayPal" local:EnumSelector.ItemValue="PayPal" />

</ComboBox>

The combo box’s selected value is bound to the PaymentType property and each item is associated with an enumeration value. When the user changes the selection, the attached behavior writes the selected value to the PaymentType property through a two-way binding.

NOTE: The current incarnation of this behavior requires a ComboBoxItem for each value. This is because the items in the selector must have the ItemValue attached property set directly on them. If we place, say, a TextBox directly within the ComboBox, and set the ItemValue property on it, the ComboBox will generate a ComboBoxItem for us, but it won’t have the ItemValue property set. We might be able to check the Content property of each ComboBoxItem for ItemValue, but that is for a future iteration.

EnumSelector

Once again, we define the behavior as a static class:

public static class EnumSelector

Next, we register the attached properties which govern the behavior. First is the SelectedValue 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 SelectedValueProperty =

  DependencyProperty.RegisterAttached(

    "SelectedValue",

    typeof(object),

    typeof(EnumSelector),

    new PropertyMetadata(OnSelectedValueChanged));

 

public static object GetSelectedValue(Selector selector)

{

  return selector.GetValue(SelectedValueProperty);

}

 

public static void SetSelectedValue(Selector selector, object value)

{

  selector.SetValue(SelectedValueProperty, value);

}

Then, we register the ItemValue property, which is a simple string we will parse later:

public static readonly DependencyProperty ItemValueProperty =

  DependencyProperty.RegisterAttached(

    "ItemValue",

    typeof(string),

    typeof(EnumSelector),

    new PropertyMetadata(OnItemValueChanged));

 

public static string GetItemValue(DependencyObject item)

{

  return (string) item.GetValue(ItemValueProperty);

}

 

public static void SetItemValue(DependencyObject item, string value)

{

  item.SetValue(ItemValueProperty, value);

}

Behavior

The nice part about the attached behavior framework is that most of the code which goes into behaviors is fairly boilerplate. First, we declare the behavior and tell it how to create instances for individual host objects:

private static readonly AttachedBehavior Behavior =

  AttachedBehavior.Register(host => new EnumSelectorBehavior(host));

Then, we update it when the SelectedValue dependency property changes:

private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  Behavior.Update(d);

}

This attached behavior has an extra step, though: we need to update the behavior not only when SelectedValue changes on the selector, but also when ItemValue changes on any item:

private static void OnItemValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  var item = d as FrameworkElement;

 

  if(item != null)

  {

    var selector = item.Parent as Selector;

 

    if(selector != null)

    {

      Behavior.Update(selector);

    }

  }

}

The behavior implementation is also more involved. The previous enumeration matching behaviors each had one match to perform; this behavior, however, has one match per item. This adds a some complexity but not much.

The class declaration is similar to other behaviors:

private sealed class EnumSelectorBehavior : Behavior<Selector>

As is the constructor:

internal EnumSelectorBehavior(DependencyObject host) : base(host)

{}

The first major difference is that we store an index associating items with their enumeration checks:

private readonly IDictionary<object, EnumCheck> _itemEnumChecks =

  new Dictionary<object, EnumCheck>();

The Attach and Detach overrides manage a handler for the selector’s SelectionChanged event:

protected override void Attach(Selector host)

{

  host.SelectionChanged += OnSelectionChanged;

}

 

protected override void Detach(Selector host)

{

  host.SelectionChanged -= OnSelectionChanged;

 

  _itemEnumChecks.Clear();

}

In the Detach method, we make sure to remove all references to items and their associated enumeration checks.

The Update method goes through all of the items, updates the enumeration check associated with each one, and sets the selector’s SelectedIndex property to the first one which matches:

protected override void Update(Selector host)

{

  var selectedValue = GetSelectedValue(host);

 

  for(var index = 0; index < host.Items.Count; index++)

  {

    var item = host.Items[index];

 

    var itemEnumCheck = GetItemEnumCheck(item);

 

    itemEnumCheck.Update(selectedValue, GetItemValue(item));

 

    if(itemEnumCheck.IsMatch)

    {

      host.SelectedIndex = index;

 

      break;

    }

  }

}

The GetItemEnumCheck method gets the EnumCheck associated with the specified item. If one doesn’t yet exist, we create and cache it:

private EnumCheck GetItemEnumCheck(object item)

{

  EnumCheck itemEnumCheck;

 

  if(!_itemEnumChecks.TryGetValue(item, out itemEnumCheck))

  {

    itemEnumCheck = new EnumCheck();

 

    _itemEnumChecks[item] = itemEnumCheck;

  }

 

  return itemEnumCheck;

}

The GetItemValue method is a bit of glue which attempts to convert an item to a DependencyObject and, if successful, accesses its ItemValue attached property:

private static string GetItemValue(object item)

{

  var dependencyObjectItem = item as DependencyObject;

 

  return dependencyObjectItem == null

    ? null

    : EnumSelector.GetItemValue(dependencyObjectItem);

}

These methods implement the updating of the selector based on the SelectedValue and ItemValue attached properties.

The other side of the coin, updating the SelectedValue attached property when the selector changes, is implemented by the OnSelectionChanged event handler. We again use the TryUpdate method, passing it the name of another method, UpdateSelectedValue, which will only be invoked if the host object hasn’t been garbage-collected:

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)

{

  TryUpdate(UpdateSelectedValue);

}

The UpdateSelectedValue method updates the enumeration check associated with the selected item and updates the SelectedValue attached property of the host with the selected item’s value:

private void UpdateSelectedValue(Selector host)

{

  var selectedValue = GetSelectedValue(host);

 

  var itemEnumCheck = GetItemEnumCheck(host.SelectedItem);

 

  itemEnumCheck.Update(selectedValue, GetItemValue(host.SelectedItem));

 

  var parsedTargetValue = itemEnumCheck.ParsedTargetValue;

 

  if(selectedValue != null && !selectedValue.Equals(parsedTargetValue))

  {

    SetSelectedValue(host, parsedTargetValue);

  }

}

The if statement prevents an infinite loop where an update to the SelectedValue property raises the SelectionChanged event which updates the SelectedValue property which raises the SelectionChanged event, etc.

Sample Project

This WPF project shows EnumSelector in action:

Attached Behaviors Part 7 EnumSelector.zip

It allows you to set the value of the EnumSelector.SelectedValue attached property and see the results when applied to a ListBox. You can also selected different items in the ListBox and see EnumSelector.SelectedValue change, showing the effect of the two-way binding.

Summary

We used enumeration matching to allow the user to select an enumeration value from many. Like radio buttons, selectors naturally represent a choice between mutually exclusive values. The behavior can be applied to anything which implements Selector, offering a large amount of flexibility and extensibility.

What Next?

These are all the attached behaviors I have for now. They cover the various situations I have encountered in my XAML-related career, mostly in an MVVM context. I am sure I will discover more, though, so I will post write-ups when I do.

My personal utilities library, Cloak, contains the framework as well as all the behaviors featured in this series, for both WPF and Silverlight. (There is a lot of other goodness in there as well.) Download it and give them a try!

(If you found this series useful or maybe even enjoyed it, leave a comment. It’s always nice to be reminded I’m not writing in a void.)

Attached Behaviors Part 6: EnumGroup

April 26th, 2011 No comments
    Our framework for matching enumeration values has helped us easily manage the Visibility and IsEnabled properties of individual controls. Now, we will write an attached behavior to select a value from a set of radio buttons.

    Scenario

    Radio buttons naturally represent an enumeration: as a related set of controls, known as a group, each is a value in a mutually exclusive selection. For example, a checkout form might offer multiple payment methods, such a credit card or PayPal, and allow the user to choose with radio buttons. Here, we can use enumeration matching to synchronize a property with the selected radio button:

<RadioButton

  Content="Credit card"

  local:EnumGroup.Value="{Binding PaymentType, Mode=TwoWay}"

  local:EnumGroup.TargetValue="CreditCard"

/>

<RadioButton

  Content="PayPal"

  local:EnumGroup.Value="{Binding PaymentType, Mode=TwoWay}"

  local:EnumGroup.TargetValue="PayPal"

/>

Each radio button is associated with an enumeration value. When the user selects an option, it writes that value to the PaymentType property through a two-way binding.

EnumGroup

If you have read the rest of the series, there isn’t much new here. We define the behavior as a static class:

public static class EnumGroup

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(EnumGroup),

    new PropertyMetadata(OnArgumentsChanged));

 

public static object GetValue(RadioButton radioButton)

{

  return radioButton.GetValue(ValueProperty);

}

 

public static void SetValue(RadioButton radioButton, object value)

{

  radioButton.SetValue(ValueProperty, value);

}

Then, 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(EnumGroup),

    new PropertyMetadata(OnArgumentsChanged));

 

public static string GetTargetValue(RadioButton radioButton)

{

  return (string) radioButton.GetValue(TargetValueProperty);

}

 

public static void SetTargetValue(RadioButton radioButton, string value)

{

  radioButton.SetValue(TargetValueProperty, value);

}

Behavior

The integration of the behavior into the static class is exactly the same as the previous behaviors. First, we declare the behavior and tell it how to create instances for individual host objects:

private static readonly AttachedBehavior Behavior =

  AttachedBehavior.Register(host => new EnumGroupBehavior(host));

Then, we update it when either of the Value or TargetValue dependency properties changes:

private static void OnArgumentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  Behavior.Update(d);

}

The behavior class is more interesting than those for other behaviors because it overrides the Attach and Detach methods to manage a handler for the Checked event:

private sealed class EnumGroupBehavior : Behavior<RadioButton>

{

  private readonly EnumCheck _enumCheck = new EnumCheck();

 

  internal EnumGroupBehavior(DependencyObject host) : base(host)

  {}

 

  protected override void Attach(RadioButton host)

  {

    host.Checked += OnChecked;

  }

 

  protected override void Detach(RadioButton host)

  {

    host.Checked -= OnChecked;

  }

 

  protected override void Update(RadioButton host)

  {

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

 

    host.IsChecked = _enumCheck.IsMatch;

  }

 

  private void OnChecked(object sender, RoutedEventArgs e)

  {

    TryUpdate(host => SetValue(host, _enumCheck.ParsedTargetValue));

  }

}

The Update method, called whenever Value or TargetValue changes (via OnArgumentsChanged), is straightforward. We leverage the EnumCheck class to manage the parsing, caching, and matching. Then, we set the IsChecked property based on whether Value matches TargetValue.

Another interesting aspect of this behavior is the handler for the radio button’s Checked event. This is the first time we have a chance to use the TryUpdate method, which executes the specified lambda expression only if the host has not been garbage-collected.

The Checked event occurs when the radio button becomes the selected value in its group. When that happens, we set the Value property to the associated target value. Voila!

Sample Project

This WPF project shows EnumGroup in action:

Attached Behaviors Part 6 EnumGroup.zip

It allows you to set the value of the EnumGroup.Value attached property and see the results when applied to two radio buttons. You can also check the radio buttons and see EnumGroup.Value change, showing the effect of the two-way binding.

As a side note, the EnumCheck class evolved since the last post. It now performs the duties of EnumTargetValue, which no longer exists. It also has a new member, ParsedTargetValue, which we used in the OnChecked method above. It returns the first value from ParsedTargetValues, which may contain multiple values if TargetValue is set to a comma-separated string.

Summary

We used enumeration matching to put another fundamental building block in place, the radio button group. Their mutually exclusive nature is a seamless fit for selecting one value from many. By associating each radio button with an enumeration value, we easily and intuitively described a choice.

Next time, we will use selectors, such as a combo boxes and list boxes, to select enumeration values.

Attached Behaviors Part 5: EnumIsEnabled

February 23rd, 2011 No comments

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.

Attached Behaviors Part 4: EnumVisibility

February 15th, 2011 No comments

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:

public static class EnumVisibility

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(EnumVisibility),

    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(EnumVisibility),

    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 Visible:

public static readonly DependencyProperty WhenMatchedProperty =

  DependencyProperty.RegisterAttached(

    "WhenMatched",

    typeof(Visibility),

    typeof(EnumVisibility),

    new FrameworkPropertyMetadata(Visibility.Visible, OnArgumentsChanged));

 

public static Visibility GetWhenMatched(UIElement uiElement)

{

  return (Visibility) uiElement.GetValue(WhenMatchedProperty);

}

 

public static void SetWhenMatched(UIElement uiElement, Visibility visibility)

{

  uiElement.SetValue(WhenMatchedProperty, visibility);

}

Finally, we register the WhenNotMatched property, giving it a default value of Collapsed:

public static readonly DependencyProperty WhenNotMatchedProperty =

  DependencyProperty.RegisterAttached(

    "WhenNotMatched",

    typeof(Visibility),

    typeof(EnumVisibility),

    new FrameworkPropertyMetadata(Visibility.Collapsed, OnArgumentsChanged));

 

public static Visibility GetWhenNotMatched(UIElement uiElement)

{

  return (Visibility) uiElement.GetValue(WhenNotMatchedProperty);

}

 

public static void SetWhenNotMatched(UIElement uiElement, Visibility visibility)

{

  uiElement.SetValue(WhenNotMatchedProperty, visibility);

}

Behavior

Just like with BooleanVisibility and NullVisibility, we declare the behavior, telling it how to create instances for individual host objects:

private static readonly AttachedBehavior Behavior =

  AttachedBehavior.Register(host => new EnumVisibilityBehavior(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 IBehavior implementation is similar to the other attached behaviors:

private sealed class EnumVisibilityBehavior : Behavior<UIElement>

{

  private readonly EnumCheck _enumCheck = new EnumCheck();

 

  internal EnumVisibilityBehavior(DependencyObject host) : base(host)

  {}

 

  protected override void 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:

public object Value { get; private set; }

 

public EnumTargetValue TargetValue { get; private set; }

 

public bool IsMatch { get; private set; }

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 = new EnumTargetValue();

}

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

public void 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:

private readonly List<object> _parsedValues = new List<object>();

 

public EnumTargetValue()

{

  this.ParsedValues = _parsedValues.AsReadOnly();

}

 

public ReadOnlyCollection<object> ParsedValues { get; private set; }

 

public string Text { get; private set; }

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:

public void 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:

private void ParseValues(object value)

{

  _parsedValues.Clear();

 

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

  {

    var parsedValues =

      from targetValue in this.Text.Split(‘,’)

      let trimmedTargetValue = targetValue.Trim()

      where trimmedTargetValue.Length > 0

      select Enum.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:

public bool 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

This WPF application shows EnumVisibility in action:

Attached Behaviors Part 4 EnumVisibility.zip

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.

Attached Behaviors Part 3: NullVisibility

February 12th, 2011 No comments

Now that we have established a framework for writing attached behaviors, we can create any number of interesting utilities. In this post we will cover binding of the Visibility property to null and non-null values.

Scenario

A useful technique is to hide a piece of UI when its DataContext is null. This is valuable when a piece of data is optional or only shown after having been lazy-loaded. In an MVVM context, UI can be conditionally shown based on the presence of a view model. The relationship between nullity and visibility is rather meaningful and provides many everyday binding opportunities.

We will encapsulate this concept in a behavior, NullVisibility. Similar to BooleanVisibility from part 1 and part 2, it will manage the translation of a nullable value into a Visibility value using attached properties:

<TextBlock

  Text="An order is selected"

  local:NullVisibility.Value="{Binding SelectedOrder}"

/>

We can invert the translation using the WhenNull and WhenNotNull properties:

<TextBlock

  Text="No order is selected"

  local:NullVisibility.Value="{Binding SelectedOrder}"

  local:NullVisibility.WhenNull="Visible"

  local:NullVisibility.WhenNotNull="Collapsed"

/>

NullVisibility

We define the attached behavior as a static class:

public static class NullVisibility

Next, we register the attached properties which govern the behavior. First is the Value property, which we give a default value of true to match the UIElement.Visibility property’s default value of Visible. We also create static accessors to facilitate XAML usage:

public static readonly DependencyProperty ValueProperty =

  DependencyProperty.RegisterAttached(

    "Value",

    typeof(object),

    typeof(NullVisibility),

    new PropertyMetadata(true, 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 WhenNull property, giving it a default value of Collapsed as the opposite of Value‘s default:

public static readonly DependencyProperty WhenNullProperty =

  DependencyProperty.RegisterAttached(

    "WhenNull",

    typeof(Visibility),

    typeof(NullVisibility),

    new PropertyMetadata(Visibility.Collapsed, OnArgumentsChanged));

 

public static Visibility GetWhenNull(UIElement uiElement)

{

  return (Visibility) uiElement.GetValue(WhenNullProperty);

}

 

public static void SetWhenNull(UIElement uiElement, Visibility visibility)

{

  uiElement.SetValue(WhenNullProperty, visibility);

}

Finally, we register the WhenNotNull property, giving it a default value of Visible to match Value‘s default:

public static readonly DependencyProperty WhenNotNullProperty =

  DependencyProperty.RegisterAttached(

    "WhenNotNull",

    typeof(Visibility),

    typeof(NullVisibility),

    new PropertyMetadata(Visibility.Visible, OnArgumentsChanged));

 

public static Visibility GetWhenNotNull(UIElement uiElement)

{

  return (Visibility) uiElement.GetValue(WhenNotNullProperty);

}

 

public static void SetWhenNotNull(UIElement uiElement, Visibility visibility)

{

  uiElement.SetValue(WhenNotNullProperty, visibility);

}

Behavior

Just like with BooleanVisibility, we declare the behavior, telling it how to create instances for individual host objects:

private static readonly AttachedBehavior Behavior =

  AttachedBehavior.Register(host => new NullVisibilityBehavior(host));

Now, we update it whenever any of the above dependency properties changes:

private static void OnArgumentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  Behavior.Update(d);

}

All that plumbing is about managing behavior instances. The real work is done in the IBehavior implementation, which is straightforward for NullVisibility:

private sealed class NullVisibilityBehavior : Behavior<UIElement>

{

  internal NullVisibilityBehavior(DependencyObject host) : base(host)

  {}

 

  protected override void Update(UIElement host)

  {

    host.Visibility = GetValue(host) == null

      ? GetWhenNull(host)

      : GetWhenNotNull(host);

  }

}

Sample Project

This WPF application shows NullVisibility in action:

Attached Behaviors Part 3 NullVisibility.zip

It allows you to set the value of the BooleanVisibility.Value attached property on two different text blocks. The first shows when Value is not null. The second uses the WhenNull and WhenNotNull attached properties to show when Value is null instead.

Summary

There is not a lot of code behind NullVisibility. It isn’t exactly terse (a tax for working with attached properties), but none of the pieces is complex or onerous. Writing attached behaviors does not have to be a chore.

Next time, we will explore some of these ideas using enumerations instead of Boolean values.

Attached Behaviors Part 2: Framework

February 6th, 2011 No comments

Last time, we devised a simpler syntax for defining attached behaviors in WPF and Silverlight. The new system approaches the concept at a higher level, encapsulating many of the rote mechanics common to the various implementations found on the web. It aims to make attached behaviors an everyday option, using an approach similar to many core WPF systems.

Now, we will flesh out the framework behind the API, which models three distinct aspects of attached behaviors:

  • Existence: Declare an object representing the attached behavior
  • Lifecycle: Attach and detach behavior instances to/from host objects
  • Logic: Update host objects based on changes in bound values

See the end of this post for a solution containing the framework and a usage sample.

    Existence

    Attached behaviors, similar to dependency properties, are represented by static fields:

private static readonly AttachedBehavior Behavior =

  AttachedBehavior.Register(host => new BooleanVisibilityBehavior(host));

The registration of the behavior of the BooleanVisibility class from part 1

The Register method creates an instance of AttachedBehavior, which encapsulates the ability to attach the behavior to host objects. The lambda expression indicates how to create instances of the behavior when required.

Here is the implementation:

public static AttachedBehavior Register(Func<DependencyObject, IBehavior> behaviorFactory)

{

  return new AttachedBehavior(RegisterProperty(), behaviorFactory);

}

Each attached behavior needs its own dependency property to store the IBehavior instance on each host. Rather than require behavior writes to register properties, we (carefully) create them programmatically:

private static DependencyProperty RegisterProperty()

{

  return DependencyProperty.RegisterAttached(

    GetPropertyName(),

    typeof(IBehavior),

    typeof(AttachedBehavior));

}

 

private static string GetPropertyName()

{

  return "_" + Guid.NewGuid().ToString("N");

}

First, we generate a unique name for the property by using the alphanumeric representation of a GUID (specified by the “N” format string). We prefix the property name with an underscore to ensure a legal identifier.

Then, we register an attached property with the unique name, indicating that its type is IBehavior and that AttachedBehavior owns it. Having all properties owned by the same type hides the use of attached properties completely from behavior authors.

Here is the constructor we called from the Register method:

private readonly DependencyProperty _property;

private readonly Func<DependencyObject, IBehavior> _behaviorFactory;

 

internal AttachedBehavior(DependencyProperty property, Func<DependencyObject, IBehavior> behaviorFactory)

{

  _property = property;

  _behaviorFactory = behaviorFactory;

}

    Lifecycle

The Behavior field encapsulates the lifecycle of the attached behavior. It has one method, Update, which recalculates a host object’s behavior in response to changes:

private static void OnArgumentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  Behavior.Update(d);

}

The handler for dependency property changes in the BooleanVisibility class of part 1

Update manages the association between the host object and an instance of the behavior. This involves three operations:

  • Attach – Create a behavior instance and associate it with the host object
  • Update –  Synchronize the behavior with the current state of the host object
  • Detach – Remove the association when the behavior no longer applies to the host object
    In concrete terms, attach and detach refer to setting and clearing the value of the dependency property we generated in the Register call. This discreetly stores the behavior instance directly within the host.

Update is straightforward to implement:

public void Update(DependencyObject host)

{

  var behavior = (IBehavior) host.GetValue(_property);

 

  if(behavior == null)

  {

    TryCreateBehavior(host);

  }

  else

  {

    UpdateBehavior(host, behavior);

  }

}

 

private void TryCreateBehavior(DependencyObject host)

{

  var behavior = _behaviorFactory(host);

 

  if(behavior.IsApplicable())

  {

    behavior.Attach();

 

    host.SetValue(_property, behavior);

 

    behavior.Update();

  }

}

 

private void UpdateBehavior(DependencyObject host, IBehavior behavior)

{

  if(behavior.IsApplicable())

  {

    behavior.Update();

  }

  else

  {

    host.ClearValue(_property);

 

    behavior.Detach();

  }

}

Before attaching or updating the behavior, we call IsApplicable to ensure it is relevant. This is the time to check things like whether dependency properties have values or whether a control is visible. For example, we may not want to attach a behavior to a combo box if it has no items.

Implementers of IBehavior supply the core logic for each of the operations:

public interface IBehavior

{

  bool IsApplicable();

 

  void Attach();

 

  void Update();

 

  void Detach();

}

A behavior is assumed to have access to the host object, as shown in the Register call where we pass the host to the constructor of BooleanVisibilityBehavior.

Logic

To minimize friction, we should allow behavior authors to work with a strongly-typed host object. We can enable this by allowing them to specify the host type as a type parameter and maintaining the untyped weak reference ourselves:

public abstract class Behavior<THost> : IBehavior where THost : DependencyObject

{

  private readonly WeakReference _hostReference;

 

  protected Behavior(DependencyObject host)

  {

    if(!(host is THost))

    {

      throw new ArgumentException("Host is not the expected type", "host");

    }

 

    _hostReference = new WeakReference(host);

  }

 

  private THost GetHost()

  {

    return (THost) _hostReference.Target;

  }

}

The GetHost method is the bridge between the weak reference and the typed API. We keep it private because derived classes won’t need it; instead, we pass them the host as a parameter in the method for each operation:

protected virtual bool IsApplicable(THost host)

{

  return true;

}

 

protected virtual void Attach(THost host)

{}

 

protected virtual void Detach(THost host)

{}

 

protected abstract void Update(THost host);

This is the core API for authoring a new behavior. Only the Update method is abstract; a behavior might not define specific logic for applicability or attaching/detaching, but it exists to respond to changes in the host.

AttachedBehavior calls them from the IBehavior methods, which ensure the host hasn’t been garbage-collected:

public bool IsApplicable()

{

  var host = GetHost();

 

  return host != null && IsApplicable(host);

}

 

public void Attach()

{

  var host = GetHost();

 

  if(host != null)

  {

    Attach(host);

  }

}

 

public void Detach()

{

  var host = GetHost();

 

  if(host != null)

  {

    Detach(host);

  }

}

 

public void Update()

{

  var host = GetHost();

 

  if(host != null)

  {

    Update(host);

  }

}

Revisiting BooleanVisibilityBehavior

The AttachedBehavior and Behavior<> types implement most of the mechanics of an attached behavior. They factor away the nitty gritty and leave behavior authors with a straightforward implementation:

private sealed class BooleanVisibilityBehavior : Behavior<UIElement>

{

  internal BooleanVisibilityBehavior(DependencyObject host) : base(host)

  {}

 

  protected override void Update(UIElement host)

  {

    host.Visibility = GetValue(host) ? GetWhenTrue(host) : GetWhenFalse(host);

  }

}

We declare the host type in the generic parameter to Behavior<>. We accept the host instance in the constructor and simply pass it through to the base class. When it comes time to update the host, it is cast to the generic type and passed to the Update method. We use the accessors for the Value, WhenTrue, and WhenFalse attached properties to determine the host’s new visibility.

(In part 1 of this series, we also listened to changes in the Visibility property and updated the Value property to match. This turned out to be more complex than expected, and so I have left that out of this iteration of the framework. The Attach/Detach methods, normally used to manage event handlers, weren’t necessary in this example.

In the majority of cases, these behaviors will manipulate a control property based on attached properties; two-way binding, a valuable but less applicable scenario, will be covered in a future post.)

Sample Project

This WPF application shows the framework in action:

Attached Behaviors Part 2 Framework.zip

It contains all of the code above and allows you to set the value of the BooleanVisibility.Value attached property on two different text blocks. The first shows when Value is true. The second uses the WhenTrue and WhenFalse attached properties to show when Value is false instead.

Summary

Different attached behaviors have very little that is unique about them. Behavior<> brings these elements to the forefront and, along with AttachedBehavior, takes care of the bookkeeping.

This core system lowers the barrier to entry for new attached behaviors, opening the door for many other ideas. Future posts in this series will cover:

  • Binding Visibility to null/not null
  • Binding Visibility to an enumeration
  • Binding IsEnabled to an enumeration
  • Binding radio buttons to an enumeration
  • Binding combo boxes and list boxes to an enumeration
Stay tuned!

Attached Behaviors Part 1: BooleanVisibility

November 11th, 2010 No comments

The DependencyObject system is at the core of the WPF and Silverlight architectures. It enables many killer features of UI development, including data binding, animation, and an overall declarative style. It is the fruit of years spent developing UI frameworks, addressing the entire idea on a more fundamental level.

One of the more interesting features is attached properties, which allow external entities to store pieces of data within dependency objects. A grid stores an object’s row and column, a canvas stores an object’s coordinates, and a docking container stores dock position. The objects remain blissfully unaware of the orthogonal features.

A particularly useful form of these properties is the attached behavior, an attached property which adds functionality to the object on which it is set. This is similar in effect to extension methods: behaviors can implement anything which uses an object’s public API. They can respond to events, such as TextChanged and Checked/Unchecked, as well as use additional attached properties to refine functionality.

Attached behaviors have been used to implement the command pattern, default buttons, and TreeView selection scrolling, as well as numerous other examples. ASP.NET even has an immensely useful implementation. The flexibility is in the simplicity: a dependency object stores a sub-object which extends it with extra behavior. This subtle form of the Mixin pattern is applicable to many problems often solved by other means.

An Example

A design consequence well-known to WPF and Silverlight developers is the Boolean/Visibility incompatibility. Visibility is an enumeration, which cannot be directly bound to Boolean properties. Instead, the colloquial approach is to define a value converter which translates as necessary:

public sealed class BooleanVisibilityConverter : IValueConverter

{

  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

  {

    return (bool) value ? Visibility.Visible : Visibility.Collapsed;

  }

 

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

  {

    return ((Visibility) value) == Visibility.Visible;

  }

}

An instance is generally included in an application’s resources:

<Application.Resources>

  <ResourceDictionary>

    <local:BooleanVisibilityConverter x:Key="booleanVisibilityConverter" />

  </ResourceDictionary>

</Application.Resources>

Then, it is used in various bindings:

<TextBlock

  Text="An error occurred."

  Visibility="{Binding HasError, Converter={StaticResource booleanVisibilityConverter}}"

/>

This is an adequate but heavy way to express the relationship between Visibility and HasError. Imagine that we instead set an attached property dedicated to the conversion:

<TextBlock

  Text="An error occurred."

  local:BooleanVisibility.Value="{Binding HasError}"

/>

This is a much clearer statement of intent, with no associated application resource. Clean.

Another aspect of the incompatibility is inverting Boolean values. I have used three approaches in the past:

  1. Add ConverterParameter=’Not’ to the binding
  2. Create a BooleanNotVisibilityConverter
  3. Add WhenTrue and WhenFalse properties to BooleanVisibilityConverter and create multiple resources
    Each of these also feels heavy. The approach with attached properties has less friction:

<TextBlock

  Text="An error occurred."

  local:BooleanVisibility.Value="{Binding HasError}"

  local:BooleanVisibility.WhenTrue="Collapsed"

  local:BooleanVisibility.WhenFalse="Visible"

/>

This neatly addresses WPF’s three-state Visibility enumeration by forcing a choice between Collapsed and Hidden. The Silverlight version works the same way, sans the Hidden option.

BooleanVisibility

Since the API is defined solely in terms of attached properties, we define it as a static class (a nice encapsulation of the feature):

public static class BooleanVisibility

First, we register the Value property, giving it a default value of true to match the UIElement.Visibility property’s default value of Visible. We also create static accessors to facilitate XAML usage:

public static readonly DependencyProperty ValueProperty =

  DependencyProperty.RegisterAttached(

    "Value",

    typeof(bool),

    typeof(BooleanVisibility),

    new PropertyMetadata(true, OnArgumentChanged));

 

public static bool GetValue(UIElement uiElement)

{

  return (bool) uiElement.GetValue(ValueProperty);

}

 

public static void SetValue(UIElement uiElement, bool value)

{

  uiElement.SetValue(ValueProperty, value);

}

Next, we register the WhenTrue property, giving it a default value of Visible to match Value‘s default:

public static readonly DependencyProperty WhenTrueProperty =

  DependencyProperty.RegisterAttached(

    "WhenTrue",

    typeof(Visibility),

    typeof(BooleanVisibility),

    new PropertyMetadata(Visibility.Visible, OnArgumentChanged));

 

public static Visibility GetWhenTrue(UIElement uiElement)

{

  return (Visibility) uiElement.GetValue(WhenTrueProperty);

}

 

public static void SetWhenTrue(UIElement uiElement, Visibility visibility)

{

  uiElement.SetValue(WhenTrueProperty, visibility);

}

Finally, we register the WhenFalse property, giving it a default value of Collapsed as the opposite of Value‘s default:

public static readonly DependencyProperty WhenFalseProperty =

  DependencyProperty.RegisterAttached(

    "WhenFalse",

    typeof(Visibility),

    typeof(BooleanVisibility),

    new PropertyMetadata(Visibility.Collapsed, OnArgumentChanged));

 

public static Visibility GetWhenFalse(UIElement uiElement)

{

  return (Visibility) uiElement.GetValue(WhenFalseProperty);

}

 

public static void SetWhenFalse(UIElement uiElement, Visibility visibility)

{

  uiElement.SetValue(WhenFalseProperty, visibility);

}

Attaching the Behavior

We create one additional attached property to hold the behavior associated with each object:

private static readonly DependencyProperty BehaviorProperty =

  DependencyProperty.RegisterAttached(

    "Behavior",

    typeof(BooleanVisibilityBehavior),

    typeof(BooleanVisibility));

The fact that we use a dependency property to associate behavior with objects is an implementation detail, so we can make it private and omit the get/set methods. BooleanVisibilityBehavior, discussed later, is the class which implements the specification.

To attach the behavior, we simply set this property. A change in Value, WhenTrue, or WhenFalse is the cue to attach it if necessary and update its state. Each property registers the OnArgumentChanged method to be called when its value changes:

private static void OnArgumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  var uiElement = d as UIElement;

 

  if(uiElement != null)

  {

    var behavior = uiElement.GetValue(BehaviorProperty) as BooleanVisibilityBehavior;

 

    if(behavior == null)

    {

      behavior = new BooleanVisibilityBehavior(uiElement);

 

      uiElement.SetValue(BehaviorProperty, behavior);

    }

 

    behavior.Update();

  }

}

First, we ensure an instance of the behavior has been attached to the UI element through BehaviorProperty. Then, we update its state, which sets the Visibility property based on the values of Value, WhenTrue, and WhenFalse.

BooleanVisibilityBehavior

The behavior implements roughly the same functionality as BooleanVisibilityConverter:

  1. Update the UI element’s Visibility property when Value, WhenTrue, or WhenFalse changes
  2. Update Value when the UI element’s Visibility property changes

A key difference is that the behavior must listen for changes in the Visibility property, while the converter is not responsible for coordination of any kind.

The behavior class is an implementation detail as well, so we can also make it private. This is convenient because the Get/Set methods for Value, WhenTrue, and WhenFalse are in scope:

private sealed class BooleanVisibilityBehavior

{

  private readonly WeakReference _uiElementReference;

 

  internal BooleanVisibilityBehavior(UIElement uiElement)

  {

    _uiElementReference = new WeakReference(uiElement);

 

    var visibilityDescriptor = DependencyPropertyDescriptor.FromProperty(

      UIElement.VisibilityProperty,

      uiElement.GetType());

 

    visibilityDescriptor.AddValueChanged(uiElement, OnVisibilityChanged);

  }

 

  internal void Update()

  {

    var uiElement = (UIElement) _uiElementReference.Target;

 

    if(uiElement != null)

    {

      uiElement.Visibility = GetValue(uiElement)

        ? GetWhenTrue(uiElement)

        : GetWhenFalse(uiElement);

    }

  }

 

  private void OnVisibilityChanged(object sender, EventArgs e)

  {

    var uiElement = (UIElement) _uiElementReference.Target;

 

    if(uiElement != null)

    {

      var value = uiElement.Visibility == GetWhenTrue(uiElement);

 

      SetValue(uiElement, value);

    }

  }

}

We store the UI element in a WeakReference, which ensures we don’t accidentally keep it alive after it goes out of scope. Then, we use the dependency property system to add a handler for changes in the object’s Visibility property.

The Update method, which we call from the OnArgumentsChanged handler, sets the Visibility property based on the values of the argument properties. We must first ensure the UI element has not been garbage-collected.

The OnVisibilityChanged handler is the other binding direction, setting the Value property based on the value of the Visibility property.

Generalizing Attached Behaviors

The above approach is solid, but wordy. There are a lot of aspects to consider and details to get right. Writing a new attached behavior is a high-friction undertaking which can be intimidating enough to deter developers even when the effort is warranted. This concept is screaming for a framework.

Following is a proposed syntax for defining attached behaviors. It encapsulates some of the stickier points, such as the attachment logic and the weak reference. Part 2 of this series will contain the framework implementation.

First, instead of registering an attached property to hold the behavior, we raise the level of abstraction and register an attached behavior:

private static readonly AttachedBehavior Behavior =

  AttachedBehavior.Register(host => new BooleanVisibilityBehavior(host));

We tell the AttachedBehavior class how to create instances of the behavior for objects which will host it. Next, we update the host’s behavior when any of its arguments changes:

private static void OnArgumentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

  Behavior.Update(d);

}

Finally, we define the behavior:

private sealed class BooleanVisibilityBehavior : Behavior<UIElement>

{

  internal BooleanVisibilityBehavior(DependencyObject host) : base(host)

  {

    var visibilityDescriptor = DependencyPropertyDescriptor.FromProperty(

      UIElement.VisibilityProperty,

      host.GetType());

 

    visibilityDescriptor.AddValueChanged(host, OnVisibilityChanged);

  }

 

  protected override void Update(UIElement host)

  {

    host.Visibility = GetValue(host) ? GetWhenTrue(host) : GetWhenFalse(host);

  }

 

  private void OnVisibilityChanged(object sender, EventArgs e)

  {

    TryUpdate(host =>

    {

      var value = host.Visibility == GetWhenTrue(host);

 

      SetValue(host, value);

    });

  }

}

We tell the base class the expected type of the host and pass the instance through to its constructor; it is responsible for the weak reference. We override Update and perform the logic without worrying about any null-checking, as the base class will ensure the host has not been garbage-collected.

OnVisibilityChanged, though, also needs access to the host. To do so, we use the TryUpdate method, giving it a lambda expression which it will only execute if the host has not been garbage-collected.

That’s it! This is much more straightforward and declarative than the raw approach. The majority of the code is concerned with the behavior itself and not with managing the instance. This style is more accessible in our everyday work, where attached behaviors may simply replace some converters.

Summary

Attached behaviors are a powerful tool in the toolkit of any WPF or Silverlight developer. They neatly encapsulate the relationship between an object and the values which drive its behavior. Their implementation complexity, though, is a large deterrent to their widespread adoption. By simplifying or removing many of the details, this framework lowers the barrier to entry and clears the path for attached behaviors to become a more common technique.

Next time, we will see how to implement the AttachedBehavior and Behavior<> classes.