Sincronizar scroll do ListView no WPF

Vou mostrar a forma que encontrei para solucionar o problema de como sincronizar as barras de rolagem de dois LisViews, embora esse mesmo código possa ser adaptado para qualquer componente que use scrolls. No C.E.S.A.R, usamos essa técnica para simular o congelamento de colunas, recurso ainda não suportado nativamente pelo WPF. Esse não é um caso real, mantive o mais simples possível para facilitar o entendimento.

No código abaixo podemos perceber dois ListViews posicionados usando Grid: um a esquerda e outro a direita. O que quero mostrar é o efeito de quando o usuário usar a barra de rolagem no lado direito, a barra do lado esquerdo também irá se movimentar na mesma proporção.

<Window x:Class="ScrollDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:models="clr-namespace:ScrollDemo.Models"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <models:People x:Key="people" />
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ListView x:Name="listView" 
                  ItemsSource="{StaticResource ResourceKey=people}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Id" 
                                        DisplayMemberBinding="{Binding Id}" />
                        <GridViewColumn Header="Name"  
                                        DisplayMemberBinding="{Binding Name}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
        <ListView ItemsSource="{StaticResource ResourceKey=people}" Grid.Column="1">
            <ListView.Resources>
                <Style TargetType="ScrollBar">
                    <EventSetter Event="ValueChanged" 
                                 Handler="ScrollValueChanged" />
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Phone" 
                                        DisplayMemberBinding="{Binding Phone}" />
                        <GridViewColumn Header="Address" 
                                        DisplayMemberBinding="{Binding Address}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

Para conseguir capturar o evento, usamos o recurso de estilo com um target para ScrollBar dentro do ListView. O que as linhas 28 a 33 estão dizendo é: para qualquer ScrollBar dentro desse ListView, dispare o evento ValueChanged chamando o método ScrollValueChanged. O método ScrollValueChanged está ilustrado abaixo.

protected void ScrollValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    ScrollBar source = sender as ScrollBar;
    if (source == null || source.Orientation == Orientation.Horizontal)
        return;

    ScrollViewer target = GetScrollViewer(this.listView) as ScrollViewer;
    if (target == null)
        return;

    target.ScrollToVerticalOffset(source.Value);
}

O grande segredo desse evento está no método GetScrollViewer.

public static DependencyObject GetScrollViewer(DependencyObject container)
{
    if (container is ScrollViewer)
        return container;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
    {
        var child = VisualTreeHelper.GetChild(container, i);
        var result = GetScrollViewer(child);
        if (result != null)
            return result;
    }

    return null;
}

Anúncios
%d blogueiros gostam disto: