Page 1 of 1

Setting Focus Directly to Expander Button

Posted: 30 Sep 2024, 19:22
by aivanov
I am trying to set focus to the `ToggleButton` of an `Expander` element when the user control gains focus. Is there a way to reference the internal element of the Expander in the target field of SetFocusAction? Is there any other way of setting focus like this through XAML?

The goal of this is to have the focus set to the button as soon as the screen becomes visible so the user can click it without needing to navigate first.
<Grid x:Name="StateTriggers">
            <b:Interaction.Triggers>                
                <b:PropertyChangedTrigger Binding="{Binding IsVisible, ElementName=MyContainer}">
                    <noesis:SetFocusAction TargetName="MyExpander"/>
                </b:PropertyChangedTrigger>
            </b:Interaction.Triggers>
</Grid>

<Grid x:Name="MyContainer" Width="400">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="80"/>
            </Grid.RowDefinitions>

            <ScrollViewer x:Name="Content" Grid.Row="0" Margin="20,20,10,20" VerticalAlignment="Top" VerticalScrollBarVisibility="Visible">
                <StackPanel>
                    <Expander x:Name="MyExpander" Template="{StaticResource MyExpanderTemplate}" OverridesDefaultStyle="True" IsExpanded="True" DataContext="{Binding MyExpanderModel}" KeyboardNavigation.DirectionalNavigation="Continue">
                        <Expander.Header>
                            <TextBlock Text="This is an expander"/>
                        </Expander.Header>
                        <ItemsControl x:Name="MyExpanderItems" ItemTemplate="{StaticResource MyExpanderItemTemplate}" ItemsSource="{Binding Items}" Margin="0,10,0,0" Focusable="False">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel Orientation="Vertical"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </Expander>
                </StackPanel>
            </ScrollViewer>
</Grid>
At runtime, the hierarchy looks something like this:
> Expander (Focus goes here)
>> Dock Panel
>>>ToggleButton (Want focus to go here)

It would be nice if there was the option to do something like the line below but I haven't found any example doing something similar yet:
<noesis:SetFocusAction TargetName="MyExpander.DockPanel.ToggleButton"/>
Any help or ideas are much appreciated!

Re: Setting Focus Directly to Expander Button

Posted: 02 Oct 2024, 11:48
by sfernandez
You cannot access template elements from outside the control like that. I suggest you just set the focus on the Expander, and then move it down so its ToggleButton acquires the focus, something like this:
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
  xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions">
  <b:Interaction.Triggers>
    <b:EventTrigger EventName="Loaded">
      <noesis:SetFocusAction TargetName="expand"/>
      <noesis:MoveFocusAction Direction="Down"/>
    </b:EventTrigger>
  </b:Interaction.Triggers>
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button Content="First Button"/>
    <Expander x:Name="expand" Header="Expander" IsExpanded="True">
      <Ellipse Height="100" Fill="Red"/>
    </Expander>
    <Button Content="Second   Button"/>
  </StackPanel>
</Grid>
Could you try that?

Re: Setting Focus Directly to Expander Button

Posted: 02 Oct 2024, 23:15
by aivanov
This does work, Thank you!

There is however, an additional edge case that I am hitting. The expander is part of a grid and a VisualStateManager is setup to periodically hide and show the grid that wraps the expander. Is there a way to set focus when the visibility of the grid container is updated and not only when the entire control is first loaded?

I have tried these things and others without much success:
- have an EventTrigger on "IsVisibleChanged" inside of grid1
- have an EventTrigger on "IsLoaded" inside of grid1
- have a PropertyChangedTrigger for the visibility of grid1 inside of the root grid

Is there a good way of updating focus with the states of the VisualStateManager or at least to correct the focus in response to the visual state changing?
<Grid x:Name"root">
<VisualStateManager.VisualStateGroups>
  <!-- Multiple states where grid1 visibility is set to either Visible or Collapsed -->
</VisualStateManager.VisualStateGroups>

<Grid x:Name"grid1">
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button Content="First Button"/>
     <Expander x:Name="expand" Header="Expander" IsExpanded="True">
    </Expander>
    <Button Content="Second   Button"/>
  </StackPanel>
</Grid>
<Grid x:Name"grid2">
 <!-- Other Content -->
</Grid>

</Grid>

Re: Setting Focus Directly to Expander Button

Posted: 04 Oct 2024, 12:46
by sfernandez
Hello, if you have some visual states that handle the visibility of some parts of the UI, and you want to change the focus after going to any of those states, you can wait for the state animation to finish and then set the focus. See this example:
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
  xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions"
  x:Name="rootGrid">
  <b:Interaction.Triggers>
    <b:EventTrigger EventName="Loaded">
      <b:GoToStateAction TargetName="rootGrid" StateName="ShowGrid1"/>
    </b:EventTrigger>
    <b:StoryboardCompletedTrigger Storyboard="{Binding Storyboard, ElementName=ShowGrid1}">
      <noesis:SetFocusAction TargetName="expand"/>
      <noesis:MoveFocusAction Direction="Down"/>
    </b:StoryboardCompletedTrigger>
    <b:StoryboardCompletedTrigger Storyboard="{Binding Storyboard, ElementName=ShowGrid2}">
      <noesis:SetFocusAction TargetName="btn"/>
    </b:StoryboardCompletedTrigger>
  </b:Interaction.Triggers>
  <VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="ScreenStates">
      <VisualState x:Name="ShowGrid1">
        <Storyboard>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="grid1" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
          </ObjectAnimationUsingKeyFrames>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="grid2" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
          </ObjectAnimationUsingKeyFrames>
        </Storyboard>
      </VisualState>
      <VisualState x:Name="ShowGrid2">
        <Storyboard>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="grid2" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
          </ObjectAnimationUsingKeyFrames>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="grid1" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
          </ObjectAnimationUsingKeyFrames>
        </Storyboard>
      </VisualState>
    </VisualStateGroup>
  </VisualStateManager.VisualStateGroups>
  <Grid x:Name="grid1">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
      <Button Content="ButtonA"/>
      <Expander x:Name="expand" Header="Expander" IsExpanded="True">
        <Ellipse Height="200" Fill="Red"/>
      </Expander>
      <Button Content="ButtonB">
        <b:Interaction.Triggers>
          <b:EventTrigger EventName="Click">
            <b:GoToStateAction TargetName="rootGrid" StateName="ShowGrid2"/>
          </b:EventTrigger>
        </b:Interaction.Triggers>
      </Button>
    </StackPanel>
  </Grid>
  <Grid x:Name="grid2">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
      <Button x:Name="btn" Content="ButtonC"/>
      <Button Content="ButtonD"/>
      <Button Content="ButtonE">
        <b:Interaction.Triggers>
          <b:EventTrigger EventName="Click">
            <b:GoToStateAction TargetName="rootGrid" StateName="ShowGrid1"/>
          </b:EventTrigger>
        </b:Interaction.Triggers>
      </Button>
    </StackPanel>
  </Grid>
</Grid>
Is this what you're looking for?

Re: Setting Focus Directly to Expander Button

Posted: 04 Oct 2024, 17:15
by aivanov
Yes! This is exactly it! Focus navigation seems to be working perfectly now.

Thank you!!

Re: Setting Focus Directly to Expander Button

Posted: 05 Oct 2024, 00:23
by jsantos
Great! Marking this as solved