Control Visibility with triggers

Date August 10, 2009 @ 2:30 pm in Silverlight

I absolutely love writing triggers for Silverlight 3.  As UX developers we often faced with the challenge of finding ways to fit a ton of information on a single user input screen.  On occasion, some of that information may only be relevant to display if users have made certain choices on the interface: checking a box, selecting an item in a combobox or listbox.  In this post I will include two custom TargetedTriggerAction’s which allow a developer to easily tie a UIElement’s visibility to an action made on another control.

VisibilityIsChecked

This first trigger I very simple.  I wanted the ability to tie a checkbox to the visibility of another UIElement.  Since a checkbox derives from ToggleButton we can get creative and write a single trigger that will work with either a CheckBox, RadioButton or ToggleButton.  Here is how I did it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;
 
namespace VisibilityTriggers
{
    public class VisibilityIsChecked : TargetedTriggerAction<frameworkelement>
    {
        public static readonly DependencyProperty VisibleWhenCheckedProperty =
            DependencyProperty.Register(&quot;VisibleWhenChecked&quot;, typeof(bool), 
            typeof(VisibilityIsChecked), new PropertyMetadata(true));
        public bool VisibleWhenChecked
        {
            get
            {
                return (bool)GetValue(VisibleWhenCheckedProperty);
            }
            set
            {
                SetValue(VisibleWhenCheckedProperty, value);
            }
        }
 
        protected override void OnAttached()
        {
            base.OnAttached();
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            if (element != null) element.Loaded += TargetLoaded;
        }
 
        void TargetLoaded(object sender, RoutedEventArgs e)
        {
            ToggleButton tb = this.AssociatedObject as ToggleButton;
            SetVisibility(tb);
        }
        protected override void Invoke(object parameter)
        {
            RoutedEventArgs args = parameter as RoutedEventArgs;
 
            if (args != null)
            {
                ToggleButton tb = args.OriginalSource as ToggleButton;
                SetVisibility(tb);
            }
        }
 
        private void SetVisibility(ToggleButton tb)
        {
            if (tb != null)
            {
                Target.Visibility = ((bool)tb.IsChecked == VisibleWhenChecked) ? 
                Visibility.Visible : Visibility.Collapsed;
            }
        }
    }
}

To implement this trigger, we simply add an EventTrigger to a checkbox and set the TargetName to the control I want to visualize. Since there are times that we want IsChecked to either show or hide the element I have included a DependencyProperty called VisibileWhenChecked that can alter this behavior.

1
2
3
4
5
6
7
8
<checkbox content="Show?" margin="5">
    <i:interaction.triggers>
        <i:eventtrigger eventname="Click">
            <triggers:visibilityischecked targetname="rectangle" />
        </i:eventtrigger>
    </i:interaction.triggers>
</checkbox>
<rectangle margin="10" x:name="rectangle" width="100" height="100" fill="Yellow" visibility="Collapsed" />

VisibilitySelectedItem

The second TargetedTriggerAction I created achieves a similar result, but instead of targeting a checkbox or radio button, it targets a ComboBox or ListBox. In this trigger I will flip a UIElements visibility based on a specific value being returned from a selection in the the ListBox. Here is how I implemented the trigger.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;
 
namespace VisibilityTriggers
{
    public class VisibilitySelectedItem : TargetedTriggerAction<frameworkelement>
    {
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(&quot;Value&quot;, typeof(object), typeof(VisibilitySelectedItem), null);
 
        public object Value
        {
            get
            {
                return (object)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }
 
        public static readonly DependencyProperty MatchMemberPathProperty =
            DependencyProperty.Register(&quot;MatchMemberPath&quot;, typeof(string), typeof(VisibilitySelectedItem), new PropertyMetadata(&quot;Content&quot;));
 
        public string MatchMemberPath
        {
            get
            {
                return (string)GetValue(MatchMemberPathProperty);
            }
            set
            {
                SetValue(MatchMemberPathProperty, value);
            }
        }
 
        protected override void OnAttached()
        {
            base.OnAttached();
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            if (element != null) element.Loaded += TargetLoaded;
        }
 
