ti-enxame.com

Pare o WPF ScrollViewer de rolar automaticamente para o conteúdo percebido

o aplicativo

Estou construindo um aplicativo que inclui um seletor de intervalo. Isso consiste em dois controles personalizados Slider contidos em uma classe derivada de UserControl. O controle do seletor de intervalo está então contido dentro de um ScrollViewer que tem o HorizonalScrollBar visível na maior parte do tempo.

Exemplo de código de aplicativo: (desculpas para a parede de texto)

Window.xaml (o arquivo Window):

<Grid>
    <ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Visible"  VerticalScrollBarVisibility="Disabled">
            <local:SliderTest x:Name="slider"                                                                         
                           LowerValue="0"
                           UpperValue="10"
                           Minimum="0"
                           Maximum="100" Width="900" Height="165" Padding="15,0,15,0" HorizontalAlignment="Left">
            </local:SliderTest>
    </ScrollViewer>
</Grid>

SliderTest.xaml:

<UserControl x:Class="scrollviewerDemoProblem.SliderTest"
             xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.Microsoft.com/expression/blend/2008" 
             x:Name="root"
             xmlns:local="clr-namespace:scrollviewerDemoProblem"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <ControlTemplate x:Key="simpleSlider" TargetType="{x:Type Slider}">
            <Border SnapsToDevicePixels="true" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Track x:Name="PART_Track" Grid.Row="1">
                        <Track.Thumb>
                            <Thumb x:Name="Thumb" FlowDirection="LeftToRight" Width="15">
                                <Thumb.Template>
                                    <ControlTemplate TargetType="Thumb">
                                        <Canvas>
                                            <Path x:Name="test1" StrokeThickness="0" Fill="DarkGreen">
                                                <Path.Data>
                                                    <GeometryGroup FillRule="NonZero">
                                                        <PathGeometry>
                                                            <PathGeometry.Figures>
                                                                <PathFigure IsClosed="True" StartPoint="0,150" IsFilled="True">
                                                                    <PathFigure.Segments>
                                                                        <PathSegmentCollection>
                                                                            <LineSegment Point="-15,150" />
                                                                            <LineSegment Point="-15,0" />
                                                                            <LineSegment Point="0,0" />
                                                                        </PathSegmentCollection>
                                                                    </PathFigure.Segments>
                                                                </PathFigure>
                                                            </PathGeometry.Figures>
                                                        </PathGeometry>
                                                    </GeometryGroup>
                                                </Path.Data>
                                            </Path>
                                        </Canvas>
                                    </ControlTemplate>
                                </Thumb.Template>
                            </Thumb>
                        </Track.Thumb>
                    </Track>
                </Grid>
            </Border>
        </ControlTemplate>

        <ControlTemplate x:Key="simpleSliderRight" TargetType="{x:Type Slider}">
            <Border SnapsToDevicePixels="true" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Track x:Name="PART_Track" Grid.Row="1">
                        <Track.Thumb>
                            <Thumb x:Name="Thumb" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Width="15">
                                <Thumb.Template>
                                    <ControlTemplate TargetType="Thumb">
                                        <Canvas>
                                            <Path Stroke="Black" StrokeThickness="0" Fill="DarkCyan">
                                                <Path.Data>
                                                    <GeometryGroup FillRule="NonZero">
                                                        <PathGeometry>
                                                            <PathGeometry.Figures>
                                                                <PathFigure IsClosed="True" StartPoint="0,150">
                                                                    <PathFigure.Segments>
                                                                        <PathSegmentCollection>
                                                                            <LineSegment Point="15,150" />
                                                                            <LineSegment Point="15,0" />
                                                                            <LineSegment Point="0,0" />
                                                                        </PathSegmentCollection>
                                                                    </PathFigure.Segments>
                                                                </PathFigure>
                                                            </PathGeometry.Figures>
                                                        </PathGeometry>
                                                    </GeometryGroup>
                                                </Path.Data>
                                            </Path>
                                        </Canvas>
                                    </ControlTemplate>
                                </Thumb.Template>
                            </Thumb>
                        </Track.Thumb>
                    </Track>
                </Grid>
            </Border>
        </ControlTemplate>
    </UserControl.Resources>

    <Grid x:Name="Gridd" VerticalAlignment="Top" Height="165" >
        <Border x:Name="timeScaleBorder" Width="auto" Height="15" VerticalAlignment="Top" Background="Black">
            <Canvas x:Name="timeCanvas" Width="auto" Height="15">
            </Canvas>
        </Border>
        <Border x:Name="background" BorderThickness="1,1,1,1" BorderBrush="Black" VerticalAlignment="Center" Height="150"
                Margin="0,15,0,0" Background="White" />
        <Slider  x:Name="LowerSlider"
                Minimum="{Binding ElementName=root, Path=Minimum}"
                Maximum="{Binding ElementName=root, Path=Maximum}"
                Value="{Binding ElementName=root, Path=LowerValue, Mode=TwoWay}"
                Template="{StaticResource simpleSlider}"
                Margin="0,15,0,0" />
        <Slider  x:Name="UpperSlider"
                Minimum="{Binding ElementName=root, Path=Minimum}"
                Maximum="{Binding ElementName=root, Path=Maximum}"
                Value="{Binding ElementName=root, Path=UpperValue, Mode=TwoWay}"
                Template="{StaticResource simpleSliderRight}"
                Margin="0,15,0,0" />
    </Grid>
