2012-04-11 15 views
12

Estoy tratando de crear una experiencia similar a la de ScrollViewerSample de los ejemplos de SDK de Windows 8 para poder ajustar a los elementos dentro de un ScrollViewer cuando se desplaza hacia la izquierda y hacia la derecha. La aplicación de la muestra (que funciona) es la siguiente:Habilitación de ScrollViewer HorizontalSnapPoints con colección enlainable

<ScrollViewer x:Name="scrollViewer" Width="480" Height="270" 
       HorizontalAlignment="Left" VerticalAlignment="Top" 
       VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" 
       ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory"> 
    <StackPanel Orientation="Horizontal"> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
    </StackPanel> 
</ScrollViewer> 

La única diferencia con mi aplicación deseada es que yo no quiero un StackPanel con elementos en el interior, pero algo que se puede unir a. Estoy tratando de lograr esto con un ItemsControl, pero por alguna razón el comportamiento de resorte no se dispara en:

<ScrollViewer x:Name="scrollViewer" Width="480" Height="270" 
       HorizontalAlignment="Left" VerticalAlignment="Top" 
       VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" 
       ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory"> 
    <ItemsControl> 
     <ItemsControl.ItemsPanel> 
      <ItemsPanelTemplate> 
       <StackPanel Orientation="Horizontal" /> 
      </ItemsPanelTemplate> 
     </ItemsControl.ItemsPanel> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
     <Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
    </ItemsControl> 
</ScrollViewer> 

sugerencias serán bienvenidos!


Gracias a Denis, que terminaron con el siguiente estilo en el ItemsControl y removidos ScrollViewer e integrados por completo ItemsPanelTemplate:

<Style x:Key="ItemsControlStyle" TargetType="ItemsControl"> 
    <Setter Property="ItemsPanel"> 
     <Setter.Value> 
      <ItemsPanelTemplate> 
       <VirtualizingStackPanel Orientation="Horizontal"/> 
      </ItemsPanelTemplate> 
     </Setter.Value> 
    </Setter> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="ItemsControl"> 
       <ScrollViewer Style="{StaticResource HorizontalScrollViewerStyle}" HorizontalSnapPointsType="Mandatory"> 
        <ItemsPresenter /> 
       </ScrollViewer> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

Respuesta

11

Obtención de puntos de ajuste para trabajar para las colecciones encuadernadas puede ser complicado. Para que los puntos de ajuste funcionen, el elemento secundario inmediato de ScrollViewer debe implementar la interfaz IScrollSnapPointsInfo. ItemsControl no implementa IScrollSnapPointsInfo y, en consecuencia, no verá el comportamiento de ajuste.

Para evitar este problema que tienes opciones par:

  • Crear clase personalizada derivada de ItemsControl e implementar la interfaz IScrollSnapPointsInfo.
  • Cree un estilo personalizado para el control de elementos y establezca la propiedad HorizontalSnapPointsType en ScrollViewer dentro del estilo.

Implementé el enfoque anterior y puedo confirmar que funciona, pero en su caso el estilo personalizado podría ser una mejor opción.

+4

¿Es posible proporcionarnos un ejemplo, por favor? – yalematta

1

Bien, este es el ejemplo más simple (e independiente) para ListView horizontal con elementos enlazados y ajuste de funcionamiento correcto (ver comentarios en el siguiente código).

xaml:

<ListView x:Name="YourListView" 
       ItemsSource="{x:Bind Path=Items}" 
       Loaded="YourListView_OnLoaded"> 
     <!--Set items panel to horizontal--> 
     <ListView.ItemsPanel> 
      <ItemsPanelTemplate> 
       <ItemsStackPanel Orientation="Horizontal" /> 
      </ItemsPanelTemplate> 
     </ListView.ItemsPanel> 
     <!--Some item template--> 
     <ListView.ItemTemplate> 
      <DataTemplate> 
       <TextBlock Text="{Binding}"/> 
      </DataTemplate> 
     </ListView.ItemTemplate> 
    </ListView> 

código de fondo:

