steveh
Topic Author
Posts: 42
Joined: 07 Oct 2019, 12:50

Focusing ListBoxItem after ItemCollection changed

11 Mar 2020, 19:36

Hi guys, I'll try my best to explain my problem.

I have derived a new UserControl from a ListBox (Let's call it "CustomListBox"), and the ItemContainerGenerator for this type generates a CustomListBoxItem. I have overridden the OnSelectionChanged virtual to focus the item when the selection changes:
void CustomListBox::OnSelectionChanged(const SelectionChangedEventArgs& args)
{
	ParentClass::OnSelectionChanged(args);
	
	ItemCollection *pItemContainer = GetItems();
	if (nullptr != pItemContainer)
	{
		if (GetSelectedIndex() >= 0 && GetSelectedIndex() < pItemContainer->Count())
		{
			BaseComponent *pComponent = pItemContainer->GetComponent(GetSelectedIndex()).GetPtr();
			if (nullptr != pComponent)
			{
				UIElement *pElement = IsItemItsOwnContainerOverride(pComponent) ?
					static_cast<UIElement*>(pComponent) :
					DynamicCast<UIElement*>(GetItemContainerGenerator()->ContainerFromItem(pComponent));
				if (nullptr != pElement)
				{
					pElement->Focus();
				}
			}
		}
	}
}
In the XAML, within the DataTemplate which is used for the ContentControl for the CustomListBoxItem, I have a DataTrigger which listens out for this focus property, and it plays a focus / unfocus storyboard:
                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentControl}}, Path=IsFocused}" Value="True">
                        <DataTrigger.EnterActions>
                            <StopStoryboard BeginStoryboardName="Off" />
                            <BeginStoryboard x:Name="On" Storyboard="{StaticResource ResourceKey=Custom_Highlight_On}"/>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <StopStoryboard BeginStoryboardName="On" />
                            <BeginStoryboard x:Name="Off" Storyboard="{StaticResource ResourceKey=Custom_Highlight_Off}"/>
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                </DataTemplate.Triggers>
The CustomListBox has items data bound via the ItemsSource property:
                    <local:CustomListBox x:Name="MyCustomListBoxInstance" ItemsSource="{Binding CustomListBoxSource}"/>
This works great. In C++, when I receive an OnInitialised I call SetSelectedIndex on the CustomListBox and it focuses the first element, taking focus off all other controls and ensures that we are only ever focusing items within this listbox which is exactly what I want.

However, at some point the data behind the binding changes:
class DataContext : public NotifyChangedPropertyBase
{
	Ptr<ObservableCollection<SomeClass>> CustomListBoxSource;	
};

NS_IMPLEMENT_REFLECTION(DataContext)
{
    NsMeta<TyepeId>("MyNamespace.DataContext");
    NsProp("CustomListBoxSource", &SelfClass::CustomListBoxSource);
}
I populate the data initially and then I clear the contents and rebuild it with some new data:
void RebuildData(DataContext &rContext)
{
    rContext.CustomListBoxSource->Clear();
    rContext.CustomListBoxSource->Add(MakePtr<SomeClass>(Args...));
    // ...etc
}
I want to be able to focus the first element after the ItemContainerGenerator has finished generating the CustomListBoxItem instances, but I can't seem to find a way to do it at the right time.

Things I have tried:
  • Assign a multidatatrigger to the style of CustomListBox. The conditions are bound to the "HasItems" and "SelectedItem" property. If the HasItems is true and IsSelected is false, it runs a setter to set the SelectedIndex to 0:
    <Style.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding IsSelected}" Value="False" />
                <Condition Binding="{Binding HasItems}" Value="True" />
            </MultiDataTrigger.Conditions>
            <MultiDataTrigger.Setters>
                 <Setter Property="SelectedIndex" Value="0" />
            </MultiDataTrigger.Setters>
        </MultiDataTrigger>
    </Style.Triggers>
    
    This didn't work... I breakpointed the multidatatrigger, it invalidated the setters but it never executed my code after the item had been created.
  • Override the ListBox::OnItemsChanged event, ensurring that I call SetSelectedItem if we have items in the itemscollection but don't have a selectedindex:
    /*virtual*/ void CustomListBox::OnItemsChanged(const Noesis::NotifyCollectionChangedEventArgs& args) /*override;*/ {
    	ParentClass::OnItemsChanged(args);
    	if (args.action == NotifyCollectionChangedAction_Add || args.action == NotifyCollectionChangedAction_Replace) {
    		if (GetSelectedIndex() == -1) {
    			if (GetHasItems()) {
    				SetSelectedIndex(0);
    			}
    		}
    	}
    }
    
    This also didn't work. Despite selecting the item and focusing the element correctly, the storyboard never played. I'm wondering if it focused the element too early?
