Search code examples
c#wpfdevexpressdevexpress-wpfflaui

Not able to get the current active /selected TabITem in DevExpress DxTabControl in WPF


I have a WPF app and I am trying to Automate it using FlaUI. I am facing a problem with the DxTabControl. I have provided Automation IDs to the DxTabControl. I am using DXTabControl.ItemHeaderTemplate to generate TabItems dynamically. According to DevExpress Team, The DXTabControl.ItemHeaderTemplate doesnt support AutoamtionPeer so a custom implementation has been added to override its default behaviour.

Now, I am able to see the TabControl and the TabItems in the Inspect.exe. Now , my requirement is to Access the currently selected Tabitem and find the CloseButton using the AutoamtionID mentioned in the XAML below and close it. Pasting below the line again. As there would be multiple TabItems generated, I am unable to get the Currently active/Selected TabItem .

The XAML is below

<dx:DXTabControl AutomationProperties.AutomationId="ViewsParentTabControl"
        MaxWidth="4000"
        MaxHeight="4000"
        Margin="1,0,-1,0"
        Focusable="False"
        ItemsSource="{Binding OpenViews}"
        SelectedIndex="{Binding SelectedTabIndex}"
        TabContentCacheMode="CacheTabsOnSelecting">
        <dx:DXTabControl.ItemHeaderTemplate>
            <DataTemplate DataType="viewModels1:OpenViewDefinitionViewModel">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Name="CreatedViewName"
                        MaxWidth="100"
                        Text="{Binding Data.ViewDefinition.Name}"
                        TextTrimming="CharacterEllipsis"
                        ToolTip="{Binding DisplayName}" />
                    <TextBlock Grid.Row="0" Grid.Column="1"><Run Text=" [" /><Run Text="{Binding ItemsCount, FallbackValue=0, Mode=OneWay}" /><Run Text="]" /></TextBlock>
                    <controls2:ProgressIndicator AutomationProperties.AutomationId="ProgressCurrentView"
                        Grid.Row="0"
                        Grid.Column="3"
                        Width="14"
                        Margin="4,0,0,0"
                        VerticalAlignment="Center"
                        CircleBorder="{StaticResource ListBoxItem.Foreground}"
                        CircleFill="{StaticResource ListBoxItem.Foreground}"
                        IndicatorEnabled="{Binding IsDataLoading}" />
                    <Button AutomationProperties.AutomationId="CloseCurrentViewButton"
                        Grid.Row="0"
                        Grid.Column="2"
                        Width="10"
                        Height="10"
                        Margin="10,1,0,0"
                        Padding="0"
                        HorizontalAlignment="Right"
                        BorderThickness="0"
                        Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dx:DXTabControl}}"
                        CommandParameter="{Binding}"
                        Focusable="False"
                        Style="{StaticResource MwButtonStyle}"
                        ToolTip="Close">
                        <Path
                            Data="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
                            Fill="White"
                            Stretch="Fill" />
                    </Button>
                </Grid>
            </DataTemplate>
        </dx:DXTabControl.ItemHeaderTemplate>
        <dx:DXTabControl.ItemTemplate>
            <DataTemplate DataType="viewModels1:OpenViewDefinitionViewModel">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <local:VoyagesGridControl />

                    <local:VoyageValidationUserControl
                        Grid.Row="1"
                        Grid.Column="0"
                        MinHeight="100"
                        MaxHeight="300"
                        Visibility="{Binding Path=IsVoyageValidationShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />
                    <local:VoyageHistoryUserControl
                        Grid.Row="2"
                        Grid.Column="0"
                        MinHeight="300"
                        MaxHeight="300"
                        Visibility="{Binding Path=IsVoyageHistoryShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />
                    <local:VesselHistoryUserControl
                        Grid.Row="3"
                        Grid.Column="0"
                        MinHeight="300"
                        MaxHeight="300"
                        Visibility="{Binding Path=IsVesselHistoryShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />
                    <local:VoyageEvents
                        Grid.Row="0"
                        Grid.RowSpan="3"
                        Grid.Column="1"
                        VerticalAlignment="Top"
                        Visibility="{Binding Path=IsVoyageEventsShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />


                    <controls2:ProgressIndicator AutomationProperties.AutomationId="showProgressForLoadingViews"
                        Grid.Row="0"
                        Grid.RowSpan="3"
                        Grid.Column="0"
                        Width="80"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        CircleBorder="{StaticResource ListBox.BorderBrush}"
                        CircleFill="{StaticResource ListBox.BorderBrush}"
                        IndicatorEnabled="{Binding IsDataLoading}" />
                    <!--  Buttons  -->
                    <Grid Grid.Row="4" Grid.Column="0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <StackPanel
                            Grid.Column="0"
                            VerticalAlignment="Center"
                            Orientation="Horizontal">
                            <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Last Refresh:" />
                            <TextBlock
                                Margin="2,0,4,0"
                                VerticalAlignment="Center"
                                Text="{Binding LoadDate, StringFormat=G}" />
                        </StackPanel>
                        <Button AutomationProperties.AutomationId="AddNewVoyageButton"
                            Grid.Row="0"
                            Grid.Column="1"
                            Padding="0"
                            Command="{Binding AddVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Add a new voyage to this View (ALT + A)">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Add}" />
                                <Label Style="{StaticResource MwLabelStyle}">_Add</Label>
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="refreshVoyageButton"
                            Grid.Row="0"
                            Grid.Column="2"
                            Padding="0"
                            Command="{Binding RefreshVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Refresh the this View (modified entries are left unchanged)">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Refresh}" />
                                <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Refresh" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="showVoyageHistroyButton"
                            Grid.Column="4"
                            Margin="2,2,2,2"
                            Padding="0"
                            VerticalAlignment="Center"
                            Command="{Binding ShowVoyageHistoryCommand}"
                            Focusable="False"
                            ToolTip="Show the selected voyage's change history"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource ShowVoyageHistory}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Hide Voyage History"
                                    Visibility="{Binding IsVoyageHistoryShowing, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Show Voyage History"
                                    Visibility="{Binding IsVoyageHistoryShowing, Converter={StaticResource MwBoolToVisibilityConverterReverse}}" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="showVesselHistroyButton"
                            Grid.Column="5"
                            Margin="2,2,2,2"
                            Padding="0"
                            VerticalAlignment="Center"
                            Command="{Binding ShowVesselHistoryCommand}"
                            Focusable="False"
                            ToolTip="Show the selected vessel's voyage history"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource ShowVesselHistory}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Hide Vessel History"
                                    Visibility="{Binding IsVesselHistoryShowing, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Show Vessel History"
                                    Visibility="{Binding IsVesselHistoryShowing, Converter={StaticResource MwBoolToVisibilityConverterReverse}}" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="showVoyageButton"
                            Grid.Column="6"
                            Margin="2,2,2,2"
                            Padding="0"
                            VerticalAlignment="Center"
                            Command="{Binding ShowVesselVisitsCommand}"
                            Focusable="False"
                            ToolTip="Show the selected voyage's events"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Anchor}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Hide Voyage Events"
                                    Visibility="{Binding IsVoyageEventsShowing, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Show Voyage Events"
                                    Visibility="{Binding IsVoyageEventsShowing, Converter={StaticResource MwBoolToVisibilityConverterReverse}}" />
                            </StackPanel>
                        </Button>

                        <Border Grid.Row="0" Grid.Column="8">
                            <Border.Style>
                                <Style TargetType="Border">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsDuplicateView, Mode=TwoWay}" Value="true">
                                            <Setter Property="Background" Value="LightGreen" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                            <Button AutomationProperties.AutomationId="DuplicateCheckButton"
                                Padding="0"
                                Command="{Binding DuplicateVoyagesCommand}"
                                Focusable="False"
                                Style="{StaticResource MwButtonStyle}"
                                ToolTip="Switch to duplicate Voyages (ALT + D)"
                                Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                                <StackPanel Orientation="Horizontal">
                                    <ContentControl Height="26" Content="{StaticResource Duplicate}" />
                                    <AccessText Style="{StaticResource MwAccessTextLabelStyle}" Text="{Binding VoyageDuplicateText}" />
                                </StackPanel>
                            </Button>
                        </Border>

                        <Button AutomationProperties.AutomationId="PublishVoyagesButton"
                            Grid.Row="0"
                            Grid.Column="9"
                            Padding="0"
                            Command="{Binding PublishVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Publish any modified Voyages (ALT + P)"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Publish}" />
                                <AccessText Style="{StaticResource MwAccessTextLabelStyle}" Text="{Binding VoyagePublishText}" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="UndoSingleVoyageButton"
                            Grid.Row="0"
                            Grid.Column="10"
                            Padding="0"
                            Command="{Binding UndoSingleChangedVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Locally Undo unpublished changes to the selected voyage"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Undo}" />
                                <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Undo Selected" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="UndoandUnpublishVoyageButton"
                            Grid.Row="0"
                            Grid.Column="11"
                            Padding="0"
                            Command="{Binding UndoChangedVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Locally Undo any changed and unpublished voyages"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Undo}" />
                                <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Undo All" />
                            </StackPanel>
                        </Button>
                    </Grid>
                </Grid>
            </DataTemplate>
        </dx:DXTabControl.ItemTemplate>
    </dx:DXTabControl>