private void YourListView_OnLoaded(object sender, RoutedEventArgs e) 
    { 
     //get ListView 
     var yourList = sender as ListView; 

     //*** yourList style-based changes *** 
     //see Style here https://msdn.microsoft.com/en-us/library/windows/apps/mt299137.aspx 

     //** Change orientation of scrollviewer (name in the Style "ScrollViewer") ** 
     //1. get scrollviewer (child element of yourList) 
     var sv = GetFirstChildDependencyObjectOfType<ScrollViewer>(yourList); 

     //2. enable ScrollViewer horizontal scrolling 
     sv.HorizontalScrollMode =ScrollMode.Auto; 
     sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; 
     sv.IsHorizontalRailEnabled = true; 

     //3. disable ScrollViewer vertical scrolling 
     sv.VerticalScrollMode = ScrollMode.Disabled; 
     sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled; 
     sv.IsVerticalRailEnabled = false; 
     // //no we have horizontally scrolling ListView 


     //** Enable snapping ** 
     sv.HorizontalSnapPointsType = SnapPointsType.MandatorySingle; //or you can use SnapPointsType.Mandatory 
     sv.HorizontalSnapPointsAlignment = SnapPointsAlignment.Near; //example works only for Near case, for other there should be some changes 
     // //no we have horizontally scrolling ListView with snapping and "scroll last item into view" bug (about bug see here http://stackoverflow.com/questions/11084493/snapping-scrollviewer-in-windows-8-metro-in-wide-screens-not-snapping-to-the-las) 

     //** fix "scroll last item into view" bug ** 
     //1. Get items presenter (child element of yourList) 
     var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(yourList); 
     // or var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(sv); //also will work here 

     //2. Subscribe to its SizeChanged event 
     ip.SizeChanged += ip_SizeChanged; 

     //3. see the continuation in: private void ip_SizeChanged(object sender, SizeChangedEventArgs e) 
    } 


    public static T GetFirstChildDependencyObjectOfType<T>(DependencyObject depObj) where T : DependencyObject 
    { 
     if (depObj is T) return depObj as T; 

     for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) 
     { 
      var child = VisualTreeHelper.GetChild(depObj, i); 

      var result = GetFirstChildDependencyObjectOfType<T>(child); 
      if (result != null) return result; 
     } 
     return null; 
    } 

    private void ip_SizeChanged(object sender, SizeChangedEventArgs e) 
    { 
     //3.0 if rev size is same as new - do nothing 
     //here should be one more condition added by && but it is a little bit complicated and rare, so it is omitted. 
     //The condition is: yourList.Items.Last() must be equal to (yourList.Items.Last() used on previous call of ip_SizeChanged) 
     if (e.PreviousSize.Equals(e.NewSize)) return; 

     //3.1 get sender as our ItemsPresenter 
     var ip = sender as ItemsPresenter; 

     //3.2 get the ItemsPresenter parent to get "viewable" width of ItemsPresenter that is ActualWidth of the Scrollviewer (it is scrollviewer actually, but we need just its ActualWidth so - as FrameworkElement is used) 
     var sv = ip.Parent as FrameworkElement; 

     //3.3 get parent ListView to be able to get elements Containers 
     var yourList = GetParent<ListView>(ip); 

     //3.4 get last item ActualWidth 
     var lastItem = yourList.Items.Last(); 
     var lastItemContainerObject = yourList.ContainerFromItem(lastItem); 
     var lastItemContainer = lastItemContainerObject as FrameworkElement; 
     if (lastItemContainer == null) 
     { 
      //NO lastItemContainer YET, wait for next call 
      return; 
     } 
     var lastItemWidth = lastItemContainer.ActualWidth; 

     //3.5 get margin fix value 
     var rightMarginFixValue = sv.ActualWidth - lastItemWidth; 

     //3.6. fix "scroll last item into view" bug 
     ip.Margin = new Thickness(ip.Margin.Left, 
      ip.Margin.Top, 
      ip.Margin.Right + rightMarginFixValue, //APPLY FIX 
      ip.Margin.Bottom); 
    } 

    public static T GetParent<T>(DependencyObject reference) where T : class 
    { 
     var depObj = VisualTreeHelper.GetParent(reference); 
     if (depObj == null) return (T)null; 
     while (true) 
     { 
      var depClass = depObj as T; 
      if (depClass != null) return depClass; 
      depObj = VisualTreeHelper.GetParent(depObj); 
      if (depObj == null) return (T)null; 
     } 
    } 

Acerca de este ejemplo.

  1. La mayor parte de las comprobaciones y el manejo de errores se ha omitido.

  2. Si reemplaza ListView Estilo/Plantilla, VisualTree partes de búsqueda debe cambiar en consecuencia

  3. Prefiero crear hereda de Control ListView con esta lógica, que el uso previsto ejemplo, tal y como está en el código real.
  4. El mismo código funciona para Vertical (o ambos) con pequeños cambios.
  5. Error de referencia mencionado - Error de ScrollViewer al manejar los casos de SnapPointsType.MandatorySingle y SnapPointsType.Mandatory.Aparece para artículos con tamaños no fijos

.

+0

Gracias por esta muestra, es bueno tenerlo en esta importante pregunta. Fyi, sin embargo, el código detrás no es necesario en absoluto. Puede usar un estilo para establecer todas las propiedades que establece en el código subyacente. (No sé sobre los errores que mencionas) –

Cuestiones relacionadas