8

I have an ItemsControl in my View, that is bound to an ObservableCollection from ViewModel. The collection is filled, and afterwards an event from VM to view is raised (think search results and SearchFinished event).

I would like to move keyboard focus to the first item in an ItemsControl, but when I do it in View's code-behind when handling SearchFinished, the items are not yet rendered (the collection is filled already, but wpf's rendering is asynchronous and didn't happen yet), so there is nothing to focus (Focus() needs to have the items' visual tree already constructed).

I wanted to do (myItemsControl.ItemContainerGenerator.ContainerFromIndex(0) as UIElement).Focus();, but as the 0th item is not yet loaded, ContainerFromIndex(0) returns null.

I tried delaying it with Dispatcher.BeginInvoke... with low priority, but that is dependent on exact timing and usually doesn't work.

How can I wait until the first item in ItemsControl is Loaded?

Tomáš Kafka
  • 4,405
  • 6
  • 39
  • 52

2 Answers2

9

You can use the ItemContainerGenerator.StatusChanged event, and then check its Status property. If the Status == GeneratorStatus.ContainersGenerated, then you can safely get the first container.

Abe Heidebrecht
  • 30,090
  • 7
  • 62
  • 66
0

In my app I found that ItemContainerGenerator.StatusChanged with Status == GeneratorStatus.ContainersGenerated did not coincide with the moment when the screen was visibly rendered. There was in fact a considerable delay when a large # of items were being rendered; this delay was after ContainersGenerated was reported.

Instead, I found that waiting for the ItemsPresenter.Loaded event worked better. This event does seem to coincide much more closely with the completion of rendering and also non-busy status of the UI.

Since the ItemsPresenter is part of the control template of a ListBox (or ItemsControl in general) you may have to redefine that template in order to get ahold of it. In my case I had a custom template anyway, so that worked well.

Specifically in my case it happens that I was using a MultiSelector, but for the purposes of this question that's no different from ItemsControl or ListBox, as far as I know:

<MultiSelector
   ItemsSource="{Binding ...}"
   >

   <MultiSelector.Template>
      <ControlTemplate TargetType="{x:Type MultiSelector}">
        <ScrollViewer>
            <ItemsPresenter Loaded="ItemsPresenter_Loaded"/>
        </ScrollViewer>
    </ControlTemplate>
</MultiSelector.Template>

...

Alternately, you probably could search the visual tree to find the ItemsPresenter.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81