从代码中:将ScrollViewer的滚动条样式更改为触摸

5

触摸:

enter image description here

鼠标:

enter image description here

我该如何在代码中告诉 ScrollViewer 开始使用触摸式滚动条?

以下是一个示例:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ScrollViewer Name="scrollViewer1" HorizontalScrollBarVisibility="Visible" >
        <Image Stretch="UniformToFill">
            <Image.Source>
                <BitmapImage x:Name="bitmapImage1" UriSource="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.png"></BitmapImage>
            </Image.Source>
        </Image>
    </ScrollViewer>
</Grid>

并且:

public sealed partial class MainPage : Page
{
    DispatcherTimer dispatcherTimer1 = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
    bool SE;

    public MainPage()
    {
        this.InitializeComponent();
        dispatcherTimer1.Tick += DispatcherTimer1_Tick;
        dispatcherTimer1.Start();
    }

    private void DispatcherTimer1_Tick(object sender, object e)
    {
        if (SE = !SE) bitmapImage1.UriSource = new Uri("https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/se/se-icon.png");
        else bitmapImage1.UriSource = new Uri("https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.png");
        scrollViewer1.ChangeView(SE ? 1 : 0, SE ? 1 : 0, null);
    }
}

如果您在触摸屏电脑上运行此程序,滚动条将初始为触摸模式。如果您随后用鼠标将光标移动到其上方,则它将变为鼠标模式。但是,如果您在滚动条被隐藏后再次进行触摸,则它将返回到触摸模式。
我希望以编程方式告诉它从一个模式切换到另一个模式。如何实现?如果唯一的方法是通过编辑模板,那么如何在不硬编码 the template 的情况下解决这个问题呢?只需修复需要修复的细节。明确一下:我想调用一个方法来切换模式:void ChangeTo(bool mouse) { ... }。(尽管失败了,强制使ScrollViewer始终处于一种模式也可以算是一种解决方法。)
3个回答

2
Windows有两种滚动条可视化效果,基于用户的输入模式:使用触摸或游戏手柄时为滚动指示器;对于包括鼠标、键盘和笔等其他输入设备,则为交互式滚动条

enter image description here

平移指南中,它声明了:

基于检测到的输入设备,有两种平移显示模式:

  • 触摸设备的平移指示器。
  • 其他输入设备(包括鼠标、触摸板、键盘和触控笔)的滚动条。

注意 只有当触摸接触点在可平移区域内时,才会显示平移指示器。同样地,只有当鼠标光标、笔/触控笔光标或键盘焦点在可滚动区域内时,才会显示滚动条。

平移指示器 平移指示器类似于滚动条中的滑块。它们指示显示内容与总可平移区域的比例以及显示内容在可平移区域中的相对位置。

注意 与标准滚动条不同,平移指示器仅用于信息提示。它们不能被输入设备操作或操纵。

