2011-05-22 22 views
6

estoy usando el ScrollViewer con el patrón MVVM, y una lista de artículos está envuelto por el ScrollViewer, comodesplazamiento ScrollViewer arriba a través viewmodel

<ScrollViewer> 
    <ListView> 
    <ListView.View> 
     <GridView> 
      <GridViewColumn 
       Header = "Name" 
       DisplayMemberBinding="{Binding Path=Name}" 
      />    
     </GridView> 
    </ListView.View> 
    </ListView> 
</ScrollViewer> 

Los elementos de la vista de lista están obligados a una colección de objetos en el modelo de vista. Quiero que el scrollviewer se desplace hasta la parte superior siempre que se agregue o elimine un elemento de la colección.
necesito el modelo de vista para desencadenar el evento, en lugar de utilizar el método ScrollToTop() en el código subyacente de la vista.

Respuesta

-1

también se han enfrentado a una situación similar en el que necesitaba para asignar HorizontalOffset y VerticalOffset de ScrollViewer mediante programación. Me temo que no hay un mecanismo de enlace directo para esto. Lo que hice fue una forma de evitarlo (créanme, todavía no me gusta el enfoque que seguí, pero no encontré ninguna otra opción). Esto es lo que sugiero:

Enganche el evento ScrollViewer Loaded, coloque el objeto remitente en ScrollViewer y asígnelo a una propiedad en DataContext (Significa que necesita mantener una propiedad ScrollViewer en DataContext que mantendrá la referencia de ScrollViewer en UI). Conecte los eventos CollectionChanged de ObservableCollection en ViewModel y con la propiedad ScrollViewer, puede llamar a métodos como ScrollToTop(), etc.

Esto es solo una forma de evitarlo. Todavía estoy buscando una mejor solución.

-1

La forma más sencilla y correcta de hacerlo en MVVM es creando un evento en su modelo de vista y suscribiéndolo desde su vista. Y luego, en el controlador de eventos, llame al ScrollToTop.

Dispare el evento desde su modelo de vista cada vez que se modifique su colección, por ejemplo, y luego depende de la vista reaccionar ante ese evento y desplazarse por la lista hasta la parte superior.

Incluso si esto implica algo de código subyacente y exige que la vista conozca parte de su modelo de vista, no viola el patrón MVVM, a diferencia de otras soluciones.

public interface IMyViewModel 
{ 
    event EventHandler MyCollectionChanged; 
} 

public class MyViewModel : IMyViewModel 
{ 
    public event EventHandler MyCollectionChanged; 

    // More viewmodel related stuff 

    protected virtual void OnMyCollectionChanged(EventArgs e) 
    { 
     if (MyCollectionChanged != null) 
      MyCollectionChanged(this, e); 
    } 
} 

