Microsoft-ui-xaml: 更灵活的 ScrollViewer

创建于 2018-12-19  ·  61评论  ·  资料来源: microsoft/microsoft-ui-xaml

这是新功能或 API 提案的模板。 例如,您可以使用它来针对现有类型提出新的 API,或者提出新的 UI 控件的想法。 如果您没有所有详细信息也没关系:您可以从摘要和基本原理开始。 此链接描述了 WinUI 功能/API 提案流程:https://github.com/Microsoft/microsoft-ui-xaml-specs/blob/master/docs/feature_proposal_process.md 为您的功能或 API 提案添加标题。 请简短描述

提案:ScrollViewer

概括

tl:博士; 在 XAML 中提供灵活但易于使用的滚动和缩放控件,在定位 Win10 的下层版本时仍然可以利用这些控件。

ScrollViewer 控件在任何 UI 中都起着至关重要的作用,因为滚动对于应用程序和控件(平台和非平台等)来说是多么基本。 该控件提供平移和缩放功能以及默认策略和 UX(例如有意识的滚动条、焦点/键盘交互、辅助功能、默认滚动动画等)。 它还必须足够灵活,可以从让日常应用场景变得简单到成为基于滚动控件的关键组件并为非常高级的用例提供服务。 当前的控件不提供这种级别的灵活性。

基本原理

请描述为什么应该为所有开发人员和用户将该功能添加到 WinUI。 如果适用,您还可以描述它如何与当前的 WinUI 路线图和优先级保持一致:https://github.com/Microsoft/microsoft-ui-xaml-specs/blob/master/docs/roadmap.md

以在核心平台的公共 API 上分层的方式将 ScrollViewer 控件引入存储库是一个重要的垫脚石:

  1. 可以将更多控制权提升到回购中(提高敏捷性),
  2. 为开发人员提供易于使用的控制,将较低级别平台功能的灵活性与较高级别框架服务(例如可访问性)的优势相结合,以及
  3. 启用 XAML 中较新的滚动相关功能,以便在较早版本的 Win10 上亮起。

现有控件是在 Win8 中基于DirectManipulation API 编写的,其中 1) 不能直接在 UWP 中使用,2) 不允许开发人员期望的自定义​​级别来创建独特的滚动体验,以及 3) 被取代由 UWP 较新的 InteractionTracker API 提供。

按原样提取现有的 ScrollViewer 控件将是一个非启动器。 该控件必须是基于InteractionTracker的更新(和已经可用)功能的几乎相同的 API 表面的重新实现。

高层计划

_Windows_.UI.Xaml.Controls 命名空间中的现有 ScrollViewer 将保持不变(除了修复关键错误)。

新的 ScrollViewer 将位于 _Microsoft_.UI.Xaml.Controls 命名空间中。 它的 API 将与 _Windows_ 版本大致相同。 我们将在有明显优势的地方(简化常见场景、引入新功能等)对控件的 API 进行有针对性的调整。

结合新的 ScrollViewer,我们将引入一种新类型 Scroller,它将位于 Microsoft.UI.Xaml.Controls._Primitives_ 命名空间中。 Scroller 将基于InteractionTracker的功能提供在 XAML 中滚动和缩放的核心功能。

这两个控件的最初目标是让核心功能交付质量成为下一个正式版本的一部分。 未最终确定的 API 将保留在“预览版”中,并且仅作为预发布包的一部分提供。

-------------------- 提交想法或提案时,以下部分是可选的。 在我们接受 PR 以掌握之前,所有部分都是必需的,但不是开始讨论所必需的。 ---------------

功能要求


| # | 特色 | | 优先级 |
|:-:|:--|-|:-:|
| 1 | 提供一个 _non-sealed_、平移和缩放控件,其默认 UX 与现有的 ScrollViewer 匹配。 || 必须|
| 2 | 仅依靠公共的、平台支持的 API 来展示平台功能。 || 必须|
| 3 | 与框架级 XAML 服务集成。
例如:
- 正确呈现系统焦点矩形
- 自动将焦点元素带入视图(键盘、游戏板、屏幕阅读器)
- 参与有效的视口变化
- 支持滚动锚定|| 必须|
| 4 | 能够执行输入驱动的动画|| 必须|
| 5 | 可以与存储库中已有的依赖滚动的现有控件(即 ParallaxView、RefreshContainer、SwipeControl)结合使用。 || 必须|
| 6 | 能够控制惯性视图变化的曲线(即自定义动画滚动或缩放)。 || 必须|
| 7 | 能够根据绝对或相对偏移量更改视图(例如滚动到兴趣点) || 必须|
| 8 | 能够根据脉冲/附加速度更改视图(例如,通过鼠标选择矩形在拖放或多选期间自动平滑滚动) || 必须|
| 9 | 能够检测overpan和多少|| 必须|
| 10 | 能够观察滚动状态的变化并做出反应|| 必须|
| 11 | 支持滚动和缩放捕捉点。 || 应该|
| 12 | 能够使用自定义的“滚动条”控件来驱动滚动(例如照片时间线擦洗器)。 || 应该|
| 13 | 能够轻松配置控件以忽略特定输入类型(例如响应触摸或笔,但忽略鼠标滚轮输入)。 || 应该|
| 14 | 能够保存和恢复滚动位置(例如在虚拟化列表中) || 应该|
| 15 | 支持粘性标题/元素。 || 应该|
| 16 | 当用户快速连续地做出重复手势时,支持加速滚动。 || 应该|
| 17 | 提供默认 UX 以支持鼠标中键和滚动 |mousewheel panning | 应该|
| 18 | 支持通过鼠标单击和平移的模式(例如在 PDF 查看器中移动内容)。 |mouse hand cursor for panning | 应该|
| 19 | 能够使用自定义控件来驱动缩放(例如缩放滑块)。 || 应该|
| 20 | 能够打印包含在可滚动区域中的内容。 || 应该|
| 21 | 支持旋转手势。 || 可以|
| 22 | 能够通过无闪烁和无卡顿滚动同步两个或多个区域(例如文本差异场景)。 || 可以|
| 23 | 能够为输入驱动的视图更改自定义动画曲线(即定义手指向下的行为,例如重力井)。 || 可以|
| 24 | 支持滚动到顶部或底部手势。 || 可以|
| 25 | 能够禁用过度平移。 || 可以|
| 26 | 能够根据 UIElement 的 ManipulationMode 属性有选择地操作 ScrollViewer 中的内容。 || 可以|
| 27 | 能够关闭惯性和/或从惯性反弹。 || 可以|
| 28 | 能够禁用剪切内容(即CanContentRenderOutsideBounds )。 || 可以|
| 29 | 能够在视图更改结束时确定完成原因。 || 可以|

术语

  • _Overpan_是当用户尝试平移或缩放超出内容大小并导致弹性/橡皮筋效果时。
  • _Railing_是当滚动条根据初始手势的方向自动将移动锁定到“首选”轴时。
  • _Chaining_是当有一个滚动表面嵌套在另一个表面内时,当内部的滚动运动达到其范围时,它会被提升到外部的继续。
  • _Snap points_是可滚动内容中的位置,在惯性滚动(即用户抬起手指后的动画滚动)结束时,视口将停止在该位置。 像 FlipView 这样的控件需要捕捉点。
  • _滚动锚定_是视口自动移动以保持可见内容的相对位置的位置。 它可以防止用户因布局而受到内容位置或大小突然变化的影响(即用户正在阅读一篇文章,几分钟后一切都跳了下来,因为文章顶部的图片/广告终于加载了) . 在处理可变大小的内容时,滚动锚定是 UI 虚拟化的必要条件。
  • _重力井_是可滚动内容内的位置,当用户操作内容时(即用户的手指向下)影响滚动行为。 例如,重力井可用于改变滚动的感知摩擦力,当用户主动平移或缩放时,移动似乎减慢或加快。

