Custom ItemsControl to maintain focused index
Posted: 16 Nov 2023, 17:11
Hello, trying to create a custom items control that maintains the index of focus when an item is replaced. For our use case we have an ItemsControl that contains a number of content cards. While we are retrieving the information to create the proper cards in the ItemsControl, it is initialized with a list of loading cards which are then replaced by the proper cards. We want to maintain the index of focus once the proper cards load in - ex: if the second loading card is in focus, when it is replaced that replacement card which is still the second card in the items control is focused. All cards inherit from the same view model class, so we have SpecificCardTypeOneViewModel, SpecificCardTypeTwoViewModel, etc. which all inherit from BaseCardViewModel. The ItemsSource for the ItemsControl is an observable collection of BaseCardViewModel.
Attempted to achieve this by overloading OnItemsChanged and OnPreviewGotKeyboardFocus as follows:
However, this only works when the card that is being replaced is the same subclass as the card that is replacing it, so they both are type SpecificCardTypeOneViewModel. If we try to replace a SpecificCardTypeOneViewModel item with a SpecificCardTypeTwoViewModel we get into the OnItemsChanged override and element->Focus() returns false but element->GetIsKeyboardFocusWithin() returns true and so we return but the ItemsControl loses focus.
Is there something different happening to the container in this case? What could be causing it to not focus correctly?
Attempted to achieve this by overloading OnItemsChanged and OnPreviewGotKeyboardFocus as follows:
Code: Select all
void CustomItemsControl::OnItemsChanged(const Noesis::NotifyCollectionChangedEventArgs& args)
{
switch(args.action)
{
case Noesis::NotifyCollectionChangedAction_Replace:
{
m_replacingElement = true;
if (m_focusedIndex == args.newIndex)
{
Noesis::ItemCollection* items = GetItems();
Noesis::ItemContainerGenerator* generator = GetItemContainerGenerator();
int index = args.newIndex;
OnBringItemIntoView(items->GetItemAt(index));
Noesis::UIElement* element = Noesis::DynamicCast<Noesis::UIElement*>(generator->ContainerFromIndex(index));
if (element != 0)
{
if (element->Focus() || element->GetIsKeyboardFocusWithin())
{
return;
}
// container is not focusable, try to focus its contents
Noesis::TraversalRequest request = { Noesis::FocusNavigationDirection_First, false };
element->MoveFocus(request);
}
}
}
}
}
Noesis::Vector<Noesis::UIElement*> CustomItemsControl::FindFocusableElements(Noesis::Visual* root)
{
Noesis::Vector<Noesis::UIElement*> children;
if (root == nullptr)
{
return children;
}
const uint32_t visualChildCount = Noesis::VisualTreeHelper::GetChildrenCount(root);
for (uint32_t n = 0; n < visualChildCount; n++) {
Noesis::Visual* child = Noesis::VisualTreeHelper::GetChild(root, n);
Noesis::UIElement* element = Noesis::DynamicCast<Noesis::UIElement*>(child);
// If not enabled or visible, don't even bother with it
if (!element->GetIsEnabled() || !element->GetIsVisible())
continue;
// If it's focusable, that's what we want
if (element->GetFocusable()) {
children.PushBack(element);
}
// Otherwise, keep searching downward
else {
auto focusableChildren = FindFocusableElements(child);
children.Insert(children.End(), focusableChildren.Begin(), focusableChildren.End());
}
}
return children;
}
void CustomItemsControl::OnPreviewGotKeyboardFocus(const Noesis::KeyboardFocusChangedEventArgs& args)
{
if (!m_replacingElement)
{
auto focusableElements = FindFocusableElements(this);
int numFocusableElements = focusableElements.Size();
for (int i = (numFocusableElements - 1); i >= 0; i--)
{
if (focusableElements.Back() == args.newFocus)
{
m_focusedIndex = i;
return;
}
focusableElements.PopBack();
}
}
else
{
m_replacingElement = false;
}
}
Is there something different happening to the container in this case? What could be causing it to not focus correctly?