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

From code: Change ScrollViewer's scrollbars'-style to touch

触摸:

鼠标:

如何通过代码告诉 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/Whosebug/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/Whosebug/company/img/logos/se/se-icon.png");
        else bitmapImage1.UriSource = new Uri("https://cdn.sstatic.net/Sites/Whosebug/company/img/logos/so/so-icon.png");
        scrollViewer1.ChangeView(SE ? 1 : 0, SE ? 1 : 0, null);
    }
}

如果您运行这样做(至少在支持触摸的 PC 上),滚动条最初将是触摸的。如果您随后用鼠标将光标移到它上面,它将变为鼠标。如果您随后触摸它(在隐藏滚动条之后),它将 return 触摸。

我想以编程方式告诉它从一个更改为另一个。那怎么办?如果唯一的方法是编辑模板 - 没有 硬编码 the template 如何做到这一点?只需修复需要修复的细节。明确一点:我希望能够调用一种方法,该方法会从一个变为另一个:void ChangeTo(bool mouse) { ... }。 (尽管如此,如果做不到这一点,仅强制 ScrollViewer 始终处于一种模式,将是一种解决方法。)

Windows 有两个滚动条可视化,它们基于用户的输入模式:滚动指示器 使用触摸或游戏手柄时;以及用于其他输入设备(包括鼠标、键盘和笔)的交互式 滚动条

而在Guidelines for panning中声明

There are two panning display modes based on the input device detected:

  • Panning indicators for touch.
  • Scroll bars for other input devices, including mouse, touchpad, keyboard, and stylus.

Note Panning indicators are only visible when the touch contact is within the pannable region. Similarly, the scroll bar is only visible when the mouse cursor, pen/stylus cursor, or keyboard focus is within the scrollable region.

Panning indicators Panning indicators are similar to the scroll box in a scroll bar. They indicate the proportion of displayed content to total pannable area and the relative position of the displayed content in the pannable area.

Note Unlike standard scroll bars, panning indicators are purely informative. They are not exposed to input devices and cannot be manipulated in any way.

因此显示模式是基于用户的输入模式,我们无法通过编程将其从一种更改为另一种。我们可以做的是编辑 ScrollViewer's template,这样 ScrollViewer 将只使用一个可视化 UI。

在默认样式中,我们可以发现ScrollViewer有三个VisualStateNoIndicatorTouchIndicatorMouseIndicator,用于控制显示模式。我们可以更改 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"];
    }
}

在默认模板中定义了 3 个 VisualStates: 没有指标, 触摸指示器和 鼠标指示器

滚动条拇指的样式根据当前设置的状态而有所不同。 要更改控件状态,您可以调用

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>

样式集:

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

现在,只要您的 ScrollViewer 状态需要更改,您就会忽略它想要的确切状态,而是设置 TouchIndicator。

我一直在寻找恢复 WPF 应用程序可能期望的简单滚动条行为(完全删除触摸指示器)。 @Jay Zuo 给出的例子没有用,所以我创建了自己的。可以在以下存储库中查看模板:

https://github.com/jsymon/UWP-Classic-ScrollBar-Template