2009-05-13 14 views
11

Tengo un WPF ListBox vinculado a un ObservableCollection. Cuando se agregan cosas a la colección, la posición de desplazamiento ListBox se desplaza por el tamaño de las entradas agregadas. Me gustaría poder conservar la posición de desplazamiento, de modo que incluso cuando las cosas se agreguen a la lista, los elementos que están actualmente a la vista no se muevan. ¿Hay alguna manera de lograr esto?Preservar una posición de desplazamiento de WPF ListBox cuando la colección cambia

+0

Acabo de hacer un enlace de datos wpf muy simple a un cuadro de lista y cuando agrego elementos los elementos que estaban a la vista quedaron a la vista ... ¿Qué más estás haciendo? – TheCodeMonk

+0

Intenta agregar nuevos elementos al biginning de la colección. Los elementos que está viendo comienzan a salir de la ventana gráfica a medida que se agregan nuevos elementos. Agregas 1 elemento: la ventana gráfica permanece en la misma posición (basada en el índice) lo que significa que 1 elemento ingresó a la ventana gráfica y 1 elemento lo dejó. – EvAlex

Respuesta

0

No estoy seguro de si hay una anulación para eso, pero lo dudo.

¿En caso de duda utilice el encuadernado manual? : -} En serio, el comportamiento de enlace predeterminado será, bueno, predeterminado, por lo que si necesita un comportamiento especial, el enlace manual es una mejor opción. Con el encuadernado manual puede guardar la posición de desplazamiento y restablecerla después de haber agregado los elementos.

+0

Lo que lleva a una pregunta obvia: ¿cómo determino la posición de desplazamiento actual? –

1

Crear una CollectionView. Prueba esto:

private ObservableCollection<int> m_Values; 
    private CollectionView m_View; 

    private void Bind() 
    { 
     m_Values = new ObservableCollection<int>(); 
     m_View = new CollectionView(m_Values); 
     MyListBox.ItemsSource = m_View; 
    } 

    private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     Debug.WriteLine(m_View.CurrentItem); 
     Debug.WriteLine(m_View.CurrentPosition); 
    } 
+1

Por favor explique más. Esta respuesta no parece resolver el problema, solo informa el elemento y la posición actuales. Estoy teniendo el mismo problema, usando un ListBox con un ItemsPanel configurado en VirtualizingStackPanel. El problema, como yo lo veo, es que los elementos del cuadro de lista continúan moviéndose a medida que se agregan nuevos elementos a la colección Observable. Si bien es posible volver a colocar los elementos en el lugar en el que se encontraban después de agregar un nuevo elemento (por ejemplo, mediante el uso de SelectedIndex), esto crea un movimiento brusco. Lo que se necesita es una forma de evitar que la posición de desplazamiento cambie en primer lugar a medida que se agregan nuevos elementos. – CyberMonk

+0

Tengo el mismo problema, pero su solución supone que algo está seleccionado. Pero, ¿y si el usuario no quiere seleccionar nada? Simplemente desplácese a algún lado y déjelo. – EvAlex

0

Cómo se controla cuando se añaden cosas? Quiero decir, ¿tiene uno o dos puntos definidos en los que se cambian los datos? Si es así, una solución fácil (quizás no elegante) sería tener una variable int local para contener el actual ListBox.SelectedIndex. Justo antes de que los datos cambien, guarde el selectedIndex en esta variable y, después de agregar los datos, establezca el SelectedIndex de listBox en el valor de esta variable.

1

Primero busque la posición actual por ListBox.SelectedIndex y luego use ListBox.ScrollIntoView (/ * Current Index * /). Incluso si los nuevos elementos se agregan en la lista, su posición actual y su vista serán las mismas.

3

Me enfrenté al mismo problema y una restricción más: el usuario no selecciona elementos, sino que solo se desplaza. Es por eso que el método ScrollIntoView() es inútil. Aquí está mi solución.