所以显示模式基于用户的输入模式,我们无法通过编程方式从一个模式更改为另一个模式。我们可以编辑ScrollViewer的模板,使ScrollViewer仅使用一种可视化UI。
在默认样式中,我们可以找到ScrollViewer有三个VisualState:NoIndicator、TouchIndicator和MouseIndicator,用于控制显示模式。我们可以更改TouchIndicatorMouseIndicator可视状态,使ScrollViewer始终处于一种显示模式。
例如,我们可以将“TouchIndicator”VisualState下的Storyboard替换为“MouseIndicator”VisualState下的Storyboard,使ScrollViewer始终处于滚动条模式,如下所示:
<ControlTemplate x:Key="MouseIndicatorTemplate" TargetType="ScrollViewer">
    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ScrollingIndicatorStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition From="MouseIndicator" To="NoIndicator">
                        <Storyboard>
                            <FadeOutThemeAnimation BeginTime="0:0:3" TargetName="ScrollBarSeparator" />
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:3">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:3">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualTransition>
                    <VisualTransition From="TouchIndicator" To="NoIndicator">
                        <Storyboard>
                            <FadeOutThemeAnimation TargetName="ScrollBarSeparator" />
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualTransition>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="NoIndicator">
                    <Storyboard>
                        <FadeOutThemeAnimation TargetName="ScrollBarSeparator" />
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="TouchIndicator">
                    <Storyboard>
                        <FadeInThemeAnimation TargetName="ScrollBarSeparator" />
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="MouseIndicator">
                    <Storyboard>
                        <FadeInThemeAnimation TargetName="ScrollBarSeparator" />
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid Background="{TemplateBinding Background}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ScrollContentPresenter x:Name="ScrollContentPresenter"
                                    Grid.RowSpan="2"
                                    Grid.ColumnSpan="2"
                                    Margin="{TemplateBinding Padding}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}" />
            <Grid Grid.RowSpan="2" Grid.ColumnSpan="2" />
            <ScrollBar x:Name="VerticalScrollBar"
                       Grid.Column="1"
                       HorizontalAlignment="Right"
                       IsTabStop="False"
                       Maximum="{TemplateBinding ScrollableHeight}"
                       Orientation="Vertical"
                       ViewportSize="{TemplateBinding ViewportHeight}"
                       Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                       Value="{TemplateBinding VerticalOffset}" />
            <ScrollBar x:Name="HorizontalScrollBar"
                       Grid.Row="1"
                       IsTabStop="False"
                       Maximum="{TemplateBinding ScrollableWidth}"
                       Orientation="Horizontal"
                       ViewportSize="{TemplateBinding ViewportWidth}"
                       Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                       Value="{TemplateBinding HorizontalOffset}" />
            <Border x:Name="ScrollBarSeparator"
                    Grid.Row="1"
                    Grid.Column="1"
                    Background="{ThemeResource ScrollViewerScrollBarSeparatorBackground}" />
        </Grid>
    </Border>
</ControlTemplate>

"反之亦然。"
<ControlTemplate x:Key="TouchIndicatorTemplate" TargetType="ScrollViewer">
    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ScrollingIndicatorStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition From="MouseIndicator" To="NoIndicator">
                        <Storyboard>
                            <FadeOutThemeAnimation BeginTime="0:0:3" TargetName="ScrollBarSeparator" />
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:3">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:3">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualTransition>
                    <VisualTransition From="TouchIndicator" To="NoIndicator">
                        <Storyboard>
                            <FadeOutThemeAnimation TargetName="ScrollBarSeparator" />
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                    <DiscreteObjectKeyFrame.Value>
                                        <ScrollingIndicatorMode>None</ScrollingIndicatorMode>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualTransition>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="NoIndicator">
                    <Storyboard>
                        <FadeOutThemeAnimation TargetName="ScrollBarSeparator" />
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="TouchIndicator">
                    <Storyboard>
                        <FadeOutThemeAnimation TargetName="ScrollBarSeparator" />
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="MouseIndicator">
                    <Storyboard>
                        <FadeOutThemeAnimation TargetName="ScrollBarSeparator" />
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode" Duration="0">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid Background="{TemplateBinding Background}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ScrollContentPresenter x:Name="ScrollContentPresenter"
                                    Grid.RowSpan="2"
                                    Grid.ColumnSpan="2"
                                    Margin="{TemplateBinding Padding}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}" />
            <Grid Grid.RowSpan="2" Grid.ColumnSpan="2" />
            <ScrollBar x:Name="VerticalScrollBar"
                       Grid.Column="1"
                       HorizontalAlignment="Right"
                       IsTabStop="False"
                       Maximum="{TemplateBinding ScrollableHeight}"
                       Orientation="Vertical"
                       ViewportSize="{TemplateBinding ViewportHeight}"
                       Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                       Value="{TemplateBinding VerticalOffset}" />
            <ScrollBar x:Name="HorizontalScrollBar"
                       Grid.Row="1"
                       IsTabStop="False"
                       Maximum="{TemplateBinding ScrollableWidth}"
                       Orientation="Horizontal"
                       ViewportSize="{TemplateBinding ViewportWidth}"
                       Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                       Value="{TemplateBinding HorizontalOffset}" />
            <Border x:Name="ScrollBarSeparator"
                    Grid.Row="1"
                    Grid.Column="1"
                    Background="{ThemeResource ScrollViewerScrollBarSeparatorBackground}" />
        </Grid>
    </Border>