使用示例

请提供一个或多个示例,说明您将如何使用该功能。 如果有帮助,您可以包含代码示例或屏幕截图

前言

默认情况下,当内容的大小大于其视口时,ScrollViewer 支持平移其内容。 内容的大小由 XAML 的布局系统确定。

此处的示例旨在突出当前 ScrollViewer 和新 ScrollViewer 之间的主要 API 差异。

垂直滚动(没有区别)

内容的宽度会自动限制为与视口相同。 高度不受约束。 如果它超过视口的高度,则用户可以通过各种输入方式进行平移。

<ScrollViewer Width="500" Height="400">
    <ItemsRepeater ItemsSource="{x:Bind ViewModel.Items}" ItemTemplate="{StaticResource MyTemplate}"/>
</ScrollViewer>

水平滚动

这是有意偏离现有的 ScrollViewer,后者以前需要更改 Horizo​​ntalScrollBarVisibility。 它让许多开发人员感到困惑。

<ScrollViewer Width="500" Height="400" ContentOrientation="Horizontal">
    <StackPanel Orientation="Horizontal">
        <!-- ... -->
    </StackPanel>
</ScrollViewer>

通过设置_ContentOrientation_属性启用仅水平滚动。 它确定在布局期间对内容使用哪些约束。 Horizo​​ntal 值意味着内容在水平方向(允许无限大小)和垂直方向不受约束以匹配视口。 类似地,Vertical(默认)意味着其垂直不受约束和水平受约束。

滚动大图像

_Both_ 的 ContentOrientation 意味着内容在水平和垂直方向都不受约束。

<ScrollViewer Width="500" Height="400" ContentOrientation="Both">
    <Image Source="Assets/LargeEiffelTower.png"/>
</ScrollViewer>

更改滚动条可见性

此示例始终隐藏两个滚动条。 用户仍然可以向任一方向平移内容。

<ScrollViewer Width="500" Height="400"
              ContentOrientation="Both"
              HorizontalScrollBarVisibility="Hidden"
              VerticalScrollBarVisibility="Hidden">
    <Image Source="Assets/LargeEiffelTower.png"/>
</ScrollViewer>

关闭对特定类型输入的内置支持

在此示例中,开发人员正在创建一个基于画布的应用程序,该应用程序将对鼠标滚轮输入执行自定义处理并通过 Pen 支持套索选择体验。 它可以将 ScrollViewer 配置为忽略这些输入类型,同时仍然接受其他类型,例如 Touch。

<ScrollViewer IgnoredInputKind="MouseWheel,Pen"
              Width="500" Height="400"
              ContentOrientation="Both" >
    <SwapChainPanel x:Name="swapChainPanel" Width="40000" Height="40000">
        ...
    </SwapChainPanel>
</ScrollViewer>

自定义程序滚动的动画

开发人员侦听 Slider 的 ValueChanged 事件并使用默认动画的自定义持续时间为 ScrollViewer 的 VerticalOffset 设置动画。

private void VerticalOffsetSlider_ValueChanged(
    object sender,
    RangeBaseValueChangedEventArgs e)
{
    double verticalOffsetDelta = GetOffsetDelta();
    TimeSpan animationDuration = GetCustomAnimationDuration();

    // Initiate the scroll and enqueue the ScrollInfo we'll use to identify related events
    ScrollInfo scrollInfo = _scrollViewer.ScrollBy(0.0, verticalOffsetDelta);
    _myScrollRequests.Add(new MyScrollRequest(scrollInfo, animationDuration));
}

// Listen to the ScrollViewer's ScrollAnimationStarting event and customize 
// the default animation
private void ScrollViewer_ScrollAnimationStarting(
    ScrollViewer scrollViewer,
    ScrollAnimationStartingEventArgs e)
{
    MyScrollRequest myScrollRequest = _myScrollRequests.FindRequest(e.ScrollInfo);
    e.Animation.Duration = myScrollRequest.AnimationDuration;
}

// Dequeue the ScrollInfo once the action completes
private void ScrollViewer_ScrollCompleted(
    ScrollViewer scrollViewer,
    ScrollCompletedEventArgs e)
{
    _myScrollRequests.RemoveRequest(e.ScrollInfo);
}

详细的功能设计

请包括任何重要的设计细节。 这可能包括以下一项或多项: - API 提案(任何受支持的语言或伪代码都可以) - 为新用户体验设计模型 - 有关新 UI 可访问性合规性的详细信息 - 其他实施说明

高策略和低策略滚动

滚动条(低策略)

Scroller 是一个无铬的低级构建块,提供所有基本的平移和缩放逻辑。 它将平台的甚至更低策略的InteractionTracker包装为 XAML 标记友好元素。
从字面上看,Scroller 是 ScrollViewer 的“视口”,取代了 ScrollContentPresenter。 但是,与 ScrollContentPresenter 不同,Scroller 的作用远不止简单地剪辑其内容。

Scroller 提供了直接使用 InteractionTracker 的灵活性以及以下优点:

  • 辅助功能支持
  • 标记友好的语法和易于使用的 API
  • 与 XAML 的布局系统和虚拟化功能集成

ScrollViewer(高策略)

新的 ScrollViewer 包装了 Scroller 并将其属性设置为通用值。 例如, <ScrollViewer/>将其 Scroller 配置为支持水平和垂直滚动交互,但限制其内容的宽度,使得默认用户体验看起来是仅垂直滚动(常见情况)。

与使用 Scroller 相比,ScrollViewer 具有以下优点:

  • 默认用户体验(例如滚动指示器、鼠标的有意识滚动条、默认动画曲线)
  • 滚动器/交互跟踪器(即键盘和游戏板)不处理的 UI 线程绑定输入的默认支持
  • GamePad 的默认焦点移动(向上/向下翻页并自动设置焦点以响应触发器)
  • 用户系统设置的默认感知(例如在 Windows 中自动隐藏滚动条)
  • (未来)捕捉点的简单配置选项
  • (未来)支持更多的鼠标平移模式(例如,用张开/合拢的手点击并拖动内容,通过鼠标滚轮/中键单击显示“指南针”光标的鼠标平移)

使用哪一种?

应用程序和许多控件作者的默认选择应该是使用 ScrollViewer。 它提供了更高的易用性和与平台一致的默认用户体验。 当不需要默认 UX/策略时,滚动器是合适的 - 例如,创建改进的 FlipView 控件或无铬滚动表面。

仅限 ScrollViewer 的 API

Horizo​​ntalScrollBarVisibility / VerticalScrollBarVisibility

水平和垂直滚动条的默认值都是 _Auto_。 它们会根据内容是否比视口宽/高而自动显示或隐藏。

新 ScrollViewer 可用的枚举选项中将不存在“禁用”选项。 相反,将控件配置为响应水平或垂直平移的用户交互将仅基于 _Horizo​​ntalScrollMode_ 和 _VerticalScrollMode_ 属性。

namespace Microsoft.UI.Xaml.Controls
{
    public enum ScrollBarVisibility
    {
        Auto = 0,    // Only visible when the ZoomFactor * content size > viewport
        Visible = 1, // Always visible
        Hidden = 2   // Always hidden
    }
}

在下图中,鼠标光标位于垂直滚动条上。 它是唯一可见的,因为内容与视口的宽度相同。

当内容在两个维度上都大于视口时,可以在右下角看到两个有意识的滚动条以及它们的分隔符。