En primer lugar, he creado clase ScrollPreserver que lo deriva de DependencyObject, con una propiedad de dependencia adjunta PreserveScroll de tipo bool:

public class ScrollPreserver : DependencyObject 
{ 
    public static readonly DependencyProperty PreserveScrollProperty = 
     DependencyProperty.RegisterAttached("PreserveScroll", 
      typeof(bool), 
      typeof(ScrollPreserver), 
      new PropertyMetadata(new PropertyChangedCallback(OnScrollGroupChanged))); 

    public static bool GetPreserveScroll(DependencyObject invoker) 
    { 
     return (bool)invoker.GetValue(PreserveScrollProperty); 
    } 

    public static void SetPreserveScroll(DependencyObject invoker, bool value) 
    { 
     invoker.SetValue(PreserveScrollProperty, value); 
    } 

    ... 
} 

cambió la propiedad de devolución de llamada asume la propiedad se establece por el ScrollViewer. El método de devolución de llamada agrega esta ScrollViewer el Diccionario privado y agrega controlador de eventos ScrollChanged a ella:

private static Dictionary<ScrollViewer, bool> scrollViewers_States = 
    new Dictionary<ScrollViewer, bool>(); 

private static void OnScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    ScrollViewer scrollViewer = d as ScrollViewer; 
    if (scrollViewer != null && (bool)e.NewValue == true) 
    { 
     if (!scrollViewers_States.ContainsKey(scrollViewer)) 
     { 
      scrollViewer.ScrollChanged += new ScrollChangedEventHandler(scrollViewer_ScrollChanged); 
      scrollViewers_States.Add(scrollViewer, false); 
     } 
    } 
} 

Este diccionario llevará a cabo todas las referencias a ScrollViewers en aplicaciones que utilizan esta clase. En mi caso, los elementos se agregan al comienzo de la colección. El problema es que la posición de la ventana gráfica no cambia. Está bien cuando la posición es 0: el primer elemento en la ventana gráfica siempre será el primer elemento. Pero cuando el primer elemento en la ventana gráfica tiene otro índice que no sea 0 y se agregan nuevos elementos, el índice de ese elemento aumenta, por lo que se desplaza hacia abajo. Así valor bool indica si compensado de ScrollViewer vertical es no 0. Si es 0 no hay necesidad de hacer nada, y si no es 0, es necesario preservar su posición con respecto a los elementos de la ventana gráfica:

static void scrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
{ 
    if (scrollViewers_States[sender as ScrollViewer]) 
     (sender as ScrollViewer).ScrollToVerticalOffset(e.VerticalOffset + e.ExtentHeightChange); 

    scrollViewers_States[sender as ScrollViewer] = e.VerticalOffset != 0; 
} 

ExtentHeightChange se usa aquí para indicar la cantidad de elementos agregados. En mi caso, los elementos se agregan solo al comienzo de la colección, por lo que todo lo que necesito hacer es aumentar VerticalOffset de ScrollViewer en este valor. Y lo último: uso. Aquí está el ejemplo de ListBox:

<Listbox ...> 
    <ListBox.Resourses> 
     <Style TargetType="ScrollViewer"> 
      <Setter Property="local:ScrollPreserver.PreserveScroll" Value="True" /> 
     </Style> 
    </ListBox.Resourses> 
</ListBox> 

Ta-da!Funciona bien :)

+0

¿No tiene pérdidas de memoria debido a un diccionario en el campo estático? – TcKs

+0

DependencyProperties son todas estáticas. Puede leer al respecto, por ejemplo aquí: http://stackoverflow.com/questions/2989431/why-are-dependency-properties-static – EvAlex

+0

Lo sé. Pero el campo "scrollViewers_States" no es un acceso directo para la propiedad de dependencia. Almacena las instancias de ScrollViewer. ¿Tiene algún código que elimine instancias antiguas del diccionario? – TcKs

Cuestiones relacionadas