</ControlTemplate>

一旦我们拥有这两个模板,我们就可以使用ScrollViewer.Template属性来从一个模式切换到另一个模式,如下所示(“MouseIndicatorTemplate”和“TouchIndicatorTemplate”放置在Page.Resources中):
void ChangeTo(bool mouse)
{
    if (mouse)
    {
        scrollViewer1.Template = (ControlTemplate)Resources["MouseIndicatorTemplate"];
    }
    else
    {
        scrollViewer1.Template = (ControlTemplate)Resources["TouchIndicatorTemplate"];
    }
}

2

默认模板中定义了三个可视状态:无指示器、触摸指示器和鼠标指示器。

滚动条拇指的样式取决于当前设置的状态。要更改控件状态,可以调用

VisualStateManager.GoToState(scrollViewer1, "TouchIndicator");

但是,如果您想始终显示TouchIndicator,则在我看来更好的解决方案是实现CustomVisualStateManager,例如:

但是,当状态发生变化时,您需要手动处理所有事件和操作。

public class MyVisualStateManager : VisualStateManager
{
    protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, 
        System.String stateName, VisualStateGroup group, VisualState state, System.Boolean useTransitions)
    {
        switch (stateName)
        {
            case "NoIndicator":
            case "TouchIndicator":
            case "MouseIndicator":
                base.GoToStateCore(control, templateRoot, "TouchIndicator", group, state, useTransitions);
                break;
        }
        return true;
    }
}

接下来,您需要从MSDN复制模板,将其设置为您的ScrollViewer,并在其中放置MyVisualStateManager:

<Style TargetType="ScrollViewer" x:Key="ScrollStyle">
        <Setter Property="HorizontalScrollMode" Value="Auto" />
        <Setter Property="VerticalScrollMode" Value="Auto" />
        <Setter Property="IsHorizontalRailEnabled" Value="True" />
        <Setter Property="IsVerticalRailEnabled" Value="True" />
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="ZoomMode" Value="Disabled" />
        <Setter Property="HorizontalContentAlignment" Value="Left" />
        <Setter Property="VerticalContentAlignment" Value="Top" />
        <Setter Property="VerticalScrollBarVisibility" Value="Visible" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="BorderBrush" Value="Transparent" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ScrollViewer">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                        <VisualStateManager.CustomVisualStateManager>
                            <local:MyVisualStateManager/>
                        </VisualStateManager.CustomVisualStateManager>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ScrollingIndicatorStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition From="MouseIndicator" To="NoIndicator">
                                        <Storyboard>
                                (... blabla ...)
</Style>

style set:

<ScrollViewer Name="scrollViewer1"  Style="{StaticResource ScrollStyle}" HorizontalScrollBarVisibility="Visible">

现在,每当需要更改ScrollViewer状态时,您忽略了它想要的确切状态,而是设置了TouchIndicator。

谢谢!看起来就是这样了。虽然我不明白你所说的“但是当状态改变时,您需要手动处理所有事件和操作”,因为我没有取消订阅任何事件,为什么会有任何变化(当然除了状态)? - ispiro
例如,每当鼠标指针进入和退出ScrollViewer时(即使您记得将鼠标移动到其中),都会触发更改ScrollViewer状态。因此,如果您使用VisualStateManager.GoToState(something)设置它,那么如果任何鼠标指针进入,则会更改并设置为IndicatorMouse。看起来像是与系统争论应设置哪个状态(我的天啊!;)使用CustomiVisualStateManager更好的一种方式是切断系统的方式,您可以决定何时以及应设置哪个状态。 - RTDev
我认为@maxp的解决方案是正确的,适用于2020年。我已经测试过它,运行得非常完美。 - John T

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接