2012-08-25 14 views
7

Tengo un ItemsControl que muestra sus elementos en un ScrollViewer, y realiza la virtualización. Estoy intentando desplazar ese ScrollViewer a un elemento (fuera de pantalla, por lo tanto virtualizado) que contiene. Sin embargo, como el elemento está virtualizado, realmente no existe en la pantalla y no tiene posición (IIUC).Desplazamiento a un elemento de virtualización ItemsControl

He intentado BringIntoView en el elemento secundario, pero no se desplaza hacia la vista. También he intentado hacerlo manualmente con TransformToAncestor, TransformBounds y ScrollToVerticalOffset, pero TransformToAncestor nunca regresa (supongo que también debido a la virtualización, porque no tiene una posición, pero no tengo ninguna prueba de eso) y el código después de que nunca se ejecuta.

¿Es posible desplazarse a un elemento con una virtualización de ItemsControl? ¿Si es así, cómo?

Respuesta

9

Buscar en el código fuente .NET me lleva a recomendarle el uso de un ListBox y su método ScrollIntoView. La implementación de este método se basa en unos pocos métodos internal como VirtualizingPanel.BringIndexIntoView que fuerza la creación del elemento en ese índice y se desplaza hasta él. El hecho de que muchos de esos mecanismos sean internos significa que si intentas hacerlo por tu cuenta vas a tener un mal momento.

(Para hacer la selección esto trae consigo invisible puede retemplate la ListBoxItems)

+0

Me gustaría evitar hacer esto porque no necesito la capacidad de "seleccionar un elemento" de 'ListBox'.¿Alguna idea de por qué 'ItemsControl' no tiene' ScrollIntoView'? –

+0

@Seth: Como he dicho, puede ocultar la selección, ¿a quién le importa si está allí? No tiene desplazamiento porque fue diseñado de esa manera, 'ItemsControl' es el más básico de los controles de elementos, la funcionalidad de desplazamiento no es necesaria para dicha clase base. –

+0

Ahora, para averiguar cómo hacer que 'ListBox' deje de desplazarse un elemento completamente a la vista al hacer clic ... –

11

He estado buscando en conseguir un ItemsControl con una VirtualizingStackPanel para desplazarse a un elemento desde hace un tiempo, y se mantiene la búsqueda de la "utilizar un ListBox" respuesta. No quería, así que encontré la manera de hacerlo. Primero necesita configurar una plantilla de control para su ItemsControl que tenga un ScrollViewer (que probablemente ya tenga si usa un control de elementos). Mi plantilla básica se parece a la siguiente (contenida en un estilo práctico para ItemsControl)

<Style x:Key="TheItemsControlStyle" TargetType="{x:Type ItemsControl}"> 
    <Setter Property="Template"> 
    <Setter.Value> 
      <ControlTemplate TargetType="{x:Type ItemsControl}"> 
       <Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="{TemplateBinding Control.Padding}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True"> 
        <ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False" HorizontalScrollBarVisibility="Auto"> 
         <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> 
        </ScrollViewer> 
       </Border> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

así que he básicamente tiene una frontera con un visor de desplazamiento thats de ir a contener mi contenido.
Mi ItemsControl se define con:

<ItemsControl x:Name="myItemsControl" [..snip..] Style="{DynamicResource TheItemsControlStyle}" ScrollViewer.CanContentScroll="True" VirtualizingStackPanel.IsVirtualizing="True"> 

Ok, ahora viene la parte divertida. He creado un método de extensión para conectar a cualquier ItemsControl conseguirlo para desplazarse hasta el elemento dado:

public static void VirtualizedScrollIntoView(this ItemsControl control, object item) { 
     try { 
      // this is basically getting a reference to the ScrollViewer defined in the ItemsControl's style (identified above). 
      // you *could* enumerate over the ItemsControl's children until you hit a scroll viewer, but this is quick and 
      // dirty! 
      // First 0 in the GetChild returns the Border from the ControlTemplate, and the second 0 gets the ScrollViewer from 
      // the Border. 
      ScrollViewer sv = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild((DependencyObject)control, 0), 0) as ScrollViewer; 
      // now get the index of the item your passing in 
      int index = control.Items.IndexOf(item); 
      if(index != -1) { 
       // since the scroll viewer is using content scrolling not pixel based scrolling we just tell it to scroll to the index of the item 
       // and viola! we scroll there! 
       sv.ScrollToVerticalOffset(index); 
      } 
     } catch(Exception ex) { 
      Debug.WriteLine("What the..." + ex.Message); 
     } 
    } 

Así que con el método de extensión en el lugar que lo utilizaría como método compañero de ListBox:

myItemsControl.VirtualizedScrollIntoView(someItemInTheList); 

Funciona muy bien!

Tenga en cuenta que también puede llamar a sv.ScrollToEnd() y a los demás métodos de desplazamiento habituales para desplazarse por sus elementos.

+0

Lamentablemente uso el desplazamiento basado en píxeles, así que esto no funciona para mí, pero estoy seguro de que esto ayudará a otros en el futuro, +1. –

+0

Si utiliza el desplazamiento basado en píxeles, puede obtener el tamaño de un elemento individual en ItemsControl (si es un tamaño fijo, entonces es fácil, pero también hay enumeraciones que podría hacer en ItemControls ItemTemplate para obtener el tamaño de un individuo ítem) y luego simplemente multiplique el índice devuelto por el tamaño de un solo ítem y luego llame a ScrollToVerticalOffset con ese número. es decir, sv.ScrollToVerticalOffset ((double) index * sizeOfAnItemInTheList); –

0

Sé que soy muy tarde a la fiesta, pero espero que esto pueda ayudar a alguien que viene a lo largo buscando la solución ...

int index = myItemsControl.Items.IndexOf(*your item*).FirstOrDefault(); 
int rowHeight = *height of your rows*; 
myScrollView.ScrollToVerticalOffset(index*rowHeight); 
//this will bring the given item to the top of the scrollViewer window 

... y mi XAML se configura como esto ...

<ScrollViewer x:Name="myScrollView"> 
    <ItemsControl x:Name="myItemsControl"> 
     <ItemsControl.ItemTemplate> 
      <DataTemplate> 
       <Grid> 
        <!-- data here --> 
       </Grid> 
      </DataTemplate> 
     </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</ScrollViewer> 
Cuestiones relacionadas