My FlaUIapproach of locating the Controls is below

public IMainWindow ConfirmCreatedView()
        {
            _logger.Info("Checking the newly created View on the screen");
            //Apoorv: Need to find TabItem here
            _controlAction.Highlight(ViewsTabControl); // This highlights the TabControl- Works
            int NumberOfActiveTabs = ViewsTabControl.TabItems.Length; // This gives me no of TabItems


          TabItem SelectedTab=  ViewsTabControl.SelectedTabItem as TabItem; // Gives me Null here
            var newTab = ViewsTabControl.SelectedTabItemIndex ; // Give me -1 here


            _controlAction.Highlight(ViewsTabControl.TabItems[2]); // Works. It highlights the TabItem at position 2

            _controlAction.ClickWait<TabItem>(ViewsTabControl.TabItems[2]); // This goes and clicks the tab item 

            TabItem SelectedTabs = ViewsTabControl.SelectedTabItem as TabItem;
            var check = ViewsTabControl.TabItems[2].FindAllChildren();
            // TabItem ti = ViewsTabControl.SelectedItem as TabItem;
            //_controlAction.Highlight()



                _controlAction.Highlight(CloseCurrentView); // highlights the close button atTabItem[0]
                _controlAction.Click<Button>(CloseCurrentView); // closes it

                      return this;
        }

