Utilizing Tab Navigation on Virtualized Items
-
Friday, December 28, 2012 7:32 PM
I know it's not Blend specific so may need moved? I'm trying to figure out how to provide proper tab navigation on virtualized items. So to reiterate what I have over on SO to maybe get additional insight, let's say for example I have a virtualized panel, something like;
<ListBox x:Name="Items"> <ListBox.Template> <ControlTemplate> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <ItemsPresenter/> </ScrollViewer> </ControlTemplate> </ListBox.Template> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel VirtualizingStackPanel.VirtualizationMode="Recycling"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Button /> </DataTemplate> </ListBox.ItemTemplate> </ListBox>If I set TabNavigation=Once or Cycle on the Scrollviewer itself, or the Listbox parent etc, it only tabs through items available in the viewport since the others haven't been generated yet. Is there a trick someone might share for when tabbing through Item Objects it will allow Tab to proceed to the next not-yet-rendered item while bringing it to view in the viewport and providing intuitive tabbing through the controls?
Please mark answers as helpful when used, and answered when completed.
All Replies
-
Thursday, January 03, 2013 10:20 PM
In case it's helpful to anyone else, here's what I ended up doing. Basically keeping the virtualization but still rendering items down the pipe and bringing them to view in the viewport as they receive focus, example for better explanation;
namespace The.Namespace { using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; /// <summary> /// Scroll selected item into view. /// </summary> public class ListBoxFocusBehavior : FocusBehavior<ListBox> { public static readonly DependencyProperty IsContinuousProperty = DependencyProperty.Register("IsContinuous", typeof(bool), typeof(ListBoxFocusBehavior), new PropertyMetadata( false, (d, e) => ((ListBoxFocusBehavior)d).IsContinuousScroll = (bool)e.NewValue)); /// <summary> /// Gets or sets a value indicating whether this instance is continuous. /// </summary> /// <value> /// <c>true</c> if this instance is continuous; otherwise, <c>false</c>. /// </value> public bool IsContinuous { get { return (bool)GetValue(IsContinuousProperty); } set { SetValue(IsContinuousProperty, value); } } /// <summary> /// Called after the behavior is attached to an AssociatedObject. /// </summary> protected override void OnAttached() { base.OnAttached(); AssociatedObject.SelectionChanged += SelectionChanged; AssociatedObject.KeyDown += KeyDown; } /// <summary> /// Keys down. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param> private void KeyDown(object sender, KeyEventArgs e) { e.Handled = false; if (e.Key == Key.Tab && Keyboard.Modifiers == ModifierKeys.None) { //forward tab ... var idx = AssociatedObject.Items.IndexOf(AssociatedObject.SelectedItem); if (idx < AssociatedObject.Items.Count-1) { AssociatedObject.SelectedItem = AssociatedObject.Items[idx + 1]; e.Handled = true; } } if (e.Key == Key.Tab && (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) { //back tab. var idx = AssociatedObject.Items.IndexOf(AssociatedObject.SelectedItem); if (idx > 0) { AssociatedObject.SelectedItem = AssociatedObject.Items[idx - 1]; e.Handled = true; } } } /// <summary> /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred. /// </summary> protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.SelectionChanged -= SelectionChanged; AssociatedObject.KeyDown -= KeyDown; } /// <summary> /// Gots the focus. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param> private void GotFocus(object sender, RoutedEventArgs e) { if (AssociatedObject.SelectedItem == null && AssociatedObject.Items.Any()) { AssociatedObject.SelectedItem = AssociatedObject.Items.First(); } } /// <summary> /// Selections the changed. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param> private void SelectionChanged(object sender, SelectionChangedEventArgs e) { if (AssociatedObject.SelectedItem == null) return; AssociatedObject.UpdateLayout(); //have to, otherwise the listbox will probably not focus. Action setFocus = () => { AssociatedObject.UpdateLayout(); AssociatedObject.ScrollIntoView(AssociatedObject.SelectedItem); //ensure that if the container did not exist yet (virtualized), it gets created. AssociatedObject.UpdateLayout(); var container = AssociatedObject.ItemContainerGenerator.ContainerFromItem( AssociatedObject.SelectedItem) as Control; if (container != null) { container.Focus(); } }; AssociatedObject.Dispatcher.BeginInvoke(setFocus); } } }
Please mark answers as helpful when used, and answered when completed.- Marked As Answer by KS.BlendDesigner Thursday, January 03, 2013 10:21 PM