So a couple of questions:
  1. What is the flow for item generation? When can I guarantee that the item container has generated the itemscontainers and they're good to be used?
  2. Does this sound like a reasonable approach? I feel like I'm missing something. I seem to be fighting the "WPF" way, is there a correct way to do what I want?
Any clarification / insight would be much appreciated.

Many thanks in advance,

-Steven
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Focusing ListBoxItem after ItemCollection changed

12 Mar 2020, 21:41

Hi,

The goal here as I understand is whenever an item gets selected it also gets focus; just remember that selecting an item using mouse or keyboard automatically focuses the item, but I guess you want also to focus the item if it gets selected by code/bindings, right?.

I think this can be simplified a lot by using a plain ListBox/ListBoxItem with interactivity triggers/actions. For example, ListBoxItem Style can have a trigger for the IsSelected property to invoke a SetFocusAction (we did something similar in our QuestLog sample but for the MouseEnter event):
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
  <Setter Property="Template" Value="{StaticResource ListBoxItemTemplate}"/>
  <Setter Property="noesis:StyleInteraction.Triggers">
    <Setter.Value>
      <noesis:StyleTriggerCollection>
        <ei:DataTrigger
            Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
            Value="True">
          <noesis:SetFocusAction/>
        </ei:DataTrigger>
      </noesis:StyleTriggerCollection>
    </Setter.Value>
  </Setter>
 </Style>
About the animations you want to trigger, can't they just be part of the ListBoxItem template?
They can be storyboards fired on Enter/Exit actions of some trigger, or just part of the Focused/Unfocused visual states.

To focus the first element in the list after the collection is being cleared and populated again, you just need to set the SelectedItem of the ListBox to that first element (becuase when the container gets generated, the triggers will automatically focus the ListBoxItem). If the ListBox.SelectedItem property is already bound to some property in the ViewModel then it is trivial to accomplish that:
myObservableCollection->Clear();
myObservableCollection->Add(...); // etc.
SetMySelectedItem(myObservableCollection->Get(0));
Hope this helps.
 
freddukes
Posts: 1
Joined: 25 Feb 2019, 13:24

Re: Focusing ListBoxItem after ItemCollection changed

12 Mar 2020, 22:59

- Oops, logged into an old account reposting in a minute on my actual account :)
 
steveh
Topic Author
Posts: 42
Joined: 07 Oct 2019, 12:50

Re: Focusing ListBoxItem after ItemCollection changed

12 Mar 2020, 23:03

Apologies for the previous post, I was logged into an old account from my home PC. I edited my post and posting the initial response here on the correct account now :)

----

Thank you for the response.
The goal here as I understand is whenever an item gets selected it also gets focus; just remember that selecting an item using mouse or keyboard automatically focuses the item, but I guess you want also to focus the item if it gets selected by code/bindings, right?.
Correct. There are lots of things in code which change the focus directly from code. Essentially I want the selected index and focus to be in sync at all points when this customlistbox is on screen.
I think this can be simplified a lot by using a plain ListBox/ListBoxItem with interactivity triggers/actions. For example, ListBoxItem Style can have a trigger for the IsSelected property to invoke a SetFocusAction (we did something similar in our QuestLog sample but for the MouseEnter event):
Thank you for the code example, I'll give this a try, it makes sense.
About the animations you want to trigger, can't they just be part of the ListBoxItem template?
They can be storyboards fired on Enter/Exit actions of some trigger, or just part of the Focused/Unfocused visual states.
Unfortunately the animation has to be part of the datatemplate. Our listboxitem is a container, but we want the elements created by the DataTemplate to animate when the ListBoxItem is focused itself. e.g. we have this in the hierarchy:
<listboxitem>
    <contentpresenter>
        <grid>
            <textblock text="blah" foreground="red" />
        </grid>
    </contentpresenter>
