Thursday, July 01, 2010

Assigning AutomationIds Dynamically in Silverlight

I came across an interesting problem recently:  How do you assign AutomationIds automatically for Items in an ItemsControl?

<UserControl 
    x:Class="ThinkFarAhead.AutoAutomationId.MainPage" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ThinkFarAhead.AutoAutomationId" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
 
  <Grid x:Name="LayoutRoot" Background="White">
    <StackPanel>
      <ItemsControl x:Name="ItemsControl">
        <ItemsControl.ItemTemplate>
          <DataTemplate>
            <TextBlock 
                local:AutomationHelper.Parent=
                    "{Binding ElementName=ItemsControl}"
                local:AutomationHelper.CurrentItem=
                    "{Binding Path=DataContext,
                    RelativeSource={RelativeSource TemplatedParent}}"
                local:AutomationHelper.IndexedId="Message{0}"
                    AutomationProperties.AutomationId=
                        "{Binding (local:AutomationHelper.IndexedId),
                        RelativeSource={RelativeSource Self}}"
                Text="{Binding (AutomationProperties.AutomationId), 
                    RelativeSource={RelativeSource Self}}" />
          </DataTemplate>
        </ItemsControl.ItemTemplate>
      </ItemsControl>
    </StackPanel>
  </Grid>
</UserControl>

 

namespace ThinkFarAhead.AutoAutomationId
{
    using System.Windows;
    using System.Windows.Controls;
 
    public static class AutomationHelper
    {
        #region Constants and Fields
 
        public static DependencyProperty CurrentItemProperty =
            DependencyProperty.RegisterAttached(
                "CurrentItem",
                typeof(object),
                typeof(AutomationHelper),
                new PropertyMetadata(null)
            );
 
        public static DependencyProperty IndexedIdProperty =
            DependencyProperty.RegisterAttached(
                "IndexedId",
                typeof(string),
                typeof(AutomationHelper),
                new PropertyMetadata(null)
            );
 
        public static DependencyProperty ParentProperty =
            DependencyProperty.RegisterAttached(
                "Parent",
                typeof(ItemsControl),
                typeof(AutomationHelper),
                new PropertyMetadata(null)
            );
 
        #endregion
 
        #region Public Methods
 
        public static object GetCurrentItem(DependencyObject element)
        {
            return element.GetValue(CurrentItemProperty);
        }
 
        public static string GetIndexedId(DependencyObject element)
        {
            return element.GetValue(IndexedIdProperty) as string;
        }
 
        public static ItemsControl GetParent(DependencyObject element)
        {
            return element.GetValue(ParentProperty) as ItemsControl;
        }
 
        public static void SetCurrentItem
        (
            DependencyObject element, object value
        )
        {
            element.SetValue(CurrentItemProperty, value);
        }
 
        public static void SetIndexedId
        (
            DependencyObject element, string value
        )
        {
            value = string.Format(value, 
                GetParent(element)
                .Items
                .IndexOf(GetCurrentItem(element)));
 
            element.SetValue(IndexedIdProperty, value);
        }
 
        public static void SetParent
        (
            DependencyObject element, ItemsControl value
        )
        {
            element.SetValue(ParentProperty, value);
        }
 
        #endregion
    }
}

 

namespace ThinkFarAhead.AutoAutomationId
{
    using System.Collections.Generic;
    using System.Windows.Controls;
 
    public partial class MainPage : UserControl
    {
        #region Constructors and Destructors
 
        public MainPage()
        {
            this.InitializeComponent();
 
            var list = new List<string>();
 
            list.Add("Zero");
            list.Add("One");
            list.Add("Two");
            list.Add("Three");
            list.Add("Four");
            list.Add("Five");
            list.Add("Six");
            list.Add("Seven");
            list.Add("Eight");
            list.Add("Nine");
            list.Add("Ten");
 
            this.ItemsControl.ItemsSource = list;
        }
 
        #endregion
    }
}

 

image

Hope it helps somebody!

 

3 comments:

Chester Kim said...

Hi, thanks for great article.
Unfortunately, I didn't have any luck to get same result with you with .Net 3.5.
Are you using .Net 4.0 for this specific example? Isn't there any workaround for 3.5 to assign dynamic automationId? I tried few days but am lost completely now.

Vyas Bharghava said...

Yeah, I was told this technique doesn't work with SL 3.0. Hard luck there! Apologies for not being to help more.

Vyas Bharghava said...

Tested and found to work with Silverlight 5.0 too.