public class MyWindow : Window 
{ 
    public MyWindow(IMyViewModel viewModel) 
    { 
     this.DataContext = viewModel; 
     InitializeComponent(); 
     (this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler; 
    } 

    private void MyCollectionChangedEventHandler(object sender, EventArgs e) 
    { 
     // Do view related stuff 
     scrollViewer.ScrollToTop(); 
    } 

} 

EDIT: Pero puede ser refinado mucho más, por supuesto. Si desea evitar el uso de código subyacente, busque DataEventTriggers. Si no te importa el código subyacente pero te preocupan las pérdidas de memoria, busca eventos débiles.

Y, por último, dado que la lógica que desea es 100% relacionada con la visualización (tiene el desplazamiento ListView cada vez que se agrega o elimina un elemento), también puede implementarlo como comportamiento/propiedad adjunta, o extender el Vista de la lista. Eso podría ser un poco más intrincado, pero te animo a pensar en esas opciones.

+0

Soy nuevo en MVVM: en su primera opción sería genial si pudiera mostrar algún código sobre cómo implementar el evento + suscribirse. ¿Es como [esto] (http://stackoverflow.com/a/10109366/345659)? Leí en algún lugar un patrón de evento débil (lo que sea que sea) es preferible para evitar pérdidas de memoria. – JumpingJezza

+0

He agregado un ejemplo de código con el enfoque de MVVM más fácil para su problema, pero puede refinarse mucho más, por supuesto. Si desea evitar el uso de código subyacente, busque DataEventTriggers. Si no te importa el código subyacente pero te preocupan las pérdidas de memoria, busca eventos débiles. – almulo

+0

Y, por último, dado que la lógica que desea es 100% relacionada con la vista (tiene el desplazamiento ListView cada vez que se agrega o elimina un elemento), también puede implementarlo como una propiedad Comportamiento/adjunta, o extender ListView. Eso podría ser un poco más intrincado, pero te animo a pensar en esas opciones. – almulo

2

Crear un nuevo control ListView que se extienden Listview y utilizar este nuevo lugar

public class ScrollListView : ListView 
    { 
     protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
     { 

      if (e.OldItems.Count > 0) 
       this.ScrollIntoView(e.OldItems[e.OldStartingIndex]); 

      base.OnItemsChanged(e); 
     } 
    } 
+0

Ese es un buen enfoque. Pero tal como es, hace algo completamente diferente de lo que preguntó @ D.Young. Él quiere que ScrollViewer se desplace hasta la parte superior cada vez que se agreguen O se eliminen elementos. – almulo

+0

¡No es una mala idea! En mi caso, tengo un Grid + ItemControls + un panel colapsando + un par de botones en lugar de un simple ListView, por lo que estoy otorgando la recompensa a @antiocol ya que su respuesta funciona mejor para mí. Esta solución probablemente se adaptaría a la pregunta original de OP. – JumpingJezza

11

en mi humilde opinión, la forma más clara de hacerlo es usando un "comportamiento" a través de un AttachedProperty. Un AttachedProperty es un mecanismo para extender la funcionalidad de controles existentes.

En primer lugar, crear una clase para mantener el AtachedProperty, por ejemplo:

public class ScrollViewerBehavior 
{ 
    public static bool GetAutoScrollToTop(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(AutoScrollToTopProperty); 
    } 

    public static void SetAutoScrollToTop(DependencyObject obj, bool value) 
    { 
     obj.SetValue(AutoScrollToTopProperty, value); 
    } 

    public static readonly DependencyProperty AutoScrollToTopProperty = 
     DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) => 
      { 
       var scrollViewer = o as ScrollViewer; 
       if (scrollViewer == null) 
       { 
        return; 
       } 
       if ((bool)e.NewValue) 
       { 
        scrollViewer.ScrollToTop(); 
        SetAutoScrollToTop(o, false); 
       } 
      })); 
} 

Esta propiedad adjunta permite una ScrollViewer tener "mágicamente" una nueva propiedad de tipo Boolean, actuando como un DependencyProperty en su XAML. Si enlaza esta propiedad a una propiedad estándar en su modelo de vista, por ejemplo:

private bool _reset; 
public bool Reset 
{ 
    get { return _reset; } 
    set 
    { 
     _reset = value; 
     if(PropertyChanged != null) 
      PropertyChanged(this, new PropertyChangedEventArgs("Reset")); 
    } 
} 

(de nuevo, el nombre depende de usted) y luego se establece este Reset propiedad a true, su ScrollViewer se desplazará hacia arriba. He llamado AtachedProperty como AutoScrollToTop, pero el nombre no es importante para este propósito.

El XAML será algo como:

<ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}"> 
    <ListView> 
     <ListView.View> 
      <GridView> 
       <GridViewColumn 
        Header = "Name" 
        DisplayMemberBinding="{Binding Path=Name}" 
       /> 
      </GridView> 
     </ListView.View> 
    </ListView> 
</ScrollViewer> 

Nota: my es el espacio de nombres donde sus vidas ScrollViewerBehavior clase. Por ejemplo: xmlns:my="clr-namespace:MyApp.Behaviors"

Finalmente, lo único que tiene que hacer en su ViewModel es establecer Reset = true cuando lo desee, en su caso, cuando agrega o elimina un elemento de la colección.

+0

Me gusta cómo dijiste "más claro" en lugar de la manera "más fácil" de hacerlo. WPF + MVVM tiene mucho de esto. – JumpingJezza

+0

esto es realmente bueno. ¡Gracias!. Y sí, estoy de acuerdo con JumpingJezza, este es el tipo de cosas donde si no necesitas usar MVVM es realmente simple, pero si necesitas utilizar MVVM es extremadamente complicado. –

Cuestiones relacionadas