Немного предыстории: в настоящее время я пишу пример проекта с использованием Winforms/C#, который эмулирует Игру жизни Конвея< /а>. Часть этого примера включает автоматизацию пользовательского интерфейса с использованием White Automation Framework. Соответствующий макет формы включает в себя настраиваемый элемент управления сеткой для настройки мира и элемент управления списком, который отображает/сохраняет прошлые поколения мира.
У меня есть объект World
, который хранит список объектов Cell
и вычисляет следующее поколение World
из его текущего состояния:
public class World
{
public IReadOnlyCollection<Cell> Cells { get; private set; }
public World(IList<Cell> seed)
{
Cells = new ReadOnlyCollection<Cell>(seed);
}
public World GetNextGeneration()
{
/* ... */
}
}
В моем пользовательском интерфейсе, когда я вычисляю следующее поколение мира, список прошлых поколений обновляется. Список прошлого поколения хранит World
объектов в качестве своих элементов, и я подписался на событие Format
списка для форматирования отображения элементов. _worldProvider.PreviousGenerations
— это набор World
объектов.
private void UpdatePastGenerationsList()
{
GenerationList.SuspendLayout();
GenerationList.Items.Add(_worldProvider.PreviousGenerations.Last());
GenerationList.SelectedItem = _worldProvider.PreviousGenerations.Last();
GenerationList.ResumeLayout();
}
Из этого фрагмента видно, что элементы ListBox являются объектами World. Что я хочу сделать в своем тестовом коде, так это получить фактический объект World
(или некоторое его представление) из выбранного элемента ListBox, а затем сравнить его с представлением мира в сетке. В сетке реализована полная автоматизация, поэтому я могу легко получить представление о сетке, используя существующие вызовы автоматизации в белом цвете.
Единственная идея, которая у меня была, заключалась в том, чтобы создать производный элемент управления ListBox, который отправляет событие автоматизации свойства ItemStatus
измененного, когда выбранный индекс изменяется из события щелчка автоматизации, а затем прослушивает это событие ItemStatus в тестовом коде. Мир сначала преобразуется в строку (WorldSerialize.SerializeWorldToString
), где каждая живая ячейка преобразуется в форматированные координаты {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);
}
}
Проблема, с которой я столкнулся, заключается в том, что код обработчика событий в тестовом классе никогда не вызывается. Я думаю, проблема в том, что вызов AutomationInteropProvider.HostProviderFromHandle
возвращает другой объект провайдера, отличный от объекта в тестовом коде, но я не уверен.
Мои вопросы:
- Есть ли лучший подход, который я могу использовать, например, что-то, предоставляемое MS Automation API?
- Если нет, есть ли способ получить реализацию С#
IRawElementProviderSimple
по умолчанию для элемента управления ListBox (чтобы вызвать событие Property Changed)? Я бы предпочел не реализовывать его повторно только для этой небольшой функциональности.
Вот код со стороны теста, который добавляет прослушиватель для события изменения ItemStatusProperty. Я использую SpecFlow для BDD, который определяет ScenarioContext.Current
как словарь. WorldGridSteps.Window
является объектом TestStack.White.Window
.
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);
}
}