Search code examples
c#wpfxamlcomboboxmicrosoft-ui-automation

Select custom ComboBox item with UI Automation


How do I select an item in a custom ComboBox with Microsoft UI Automation? I have ComboBox that looks like this:

<ComboBox AutomationProperties.AutomationId="Rm8Function"
          ItemsSource="{Binding Source={StaticResource Functions}}"
          SelectedItem="{Binding Function, UpdateSourceTrigger=PropertyChanged}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock
                Text="{Binding Mode=OneTime, Converter={StaticResource FunctionEnumConverter}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

I.e. I have overriden the ItemTemplate with a custom DataTemplate.

However, now I am not able to select the item using the answer on selecting combobox item using ui automation:

public static void SelectComboBoxItem(this AutomationElement comboBox, string item)
{
    var expandCollapsePattern = comboBox.GetPattern<ExpandCollapsePattern>(ExpandCollapsePatternIdentifiers.Pattern);
    expandCollapsePattern.Expand();
    expandCollapsePattern.Collapse();
    var listItem = comboBox.FindFirst(TreeScope.Subtree,
        new PropertyCondition(AutomationElement.NameProperty, item));
    var selectionItemPattern = listItem.GetPattern<SelectionItemPattern>(SelectionItemPatternIdentifiers.Pattern);
    selectionItemPattern.Select();
}

public static T GetPattern<T>(this AutomationElement element, AutomationPattern pattern) where T: BasePattern
{
    try
    {
        return (T) element.GetCurrentPattern(pattern);
    }
    catch (InvalidOperationException)
    {
        element.PrintSupportedPatterns();
        throw;
    }
}

It throws an error telling me that SelectionItemPatternIdentifiers.Pattern is an unsupported pattern. It's only SynchronizedInputPatternIdentifiers.Pattern that is supported by the element it's trying to select in the ComboBox.

How should I write my DataTemplate so that it becomes selectable?


Solution

  • I re-defined my ComboBox in the following way:

    <ComboBox AutomationProperties.AutomationId="Rm8Function"
              ItemsSource="{Binding Source={StaticResource Functions}}"
              SelectedItem="{Binding Function, UpdateSourceTrigger=PropertyChanged}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock
                    AutomationProperties.Name="{Binding Mode=OneTime, Converter={StaticResource FunctionEnumConverter}}"
                    Text="{Binding Mode=OneTime, Converter={StaticResource FunctionEnumConverter}}" />
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
    

    Giving the TextBlock the same AutomationProperties.Name value as its Text value.

    I also updated my function that selects the ComboBox item to the following:

    public static void SelectComboBoxItem(this AutomationElement comboBox, string item)
    {
        var expandCollapsePattern = comboBox.GetPattern<ExpandCollapsePattern>(ExpandCollapsePatternIdentifiers.Pattern);
        expandCollapsePattern.Expand();
        expandCollapsePattern.Collapse();
        var listItem = comboBox.FindFirst(TreeScope.Subtree,
            new PropertyCondition(AutomationElement.NameProperty, item));
        var walker = TreeWalker.ControlViewWalker;
        var parent = walker.GetParent(listItem);
        while (parent != comboBox)
        {
            listItem = parent;
            parent = walker.GetParent(listItem);
        }
        var selectionItemPattern = listItem.GetPattern<SelectionItemPattern>(SelectionItemPatternIdentifiers.Pattern);
        selectionItemPattern.Select();
    }
    

    Apparently, when using a ComboBox as-is without overriding the ItemTemplate, the function above finds its immediate child which is a ListBoxItem. It's the ListBoxItem that is selectable through the SelectionItemPattern pattern. But when overriding the ItemTemplate, the function instead finds the TextBlock that is a child to the ListBoxItem. Therefore, I had to modify my function in such a way that it traverses upwards until it finds the immediate child to the ComboBox and selects it.