I am using FlaUI to Find the TabControl using AutomationID as shown below

 private Tab ViewsTabControl => _uiAutomation.FindElement("ViewsParentTabControl", Automation.FindBy.Id).AsTab();
        private TabItem ViewsTabItem => _uiAutomation.FindElement("DXTabItem", Automation.FindBy.Id).AsTabItem();

I would like to find the curently active TabItem based on the Index and then go and click the close button by automating it.

TabItem SelectedTab=  ViewsTabControl.SelectedTabItem as TabItem; // Gives me Null here
            var newTab = ViewsTabControl.SelectedTabItemIndex ; // Give me -1 here

Solution

  • DevEXpress don't create automation peers for controls within ItemHeaderTemplate. It will be necessary to use a custom automation peer to provide this functionality. For example, I used the following class for test purposes:

    public class DXTabItemAutomationPeerEx : DXTabItemAutomationPeer, ISelectionItemProvider {
        private DXTabItem TabItem => base.Owner as DXTabItem;
        private DXTabControl TabControl => TabItem.With((DXTabItem x) => x.Owner);
        public DXTabItemAutomationPeerEx(DXTabItem ownerCore) : base(ownerCore) { }
        protected override List<AutomationPeer> GetChildrenCore() {
            List<AutomationPeer> children = base.GetChildrenCore();
            foreach (var button in LayoutTreeHelper.GetVisualChildren(Owner).OfType<Button>())
                children.Add(new ButtonAutomationPeer(button));
            return children;
        }
        bool ISelectionItemProvider.IsSelected { get {
                if (TabControl != null) {
                    return TabControl.SelectedContainer == TabItem;
                }
                return false;
            } 
        }
    }
    

    Post adding this code inside my MainPage.Xaml.cs , I added a static constructor to register it

    static MainWindow() {
        NavigationAutomationPeersCreator.Default.RegisterObject(typeof(DXTabItem), typeof(DXTabItemAutomationPeerEx), owner => new DXTabItemAutomationPeerEx((DXTabItem)owner));
    }
    

    post this , these lines work as charm

    TabItem CurrentTab = ViewsTabControl.SelectedTabItem as TabItem;
    var Children = ViewsTabControl.FindAllChildren(); 
    foreach (var child in Children) {
        var subChildren = child.FindAllChildren();
    }