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!

 

6 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.

Sreeni Jaboodi said...

Yeah sounds like a familiar problem. I've been sniffing out forums for solutions on this one. Thanks for the insight.
web design perth

Dheeraj said...

I'm impressed. You're truly well informed and very intelligent. You wrote something that people could understand and made the subject intriguing for everyone. I'm saving this for future use. SEO Services

Ross Taylor said...

Great post. I was checking continuously this blog and I am impressed! Extremely helpful info specially the last part :) I care for such info much. I was looking for this particular information for a long time. Thank you and best of luck.
Web design Birmingham Alabama