</UserControl>

SliderText.xaml.cs:

public partial class SliderTest : UserControl
{
    public SliderTest()
    {
        InitializeComponent();
    }

    #region Dependency properties, values etc.

    public static readonly DependencyProperty MinimumProperty =
        DependencyProperty.Register("Minimum", typeof(double), typeof(SliderTest), new UIPropertyMetadata(0d));

    public double LowerValue
    {
        get { return (double)GetValue(LowerValueProperty); }
        set { SetValue(LowerValueProperty, value); }
    }

    public static readonly DependencyProperty LowerValueProperty =
        DependencyProperty.Register("LowerValue", typeof(double), typeof(SliderTest), new UIPropertyMetadata(0d));

    public double UpperValue
    {
        get { return (double)GetValue(UpperValueProperty); }
        set { SetValue(UpperValueProperty, value); }
    }

    public static readonly DependencyProperty UpperValueProperty =
        DependencyProperty.Register("UpperValue", typeof(double), typeof(SliderTest), new UIPropertyMetadata(0d));

    public double Maximum
    {
        get { return (double)GetValue(MaximumProperty); }
        set { SetValue(MaximumProperty, value); }
    }

    public static readonly DependencyProperty MaximumProperty =
        DependencyProperty.Register("Maximum", typeof(double), typeof(SliderTest), new UIPropertyMetadata(1d));

    public double Minimum
    {
        get { return (double)GetValue(MinimumProperty); }
        set { SetValue(MinimumProperty, value); }
    }
    #endregion        
}

O problema A maior parte do código de amostra fornecido é enfadonho e a mecânica funciona muito bem. O problema que estou tendo é um problema visual especificamente com o controle ScrollViewer que tenho na janela principal. O ScrollViewer parece estar ajustando automaticamente o deslocamento horizontal do ScrollViewer quando qualquer um dos Slider ganha o foco (de um clique do mouse, por exemplo).

Reproduzindo o comportamento

  1. Execute o aplicativo, você verá que a barra de rolagem horizontal do ScrollViewer está visível.
  2. Clique em Verde (extrema esquerda) Slider, você notará que o ScrollViewer se ajusta automaticamente para deslocar o deslocamento horizontal para onde o 'conteúdo' percebido começa.

Esses sintomas ocorrem em qualquer extremidade do painel de rolagem.

Captura de tela do aplicativo quando executado (o aplicativo foi ampliado em 200% para maior clareza dos detalhes):

Screenshot1

Captura de tela do comportamento quando o controle deslizante esquerdo é clicado:

enter image description here

O que eu quero que aconteça:

Quando eu clico em qualquer item do controle deslizante (em qualquer extremidade) quando um controle deslizante parece estar além do final do controle deslizante (o intervalo do controle deslizante é indicado pela barra preta no topo) Eu não deseja que o ScrollViewer ajuste automaticamente seu deslocamento horizontal.

Suspeita de problema:

Suspeito que o problema é que o ScrollViewer percebe que o "conteúdo" real de seus filhos começa em 15 pixels (a largura desenhada de ambos os controles deslizantes) de onde o conteúdo real desenhado começa. O Canvas só desenha porque eu incluí um preenchimento de 15 pixels dentro do controle SliderTest na janela principal, se esse preenchimento for removido, o ScrollViewer não mostra nenhuma tela do Slider.

EDITAR : parece que o preenchimento não é o problema, leia os comentários para saber o motivo.

coisas que eu tentei

Tentei substituir o evento OnPreviewMouseDown da janela principal. O problema aqui é que eu ainda quero que os dois Sliders se comportem normalmente, definir o evento como Handled faz com que o Slider pare de funcionar completamente.

Notas:

O Slider dentro do controle do seletor de intervalo (chamado SliderTest neste exemplo) deve ter a largura de 1 pixel. O controle deslizante deve ser capaz de estender 15 pixels além do final do intervalo de seleção de tempo (consulte a barra preta na parte superior para uma referência).

Obrigado por ler este problema extenso.

33
user989056

Por padrão, quando um controle recebe o foco lógico, FrameworkElement chama seu próprio método BringIntoView (de dentro de seu método OnGotFocus se ele tiver o foco do teclado). Isso resulta na geração de um evento RequestBringIntoView que surge na árvore de elementos para permitir que os elementos ancestrais tragam essa parte do elemento à vista. O ScrollViewer escuta esse evento e, eventualmente, chamará MakeVisible no IScrollInfo/ScrollContentPresenter associado, o que deixa a cargo do painel trazer essa parte à vista (já que o painel saberia como ele organiza seus filhos). Em seguida, ele pega o retângulo retornado que recebe de volta e pede que essa parte de si mesmo seja exibida (no caso de você ter elementos aninhados que exigiriam alguma ação para garantir que o elemento original fosse trazido à exibição). Portanto, uma maneira de suprimir esse comportamento seria tratar o evento RequestBringIntoView nos controles deslizantes e marcar o evento como tratado.

41
AndrewS