        void TargetLoaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            Selector cb = this.Target as Selector;
            if (cb != null)
            {
                int index = cb.SelectedIndex &gt; -1 ? cb.SelectedIndex : 0;
                object item = (cb.Items.Count &gt; 0) ? cb.Items[index] as object : null;
 
                if (item != null)
                {
                    System.Reflection.PropertyInfo pi = item.GetType().GetProperty(MatchMemberPath);
                    var value = (pi != null) ? pi.GetValue(item, null) as object : item;
                    bool match = Equals(value, Value);
                    if (element != null) element.Visibility = match ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }
        protected override void Invoke(object parameter)
        {
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            SelectionChangedEventArgs args = parameter as SelectionChangedEventArgs;
 
            if (args != null)
            {
                IList list = args.AddedItems as IList;
                if (list != null)
                {
                    foreach (object item in list)
                    {
                        System.Reflection.PropertyInfo pi = item.GetType().GetProperty(MatchMemberPath);
                        var value = (pi != null) ? pi.GetValue(item, null) as object : item;
 
                        bool match = Equals(value, Value);
                        if (element != null) element.Visibility = match ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
            }
        }
    }
}

To implement this TargetedTriggerAction you will place the custom EventTrigger on the item you want to visualize and target the SelectionChanged event on the ListBox (or ComboBox). Since we only want to visualize when a specific value is returned, we will also specifiy a "Value" and which property of our ListBox’s ItemSource we are targeting (MatchMemeberPath). The following is one way in which we can implement the trigger. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<listbox x:name="myComboBox2" width="100" itemssource="{Binding Path=Items}" displaymemberpath="Description" />
<rectangle margin="10" width="100" height="100" fill="Silver" visibility="Collapsed">
    <i:interaction.triggers>
        <i:eventtrigger eventname="SelectionChanged" sourcename="myComboBox2">
            <triggers:visibilityselecteditem targetname="myComboBox2" value="I1" matchmemberpath="Code" />
        </i:eventtrigger>
    </i:interaction.triggers>
</rectangle>
<ellipse margin="10" width="100" height="100" fill="SaddleBrown" visibility="Collapsed">
    <i:interaction.triggers>
        <i:eventtrigger eventname="SelectionChanged" sourcename="myComboBox2">
            <triggers:visibilityselecteditem targetname="myComboBox2" value="I2" matchmemberpath="Code" />
        </i:eventtrigger>
    </i:interaction.triggers>
</ellipse>





Code: VisibilityTriggers.zip

/p>

7 Responses to “Control Visibility with triggers”

  1. Rob Eisenberg said:

    Triggers are an extremely powerful construct and very easily abused. This type of usage puts too much behavior in the Xaml, where it doesn’t belong. A better approach would be to use a ViewModel and bind interested elements to the same property. So, the RadioButton.IsChecked is bound to the same property as the UIElement.IsVisible. Doing it this way even requires significantly less code as well.

  2. Foster said:

    Triggers are cool, but I wonder if your first one can be achieved with element binding?

  3. joel said:

    To bind the IsChecked from one element to the visibility of another, you would need to use a converter to go from a nullable bool to the System.Windows.Visibility enum. It’s how many of us did it in Silverlight 2.

  4. Control Visibility with triggers | Silverlight Travel said:

    [...] more here [...]

  5. joel said:

    Rob thanks for your comment. I hear what you are saying, but I am not sure I would classify it as abuse. What I like about the trigger is its ease of implementation (drag and drop). Just yesterday I was mocking a few screens in Sketchflow and found the trigger very valuable for rapid prototyping. I had a part of a screen where I needed a checkbox to acknowledge a user was a member of AAA?. Checking the box displayed a group of fields to capture the AAA information. My trigger was easier then constructing a ViewModel and adding a few properties.

    Quick question about your approach….I did not think that UIElement.IsVisible had a setter, nor did I think it existed in Silverlight 3?

  6. Rob Eisenberg said:

    I think it’s on Control in Silverlight; UIElement in WPF. For prototyping, I wouldn’t hesitate to use the approach you mention. However, when you move to an actual implementation, its easy for this to get out of hand. It’s something to be continually evaluating because you can very easily find that you’ve put a lot of “code” in the Xaml without realizing it.

  7. Michael Washington said:

    This works for me. I have used the Visual States before, but at this point they are not easy to use. Using your code was easy to use, simply set the properties and the control works. For SketchFlow it is all about fast and easy so this works.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">