仅限滚动器的 API

水平滚动控制器/垂直滚动控制器

通过将 _Horizo​​ntalScrollController_ 和 _VerticalScrollController_ 设置为实现 _IScrollController_ 接口的类型,可以将 Scroller 连接到控制滚动的交互式“小部件”。 滚动条是此类小部件的常见示例。 ScrollViewer 为其 Scroller 提供了两个这样的小部件。 例如,开发人员可以创建一个自定义 IScrollController 实现,该实现依赖于用于 UI 线程独立输入的 Composition.Visual。

_scroller.HorizontalScrollController = new Acme.Slider(orientation: Orientation.Horizontal);
_scroller.VerticalScrollController = new Acme.Slider(orientation: Orientation.Vertical);

ScrollViewer / Scroller 的下层限制

从 Windows 2018 年 4 月 10 日更新开始,该框架应公开所有必要的挂钩以构建自定义滚动控件。 以早期版本为目标时,可能存在以下限制:
| 发布 | 限制 | 原因 |
|:-:|:--|:-:|
| Windows 10 Fall Creators Update(内部版本 16299)及更早版本 | 接收焦点后,元素不会自动进入视图。 | UIElement 的BringIntoViewRequested 事件不可用|
| | 该控件不能参与 EffectiveViewportChanged 事件。 系统渲染的焦点矩形不会被裁剪到视口的边界。 | UIElement 的 RegisterAsScrollPort 不可用 |

提议的API

滚动查看器

