maxnt
Topic Author
Posts: 3
Joined: 28 Jun 2021, 11:18

Help setting focus to element in ItemsControl

10 Feb 2025, 18:41

Hi Noesis fokes,

I'm trying to set the default focus to the first element of an ItemControl, on load and if the items change. However, my below code doesn't work.
element->Focus() and element->MoveFocus(request) return false. The end result is no items have focus.

Please advise on what I'm doing wrong.

Thanks in advanced!
MAUserControl::MAUserControl() : UserControl()
{
    Loaded() += Noesis::MakeDelegate(this, &MAUserControl::OnLoaded);
}

void MAUserControl::OnLoaded(Noesis::BaseComponent*, const Noesis::RoutedEventArgs&)
{
    const char* focus_name = GetValue<Noesis::String>(DefaultFocusElementNameProperty).Str();

    if (focus_name != nullptr)
    {
        Noesis::BaseComponent* focus_child = FindName(focus_name);

        if (focus_child != nullptr)
        {
            Noesis::ItemsControl* item_control = Noesis::DynamicCast<Noesis::ItemsControl*>(focus_child);

            if (item_control != nullptr)
            {
                Noesis::ItemCollection* items = item_control->GetItems();
                if (items != nullptr)
                {
                    items->CollectionChanged() += Noesis::MakeDelegate(this, &MAUserControl::OnItemsChaged);
                }
            }
        }
    }

    SetDefaultFocus();
}

void MAUserControl::OnItemsChaged(Noesis::BaseComponent*, const Noesis::NotifyCollectionChangedEventArgs&)
{
    SetDefaultFocus();
}

void MAUserControl::SetDefaultFocus()
{
    const char* focus_name = GetValue<Noesis::String>(DefaultFocusElementNameProperty).Str();

    if (focus_name != nullptr)
    {
        Noesis::BaseComponent* focus_child = FindName(focus_name);

        if (focus_child != nullptr)
        {

            Noesis::ItemsControl* item_control = Noesis::DynamicCast<Noesis::ItemsControl*>(focus_child);

            if (item_control != nullptr && item_control->GetHasItems())
            {
                Noesis::ItemContainerGenerator* generator = item_control->GetItemContainerGenerator();
                Noesis::UIElement* element = Noesis::DynamicCast<Noesis::UIElement*>(generator->ContainerFromIndex(0));

                if (element != nullptr)
                {
                    if (element->Focus() || element->GetIsKeyboardFocusWithin())
                    {
                        return;
                    }

                    // container is not focusable, try to focus its contents
                    Noesis::TraversalRequest request = { Noesis::FocusNavigationDirection_First, false };
                    if (element->MoveFocus(request))
                    {
                        return;
                    }

                    Noesis::DependencyObject* focusScope = Noesis::FocusManager::GetFocusScope(element);
                    if (focusScope != nullptr && focusScope != element)
                    {
                        Noesis::FocusManager::SetFocusedElement(focusScope, element);
                        return;
                    }
                }
            }
        }
    }
}
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Button Height="50" Margin="0 0 0 5"
                DockPanel.Dock="Bottom"
                Style="{StaticResource ButtonStyleRectangular_Resize}"
                Content="{Binding PlayerChoiceText}">
        </Button>
    </DataTemplate>
</ItemsControl.ItemTemplate>
Update: I have found that the above works if I add a very small timer to set the focus after a few ms. Any thought on how I can get round this hack would be great :D
 
maxnt
Topic Author
Posts: 3
Joined: 28 Jun 2021, 11:18

Re: Help setting focus to element in ItemsControl

13 Feb 2025, 23:49

I have found the solution, posting my findings for others :)

ItemControls go through a few stages before its children can accept a focus. Turns out the OnItemsChanged changed event is purely a data change and is too early, the visual containers have not been built yet.

A rough order of execution when changing the ItemsControl Items Source

ItemsControl->OnItemsChanged
ItemsControl->ContentGenerator->OnItemsChanged
ItemsControl->ContentGenerator->BatchGeneration
ItemsControl->ContentGenerator->OnStatusChanged:GeneratorStatus_GeneratingContainers
ItemsControl->ContentGenerator->OnStatusChanged:GeneratorStatus_ContainersGenerated
ItemsControl->ContentGenerator->ItemContainer->OnInitialised
ItemsControl->ContentGenerator->ItemContainer->OnLoaded <--- at this point I found I can reliably set focus

Here is a snippet of my custom focus Dependency object
void MAFocusDependencyObject::FocusOnItemChange(Noesis::FrameworkElement* element, bool attach)
{
    Noesis::ItemsControl* item_control = Noesis::DynamicCast<Noesis::ItemsControl*>(element);
    if (item_control != nullptr)
    {
        Noesis::ItemContainerGenerator* generator = item_control->GetItemContainerGenerator();
        generator->StatusChanged() -= Noesis::MakeDelegate(this, &MAFocusDependencyObject::OnCollectionGenStatusChanged);

        if (attach)
        {
            generator->StatusChanged() += Noesis::MakeDelegate(this, &MAFocusDependencyObject::OnCollectionGenStatusChanged);
        }
    }
}

void MAFocusDependencyObject::OnCollectionGenStatusChanged(Noesis::BaseComponent* component, const Noesis::EventArgs&)
{
    Noesis::ItemContainerGenerator* generator = Noesis::DynamicCast <Noesis::ItemContainerGenerator*>(component);
    if (generator->GetStatus() == Noesis::GeneratorStatus::GeneratorStatus_ContainersGenerated)
    {
        Noesis::FrameworkElement* item_focus_child = Noesis::DynamicCast<Noesis::FrameworkElement*>(generator->ContainerFromIndex(0));
        if (item_focus_child != nullptr)
        {
            item_focus_child->Loaded() -= Noesis::MakeDelegate(this, &MAFocusDependencyObject::OnCollectionItemLoaded);
            item_focus_child->Loaded() += Noesis::MakeDelegate(this, &MAFocusDependencyObject::OnCollectionItemLoaded);
        }
    }
}

void MAFocusDependencyObject::OnCollectionItemLoaded(Noesis::BaseComponent* component, const Noesis::RoutedEventArgs&)
{
    Noesis::FrameworkElement* focus_element = Noesis::DynamicCast<Noesis::FrameworkElement*>(component);
    if (focus_element != nullptr)
    {
        SetFocus(focus_element);
    }
}
 
User avatar
sfernandez
Site Admin
Posts: 3222
Joined: 22 Dec 2011, 19:20

Re: Help setting focus to element in ItemsControl

17 Feb 2025, 19:13

Thanks for sharing your solution.

As you described, a UI element must be connected to the View and visible/enabled to receive focus. Waiting to the Loaded event is a good place for this to be true.

Another option would be to hook to the LayoutUpdated event instead when the ItemsSource changed.

Who is online

Users browsing this forum: Google [Bot], Semrush [Bot] and 4 guests