A little background: I'm currently writing a sample project using Winforms/C# that emulates Conway's Game of Life. Part of this sample involves UI Automation using the White Automation Framework. The relevant layout of the form includes a custom grid control for setting up the world and a list box control that displays/stores past generations of the world.
I have a World object that stores a list of Cell objects and calculates the next generation of a World from its current state:
public class World
{
public IReadOnlyCollection<Cell> Cells { get; private set; }
public World(IList<Cell> seed)
{
Cells = new ReadOnlyCollection<Cell>(seed);
}
public World GetNextGeneration()
{
/* ... */
}
}
In my UI, when I calculate the next world generation, the past generations list is updated. The past generation list stores World objects as its items, and I have subscribed to the Format event of the list box to format the item display. _worldProvider.PreviousGenerations is a collection of World objects.
private void UpdatePastGenerationsList()
{
GenerationList.SuspendLayout();
GenerationList.Items.Add(_worldProvider.PreviousGenerations.Last());
GenerationList.SelectedItem = _worldProvider.PreviousGenerations.Last();
GenerationList.ResumeLayout();
}
From this snippet you can see that the items of the ListBox are World objects. What I want to do in my test code is get the actual World object (or some representation of it) from the selected ListBox item, and then compare it to the grid's representation of the world. The grid has a full automation implementation so I can easily get a representation of the grid using existing automation calls in White.
The only idea I had was to make a derived ListBox control that sends an ItemStatus property changed automation event when the selected index changes from an automation click event, and then listening for that ItemStatus event in the test code. The World is first converted to a string (WorldSerialize.SerializeWorldToString) where each live cell is converted to formatted coordinates {x},{y};.
public class PastGenerationListBox : ListBox
{
public const string ITEMSTATUS_SELECTEDITEMCHANGED = "SelectedItemChanged";
protected override void OnSelectedIndexChanged(EventArgs e)
{
FireSelectedItemChanged(SelectedItem as World);
base.OnSelectedIndexChanged(e);
}
private void FireSelectedItemChanged(World world)
{
if (!AutomationInteropProvider.ClientsAreListening)
return;
var provider = AutomationInteropProvider.HostProviderFromHandle(Handle);
var args = new AutomationPropertyChangedEventArgs(
AutomationElementIdentifiers.ItemStatusProperty,
ITEMSTATUS_SELECTEDITEMCHANGED,
WorldSerialize.SerializeWorldToString(world));
AutomationInteropProvider.RaiseAutomationPropertyChangedEvent(provider, args);
}
}
The problem I have with this is that the event handler code in the test class is never being called. I think the problem is with the AutomationInteropProvider.HostProviderFromHandle call returning a different provider object from the one in the test code, but I am not sure.
My questions are:
- Is there a better approach I can take, such as something provided by the MS Automation API?
- If not - is there a way I can get the default C#
IRawElementProviderSimpleimplementation for the ListBox control (to raise the Property Changed event)? I would rather not re-implement it just for this little bit of functionality.
Here is the code from the test side, which adds the listener for ItemStatusProperty change event. I am using SpecFlow for BDD which defines ScenarioContext.Current as a dictionary. WorldGridSteps.Window is a TestStack.White.Window object.
private static void HookListItemStatusEvent()
{
var list = WorldGridSteps.Window.Get<ListBox>(GENERATION_LIST_NAME);
Automation.AddAutomationPropertyChangedEventHandler(list.AutomationElement,
TreeScope.Element,
OnGenerationSelected,
AutomationElementIdentifiers.ItemStatusProperty);
}
private static void UnhookListItemStatusEvent()
{
var list = WorldGridSteps.Window.Get<ListBox>(GENERATION_LIST_NAME);
Automation.RemoveAutomationPropertyChangedEventHandler(list.AutomationElement, OnGenerationSelected);
}
private static void OnGenerationSelected(object sender, AutomationPropertyChangedEventArgs e)
{
if (e.EventId.Id != AutomationElementIdentifiers.ItemStatusProperty.Id)
return;
World world = null;
switch (e.OldValue as string)
{
case PastGenerationListBox.ITEMSTATUS_SELECTEDITEMCHANGED:
world = WorldSerialize.DeserializeWorldFromString(e.NewValue as string);
break;
}
if (world != null)
{
if (ScenarioContext.Current.ContainsKey(SELECTED_WORLD_KEY))
ScenarioContext.Current[SELECTED_WORLD_KEY] = world;
else
ScenarioContext.Current.Add(SELECTED_WORLD_KEY, world);
}
}