</listboxitem>
And then the focus animation actually modifies properties on the textblock. Obviously our exact example is a far more complex hierarchy, but essentially we have a structure defined in a datatemplate which wants to animate when the parent listbox is focused. In our exact case, we have a ControlTemplate which makes our CustomListBoxItem a generic button, and inside that button we can either have vector paths, texts, bitmap images which pull from live off-screen render targets etc. The artist wants to actually animate the inner content for each bespoke type of data we have (e.g. text animates differently to the vector paths). Since the content is driven via a DataTemplate, the storyboard has to be defined in the DataTemplate as well. However, we do have a trigger defined in the datatemplate, and the binding is set to the IsSelected property on the CustomListBoxItem which is part of the hierarchy. The specific XAML is as follows:
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Shared:CustomListBoxItem}}, Path=IsSelected}" Value="True">
                <DataTrigger.EnterActions>
                    <BeginStoryboard Storyboard="{StaticResource ResourceKey=PopUp_Highlight_On}"/>
                    <BeginStoryboard x:Name="Highlight_Idle" Storyboard="{StaticResource ResourceKey=PopUp_Highlilght_Idle}"/>
                </DataTrigger.EnterActions>
                <DataTrigger.ExitActions>
                    <StopStoryboard BeginStoryboardName="Highlight_Idle" />
                    <BeginStoryboard Storyboard="{StaticResource ResourceKey=PopUp_Highlight_Off}"/>
                </DataTrigger.ExitActions>
            </DataTrigger>
        </DataTemplate.Triggers>