```C#
公共类 Microsoft.UI.Xaml.Controls.ScrollViewer :控件
{
滚动查看器();

// Default Value: non-null instance
Windows.UI.Composition.CompositionPropertySet ExpressionAnimationSources { get; }

/*

  • 以布局为中心的属性
    */
    // 默认值:空
    UIElement 内容 { 获取; 放; };
// Default Value: Vertical
Microsoft.UI.Xaml.Controls.ContentOrientation ContentOrientation { get; set; };

// Default Value: 0.0
Double HorizontalOffset { get; };

// Default Value: 0.0
Double VerticalOffset { get; };

// Default Value: 1.0
Single ZoomFactor { get; };

// Default Value: 0.0
Double ExtentWidth { get; };

// Default Value: 0.0
Double ExtentHeight { get; };

// Default Value: 0.0
Double ViewportWidth { get; };

// Default Value: 0.0
Double ViewportHeight { get; };

// Default Value: 0.0
Double ScrollableWidth { get; };

// Default Value: 0.0
Double ScrollableHeight { get; };

// Default Value: Auto
M.UI.Xaml.Controls.ScrollBarVisibility HorizontalScrollBarVisibility {get;set;};

// Default Value: Auto
M.UI.Xaml.Controls.ScrollBarVisibility VerticalScrollBarVisibility {get;set;};

// Default Value: Collapsed
// Used for template binding the Visibility property of the horizontal
// ScrollBar in the control template
Visibility ComputedHorizontalScrollBarVisibility{ get; };

// Default Value: Collapsed
// Used for template binding the Visibility property of the vertical
// ScrollBar in the control template
Visibility ComputedVerticalScrollBarVisibility{ get; };

/*

  • 以用户交互为中心的属性
    */
    // 默认值:启用
    Microsoft.UI.Xaml.Controls.ScrollMode Horizo​​ntalScrollMode { 获取; 放; };
// Default Value: Enabled
Microsoft.UI.Xaml.Controls.ScrollMode VerticalScrollMode { get; set; };

// Default Value: Disabled
Microsoft.UI.Xaml.Controls.ZoomMode ZoomMode { get; set; };

// Default Value: All
Microsoft.UI.Xaml.Controls.InputKind IgnoredInputKind { get; set; };

// Default Value: Idle
Microsoft.UI.Xaml.Controls.InteractionState State { get; };

// Default Value: Auto
Microsoft.UI.Xaml.Controls.ChainingMode HorizontalScrollChainingMode { get; set; };

// Default Value: Auto
Microsoft.UI.Xaml.Controls.ChainingMode VerticalScrollChainingMode { get; set; };

// Default Value: True
boolean IsHorizontalRailEnabled { get; set; };

// Default Value: True
boolean IsVerticalRailEnabled { get; set; };

// Default Value: Auto
Microsoft.UI.Xaml.Controls.ChainingMode ZoomChainingMode { get; set; };

// Default Value: None
M.UI.Xaml.Controls.SnapPointsType HorizontalSnapPointsType { get; set; };

// Default Value: None
M.UI.Xaml.Controls.SnapPointsType VerticalSnapPointsType { get; set; };

// Default Value: Near
M.UI.Xaml.C.Primitives.SnapPointsAlignment HorizontalSnapPointsAlignment { g;s; };

// Default Value: Near
M.UI.Xaml.C.Primitives.SnapPointsAlignment VerticalSnapPointsAlignment { g;s; };

// Default Value: 0.95, 0.95
Windows.Foundation.Numerics.Vector2 ScrollInertiaDecayRate { get; set; }; 

// Default Value: 0.95
Single ZoomInertiaDecayRate { get; set; }; 

// Default Value: 0.1
Double MinZoomFactor { get; set; };

// Default Value: 10.0
Double MaxZoomFactor { get; set; };

// Default Value: 0.0
Double HorizontalAnchorRatio { get; set; };

// Default Value: 0.0
Double VerticalAnchorRatio { get; set; };

// Forwarded to inner Scroller’s IScrollAnchorProvider implementation
// Default Value: null
Windows.UI.Xaml.UIElement CurrentAnchor { get; };

/*

  • 方法
    */
    // 异步滚动到指定的偏移量。 允许动画,
    // 尊重捕捉点。 返回一个 ScrollInfo 结构。
    Microsoft.UI.Xaml.Controls.ScrollInfo ScrollTo(
    双水平偏移,
    双垂直偏移);
// Asynchronously scrolls to specified offsets with optional animation,
// with optional snap points respecting. Returns a ScrollInfo struct.
Microsoft.UI.Xaml.Controls.ScrollInfo ScrollTo(
    double horizontalOffset,
    double verticalOffset,
    Microsoft.UI.Xaml.Controls.ScrollOptions options);

// Asynchronously scrolls by the provided delta amount.
// Allows animation, respects snap points. Returns a ScrollInfo struct.
Microsoft.UI.Xaml.Controls.ScrollInfo ScrollBy(
    double horizontalOffsetDelta,
    double verticalOffsetDelta);

// Asynchronously scrolls by the provided delta amount with
// optional animation, with optional snap points respecting.
// Returns a ScrollInfo struct.
Microsoft.UI.Xaml.Controls.ScrollInfo ScrollBy(
    double horizontalOffsetDelta,
    double verticalOffsetDelta,
    Microsoft.UI.Xaml.Controls.ScrollOptions options);

// Asynchronously adds scrolling inertia. Returns a ScrollInfo struct.
Microsoft.UI.Xaml.Controls.ScrollInfo ScrollFrom(
    Vector2 offsetsVelocity,
    Nullable<Vector2> inertiaDecayRate);

// Asynchronously zooms to specified zoom factor. Allows animation
// (respects snap points in v2). Returns a ZoomInfo struct.
Microsoft.UI.Xaml.Controls.ZoomInfo ZoomTo(
    float zoomFactor,
    Nullable<Vector2> centerPoint);

// Asynchronously zooms to specified offsets with optional animation
// (with optional snap points respecting in v2). Returns a ZoomInfo struct.
Microsoft.UI.Xaml.Controls.ZoomInfo ZoomTo(
    float zoomFactor,
    Nullable<Vector2> centerPoint,
    Microsoft.UI.Xaml.Controls.ZoomOptions options);

// Asynchronously zooms by the provided delta amount. Allows animation
// (respects snap points in v2). Returns a ZoomInfo struct.
Microsoft.UI.Xaml.Controls.ZoomInfo ZoomBy(
    float zoomFactorDelta,
    Nullable<Vector2> centerPoint);

// Asynchronously zooms by the provided delta amount with optional animation
// (with optional snap points respecting in v2). Returns an ZoomInfo struct.
Microsoft.UI.Xaml.Controls.ZoomInfo ZoomBy(
    float zoomFactorDelta,
    Nullable<Vector2> centerPoint,
    Microsoft.UI.Xaml.Controls.ZoomOptions options);

// Asynchronously adds zooming inertia. Returns a ZoomInfo struct.
Microsoft.UI.Xaml.Controls.ZoomInfo ZoomFrom(
    float zoomFactorVelocity,
    Nullable<Vector2> centerPoint,
    Nullable<float> inertiaDecayRate);

/*

  • 转发到内部 Scroller 的 IScrollAnchorProvider 实现
    */
    void RegisterAnchorCandidate(UIElement 元素);
    void UnregisterAnchorCandidate(UIElement 元素);

/*

  • 活动
    */
    // 每当 Horizo​​ntalOffset、VerticalOffset 和 ZoomFactor 时引发
    // 依赖属性已更改。
    事件类型化事件处理程序视图已更改;
// Raised when any of the ExtentWidth and ExtentHeight dependency property changed.
event TypedEventHandler<ScrollViewer, Object> ExtentChanged;

// Raised when the State dependency property changed.
event TypedEventHandler<ScrollViewer, Object> StateChanged;

// Raised when a ScrollTo or ScrollBy call triggers an animation.
// Allows customization of that animation.
event TypedEventHandler<ScrollViewer, Microsoft.UI.Xaml.Controls.ScrollAnimationStartingEventArgs>
    ScrollAnimationStarting;

// Raised when a ZoomTo or ZoomBy call triggers an animation.
// Allows customization of that animation.
event TypedEventHandler
    <ScrollViewer, Microsoft.UI.Xaml.Controls.ZoomAnimationStartingEventArgs>
    ZoomAnimationStarting;

// Raised at the end of a ScrollTo, ScrollBy, or ScrollFrom asynchronous
// operation. Provides the original ScrollInfo struct.
event TypedEventHandler
    <ScrollViewer, Microsoft.UI.Xaml.Controls.ScrollCompletedEventArgs>
    ScrollCompleted;

// Raised at the end of a ZoomTo, ZoomBy, or ZoomFrom asynchronous operation.
// Provides the original ZoomInfo struct.
event TypedEventHandler
    <ScrollViewer, Microsoft.UI.Xaml.Controls.ZoomCompletedEventArgs>
    ZoomCompleted;

// Raised at the beginning of a bring-into-view-request participation.
// Allows customization of that participation. 
event TypedEventHandler
    <ScrollViewer, Microsoft.UI.Xaml.Controls.BringingIntoViewEventArgs>
    BringingIntoView;

// Raised to allow the listener to pick an anchor element, when anchoring
// is turned on.
event TypedEventHandler
    <ScrollViewer, Microsoft.UI.Xaml.Controls.AnchorRequestedEventArgs>
    AnchorRequested;

/*

  • 依赖属性
    */
    静态 DependencyProperty ContentProperty { 获取; };
    静态 DependencyProperty ContentOrientationProperty { 获取; };
    静态 DependencyProperty ComputedHorizo​​ntalScrollBarVisibilityProperty { 获取; };
    静态 DependencyProperty ComputedVerticalScrollBarVisibilityProperty { 获取; };
    静态 DependencyProperty Horizo​​ntalScrollBarVisibilityProperty { 获取; };
    静态 DependencyProperty VerticalScrollBarVisibilityProperty { 获取; };
static DependencyProperty IgnoredInputKindProperty { get; };
static DependencyProperty HorizontalScrollModeProperty { get; };
static DependencyProperty VerticalScrollModeProperty { get; };
static DependencyProperty ZoomModeProperty { get; };
static DependencyProperty HorizontalScrollChainingModeProperty {g};
static DependencyProperty VerticalScrollChainingModeProperty {g};
static DependencyProperty IsHorizontalRailEnabledProperty {g};
static DependencyProperty IsVerticalRailEnabledProperty {g};
static DependencyProperty ZoomChainingModeProperty { get; };
static DependencyProperty MinZoomFactorProperty { get; };
static DependencyProperty MaxZoomFactorProperty { get; };
static DependencyProperty HorizontalAnchorRatioProperty { get; };
static DependencyProperty VerticalAnchorRatioProperty { get; };

}
``

开放问题

  • 拥有一个 ContentOrientation 属性(或具有不同名称的属性)会影响如何将布局约束应用于内容会使事情变得更复杂或更容易吗?
  • 是否应该将与缩放相关的 API 分离到派生控件(例如 ZoomViewer)中,以便 ScrollViewer 严格控制滚动?
Epic area-Scrolling feature proposal proposal-NewControl team-Controls

最有用的评论

这是正确的。 我们考虑了 3 种情况:

  1. 我不在乎我现在的职位是什么。 我想滚动/缩放到特定目的地。
  2. 我关心我当前的位置是什么,我想相对于该位置滚动/缩放特定数量。
  3. 我不在乎最终目的地是什么。 我只想从我当前的位置滚动/缩放。

后者的场景可能类似于鼠标滚轮单击以平移体验。 根据光标相对于点击时的位置,从当前位置(无论是什么)滚动插入一些惯性。

所有61条评论

在我看来,需要可以滚动的内容和可以缩放的内容是非常不同的。 我知道当内容被缩放时,它通常也需要滚动,但我很好奇是否考虑过制作这些单独的控件? 可能带有从滚动控件扩展的可缩放控件。
我的担忧是:1)通过添加大多数不需要的缩放相关功能,使 ScrollViewer 变得过于复杂; 2) 对于正在寻找 ScrollViewer 支持的用于放大内容的控件的人来说,这并不明显。


此外,在提案中,我希望看到能够在滚动查看器中滚动到特定元素。 从理论上讲,这可以由开发人员通过测量它之前的所有项目然后滚动到适当的偏移量来完成,但这对于虚拟化列表可能很难做到,并且(在我看来)是许多应用程序(和开发人员)将从内置中受益。
考虑将项目添加到“列表”的位置,并且开发人员希望确保该项目可见。 或者考虑一个带有主/详细视图的页面,其中页面在列表下方的详细项目处打开,但开发人员希望确保显示主列表中的选定项目,因此它提供了显示在列表中的内容的上下文详细视图。

类似地,当可滚动区域内的其他项目从可视化树中移除(或添加到)时,能够确保项目停留在可见区域中也是有用的。 我不确定执行此操作的最佳方法是什么,但是当可滚动区域内的选定项目由于背景(或 UI 外线程)操作删除或向可滚动区域添加其他项目而移动时,情况非常糟糕。
当您点击/点击项目时,让项目移动会导致糟糕的可用性体验。 将焦点/选定项目移出可见区域也会导致辅助工具和屏幕阅读器出现问题。
我知道在周围的人移动时尝试将项目保持在同一位置存在潜在问题(特别是如果该项目从其他地方删除),但什么都不做似乎还不够好。

谢谢@mrlacey。 我可以看到缩放功能在现有的 ScrollViewer 以及这个提案中是如何不易被发现的。 我已将此添加为一个开放性问题以供讨论,因为它是现有 UWP ScrollViewer 的另一个出发点(也许更好?)。

虚拟化使事情变得棘手,因为首先可能需要创建特定元素并在布局系统中运行以获得一个位置,然后您可以滚动到该位置。 这就是为什么在过去某些控件(即 ListView)有自己的ScrollIntoView 方法来处理上述问题以使其变得容易。 这是您设想的方法类型吗? 或者,每个 UIElement 都有一个StartBringIntoView 方法,可用于非常轻松地滚动到一个项目,并且有许多选项可以提供细粒度控制它在进入视图时在视口中的位置。 StartBringIntoView 方法甚至适用于嵌套滚动表面的场景,因为它会生成一个请求,该请求会冒泡以供每个滚动控件响应。 这本质上是当项目接收键盘/游戏板/讲述人焦点时发生的情况。 当然,它要求您首先拥有一个 UIElement,在没有方法显式触发要实现的元素的情况下,您可能不会在虚拟化场景中使用它。 有一种方法可以显式触发元素的实现吗? 除此之外,您还可以使用它来触发 StartBringIntoView。

回复:在添加/删除其他内容或更改大小时保持项目的位置......完全同意什么都不做还不够好。 这正是滚动锚定功能存在的原因。 :) 这是一个鲜为人知的功能,过去已内置到 ListView/GridView 的虚拟化面板中。 在最近的版本中,我们一直在努力将此类内容与仅在 ListView 中可用的内容分离开来。

如果要探索扩展 ScrollViewer 的想法,也许 Fluent Design 团队可以建议您处理视差和默认设置 z 深度值的项目。

随着 XAML 进入 MR 应用程序的 3D 表示,以及深度和阴影进入 2D XAML - 滚动将如何移动不同深度值的项目,阴影如何在滚动指示器上方或下方呈现,以及其他可能突然出现的问题在 XAML 团队的 3D 实验中 - 可以内置到可能成为新默认值的内容中,因为 Windows UI 库慢慢成为新的默认值。

在我看来,需要可以滚动的内容和可以缩放的内容是非常不同的。

除了 Web 浏览器、图形设计应用程序、办公应用程序、PDF 查看器......任何显示内容文档的东西。

同意@mrlacey 的观点,即将特定元素引入视图以及指定它在视图中的显示位置(顶部、中心、底部等)很重要。 还需要有一种方法来检测元素是完全在视图中还是被剪裁了。 这样我就不必滚动视图来显示元素,如果我知道元素已经完全或部分在视图中。

谢谢大家! 我们收集了一些关于应用程序倾向于在其标记中设置哪些属性的遥测数据,并且 ZoomMode 会在 *ScrollMode 和 *ScrollBarVisibility 之后立即显示。 大多数情况下,应用程序不会更改任何属性。 我在设置 ScrollMode 和 ScrollBarVisibility 属性时观察到的是,它通常会启用内容的水平滚动。 ContentOrientation 属性的引入旨在使这更容易。 水平滚动之后,下一个最常见的场景是缩放。

我相信正确的文档/示例可能会更有效,而不是引入单独的(派生的)控件来解决缩放的可发现性。

添加行为或上下文枚举怎么样?

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom;
ScrollViewer.ScrollBehaviour = ScrollBehaviour.Scroll;
ScrollViewer.ScrollBehaviour = ScrollBehaviour.ParallaxScroll;

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom;非常混乱。 我认为ZoomMode应该保持原样。

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom;非常混乱。 我认为ZoomMode应该保持原样。

我可能应该将建议作为解决评论的开头,这些评论认为应该有缩放内容的派生控件,而不是滚动

对我来说,使用ScrollViewer内置缩放功能实际上是有意义的,尤其是在触摸设备上。 目前没有简单的方法可以在 UWP 中对图像/控件进行自由式平移/缩放/旋转,并且已经在 StackOverflow 和 GitHub 上提出了许多相关问题(例如添加可拖动内容控件)。

@micahl ,我假设缩放和

支持旋转手势

将来会在本地支持这种情况吗?

对我来说,使用ScrollViewer内置缩放功能实际上是有意义的,尤其是在触摸设备上。 目前没有简单的方法可以在 UWP 中对图像/控件进行自由式平移/缩放/旋转,并且已经在 StackOverflow 和 GitHub 上提出了许多相关问题(例如添加可拖动内容控件)。

如果需要对缩放和滚动有更多控制...

ScrollViewer.ScrollBehaviour = ScrollBehaviour.ZoomAndScroll;

但是在触摸设备上,允许捏合和滑动手指感觉很自然。 但是,无需触摸即可进行缩放需要出现 [ + ] 和 [ − ] 按钮。 或者如果它只是缩放,那么滚动条拇指拖动会放大和缩小。

如果您查看 Microsoft Word UI,状态栏中有一个缩放控件。 以及文档区域的滚动条。

鼠标缩放支持是否应该内置到新的 ScrollViewer 控件中? 那么当 ZoomMode 开启时,或者一个 Behavior 属性允许 Zoom - 然后出现加号和减号按钮?

@JustinXinLiu支持旋转手势可能需要 InteractionTracker (@likuba) 的支持。

@mdtauk新的 ScrollViewer 将通过类似于现有 ScrollViewer 的 Ctrl + 鼠标滚轮支持鼠标滚轮缩放。 我不清楚在启用缩放时控件是否应该具有默认的加/减按钮。 我目前的印象是,那些通常支持缩放的应用程序(例如内容文档应用程序)选择以各种方式将这些按钮合并到他们的应用程序体验中。 例如,状态栏中的 - / + 由滑块(即 Office)分隔,或者只是一个简单的 -/+ 垂直显示在其他命令(即地图)下方。

@micahl我不明白为什么 ScrollFrom 被这样称呼。 是不是就像 ScrollBy 一样,但有一个速度参数?

@adrientetar你在问为什么不用一组不同的参数来重载 ScrollBy,而不是将它命名为 ScrollFrom?

是的,我猜 😃 但我想 ScrollFrom 背后的概念是以给定的速度向一个方向移动? 与 ScrollBy 不同,后者只是移动到给定点。

这是正确的。 我们考虑了 3 种情况:

  1. 我不在乎我现在的职位是什么。 我想滚动/缩放到特定目的地。
  2. 我关心我当前的位置是什么,我想相对于该位置滚动/缩放特定数量。
  3. 我不在乎最终目的地是什么。 我只想从我当前的位置滚动/缩放。

后者的场景可能类似于鼠标滚轮单击以平移体验。 根据光标相对于点击时的位置,从当前位置(无论是什么)滚动插入一些惯性。

谢谢,这清除了事情。 我想我更希望将 ScrollBy 用于我自己的鼠标滚轮点击体验,但我可以看到 ScrollFrom 在特定情况下很有用。

在当前的 ScrollViewer 控件中,可以绑定到 ViewportWidth 和 ViewportHeight 属性,但目前在新的 ScrollViewer 控件中似乎无法做到这一点。 这个会加吗?

@lhak ,你会将这些值绑定到什么? 添加它们不会是一个很大的变化。 根据您的场景的上下文,我们可能会推荐不同的方法而不是绑定。 虽然我们希望对难以从一个转移到另一个的差异保持敏感,但我们也希望推广更好的替代方案。

@micahl我很确定我过去也使用过这些值进行一些计算。 不确定我是否已经绑定到它们。

@michael-hawker 需要明确的是,属性将在 ScrollViewer 上可用。 但是,在提议的 API 中,它们作为常规属性而不是 DependencyProperties 公开。 对于有人基于 Composition 构建输入驱动动画的情况,这些相同的值将作为 ExpressionAnimationSources 属性的一部分提供。

我目前在我的应用程序中使用了一些类似于以下内容的代码:

<ScrollViewer x:Name="outputScrollViewer">
  <Viewbox MaxWidth="{x:Bind outputScrollViewer.ViewportWidth, Mode=OneWay}" MaxHeight="{x:Bind outputScrollViewer.ViewportHeight, Mode=OneWay}">
    <TheXamlElement Width=X Height=Y/>
  </Viewbox>
</ScrollViewer>

这使得可以在保持纵横比的同时滚动/缩放内部 xaml 元素(具有固定大小)。 该行为类似于显示图像的照片应用程序,不同之处在于 xaml 元素永远不会小于滚动查看器的视口。

@lhak ,好场景! 大声思考如何在当前提案中适应这种情况......我认为我们可以为 ContentOrientation 属性添加另一个选项。 提案中的当前选项:

  • 无 = 无首选方向。 在两个方向上为内容提供无限的可用大小。
  • 水平 = 仅在水平方向上为内容提供无限可用大小,并在布局期间将高度限制为视口。
  • 垂直 = 水平转置

第四个选项是在布局期间将高度和宽度限制为视口的大小。 目前,我将其称为“视口”方向,尽管我对这样命名的反应不一。
至少在(更常见的?)图像的情况下,我相信它会使 Viewbox 和绑定变得不必要,因为“正确的事情”应该作为布局过程的一部分发生。

<ScrollViewer ContentOrientation="Viewport">
  <Image Stretch="Uniform" .../>
</ScrollViewer>

对于更复杂的内容,您可以将其包装在 Viewbox 中并仍然跳过绑定。

<ScrollViewer ContentOrientation="Viewport">
  <Viewbox>
    <TheXamlElement Width=X Height=Y/>
  </Viewbox>
</ScrollViewer>

@micahl还有一个问题:我想更改 Scroller 的鼠标滚轮处理以加快缩放(更大的增量)并删除平滑动画。 似乎没有 API 来调整滚轮缩放,我是否需要手动处理鼠标滚轮事件?

您可能必须手动处理鼠标滚轮,但如果您最终需要重新实现控件的功能,那将是不幸的。 我们认为可能有一种方法可以实现您的目标。 其要点是将 ZoomInertiaDecayRate 设置为接近 1.0 的某个值,然后定义一个重复的缩放捕捉点。 一旦我们添加了 ZoomInertiaDecayRate 属性并修复了一些与强制捕捉点相关的问题,就可以尝试一下。

如果这不起作用,那么您可以通过设置 IgnoredInputKind="Mousewheel" 然后将 Scroller 子类化以覆盖 OnPointerWheelChanged,或者为 Scroller 上的 PointerWheelChanged 事件添加事件处理程序,从而退回到尝试自己处理它。

@micahl谢谢! 我不确定您针对什么场景校准了鼠标滚轮行为,我个人喜欢它的方式,例如在 Adob​​e XD 中:似乎有一些加速动画,但它很活泼,并且具有足够大的缩放增量。 滚动鼠标滚轮缩放动画感觉有点迟钝,并且具有非常小的缩放增量 imo。

@adrientetar ,良好的反馈。 我们可以考虑调整一下。

控制增量和值的任何类型的变量 - 都可以变成可覆盖的属性

对。 我们面临的一个挑战是,在 Win10 版本 1809 及更高版本上,控件将鼠标滚轮输入传递给底层 InteractionTracker,使其在 UI 线程之外进行处理以获得更平滑的运动。 那些可覆盖的属性需要首先在较低级别公开。 对于 1809 之前的 Win10 版本,我们正在添加逻辑以在 UI 线程上控制进程鼠标滚轮输入。

我们可以考虑有一些行为,我们公开一些可覆盖的属性,如果有任何显式设置,那么我们回退到在 UI 线程上使用控制进程鼠标滚轮。 如果/当这些属性存在于较低级别时,那么我们可以切换到依赖它们并将其踢回合成器。 对于鼠标滚轮,滚动可能不会那么平滑。 也许这里没问题?

那么当前的缩放行为会被嵌入到 InteractionTracker 中而无法覆盖它? 我想现在没有办法改变它,因为它会破坏向后兼容性🤔 cc @likuba

我认为 InteractionTracker 上有一些选项可以在这里提供帮助😊。 具体来说,我们构建了PointerWheelConfigDeltaScaleModifier等功能来启用这些类型的自定义。 我们将同步并查看在控件中使用这些是否可以使此处发生某些事情而不会出现您上面提到的问题/风险。

@micahl我正在处理的一个项目中
假设您有一个 10000x5000 的巨大画布,因此要在其周围移动,请将其封装在滚动查看器中。
现在在画布上您有 2 个 InkCanvases(或更多),并且您想用不同的手指或 1 个手指和 1 个鼠标指针在两个(或更多)上书写。 您将如何使用此控件实现这一目标(或者甚至可能)? :)
我所看到的默认行为是,只要您在一个 InkCanvas 上绘图时触碰其他任何东西,该特定 InkCanvas 就会触发“PointerLost”,仅此而已 - 没有其他事情可做。
显然,我的滚动查看器远非我自己编写的完美 - 所以我想看看这个控件是否可以实现?

非常感谢你 !

@stefangavrilasengage ,有趣的场景。 :) 从您的 PoC 来看,当手指放在 InkCanvas 上然后开始移动时,预期的行为是什么? 它是绘制还是平移?

你好@micahl - 谢谢你的回复! 我假设现在您已经猜到它类似于数字白板应用程序。
现在我将介绍目前发生的情况:

  • ScrollViewer(默认):在 InkCanvas 上用一根手指画画就可以了。 第二根手指使第一根 InkCanvas 失去输入。
  • ScrollViewerEx(自定义控件):使用尽可能多的手指和尽可能多的 InkCanvases 来绘制并且它可以工作。

这有帮助吗? :)
谢谢 !

@stefangavrilasengage我在问是什么消除了单个触摸手势是平移(ScrollViewer 的默认行为)还是绘制墨水之间的歧义。 那里有内在的冲突。 你如何解决它? 需要两根手指才能像地图一样滚动? 使用编辑按钮进入/离开编辑模式? 等等...

@micahl我很抱歉 - 忘了提及我是如何解决冲突的。
我们在画布上的对象在 Win2D 容器中有它们的墨水。 只有当您为每个对象进入编辑模式时,才会在顶部显示 InkCanvas 进行编辑(使用自定义干燥)。
因此,在我的情况下(不确定这是否适用于其他任何人),如果您有一个 InkCanvas 活动的“可绘制”,那么 ScrollViewer 不应该有任何内容。
我很想知道您认为这不适用的用例,如果有的话?

谢谢 !

很难相信这是@micahl和我第一次在 github 问题中混为一谈!

很难相信这是@micahl和我第一次在 github 问题中混为一谈!

对于那个很抱歉 ! 已编辑!

@micahgodbolt它可能不是最后一个。 ;)
@stefangavrilasengage谢谢! 这回答了我的问题。 我注意到将 ScrollViewer 换成其他类似 Border 的东西作为包含多个 InkCanvas 控件的 Canvas 的父级不会有同样的问题。 因此,我在内部联系了一些人,以了解可能导致多输入/多 InkCanvas 功能在 ScrollViewer 中发生故障的原因。

@stefangavrilasengage ,弄清楚为什么它不起作用需要一些时间/调查。 让我们与此提案分开跟踪。 你会为它打开一个问题吗?

@micahl我在使用 Scroller 时遇到问题,在 OnControlLoaded 中滚动和缩放我的内容会使我处于错误的位置。 如果我只滚动,我最终会在正确的位置,但如果我在滚动后立即缩放我处于完全关闭的位置,即使我指定了 centerPoint=null 所以它默认为视口中心(从查看代码,因为有尚无文件证明)。 有了这个代码

感谢您的尝试,@adrientetar! 让我们将这个话题集中在提案上。 你可以为你所看到的打开一个单独的问题吗? 根据我们在讨论中的具体位置,我们可能希望返回到此线程以讨论它是否需要对提案进行更改。

@micahl为什么 ExtentChanged/StateChanged/ViewChanged 事件将 object 作为参数,而不是例如 StateChangedEventArgs 与 State 成员? 鉴于 InteractionTracker 在不同的线程上运行,消息传递模式(带参数的事件)是否比在事件处理程序中查询控件属性更受欢迎?

https://github.com/XeorgeXeorge/Extended-Image-Viewer

没什么大不了的,但是这个 repo 的第一部分展示了如何在水平/垂直滚动条被锁定(禁用)时实现缩放,

简单来说:
允许平移放大的内容,同时仍然强制拟合
有效的垂直/水平视口。

在发布此内容时,必须在 ScrollViewer 中启用 Horizo​​ntalScrollbarVisibility 属性才能实际获得功能性缩放操作,尽管可能有更好的方法来解决此问题,但我认为我废弃的双滚动查看器解决方案可能就足够了足够的。

@micahl我看到 Scroller 在执行非动画缩放时不发送 StateChanged 事件,这是设计
正如您在https://github.com/microsoft/microsoft-ui-xaml/issues/541#issuecomment -488749469 中所建议的那样,当 Scroller 变为空闲时,我确实设置了我的 CanvasVirtualControl 缩放,这样控件就不会最终光栅化太大相对于缩放因子的画布部分。 但是对于非动画缩放,我需要在调用 ZoomTo 方法时执行此操作,即使尚未实现缩放更改(afact)。

@adrientetar ,创建事件参数以在框架和应用程序之间来回传递有一些开销。 因为事件 arg 只会提供发送者公开的属性的副本,所以我们当前的设计是没有事件 arg。

是的,当前的设计是在非动画缩放中您以编程方式调用 StateChanged 不会触发。 将引发 ZoomCompleted 和 ScrollCompleted 事件以响应编程更改。 IIRC 它们不是由用户更改触发的。 您可以使用其中之一,也许与 StateChanged 结合使用。 仅供参考,只要视图更改(用户或编程),就会引发 ViewChanged 事件,这使其成为非常敏感的代码路径,而不是您想要使用的。

我们仍在听取反馈,所以如果事情看起来很迟钝,请告诉我们。

是的,当前的设计是在非动画缩放中您以编程方式调用 StateChanged 不会触发。

啊好吧,我现在明白了。 也许该事件可以始终触发并包含一个原始参数,例如 SetFocus,但这样做可能会产生一些开销。

将引发 ZoomCompleted 和 ScrollCompleted 事件以响应编程更改。 IIRC 它们不是由用户更改触发的。

好的,所以对于我的用例,我必须处理 ZoomCompleted 和 StateChanged iff State == Idle,具体取决于更改是否是程序化的。 这不是很奇怪吗? 我的意思是,为同一件事使用不同名称的不同事件只会以不同的方式触发🤔

@RBrid和我就引入新状态“Transitioning”和进行一些小的重命名进行了简短的交谈。 让我们知道这对您的用例是否更有意义。

讨论的想法是,当滚动/缩放位置的更改即将得到服务(无论是用户启动的还是程序化的)时,我们将引发 StateChanged 事件并将当前状态报告为 Transitioning。 以下是程序化滚动(动画和非动画)的序列:

// 程序化请求,动画
myScrollInfo = ScrollTo(100, animate=true);
// 几个滴答声
StateChanged 引发(Scroller.State == Transitioning)
// 几个滴答声
StateChanged 引发(Scroller.State == Animating)
// 许多滴答声
StateChanged 引发(Scroller.State == Idling)

// 程序化请求,无动画
myScrollInfo = ScrollTo(100, animate=false);
// 几个滴答声
StateChanged 引发(Scroller.State == Transitioning)
// 几个滴答声
StateChanged 引发(Scroller.State == Idling)

我们可能希望将 InteractionState 枚举重命名为 ScrollState 之类的东西,因为它代表的不仅仅是交互。

enum ScrollState
{
    Idling = 0,
    Transitioning = 1,
    Interacting = 2,
    Inertial = 3,
    Animating = 4,
};

@micahl是的,这看起来非常像我所期望的! 只想处理动画更改的人可以拦截 Animating 状态。 对于名称,我会保持空闲而不是空闲(“滚动器空闲”/“滚动器正在动画”),过渡→搅拌? (过渡非常令人困惑,我宁愿我们传达它是在运动的开始,所以 imo 搅拌可能会更好地工作),惯性可能是漂移 😛 但我想 API Review 也会加入。

搅拌最初让我想到了混合。 :) 当我们最终确定名称并倾向于寻找先例时,API 审查将需要权衡。 有一些选择总是好的。 其他替代方案可能是未知或待定。 在现有 API 中,这两个 API 以及 Idle 都有先例,这意味着我们可能不会做 Idling。

酷酷。 Pending sgtm,imo Unknown 比 Transitioning 更令人困惑。

@micahl——功能要求 3 包括这个子点:

参与有效的视口更改(必须)

该子点指的是预先存在的事件FrameworkElement.EffectiveViewportChanged 。 该子点是否足以(间接地)说明异步按需部分加载内容也是一项要求/优先事项? 我的意思是,例如,当 MS Edge 显示 PDF 文档(或 SVG 或其他复杂文档)的放大页面时,ScrollViewer 的运行方式。 当缩放为 900% 时(例如),它不会以 900% 的大小预渲染整个 PDF 页面,因为以下两个原因:

  • 此渲染任务(在高缩放百分比下)大约需要 3 .. 20 秒,具体取决于文档复杂性(矢量元素的数量、效果等)、文档大小和缩放百分比。 这种延迟让最终用户无法接受。
  • 当缩放百分比很高时,整个 PDF 页面的渲染图像(位图)可能会消耗数百兆字节的 RAM,因为它相当于以非常高的 DPI 渲染。

MS Edge 按需呈现放大的 PDF 页面的一部分,这意味着当用户将这些部分滚动到视图中时。 这种技术避免了页面显示之前的长时间延迟。 我建议功能需求明确提到这种情况,但也间接支持async Task 。 当未渲染的(或其他不立即可用的)部分滚动到视图中时,ScrollViewer 可以使用可配置的Brush临时填充该空间,然后触发事件,事件处理程序可以启动async Task渲染(或以其他方式检索/加载)零件。 稍后,当Task完成时,渲染/检索/加载的部分显示在 ScrollViewer 中,覆盖了临时填充Brush

这个问题不仅与高变焦渲染的高成本有关。 它也适用于按需检索的内容。 例如,想象一下在 ScrollViewer 中显示的地图或卫星图像。 地图的一部分仅在用户将它们滚动到视图中时才从服务器下载。

是否应该将与缩放相关的 API 分离到派生控件(例如 ZoomViewer)中,以便 ScrollViewer 严格控制滚动?

虽然我很欣赏ScrollViewer.ZoomFactor ,但我惊讶地看到它直接添加到 ScrollViewer 中。 我认为将 zoom 放在单独的类(不一定是派生类)中会更简单、更可靠。 至少有 3 种方法是可能的:

  1. 一个名为ZoomViewer不继承ScrollViewer ,而是使用ScrollViewer实例作为子元素(在其ControlTemplate )。
  2. 一个名为ZoomableScrollViewer确实继承了ScrollViewer
  3. 直接支持ScrollViewer内的缩放,如果它不是过于复杂或凌乱。

17 提供默认 UX 以支持鼠标中键和滚动。 (应该)
18 支持通过鼠标点击和平移的模式(例如在PDF查看器内移动内容)。 (应该)

我建议考虑是否取消对 17 的支持,因为 18 比 17 效果好得多,而且在实践中几乎没有人使用 17。 诚然,我可能认为 17 的含义与您预期的含义不同(对 17 的描述只是一个句子,我不能 100% 确定它的含义是我认为的含义)。 说18是非常人性化和易于使用的,而17是每个人都难以使用和避免使用的尴尬东西,这不是正确的吗?
(如果出于可访问性原因需要 17,则此问题会发生变化,但到目前为止,我从未听过有人说 17 是可访问性问题。)

4 能够执行输入驱动的动画

第 4 点是与可访问性相关的问题。 我想请求 ScrollViewer(以及 WinUI 中的所有其他控件)尊重辅助功能设置:

Windows 10 -> 开始 -> 设置 -> 轻松访问 -> 显示 -> 在 Windows 中显示动画(开或关)。

不幸的是,多年来,我注意到许多 Microsoft 应用程序忽略 Windows 辅助功能设置的示例。 动画已关闭,但 Microsoft 应用程序仍会显示动画。 这给依赖这些辅助功能设置的用户带来了真正的困难。 不是每个人都有能力享受动画而不遭受负面影响。 在查看动画等方面没有任何困难的用户会发现很难理解 Windows 中辅助功能设置的重要性。

@verelpode@predavid将围绕新的 ScrollViewer 推动工作,所以我会听从她的意见。

17 提供默认 UX 以支持鼠标中键和滚动。 (应该)
18 支持通过鼠标点击和平移的模式(例如在PDF查看器内移动内容)。 (应该)

我建议考虑是否取消对 17 的支持,因为 18 比 17 效果好得多,而且在实践中几乎没有人使用 17。 诚然,我可能认为 17 的含义与您预期的含义不同(对 17 的描述只是一个句子,我不能 100% 确定它的含义是我认为的含义)。 说18是非常人性化和易于使用的,而17是每个人都难以使用和避免使用的尴尬东西,这不是正确的吗?
(如果出于可访问性原因需要 17,则此问题会发生变化,但到目前为止,我从未听过有人说 17 是可访问性问题。)

@verelpode我认为你理解 17 是正确的,我同意 18 比 17 更重要,通常更直观。但是在某些情况下,“左键单击并拖动”可能已用于其他目的,例如选择或拖放. 对于这种情况,如果应用程序可以启用通过中键滚动(17),那就太好了。

我个人不时在浏览器中使用中键滚动,其中左键单击 + 拖动会导致文本选择或图像的拖放。 UWP 浏览器将禁用 18 个,启用 17 个。 两者都应该通过单独的属性选择加入。

@lukasf

我个人不时在浏览器中使用中键滚动,其中左键单击 + 拖动会导致文本选择或图像的拖放。

好点是需要避免导致文本选择等停止工作。 您如何看待这种可能的解决方案:让鼠标中键启动基于鼠标的平移模式 (18),而不是启动笨拙的 17 模式。

当我查看其他应用程序的功能时,有些应用程序允许您通过按住空格键同时在可滚动内容区域中单击鼠标左键来启动基于鼠标的平移。 该解决方案允许在可滚动内容区域内左键单击正常操作,因为平移模式仅通过空格键+单击启动。 我发现通过空格键+点击平移非常舒适、方便和快速。

但是,在不需要在可滚动内容区域内显示任何可编辑文本框的应用程序中,这种空格键+单击解决方案更容易实现。 如果可滚动内容区域中确实存在可编辑文本框,那么平移功能会导致用户无法在文本框中键入空格。 因此,正如您所说,此功能应通过属性选择加入。 或者,不要使用空格键,而是通过鼠标中键启动基于鼠标的平移模式(18),这样可以消除用户无法在文本框中键入空格的问题。

@verelpode
对于所有浏览器以及一堆其他应用程序(Word、Adobe Reader、Outlook 等)都支持模式 17,我仍然认为应该实现这一点。 仅仅因为你个人觉得它很尴尬并不意味着它对其他人没有用。 当然,这两种模式都应该选择加入,然后开发人员可以决定在他们的应用程序中使用什么。 这也将允许您的空格键 + 单击行为,如果它对应用程序有意义:在空格键向下启用模式 18,在空格键向上再次禁用它。

@lukasf——好的,听起来不错。 我以为你的意思是,如果平移模式 (18) 不会阻止文本选择或拖放图像等,那么你将停止使用模式 17 并切换到 18,但现在我看到你的偏好实际上是两种模式得到支持。

感谢您的反馈@verelpode@lukasf ,我同意您的看法,最终我们希望 Scroller 控件上的公共旋钮可以打开/关闭数字 17 和 18。让我们称它们为 17=基于鼠标的恒定速度平移和18=基于鼠标的平移。

我刚刚发送了一个 PR https://github.com/microsoft/microsoft-ui-xaml/pull/1472以添加一些我所做的调查工作。 我想看看使用今天的 Scroller 能有多接近支持 17 和 18。

与基于触摸或基于鼠标滚轮的体验不同,18=基于鼠标的平移的情况非常顺利,尽管该解决方案是 100% 的 UI 线程绑定的。 我在侦听鼠标的 PointerMoved 事件时使用了 ScrollTo 方法。 最终,我们希望底层 InteractionTracker 组件像处理手指移动一样处理鼠标移动。

对于 17=基于鼠标的恒速平移(我个人不喜欢这种体验),事情要复杂得多。 原型远非理想,我没有试图解决所有问题,但有两个特别涉及:

  • 在恒定速度平移(即 0 速度衰减平移)期间,我认为当前 Scroller 没有办法在不跳回旧位置的情况下停止运动(使用 ScrollBy(0, 0)) . 无法可靠地知道组合线程领先 UI 线程多远。 因此,需要一些新的公共 Scroller API 以无故障的方式停止速度。
  • 在鼠标的 PointerReleased 事件之后,我无法保持鼠标捕获。 似乎需要一个新的 Xaml 框架功能来实现这一点。
    无论如何,这可能是我们希望底层 InteractionTracker 直接处理一些体验的另一种情况。
    在讨论未来的 Scroller/InteractionTracker 功能时,我会牢记这些。

@RBrid

对于 18=基于鼠标的平移,事情进展得很顺利,......
对于 17=基于鼠标的恒速平移(我个人不喜欢这种体验),事情要复杂得多。

有趣的结果! 在这种情况下,考虑到 17=constant-velocity 的困难,并考虑到 18 可以以不妨碍正常使用左键单击的方式实现,那么在我个人看来,我认为该提案可能应该更新为放弃 17 并支持 18,但不可否认,如果放弃 17=constant-velocity,我不知道有多少用户会抱怨。

感谢@RBrid进行这些调查。 很高兴听到模式 18 已经有效! 我同意理想情况下它应该由 InteractionTracker 处理,以便即使在 UI 线程加载期间也能顺利运行。

@verelpode两种模式都设置为“应该”。 因此,如果实现模式 17 的工作量太大,则可以将其忽略(如果需要,可以稍后添加)。 但也许有人会找到一种无需太多更改即可实现的方法。

我希望新的 ScrollViewer 能够使用“Ctrl”键禁用或自定义缩放。

此页面是否有帮助?
0 / 5 - 0 等级