To focus the first element in the list after the collection is being cleared and populated again, you just need to set the SelectedItem of the ListBox to that first element (becuase when the container gets generated, the triggers will automatically focus the ListBoxItem). If the ListBox.SelectedItem property is already bound to some property in the ViewModel then it is trivial to accomplish that:
I've tried to do precisely this, and it doesn't seem to work. However, I was looking through the patch notes earlier and I saw this snippet from 2.2.3:
Fixed Selector resetting SelectedIndex and SelectedItem bound properties on DataContext changes.
We're currently on version 2.2.1. Do you think this issue may be causing some of my other problems? We also experience a few other issues like the following:
Enhancement No more binding errors when clearing or changing ObservableCollection (#1572).
Perhaps it's time for me to upgrade to 2.2.6. I was hoping to avoid doing that before our near deadlines and I don't want to risk introducing new bugs, but it looks like there's a bunch of bug fixes which have gone in which would benefit us.

Many thanks,

-Steven
 
steveh
Topic Author
Posts: 42
Joined: 07 Oct 2019, 12:50

Re: Focusing ListBoxItem after ItemCollection changed

13 Mar 2020, 00:40

Hi there, just a quick update. I tried to add the style trigger and it hit an assert. Essentially the Selector::OnSelectedIndexChanged sets the IsUpdating scope flag when the selected index has changed. This ultimately causes the style trigger to execute which sets focus. The focus calls ListBoxItem::OnGotFocus which attempts to call Selector::InternalSelectRange, which asserts because the instance already has the flag set.
>>>	MyExe.exe!Noesis::Selector::InternalSelectRange(int start, int end) Line 660	C++
 	MyExe.exe!Noesis::ListBox::SingleSelection(Noesis::ListBoxItem * lbi) Line 146	C++
 	MyExe.exe!Noesis::ListBox::ItemClicked(Noesis::ListBoxItem * lbi, bool toggleSelection, bool shiftKeyDown, bool ctrlKeyDown) Line 305	C++
 	MyExe.exe!Noesis::ListBox::ItemClicked(Noesis::ListBoxItem * lbi, bool toggleSelection) Line 284	C++
 	MyExe.exe!Noesis::ListBoxItem::OnGotFocus(const Noesis::RoutedEventArgs & e) Line 195	C++
 	MyExe.exe!Noesis::UIElement::OnIsFocusedChanged(const Noesis::DependencyPropertyChangedEventArgs & e) Line 2807	C++
 	MyExe.exe!Noesis::UIElement::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 2003	C++
 	MyExe.exe!Noesis::FrameworkElement::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 1636	C++
 	MyExe.exe!Noesis::Control::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 442	C++
 	MyExe.exe!Noesis::ContentControl::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 146	C++
 	MyExe.exe!Noesis::ListBoxItem::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args_) Line 120	C++
 	MyExe.exe!Noesis::DependencyObject::NotifyPropertyChanged(const Noesis::DependencyProperty * dp, Noesis::StoredValue * storedValue, const void * oldValue, const void * newValue, bool valueChanged, bool isBaseComponent, const Noesis::PropertyMetadata * metadata) Line 1155	C++
 	MyExe.exe!Noesis::DependencyObject::InternalSetValue(const Noesis::DependencyProperty * dp, void * oldValue, const void * newValue, void * coercedValue, unsigned char priority, Noesis::Expression * newExpression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination, bool isBaseComponent) Line 797	C++
 	MyExe.exe!Noesis::DependencyObject::SetValue_<bool>(Noesis::Int2Type<0> __formal, const Noesis::DependencyProperty * dp, bool value, Noesis::Value::Destination destination) Line 168	C++
 	MyExe.exe!Noesis::DependencyObject::SetReadOnlyProperty<bool>(const Noesis::DependencyProperty * dp, bool value) Line 62	C++
 	MyExe.exe!Noesis::Keyboard::Focus(Noesis::UIElement * element, bool askOld, bool askNew, bool canBeNull) Line 442	C++
 	MyExe.exe!Noesis::Keyboard::Focus(Noesis::UIElement * element) Line 149	C++
 	MyExe.exe!Noesis::UIElement::Focus() Line 453	C++
 	MyExe.exe!NoesisApp::SetFocusAction::Invoke(Noesis::BaseComponent * __formal) Line 30	C++
 	MyExe.exe!NoesisApp::TriggerAction::CallInvoke(Noesis::BaseComponent * parameter) Line 47	C++
 	MyExe.exe!NoesisApp::TriggerBase::InvokeActions(Noesis::BaseComponent * parameter) Line 76	C++
 	MyExe.exe!NoesisApp::DataTrigger::Evaluate() Line 99	C++
 	MyExe.exe!NoesisApp::DataTrigger::EvaluateBindingChange() Line 81	C++
 	MyExe.exe!NoesisApp::PropertyChangedTrigger::OnBindingChanged(Noesis::DependencyObject * d, const Noesis::DependencyPropertyChangedEventArgs & __formal) Line 70	C++
 	MyExe.exe!Noesis::Delegate<void __cdecl(Noesis::DependencyObject *,Noesis::DependencyPropertyChangedEventArgs const &)>::FreeFuncStub<void (__cdecl*)(Noesis::DependencyObject *,Noesis::DependencyPropertyChangedEventArgs const &)>::Invoke(Noesis::DependencyObject * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 391	C++
 	MyExe.exe!Noesis::Delegate<void __cdecl(Noesis::DependencyObject *,Noesis::DependencyPropertyChangedEventArgs const &)>::operator()(Noesis::DependencyObject * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 155	C++
 	MyExe.exe!Noesis::DependencyObject::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 513	C++
 	MyExe.exe!Noesis::Freezable::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & e) Line 237	C++
 	MyExe.exe!Noesis::DependencyObject::NotifyPropertyChanged(const Noesis::DependencyProperty * dp, Noesis::StoredValue * storedValue, const void * oldValue, const void * newValue, bool valueChanged, bool isBaseComponent, const Noesis::PropertyMetadata * metadata) Line 1155	C++
 	MyExe.exe!Noesis::DependencyObject::InternalSetValue(const Noesis::DependencyProperty * dp, void * oldValue, const void * newValue, void * coercedValue, unsigned char priority, Noesis::Expression * newExpression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination, bool isBaseComponent) Line 797	C++
 	MyExe.exe!Noesis::ValueStorageManagerImpl<Noesis::Ptr<Noesis::BaseComponent> >::SetValue(Noesis::DependencyObject * dob, const Noesis::DependencyProperty * dp, Noesis::BaseComponent * value, unsigned char priority, Noesis::Expression * expression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination) Line 234	C++
 	MyExe.exe!Noesis::ValueStorageManager::SetValueObject(Noesis::DependencyObject * dob, const Noesis::DependencyProperty * dp, Noesis::BaseComponent * value, unsigned char priority, Noesis::Expression * expression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination) Line 40	C++
 	MyExe.exe!Noesis::DependencyProperty::SetValueObject(Noesis::DependencyObject * obj, Noesis::BaseComponent * value, unsigned char priority, Noesis::Expression * expression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination) Line 210	C++
 	MyExe.exe!Noesis::DependencyObject::InternalSetExpression(const Noesis::DependencyProperty * dp, Noesis::Expression * newExpression, unsigned char priority) Line 599	C++
 	MyExe.exe!Noesis::DependencyObject::InternalInvalidateProperty(const Noesis::DependencyProperty * dp, unsigned char priority) Line 981	C++
 	MyExe.exe!Noesis::DependencyObject::InvalidateProperty(const Noesis::DependencyProperty * dp, unsigned char priority) Line 218	C++
 	MyExe.exe!Noesis::BindingExpression::OnDependencyPropertyChanged(Noesis::BaseComponent * sender, const Noesis::DependencyPropertyChangedEventArgs & args) Line 1747	C++
 	MyExe.exe!Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::MemberFuncStub<Noesis::BindingExpression,void (__cdecl Noesis::BindingExpression::*)(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::Invoke(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 465	C++
 	MyExe.exe!Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::operator()(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 155	C++
 	MyExe.exe!Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::MultiDelegate::Invoke(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 577	C++
 	MyExe.exe!Noesis::Delegate<void __cdecl(Noesis::BaseComponent *,Noesis::DependencyPropertyChangedEventArgs const &)>::operator()(Noesis::BaseComponent * <args_0>, const Noesis::DependencyPropertyChangedEventArgs & <args_1>) Line 155	C++
 	MyExe.exe!Noesis::DependencyObject::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 514	C++
 	MyExe.exe!Noesis::Visual::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 766	C++
 	MyExe.exe!Noesis::UIElement::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 1887	C++
 	MyExe.exe!Noesis::FrameworkElement::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 1636	C++
 	MyExe.exe!Noesis::Control::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 442	C++
 	MyExe.exe!Noesis::ContentControl::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 146	C++
 	MyExe.exe!Noesis::ListBoxItem::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args_) Line 120	C++
 	MyExe.exe!Noesis::DependencyObject::NotifyPropertyChanged(const Noesis::DependencyProperty * dp, Noesis::StoredValue * storedValue, const void * oldValue, const void * newValue, bool valueChanged, bool isBaseComponent, const Noesis::PropertyMetadata * metadata) Line 1155	C++
 	MyExe.exe!Noesis::DependencyObject::InternalSetValue(const Noesis::DependencyProperty * dp, void * oldValue, const void * newValue, void * coercedValue, unsigned char priority, Noesis::Expression * newExpression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination, bool isBaseComponent) Line 797	C++
 	MyExe.exe!Noesis::DependencyObject::SetValue_<bool>(Noesis::Int2Type<0> __formal, const Noesis::DependencyProperty * dp, bool value, Noesis::Value::Destination destination) Line 168	C++
 	MyExe.exe!Noesis::DependencyObject::SetValue<bool>(const Noesis::DependencyProperty * dp, bool value) Line 47	C++
 	MyExe.exe!Noesis::Selector::SetIsSelected(Noesis::DependencyObject * element, bool value) Line 81	C++
 	MyExe.exe!Noesis::Selector::OnItemSelected(Noesis::SelectionChangedEventArgs & e, Noesis::BaseComponent * item, int index) Line 1003	C++
 	MyExe.exe!Noesis::Selector::UpdateSingleSelectedList(Noesis::SelectionChangedEventArgs & e, Noesis::BaseComponent * selectedItem, int selectedIndex) Line 913	C++
>>> MyExe.exe!Noesis::Selector::OnSelectedIndexChanged(int oldIndex, int newIndex) Line 1048	C++
 	MyExe.exe!Noesis::Selector::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 559	C++
 	MyExe.exe!Noesis::ListBox::OnPropertyChanged(const Noesis::DependencyPropertyChangedEventArgs & args) Line 71	C++
 	MyExe.exe!Noesis::DependencyObject::NotifyPropertyChanged(const Noesis::DependencyProperty * dp, Noesis::StoredValue * storedValue, const void * oldValue, const void * newValue, bool valueChanged, bool isBaseComponent, const Noesis::PropertyMetadata * metadata) Line 1155	C++
 	MyExe.exe!Noesis::DependencyObject::InternalSetValue(const Noesis::DependencyProperty * dp, void * oldValue, const void * newValue, void * coercedValue, unsigned char priority, Noesis::Expression * newExpression, const Noesis::PropertyMetadata * metadata, Noesis::Value::Destination destination, bool isBaseComponent) Line 797	C++
 	MyExe.exe!Noesis::DependencyObject::SetValue_<int>(Noesis::Int2Type<0> __formal, const Noesis::DependencyProperty * dp, int value, Noesis::Value::Destination destination) Line 168	C++
 	MyExe.exe!Noesis::DependencyObject::SetValue<int>(const Noesis::DependencyProperty * dp, int value) Line 47	C++
 	MyExe.exe!Noesis::Selector::SetSelectedIndex(int index) Line 112	C++
I've highlighted the 2 functions where the scope flag is attempting to be set by prefixing the signature with ">>>".

-Steven
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Focusing ListBoxItem after ItemCollection changed

18 Mar 2020, 17:19

Since the content is driven via a DataTemplate, the storyboard has to be defined in the DataTemplate as well
Firing the animations from the DataTemplate should work fine too. Just with the xaml you attached using DataTriggers listening to the IsSelected property of the ListBoxItem ancestor.
Do you have any problems with that?
I've tried to do precisely this, and it doesn't seem to work
I've been thinking, maybe what is happening is that the selected item corresponds to a ListBoxItem that is not visible and virtualized, so the container is not yet generated. If that is the problem you can use ListBox.ScrollIntoView() that will scroll the list to show the item, generating the corresponding container and it will get focused then.
I tried to add the style trigger and it hit an assert
I created a ticket (#1641) in our bugtracker to investigate this.
 
steveh
Topic Author
Posts: 42
Joined: 07 Oct 2019, 12:50

Re: Focusing ListBoxItem after ItemCollection changed

18 Mar 2020, 18:24

Firing the animations from the DataTemplate should work fine too. Just with the xaml you attached using DataTriggers listening to the IsSelected property of the ListBoxItem ancestor.
Do you have any problems with that?
No, this part works great. The animations always trigger when I'd expect.
I've been thinking, maybe what is happening is that the selected item corresponds to a ListBoxItem that is not visible and virtualized, so the container is not yet generated. If that is the problem you can use ListBox.ScrollIntoView() that will scroll the list to show the item, generating the corresponding container and it will get focused then.
After a bit more testing, it's only fails if I call SetSelectedIndex prior to the listbox being generated. The reason being is it does indeed call the SetSelectedIndex, but because the element is not yet generated it is unable to focus it. I added this bit of code and it fixed my problem:
/*virtual*/ void CustomListBox::OnContainersGenerated() /*override*/
{
	ParentClass::OnContainersGenerated();

	if (GetIsVisible())
	{
		s32 nDesiredIdx = 0;
		if (GetSelectedIndex() != -1)
		{
			nDesiredIdx = GetSelectedIndex();
		}
		ItemCollection *pItemContainer = GetItems();
		if (nullptr != pItemContainer)
		{
			if (nDesiredIdx >= 0 && nDesiredIdx < pItemContainer->Count())
			{
				BaseComponent *pComponent = pItemContainer->GetComponent(nDesiredIdx).GetPtr();
				if (nullptr != pComponent)
				{
					UIElement *pElement = IsItemItsOwnContainerOverride(pComponent) ?
						static_cast<UIElement*>(pComponent) :
						DynamicCast<UIElement*>(GetItemContainerGenerator()->ContainerFromItem(pComponent));
					if (nullptr != pElement)
					{
						if (!pElement->GetIsFocused())
						{
							pElement->Focus();
						}
					}
				}
			}
		}
	}
}
This means if we call SetSelectedIndex before the containers have been generated, it will focus it after they have been created.
I created a ticket (#1641) in our bugtracker to investigate this.
Much appreciated! Thank you

-Steven

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot], Google [Bot] and 19 guests