Microsoft-ui-xaml: Un ScrollViewer plus flexible

Créé le 19 déc. 2018  ·  61Commentaires  ·  Source: microsoft/microsoft-ui-xaml

Il s'agit d'un modèle pour les nouvelles fonctionnalités ou propositions d'API. Par exemple, vous pouvez l'utiliser pour proposer une nouvelle API sur un type existant, ou une idée pour un nouveau contrôle d'interface utilisateur. Ce n'est pas un problème si vous n'avez pas tous les détails : vous pouvez commencer par le résumé et la justification. Ce lien décrit le processus de proposition de fonctionnalité/API WinUI : https://github.com/Microsoft/microsoft-ui-xaml-specs/blob/master/docs/feature_proposal_process.md Ajoutez un titre pour votre proposition de fonctionnalité ou d'API. Veuillez être bref et descriptif

Proposition : ScrollViewer

Sommaire

tl:dr; Fournissez un contrôle de défilement et de zoom flexible, mais facile à utiliser, en XAML qui peut toujours être exploité lors du ciblage des versions inférieures de Win10.

Le contrôle ScrollViewer joue un rôle essentiel dans toute interface utilisateur en raison du caractère fondamental du défilement pour les applications et les contrôles (plates-formes et non-plateformes). Le contrôle fournit des capacités de panoramique et de zoom ainsi que des politiques et une expérience utilisateur par défaut (par exemple, barres de défilement conscientes, interaction focus/clavier, accessibilité, animation de défilement par défaut, etc.). Il doit également être suffisamment flexible pour pouvoir passer de la simplification des scénarios d'applications quotidiens à un élément clé des contrôles basés sur le défilement et au service de cas d'utilisation très avancés. Le contrôle actuel n'offre pas ce niveau de flexibilité.

Raisonnement

Veuillez décrire POURQUOI la fonctionnalité doit être ajoutée à WinUI pour tous les développeurs et utilisateurs. Le cas échéant, vous pouvez également décrire comment il s'aligne sur la feuille de route et les priorités WinUI actuelles : https://github.com/Microsoft/microsoft-ui-xaml-specs/blob/master/docs/roadmap.md

L'apport du contrôle ScrollViewer au référentiel d'une manière superposée aux API publiques de la plate-forme principale est un tremplin important qui :

  1. permet de lever plus de contrôles dans le repo (agilité accrue),
  2. donne aux développeurs un contrôle facile à utiliser qui met en évidence la flexibilité des capacités de la plate-forme de niveau inférieur combinées aux avantages des services de cadre de niveau supérieur (par exemple, l'accessibilité), et
  3. permet aux nouvelles fonctionnalités liées au défilement dans XAML de s'allumer sur les versions antérieures de Win10.

Le contrôle existant a été créé dans Win8 sur la base des API DirectManipulation qui 1) ne sont pas disponibles pour une utilisation directe dans UWP, 2) ne permettent pas le niveau de personnalisation auquel les développeurs s'attendent pour créer des expériences de défilement uniques, et 3) sont remplacés par les nouvelles API InteractionTracker d'UWP.

L'extraction du contrôle ScrollViewer existant tel quel serait un non-démarreur. Le contrôle doit plutôt être une réimplémentation avec une surface d'API presque identique basée sur les capacités plus récentes (et déjà disponibles) d' InteractionTracker .

Plan de haut niveau

Le ScrollViewer existant dans l'espace de noms _Windows_.UI.Xaml.Controls restera intact (autre que la correction de bogues critiques).

Le nouveau ScrollViewer vivra dans l'espace de noms _Microsoft_.UI.Xaml.Controls. Son API sera en grande partie identique à la version _Windows_. Nous apporterons des ajustements ciblés à l'API du contrôle là où il y a un avantage évident (simplifier un scénario commun, introduire de nouvelles fonctionnalités, etc.).

En combinaison avec le nouveau ScrollViewer, nous introduirons un nouveau type, Scroller, qui vivra dans l'espace de noms Microsoft.UI.Xaml.Controls._Primitives_. Le Scroller fournira les fonctionnalités de base pour le défilement et le zoom en XAML en fonction des capacités d' InteractionTracker .

L'objectif initial pour les deux contrôles sera de faire en sorte que les fonctionnalités de base pour expédier la qualité fassent partie de la prochaine version officielle. Les API non finalisées resteront en « préversion » et ne seront disponibles que dans le cadre des packages de pré-version.

-------------------- Les sections ci-dessous sont facultatives lors de la soumission d'une idée ou d'une proposition. Toutes les sections sont requises avant que nous acceptions un PR à maîtriser, mais ne sont pas nécessaires pour démarrer la discussion. ----------------------

Exigences fonctionnelles


| # | Fonctionnalité | | Priorité |
|:-:|:--|-|:-:|
| 1 | Fournit un contrôle _non scellé_, de panoramique et de zoom avec une UX par défaut correspondant au ScrollViewer existant. || Doit |
| 2 | Démontre les capacités de la plate-forme en s'appuyant uniquement sur des API publiques prises en charge par la plate-forme. || Doit |
| 3 | S'intègre aux services XAML au niveau du framework.
Par exemple:
- rend correctement les rects de mise au point du système
- affiche automatiquement les éléments ciblés (clavier, GamePad, lecteurs d'écran)
- participe à des changements de fenêtre efficaces
- prend en charge l' ancrage de défilement || Doit|
| 4 | Capable d'effectuer des animations basées sur les entrées || Doit |
| 5 | Peut être utilisé en combinaison avec des contrôles dépendants du défilement existants déjà dans le référentiel (c'est-à-dire ParallaxView, RefreshContainer, SwipeControl). || Doit |
| 6 | Capable de contrôler la courbe pour les changements de vue inertielle (c.-à-d. défilement ou zoom animé personnalisé). || Doit |
| 7 | Capable de changer la vue en fonction des décalages absolus ou relatifs (par exemple, faites défiler jusqu'à un point d'intérêt) || Doit |
| 8 | Capable de changer la vue en fonction d'une impulsion / vitesse supplémentaire (par exemple, défilement fluide automatique pendant le glisser-déposer ou la sélection multiple via un rectangle de sélection de souris) || Doit |
| 9 | Capable de détecter le dépassement et de combien || Doit |
| 10 | Capable d'observer et de réagir aux changements d'état de défilement || Doit |
| 11 | Prise en charge du défilement et du zoom des points d'accrochage. || Devrait |
| 12 | Capable d'utiliser un contrôle de "barre de défilement" personnalisé pour piloter le défilement (par exemple, le défilement de la chronologie des photos). || Devrait |
| 13 | Capable de configurer facilement le contrôle pour ignorer des types d'entrée spécifiques (par exemple, répondre au toucher ou au stylet, mais ignorer l'entrée de la molette de la souris). || Devrait |
| 14 | Capable d'enregistrer et de restaurer la position de défilement (par exemple dans une liste de virtualisation) || Devrait |
| 15 | Prise en charge des en-têtes / éléments collants. || Devrait |
| 16 | Prise en charge du défilement accéléré lorsqu'un utilisateur effectue des gestes répétés en succession rapide. || Devrait |
| 17 | Fournissez une UX par défaut pour prendre en charge un clic du milieu de la souris et un défilement . |mousewheel panning | Devrait |
| 18 | Prend en charge un mode pour cliquer et faire un panoramique via la souris (par exemple, déplacer du contenu dans une visionneuse PDF). |mouse hand cursor for panning | Devrait |
| 19 | Capable d'utiliser un contrôle personnalisé pour piloter le zoom (par exemple un curseur de zoom). || Devrait |
| 20 | Capable d'imprimer le contenu contenu dans la zone de défilement. || Devrait |
| 21 | Soutenir les gestes de rotation. || Pourrait |
| 22 | Capable de synchroniser deux zones ou plus avec un défilement sans scintillement ni secousse (par exemple, un scénario de diff de texte). || Pourrait |
| 23 | Capable de personnaliser la courbe d'animation pour les changements de vue pilotés par les entrées (c'est-à-dire définir des comportements de doigt vers le bas tels que les puits de gravité). || Pourrait |
| 24 | Prend en charge un geste de défilement vers le haut ou vers le bas . || Pourrait |
| 25 | Capable de désactiver l'overpaning . || Pourrait |
| 26 | Capable de manipuler de manière sélective le contenu dans ScrollViewer en fonction de la propriété ManipulationMode d'un UIElement . || Pourrait |
| 27 | Capable de désactiver l'inertie et/ou de rebondir sur l'inertie. || Pourrait|
| 28 | Capable de désactiver le découpage du contenu (c'est-à-dire CanContentRenderOutsideBounds ). || Pourrait |
| 29 | Capable de déterminer la raison de l'achèvement à la fin d'un changement de vue. || Pourrait |

Terminologie

  • _Overpan_ est lorsque l'utilisateur tente de faire un panoramique ou un zoom au-delà de la taille du contenu et entraîne l'effet élastique / élastique.
  • _Railing_ est lorsque le scroller verrouille automatiquement le mouvement sur un axe "préféré" en fonction de la direction du geste initial.
  • _Chaining_ est lorsqu'il y a une surface de défilement imbriquée dans une autre et lorsque le mouvement de défilement de l'intérieur atteint son ampleur, il est encouragé à continuer sur l'extérieur.
  • Les _points d'accrochage_ sont des emplacements dans le contenu défilant où la fenêtre s'arrêtera à la fin du défilement inertiel (c'est-à-dire un défilement animé après que l'utilisateur ait levé le doigt). Des points d'accrochage sont requis pour un contrôle comme FlipView.
  • _L'ancrage du défilement_ est l'endroit où la fenêtre se déplace automatiquement pour maintenir la position relative du contenu visible. Cela empêche les utilisateurs d'être impactés par des changements soudains dans la position ou la taille du contenu en raison de la mise en page (c'est-à-dire que l'utilisateur lit un article et quelques instants plus tard, tout saute car une image/annonce en haut de l'article est finalement chargée) . L'ancrage de défilement est une nécessité pour la virtualisation de l'interface utilisateur lorsqu'il s'agit de contenu de taille variable.
  • Les _puits de gravité_ sont des emplacements dans le contenu défilant qui affectent le comportement de défilement pendant que l'utilisateur manipule le contenu (c'est-à-dire que le doigt de l'utilisateur est vers le bas). Par exemple, les puits de gravité pourraient être utilisés pour modifier la friction perçue du défilement, où le mouvement semble ralentir ou s'accélérer lorsque l'utilisateur effectue un panoramique ou un zoom.

Exemples d'utilisation

Veuillez inclure un ou plusieurs exemples montrant comment vous utiliseriez la fonctionnalité. Vous pouvez inclure des exemples de code ou des captures d'écran si cela vous aide

Avant-propos

Par défaut, ScrollViewer prend en charge le panoramique sur son contenu lorsque la taille du contenu est plus grande que sa fenêtre d'affichage. La taille du contenu est déterminée par le système de mise en page de XAML.

Les exemples ici sont destinés à mettre en évidence la principale différence d'API entre le ScrollViewer actuel et le nouveau ScrollViewer.

Défilement vertical (aucune différence)

La largeur du contenu est automatiquement contrainte pour être la même que la fenêtre. La hauteur n'est pas contrainte. S'il dépasse la hauteur de la fenêtre, l'utilisateur peut effectuer un panoramique via diverses modalités de saisie.

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

Défilement horizontal

Il s'agit d'un écart intentionnel par rapport au ScrollViewer existant qui nécessitait auparavant de modifier le HorizontalScrollBarVisibility. Cela a déconcerté de nombreux développeurs.

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

Le défilement horizontal uniquement est activé en définissant la propriété _ContentOrientation_ . Il détermine quelles contraintes sont utilisées sur le contenu lors de la mise en page. Une valeur de Horizontal implique que le contenu n'est pas contraint horizontalement (taille infinie autorisée) et contraint verticalement pour correspondre à la fenêtre. De même, Vertical (valeur par défaut) implique qu'il n'est pas contraint verticalement et qu'il est contraint horizontalement.

Faire défiler une grande image

Une ContentOrientation de _Both_ implique que le contenu n'est pas contraint à la fois horizontalement et verticalement.

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

Modification de la visibilité de la barre de défilement

Cet exemple masque toujours les deux barres de défilement. L'utilisateur peut toujours déplacer le contenu dans les deux sens.

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

Désactivation de la prise en charge intégrée pour des types d'entrée spécifiques

Dans cet exemple, un développeur crée une application basée sur un canevas qui effectuera un traitement personnalisé sur l'entrée de la molette de la souris et prendra en charge une expérience de sélection au lasso via Pen. Il peut configurer le ScrollViewer pour ignorer ces types d'entrée tout en acceptant d'autres tels que Touch.

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

Personnaliser l'animation d'un scroll programmatique

Le développeur écoute l'événement ValueChanged d'un Slider et anime le VerticalOffset d'un ScrollViewer en utilisant une durée personnalisée sur l'animation par défaut.

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);
}

Conception détaillée des fonctionnalités

Veuillez inclure tous les détails de conception importants. Cela peut inclure un ou plusieurs des éléments suivants : - une proposition d'API (tout langage ou pseudocode pris en charge est acceptable) - des maquettes de conception pour une nouvelle expérience utilisateur - des détails sur la conformité à l'accessibilité pour la nouvelle interface utilisateur - d'autres notes de mise en œuvre

Défilement à haute politique et à faible politique

Scroller (faible politique)

Le Scroller est un bloc de construction de bas niveau sans chrome qui fournit toute la logique essentielle de panoramique et de zoom. Il encapsule l' InteractionTracker à politique encore plus faible de la plate-forme en tant qu'élément compatible avec le balisage XAML.
D'une manière très littérale, le Scroller est "la fenêtre d'affichage" de ScrollViewer et prend la place du ScrollContentPresenter. Cependant, le Scroller, contrairement au ScrollContentPresenter, fait bien plus que simplement couper son contenu.

Scroller offre la possibilité d'utiliser InteractionTracker directement avec les avantages suivants :

  • Prise en charge de l'accessibilité
  • Syntaxe conviviale pour le balisage et API facile à utiliser
  • Intégration avec le système de mise en page et les capacités de virtualisation de XAML

ScrollViewer (haute politique)

Le nouveau ScrollViewer enveloppe le Scroller et définit ses propriétés sur des valeurs communes. Par exemple, un <ScrollViewer/> configure son Scroller pour prendre en charge les interactions de défilement horizontal et vertical, mais limite la largeur de son contenu de sorte que l'expérience utilisateur par défaut semble être un défilement vertical uniquement (cas courant).

ScrollViewer offre les avantages suivants par rapport à l'utilisation d'un Scroller :

  • Un UX par défaut (par exemple l'indicateur de défilement, les barres de défilement conscientes pour la souris, une courbe d'animation par défaut)
  • Prise en charge par défaut de l'entrée liée au fil d'interface utilisateur non gérée par Scroller/InteractionTracker (c'est-à-dire clavier et GamePad)
  • Mouvement de mise au point par défaut pour GamePad (page haut/bas et mise au point automatique en réponse aux déclencheurs)
  • Conscience par défaut des paramètres système de l'utilisateur (par exemple, masquer automatiquement les barres de défilement dans Windows)
  • (Futur) Options de configuration faciles pour les points d'accrochage
  • (Futur) Prise en charge de plusieurs modes de panoramique de la souris (par exemple, cliquez et faites glisser le contenu avec une main ouverte/fermée, panoramique de la souris via la molette de la souris/clic du bouton central affichant un curseur « compas »)

Lequel utiliser ?

Le choix par défaut pour les applications et de nombreux auteurs de contrôles devrait être d'utiliser ScrollViewer. Il offre une plus grande facilité d'utilisation et une UX par défaut cohérente avec la plate-forme. Le Scroller est approprié lorsque les politiques/UX par défaut ne sont pas requises - par exemple, pour créer un contrôle FlipView amélioré ou une surface de défilement sans chrome.

API ScrollViewer uniquement

HorizontalScrollBarVisibility / VerticalScrollBarVisibility

La valeur par défaut pour les barres de défilement horizontales et verticales est _Auto_. Ils sont automatiquement affichés ou masqués selon que le contenu est plus large/plus haut que la fenêtre ou non.

L'option 'Désactivé' n'existera pas dans les options d'énumération disponibles pour le nouveau ScrollViewer. Au lieu de cela, la configuration du contrôle pour répondre aux interactions de l'utilisateur qui se déplacent horizontalement ou verticalement sera basée uniquement sur les propriétés _HorizontalScrollMode_ et _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
    }
}

Dans l'image ci-dessous, le curseur de la souris se trouve sur la barre de défilement verticale. C'est le seul visible car le contenu a la même largeur que la fenêtre.

Lorsque le contenu est plus grand que la fenêtre dans les deux dimensions, les deux barres de défilement conscientes peuvent être vues ainsi que leur séparateur dans le coin inférieur droit.

API de défilement uniquement

Contrôleur de défilement horizontal / Contrôleur de défilement vertical

Le Scroller peut être connecté à un "widget" interactif qui contrôle le défilement en définissant ses _HorizontalScrollController_ et _VerticalScrollController_ sur un type qui implémente une interface _IScrollController_. Les ScrollBars sont des exemples familiers de ces widgets. Le ScrollViewer fournit à son Scroller deux de ces widgets. Par exemple, un développeur peut créer une implémentation IScrollController personnalisée qui repose sur un Composition.Visual pour une entrée indépendante du thread d'interface utilisateur.

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

Limitations de niveau inférieur de ScrollViewer / Scroller

Le framework doit avoir tous les crochets nécessaires exposés pour créer un contrôle de défilement personnalisé à partir de la mise à jour Windows 10 avril 2018. Lorsque vous ciblez des versions antérieures, il peut y avoir des limitations :
| Libération | Limite | Raison |
|:-:|:--|:-:|
| Windows 10 Fall Creators Update (Build 16299) et versions antérieures | Les éléments ne seront pas automatiquement affichés lors de la réception du focus. | L'événement BringIntoViewRequested de UIElement n'est pas disponible |
| | Le contrôle ne peut pas participer aux événements EffectiveViewportChanged. Les rectangles de mise au point rendus par le système ne seront pas tronqués aux limites de la fenêtre. | Le RegisterAsScrollPort de UIElement n'est pas disponible |

API proposée

ScrollViewer

```C#
classe publique Microsoft.UI.Xaml.Controls.ScrollViewer : Contrôle
{
ScrollViewer();

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

/*

  • Propriétés centrées sur la mise en page
    */
    // Valeur par défaut : nulle
    Contenu UIElement { get; ensemble; } ;
// 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; };

/*

  • Propriétés centrées sur l'interaction utilisateur
    */
    // Valeur par défaut : Activé
    Microsoft.UI.Xaml.Controls.ScrollMode HorizontalScrollMode { get; ensemble; } ;
// 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; };

/*

  • Méthodes
    */
    // Défile de manière asynchrone jusqu'aux décalages spécifiés. Permet l'animation,
    // respecte les points d'accrochage. Renvoie une structure ScrollInfo.
    Microsoft.UI.Xaml.Controls.ScrollInfo ScrollTo(
    double décalage horizontal,
    double verticalOffset);
// 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);

/*

  • Transféré à l'implémentation IScrollAnchorProvider de Scroller interne
    */
    void RegisterAnchorCandidate (élément UIElement);
    void UnregisterAnchorCandidate (élément UIElement);

/*

  • Événements
    */
    // Élevé chaque fois que l'un des HorizontalOffset, VerticalOffset et ZoomFactor
    // propriété de dépendance modifiée.
    événement TypedEventHandlerVueChangé;
// 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;

/*

  • Propriétés de dépendance
    */
    static DependencyProperty ContentProperty { get; } ;
    static DependencyProperty ContentOrientationProperty { get; } ;
    static DependencyProperty ComputedHorizontalScrollBarVisibilityProperty { get; } ;
    static DependencyProperty ComputedVerticalScrollBarVisibilityProperty { get; } ;
    static DependencyProperty HorizontalScrollBarVisibilityProperty { get; } ;
    static DependencyProperty VerticalScrollBarVisibilityProperty { get; } ;
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; };

}
```

Questions ouvertes

  • Est-ce qu'avoir une propriété ContentOrientation (ou une avec un nom différent) qui affecte la façon dont les contraintes de mise en page sont appliquées au contenu rendrait les choses plus compliquées ou plus faciles ?
  • Les API liées au zoom devraient-elles être séparées en un contrôle dérivé (par exemple ZoomViewer) de telle sorte que ScrollViewer concerne uniquement le défilement ?
Epic area-Scrolling feature proposal proposal-NewControl team-Controls

Commentaire le plus utile

C'est exact. Nous avions 3 situations en tête :

  1. Je me fiche de ma position actuelle. Je veux faire défiler/zoomer vers une destination spécifique.
  2. Je me soucie de ma position actuelle et je veux faire défiler/zoomer D'un montant spécifique par rapport à cette position.
  3. Peu m'importe quelle est la destination finale. Je veux juste faire défiler/zoomer DEPUIS ma position actuelle.

Un scénario pour ce dernier pourrait être quelque chose comme l'expérience d'un clic de souris pour effectuer un panoramique. En fonction de la position du curseur par rapport au moment où il a été cliqué, une certaine inertie est insérée au défilement de la position actuelle (quelle qu'elle soit).

Tous les 61 commentaires

Dans mon esprit, le besoin d'avoir un contenu qui peut être fait défiler et un contenu qui peut être zoomé est très différent. Je comprends que lorsque le contenu est zoomé, il doit souvent également être fait défiler, mais je suis curieux de savoir s'il a été envisagé de créer ces contrôles séparés ? Peut-être avec une commande zoomable s'étendant d'une commande à défilement.
Mes préoccupations sont les suivantes : 1) le ScrollViewer est rendu trop complexe en ajoutant des fonctionnalités liées au zoom qui ne seront pas nécessaires pour la plupart ; 2) il n'est pas évident pour quelqu'un à la recherche d'un contrôle pour zoomer sur le contenu que ScrollViewer le prend en charge.


De plus, dans la proposition, j'aimerais voir la possibilité de faire défiler jusqu'à un élément spécifique dans la visionneuse de défilement. En théorie, cela pourrait être fait par un développeur en mesurant tous les éléments avant lui, puis en faisant défiler jusqu'au décalage approprié, mais cela peut être difficile à faire avec des listes virtualisées et est (à mon avis) un scénario assez courant que de nombreuses applications ( et les développeurs) bénéficieraient de cette intégration.
Considérez où un élément est ajouté à une "liste" et le développeur veut s'assurer que cet élément est visible. Ou considérez une page avec une vue maître/détail où la page est ouverte à un élément de détail en bas de la liste, mais le développeur veut s'assurer que l'élément sélectionné dans la liste principale est affiché, de sorte qu'il fournit un contexte pour ce qui est affiché dans le vue détaillée.

De même, il serait également utile de pouvoir s'assurer qu'un élément reste dans la zone visible tandis que d'autres éléments dans les zones de défilement sont supprimés (ou ajoutés à) l'arborescence visuelle. Je ne sais pas quelle serait la meilleure façon de procéder, mais c'est très grave lorsque l'élément sélectionné dans une zone de défilement se déplace car une action d'arrière-plan (ou de thread hors interface utilisateur) supprime ou ajoute d'autres éléments à la zone de défilement.
Le déplacement d'un élément au fur et à mesure que vous cliquez/tapez dessus conduit à une expérience d'utilisation terrible. Le fait que des éléments ciblés/sélectionnés sortent de la zone visible peut également entraîner des problèmes pour les outils d'accessibilité et les lecteurs d'écran.
Je suis conscient qu'il y a des problèmes potentiels à essayer de garder un élément dans la même position pendant que ceux qui l'entourent bougent (surtout si cet élément est supprimé d'ailleurs), mais ne rien faire ne semble pas suffisant.

Merci @mrlacey. Je peux voir à quel point la fonctionnalité de zoom serait moins détectable dans le ScrollViewer existant ainsi que dans cette proposition. J'ai ajouté ceci en tant que question ouverte à la discussion car ce serait un autre point de départ de l'UWP ScrollViewer existant (peut-être pour le mieux ?).

La virtualisation rend les choses délicates car l'élément spécifique doit d'abord être créé et exécuté dans le système de mise en page pour avoir une position vers laquelle vous pouvez ensuite faire défiler. C'est pourquoi, dans le passé, certains contrôles (c'est-à-dire ListView) avaient leur propre méthode ScrollIntoView qui gère ce qui précède pour le rendre facile. Est-ce le type de méthode que vous envisagez? Alternativement, chaque UIElement a une méthode StartBringIntoView qui peut être utilisée pour faire défiler très facilement un élément et dispose d'un certain nombre d'options pour fournir un contrôle précis sur l'endroit où dans la fenêtre il atterrit lorsqu'il est affiché . L'approche StartBringIntoView fonctionne même dans les scénarios avec des surfaces de défilement imbriquées, car elle génère une demande qui bouillonne pour que chaque contrôle de défilement réponde. C'est essentiellement ce qui se passe lorsqu'un élément reçoit le focus clavier/GamePad/Narrateur. Bien sûr, cela nécessite que vous disposiez d'abord d'un UIElement que vous ne pouvez pas dans un scénario de virtualisation sans un moyen de déclencher explicitement la réalisation d'un élément. Serait-il utile d'avoir un moyen de déclencher explicitement la réalisation d'un élément ? Entre autres choses, vous pouvez ensuite l'utiliser pour déclencher un StartBringIntoView.

Re : maintenir la position d'un élément alors qu'un autre contenu est ajouté/supprimé ou change de taille... Tout à fait d'accord pour dire que ne rien faire n'est pas suffisant. C'est exactement pourquoi la fonction d' ancrage de défilement existe. :) C'est une capacité peu connue qui a été intégrée dans le passé aux panneaux de virtualisation pour ListView/GridView. Dans les versions récentes, nous avons fait un effort explicite pour dissocier des choses comme celle-ci du fait qu'elles ne soient disponibles que dans ListView.

Si l'idée d'un ScrollViewer étendu doit être explorée, les équipes de Fluent Design pourraient peut-être vous conseiller sur la gestion de la parallaxe et des éléments avec des valeurs de profondeur z définies par défaut.

Au fur et à mesure que XAML passe aux représentations 3D pour les applications MR, et que Depth and Shadows arrivent au 2D XAML - comment le défilement déplacerait des éléments de différentes valeurs de profondeur, comment les ombres peuvent s'afficher au-dessus ou derrière les indicateurs de défilement, et d'autres problèmes qui peuvent survenir dans les expériences 3D de l'équipe XAML - pourrait être intégré à ce qui pourrait devenir la nouvelle valeur par défaut, car la bibliothèque d'interface utilisateur Windows devient lentement la nouvelle valeur par défaut.

Dans mon esprit, le besoin d'avoir un contenu qui peut être fait défiler et un contenu qui peut être zoomé est très différent.

Sauf dans les navigateurs Web, les applications de conception graphique, les applications bureautiques, les visionneuses PDF... tout ce qui affiche un document de contenu.

Convenez avec

Merci a tous! Nous avons rassemblé des données télémétriques concernant les propriétés que les applications ont tendance à définir dans leur balisage et le ZoomMode apparaît juste après le *ScrollMode et *ScrollBarVisibility. La plupart du temps, les applications ne modifient aucune propriété. Ce que j'ai observé lorsque les propriétés ScrollMode et ScrollBarVisibility sont définies, c'est qu'il s'agit souvent d'activer le défilement horizontal sur le contenu. L'introduction de la propriété ContentOrientation est destinée à rendre cela plus facile. Après le défilement horizontal, le prochain scénario le plus courant est le zoom.

Plutôt que d'introduire un contrôle (dérivé) séparé pour traiter la découverte du zoom, je pense que la bonne documentation / les bons échantillons pourraient être plus efficaces.

Qu'en est-il de l'ajout d'une énumération de comportement ou de contexte ?

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

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom; est très déroutant cependant. Je pense que ZoomMode devrait rester tel quel.

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom; est très déroutant cependant. Je pense que ZoomMode devrait rester tel quel.

J'aurais probablement dû préfacer la suggestion en abordant les commentaires qui pensent qu'il devrait y avoir un contrôle dérivé pour le zoom sur le contenu, plutôt que pour le défilement

Pour moi, il est en fait logique d'avoir une fonctionnalité de zoom intégrée avec ScrollViewer , en particulier sur les appareils tactiles. Il n'y a actuellement aucun moyen facile de faire un panoramique/zoom/rotation de style libre sur une image/un contrôle dans UWP, et de nombreuses questions connexes ont été posées sur StackOverflow et GitHub (par exemple, ajouter un contrôle de contenu déplaçable ).

@micahl , je suppose qu'avec le zoom et

Soutenir les gestes de rotation

ce scénario serait supporté nativement à l'avenir ?

Pour moi, il est en fait logique d'avoir une fonctionnalité de zoom intégrée avec ScrollViewer , en particulier sur les appareils tactiles. Il n'y a actuellement aucun moyen facile de faire un panoramique/zoom/rotation de style libre sur une image/un contrôle dans UWP, et de nombreuses questions connexes ont été posées sur StackOverflow et GitHub (par exemple, ajouter un contrôle de contenu déplaçable ).

S'il est nécessaire d'avoir plus de contrôle sur le zoom et le défilement...

ScrollViewer.ScrollBehaviour = ScrollBehaviour.ZoomAndScroll;

Mais sur les appareils tactiles, il semble naturel de permettre le pincement et le glissement des doigts. Le zoom sans toucher nécessiterait cependant l'apparition des boutons [ + ] et [ − ]. Ou peut-être que s'il s'agissait uniquement de zoom, le déplacement du pouce de la barre de défilement effectuerait un zoom avant et arrière.

Si vous regardez l'interface utilisateur de Microsoft Word, il y a un contrôle de zoom dans la barre d'état. Et des barres de défilement pour la zone de document.

La prise en charge du zoom de la souris doit-elle être intégrée au nouveau contrôle ScrollViewer ? Ainsi, lorsque ZoomMode est activé ou qu'une propriété de comportement autorise le zoom, les boutons Plus et Moins apparaissent ?

@JustinXinLiu La prise en charge des gestes de rotation nécessiterait probablement l'assistance d'InteractionTracker (@likuba).

@mdtauk Le nouveau ScrollViewer prendrait en charge le zoom de la molette de la souris via un Ctrl + molette de la souris similaire au ScrollViewer existant. Je ne sais pas si le contrôle doit avoir un bouton plus/moins par défaut lorsque le zoom est activé. Mon impression actuelle est que les types d'applications qui permettent généralement le zoom (par exemple, une application de document de contenu) choisissent d'intégrer ces boutons dans leur expérience d'application de différentes manières. Par exemple, un - / + dans une barre d'état qui est séparé par un curseur (c'est-à-dire Office) ou simplement un simple -/+ qui apparaît verticalement sous d'autres commandes (c'est-à-dire Maps).

@micahl Je ne comprends pas pourquoi ScrollFrom s'appelle ainsi. N'est-ce pas comme ScrollBy mais avec un paramètre de vélocité ?

@adrientetar demandez -vous pourquoi ne pas avoir une autre surcharge de ScrollBy avec un ensemble de paramètres différent au lieu de le nommer ScrollFrom ?

Oui je suppose 😃 mais j'imagine que le concept derrière ScrollFrom est de se déplacer dans une direction avec une vitesse donnée ? par opposition à ScrollBy qui consiste simplement à se déplacer vers un point donné.

C'est exact. Nous avions 3 situations en tête :

  1. Je me fiche de ma position actuelle. Je veux faire défiler/zoomer vers une destination spécifique.
  2. Je me soucie de ma position actuelle et je veux faire défiler/zoomer D'un montant spécifique par rapport à cette position.
  3. Peu m'importe quelle est la destination finale. Je veux juste faire défiler/zoomer DEPUIS ma position actuelle.

Un scénario pour ce dernier pourrait être quelque chose comme l'expérience d'un clic de souris pour effectuer un panoramique. En fonction de la position du curseur par rapport au moment où il a été cliqué, une certaine inertie est insérée au défilement de la position actuelle (quelle qu'elle soit).

Merci, ça clarifie les choses. Je pense que je cherche davantage à utiliser ScrollBy pour ma propre expérience de clic avec la molette de la souris, mais je peux voir que ScrollFrom est utile dans des cas spécifiques.

Dans le contrôle ScrollViewer actuel, il est possible de se lier aux propriétés ViewportWidth et ViewportHeight, mais cela ne semble pas actuellement possible dans le nouveau contrôle ScrollViewer. Cela sera-t-il ajouté ?

@lhak , à quoi

@micahl Je suis presque sûr d'avoir également utilisé ces valeurs dans le passé pour certains calculs. Je ne sais pas si je me suis lié à eux.

@michael-hawker pour être clair, les propriétés seront disponibles sur ScrollViewer. Cependant, dans l'API proposée, elles sont exposées en tant que propriétés normales plutôt qu'en tant que DependencyProperties. Ces mêmes valeurs seront disponibles dans le cadre de la propriété ExpressionAnimationSources pour les situations où quelqu'un crée des animations basées sur les entrées basées sur Composition.

J'utilise actuellement un code similaire au suivant dans mon application :

<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>

Cela permet de faire défiler/zoomer l'élément xaml interne (qui a une taille fixe) tout en gardant son rapport hauteur/largeur. Le comportement est similaire à celui de l'application de photos affichant une image, à l'exception du fait que l'élément xaml n'est jamais plus petit que la fenêtre d'affichage du scrollviewer.

@lhak , bon scénario ! Réfléchir à haute voix à la manière dont ce scénario pourrait être intégré dans la proposition actuelle... Je pense que nous pourrions ajouter une autre option pour la propriété ContentOrientation. Les options actuelles de la proposition :

  • Aucun = Aucune orientation préférée. Donnez au contenu une taille disponible infinie dans les deux sens.
  • Horizontal = Donne au contenu une taille disponible infinie uniquement horizontalement et contraint la hauteur à la fenêtre pendant la mise en page.
  • Vertical = Transposer de l'horizontale

Une quatrième option serait de contraindre à la fois la hauteur et la largeur lors de la mise en page à la taille de la fenêtre. Pour le moment, j'appellerai cela une orientation "Viewport", bien que j'aie des réactions mitigées à propos de la nommer comme telle.
Au moins dans le cas (le plus courant?) D'une image, je pense que cela rendrait la Viewbox et les liaisons inutiles, car "la bonne chose" devrait simplement se produire dans le cadre du processus de mise en page.

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

Pour un contenu plus complexe, vous pourrez l'envelopper dans une Viewbox tout en ignorant les liaisons.

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

@micahl J'ai une autre question : j'aimerais modifier la manipulation de la molette de la souris de Scroller pour accélérer le zoom (incréments plus importants) et supprimer l'animation de lissage. Il semble qu'il n'y ait pas d'API pour modifier le zoom de la molette, dois-je gérer l'événement de la molette de la souris manuellement ?

Vous devrez peut-être manipuler la molette de la souris manuellement, mais il serait regrettable s'il s'avère que vous devez ré-implémenter ce que fait le contrôle. Nous pensons qu'il pourrait y avoir un moyen d'atteindre ce que vous recherchez. L'essentiel serait de définir le ZoomInertiaDecayRate sur une valeur proche de 1,0, puis de définir un point d'accrochage de zoom répété. C'est quelque chose à essayer une fois que nous avons ajouté la propriété ZoomInertiaDecayRate et corrigé certains problèmes liés aux points d'accrochage obligatoires.

Si cela ne fonctionne pas, vous pouvez essayer de le gérer vous-même en définissant IgnoredInputKind="Mousewheel", puis en sous-classant le Scroller pour remplacer OnPointerWheelChanged, ou en ajoutant un gestionnaire d'événements pour l'événement PointerWheelChanged sur Scroller.

@micahl Merci ! Je ne sais pas sur quel scénario vous avez calibré le comportement de la molette de la souris, personnellement j'aime la façon dont c'est par exemple dans Adobe XD : il y a un peu d'animation qui s'accélère, mais c'est vif et a des incréments de zoom assez grands. L'animation de zoom de la molette de la souris à défilement est un peu lente et a de très petits incréments de zoom imo.

@adrientetar , bons retours. Nous pouvons envisager de l'ajuster.

Tout type de variable qui contrôle les incréments et les valeurs - pourrait être transformé en propriétés remplaçables

Droit. Un défi pour nous ici est que sur Win10 version 1809 et versions ultérieures, le contrôle transfère l'entrée de la molette de la souris à l'InteractionTracker sous-jacent pour qu'il soit traité à partir du fil d'interface utilisateur pour un mouvement plus fluide. Ces propriétés prioritaires devraient d'abord être exposées au niveau inférieur. Pour les versions de Win10 antérieures à 1809, nous ajoutons une logique pour que la molette de la souris du processus de contrôle soit entrée sur le thread d'interface utilisateur.

Nous pourrions envisager d'avoir un comportement où nous exposons certaines propriétés remplaçables et si certaines sont explicitement définies, nous nous contenterons d'avoir la molette de la souris du processus de contrôle sur le thread d'interface utilisateur. Si/lorsque ces propriétés existent au niveau inférieur, nous pourrions nous appuyer sur elles et les renvoyer au compositeur. Le défilement ne serait probablement pas aussi fluide pour la molette de la souris. C'est peut-être OK ici ?

Ainsi, le comportement de zoom actuel est intégré à InteractionTracker sans aucun moyen de le remplacer? Et je suppose que maintenant il n'y a aucun moyen de le changer car cela casserait la compatibilité descendante 🤔 cc @likuba

Je pense qu'il y a quelques options sur InteractionTracker qui pourraient aider ici 😊. Plus précisément, nous avons construit des fonctionnalités telles que PointerWheelConfig et DeltaScaleModifier pour permettre ces types de personnalisations. Nous allons synchroniser et voir si leur utilisation dans le contrôle pourrait permettre à quelque chose de se produire ici sans les problèmes/risques que vous mentionnez ci-dessus.

@micahl J'ai une situation intéressante sur un projet sur
Supposons que vous ayez une toile géante, 10000x5000, donc pour la déplacer, vous l'encapsulez dans un scrollviewer.
Maintenant, sur le canevas, vous avez 2 InkCanvas (ou plus) et vous aimeriez écrire sur les deux (ou plus), avec des doigts différents ou avec 1 doigt et 1 pointeur de souris. Comment y parviendriez-vous (ou est-ce même possible) avec ce contrôle ? :)
Ce que j'ai vu, le comportement par défaut est que dès que vous touchez autre chose en dessinant sur un InkCanvas, cet InkCanvas particulier déclenche "PointerLost" et c'est tout - rien d'autre à faire.
Évidemment, mon scrollviewer est loin d'être parfait comme je l'ai écrit moi-même - donc je cherche à voir si cela pourrait être possible avec ce contrôle ?

Merci de bien vouloir !

Salut @stefangavrilasengage , scénario intéressant. :) À partir de votre PoC, quel est le comportement attendu lorsqu'un doigt descend sur un InkCanvas puis commence à bouger ? Est-ce qu'il dessine ou panoramique?

Salut @micahl - merci d'avoir répondu ! Je suppose que maintenant vous avez deviné que c'est quelque chose comme une application de tableau blanc numérique.
Maintenant je vais vous présenter ce qui se passe actuellement :

  • ScrollViewer (par défaut) : un doigt sur un InkCanvas est très bien. Le 2e doigt fait perdre la saisie au 1er InkCanvas.
  • ScrollViewerEx (contrôle personnalisé) : utilisez autant de doigts et autant d'InkCanvases pour dessiner et ça marche.

est-ce que cela aide? :)
Merci !

@stefangavrilasengage Je demandais ce qui permet de

@micahl Mes excuses - j'ai oublié de mentionner comment j'ai résolu le conflit.
Les objets que nous avons sur le canevas ont leur encre dans un conteneur Win2D. Ce n'est que si vous entrez dans un mode d'édition pour chaque objet qu'un InkCanvas sera présenté en haut pour l'édition (avec séchage personnalisé).
Par conséquent, dans ma situation (je ne sais pas si cela s'applique à quelqu'un d'autre), si vous avez un "dessinable" actif InkCanvas, rien ne devrait aller jusqu'à ScrollViewer.
Je serais curieux de connaître un cas d'utilisation où vous pensez que cela ne s'applique pas, le cas échéant?

Merci !

Difficile de croire que c'est la première fois que @micahl et moi sommes mêlés à un problème github !

Difficile de croire que c'est la première fois que @micahl et moi sommes mêlés à un problème github !

Désolé pour ça ! Edité !

@micahgodbolt ce n'est peut-être pas le dernier. ;)
@stefangavrilasengage merci ! Cela répond à ma question. J'ai remarqué qu'échanger un ScrollViewer avec quelque chose d'autre comme un Border en tant que parent d'un Canvas contenant plusieurs contrôles InkCanvas n'a pas le même problème. J'ai donc contacté certaines personnes en interne pour comprendre ce qui peut provoquer la panne de la fonctionnalité multi-entrée / multi-InkCanvas lorsqu'elle se produit dans un ScrollViewer.

@stefangavrilasengage , comprendre pourquoi cela ne fonctionne pas prendra un certain temps/une enquête. Suivons cela séparément de cette proposition. Allez-vous ouvrir un problème pour cela?

@micahl J'ai un problème avec Scroller où le défilement et le zoom autour de mon contenu dans OnControlLoaded me mettent dans une mauvaise position. Si je ne fais que défiler, je me retrouve dans la bonne position, mais si je zoome juste après le défilement, je suis dans une position totalement éteinte, même si j'ai spécifié centerPoint=null donc il est par défaut au centre de la fenêtre (en regardant le code car il y a pas de documentation encore en cause). Avec ce code .

Merci d'avoir essayé les choses, @adrientetar ! Gardons ce fil concentré sur la proposition. Pouvez-vous ouvrir une question distincte pour ce que vous voyez? Selon l'endroit où nous entrons dans cette discussion, nous pouvons vouloir revenir à ce fil pour discuter si cela justifie des modifications à la proposition.

@micahl Pourquoi les événements ExtentChanged/StateChanged/ViewChanged ont-ils un objet comme argument et pas, par exemple, StateChangedEventArgs avec un État membre ? Étant donné qu'InteractionTracker s'exécute sur un thread différent, le modèle de passage de message (événement avec un argument) n'est-il pas préférable à l'interrogation de la propriété de contrôle dans le gestionnaire d'événements ?

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

Ce n'est pas très grave, mais la première partie de ce référentiel montre comment implémenter le zoom même lorsque les barres de défilement horizontales/verticales sont verrouillées (désactivées),

En termes plus simples :
Permet de faire un panoramique sur un contenu zoomé tout en forçant l'ajustement à
Fenêtre verticale/horizontale efficace.

Au moment de publier ceci, il est obligatoire d'activer la propriété HorizontalScrollbarVisibility dans un ScrollViewer afin d'obtenir une opération de zoom fonctionnelle, et bien qu'il puisse y avoir de meilleures façons de contourner cela, je pense que la solution à double scrollviewer que j'ai mis au rebut pourrait être suffisante assez.

@micahl Je vois que Scroller n'envoie pas d'événements StateChanged lors de l'exécution d'un zoom non animé, est-ce par conception ?
Je définis ma mise à l'échelle CanvasVirtualControl lorsque Scroller devient inactif, comme vous l'avez suggéré dans https://github.com/microsoft/microsoft-ui-xaml/issues/541#issuecomment -488749469, de sorte que le contrôle ne finisse pas par être trop grand parties du canevas par rapport au facteur de zoom. Mais avec un zoom non animé, je dois le faire au moment où j'appelle la méthode ZoomTo, même si le changement de zoom n'a pas été réalisé (afaict).

@adrientetar , il y a une certaine surcharge à créer des arguments d'événement à passer entre le framework et l'application. Parce que l'événement arg ne fournirait qu'une copie des propriétés exposées par l'expéditeur, notre conception actuelle consiste à ne pas avoir l'événement arg.

Oui, la conception actuelle est que dans un zoom non animé que vous invoquez par programme, le StateChanged ne se déclenche pas. L'événement ZoomCompleted et ScrollCompleted serait déclenché en réponse aux modifications du programme. IIRC, ils ne sont pas déclenchés par les changements de l'utilisateur. Vous pouvez en utiliser un, peut-être en combinaison avec StateChanged. Pour info, l'événement ViewChanged est déclenché à chaque fois que la vue change (utilisateur ou programmatique), ce qui en fait un chemin de code très sensible aux performances et n'est pas ce que vous voudriez utiliser.

Nous sommes toujours à l'écoute des commentaires, donc si les choses semblent obtus, faites-le nous savoir.

Oui, la conception actuelle est que dans un zoom non animé que vous invoquez par programme, le StateChanged ne se déclenche pas.

Ah d'accord, je comprends maintenant. Peut-être que l'événement pourrait toujours se déclencher et contenir un paramètre d'origine, comme SetFocus, mais peut-être qu'il y a une surcharge à faire.

L'événement ZoomCompleted et ScrollCompleted serait déclenché en réponse aux modifications du programme. IIRC, ils ne sont pas déclenchés par les changements de l'utilisateur.

D'accord, donc pour mon cas d'utilisation, je dois gérer ZoomCompleted et StateChanged iff State == Idle, selon que le changement est programmatique ou non. N'est-ce pas bizarre ? Je veux dire, avoir différents événements avec des noms différents pour la même chose n'a déclenché qu'une manière différente 🤔

@RBrid et moi avons eu une brève conversation sur l'introduction d'un nouvel état "Transition" et la modification de certains noms mineurs. Faites-nous savoir si cela aurait plus de sens pour votre cas d'utilisation.

L'idée discutée était que lorsqu'un changement de la position de défilement/zoom est sur le point d'être traité (que ce soit à l'initiative de l'utilisateur ou par programmation), nous déclenchions l'événement StateChanged et signalions l'état actuel comme Transitioning. Voici à quoi ressemblerait la séquence pour un défilement programmatique (animé et non animé) :

// demande programmatique, animée
myScrollInfo = ScrollTo(100, animer=true);
// quelques ticks
StateChanged levé (Scroller.State == Transitioning)
// quelques ticks
StateChanged levé (Scroller.State == Animation)
// plusieurs ticks
StateChanged levé (Scroller.State == Au ralenti)

// demande programmatique, pas d'animation
myScrollInfo = ScrollTo(100, animer=false);
// quelques ticks
StateChanged levé (Scroller.State == Transitioning)
// quelques ticks
StateChanged levé (Scroller.State == Au ralenti)

Nous voudrions probablement renommer l'énumération InteractionState en quelque chose comme ScrollState car cela représenterait plus que les interactions.

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

@micahl Oui, cela ressemble beaucoup à ce à quoi je m'attendrais ! Quelqu'un qui souhaite gérer uniquement les modifications animées peut intercepter l'état Animation. Pour les noms, je garderais Idle not Idling ("Le défilement est inactif"/"Le défilement s'anime"), Transitioning → Stirring ? (La transition est très déroutante, je préférerais que nous transmettions que c'est au début du mouvement, donc l'agitation imo pourrait mieux fonctionner ), l'inertie pourrait être en dérive 😛 mais je suppose que l'examen de l'API interviendra également.

L'agitation m'a d'abord fait penser au mixage. :) L'examen de l'API voudra peser lorsque nous finaliserons les noms et tendra à rechercher des précédents. Il est toujours bon d'avoir des options. D'autres alternatives peuvent être Inconnues ou En attente. Il existe un précédent dans les API existantes pour les deux avec Idle, ce qui signifie que nous ne ferions probablement pas Idling.

Cool cool. En attendant sgtm, imo Unknown est encore plus déroutant que Transitioning.

@micahl -- L'exigence fonctionnelle numéro 3 comprend ce sous-point :

participe aux modifications effectives de la fenêtre (Obligatoire)

Ce sous-point fait référence à l'événement préexistant FrameworkElement.EffectiveViewportChanged . Ce sous-point est-il suffisant pour dire (indirectement) que le chargement partiel asynchrone à la demande du contenu est également une exigence/une priorité ? Je veux dire, par exemple, l'équivalent du fonctionnement de ScrollViewer lorsque MS Edge affiche une page agrandie d'un document PDF (ou SVG ou autre document complexe). Lorsque le zoom est de 900 % (par exemple), il ne pré-rend pas toute la page PDF à une taille de 900 %, pour ces 2 raisons :

  • Cette tâche de rendu (à pourcentage de zoom élevé) nécessite environ 3 .. 20 secondes, selon la complexité du document (quantité d'éléments vectoriels, effets, etc.), la taille du document et le pourcentage de zoom. Ce délai est trop important pour que les utilisateurs finaux l'acceptent.
  • Une image rendue (bitmap) de la page PDF entière peut consommer des centaines de mégaoctets de RAM lorsque le pourcentage de zoom est élevé, car cela équivaut à un rendu à un DPI très élevé.

MS Edge restitue à la demande des parties de la page PDF agrandie, c'est-à-dire lorsque l'utilisateur fait défiler ces parties pour les afficher. Cette technique évite le long délai avant l'affichage de la page. Je suggère que les exigences fonctionnelles mentionnent explicitement ce scénario, mais également avec un support indirect pour async Task . Lorsqu'une partie non rendue (ou non immédiatement disponible) défile dans la vue, ScrollViewer peut temporairement remplir cet espace avec un Brush configurable, puis déclencher un événement, et le gestionnaire d'événement peut démarrer un async Task pour rendre (ou autrement récupérer/charger) la pièce. Plus tard, lorsque le Task terminé, la partie rendue/récupérée/chargée est affichée dans ScrollViewer, couvrant l'espace qui a été temporairement rempli avec le Brush .

Ce problème ne concerne pas seulement le coût élevé du rendu à un zoom élevé. Elle s'applique également au contenu récupéré à la demande. Par exemple, imaginez une carte ou une image satellite affichée dans ScrollViewer. Des parties de la carte ne sont téléchargées à partir du serveur que lorsque l'utilisateur les fait défiler pour les visualiser.

Les API liées au zoom devraient-elles être séparées en un contrôle dérivé (par exemple ZoomViewer) de telle sorte que ScrollViewer concerne uniquement le défilement ?

Bien que j'aie beaucoup apprécié l'ajout de ScrollViewer.ZoomFactor , j'ai été surpris de voir qu'il a été directement ajouté à ScrollViewer. J'ai supposé qu'il serait plus simple et plus fiable de mettre zoom dans une classe distincte (pas nécessairement une classe dérivée). Au moins 3 voies sont possibles :

  1. Une classe nommée peut-être ZoomViewer qui n'hérite PAS de ScrollViewer , mais utilise une instance ScrollViewer comme élément enfant (dans son ControlTemplate ).
  2. Une classe nommée peut-être ZoomableScrollViewer qui hérite de ScrollViewer .
  3. Prise en charge directe du zoom dans ScrollViewer , si ce n'est pas excessivement compliqué ou désordonné.

17 Fournissez une UX par défaut pour prendre en charge un clic du milieu de la souris et un défilement. (Devrait)
18 Prend en charge un mode pour cliquer et faire un panoramique via la souris (par exemple, déplacer du contenu dans une visionneuse PDF). (Devrait)

Je suggère d'envisager de supprimer la prise en charge de 17, car 18 fonctionne beaucoup mieux que 17, et pratiquement personne n'utilise 17 dans la pratique. Certes, je pourrais penser que 17 signifie autre chose que le sens que vous souhaitez (la description de 17 n'est qu'une phrase et je ne suis pas sûr à 100% que cela signifie ce que je pense que cela signifie). N'est-il pas exact de dire que 18 est très convivial et facile à utiliser, alors que 17 est une chose maladroite que tout le monde a du mal à utiliser et évite d'utiliser ?
(Ce problème change si 17 est nécessaire pour des raisons d'accessibilité, mais jusqu'à présent, je n'ai jamais entendu personne dire que 17 est un problème d'accessibilité.)

4 Capable d'effectuer des animations basées sur les entrées

Le numéro 4 est un problème lié à l'accessibilité. Je voudrais demander à ce que ScrollViewer (et tous les autres contrôles dans WinUI) respecte les paramètres d'accessibilité :

Windows 10 -> Démarrer -> Paramètres -> Facilité d'accès -> Affichage -> Afficher les animations sous Windows (Activé ou Désactivé).

Malheureusement, au fil des ans, j'ai remarqué de nombreux exemples où les applications Microsoft ignorent les paramètres d'accessibilité de Windows. Les animations sont désactivées, mais les applications Microsoft affichent quand même des animations. Cela pose de réelles difficultés aux utilisateurs qui se fient à ces paramètres d'accessibilité. Tout le monde n'a pas la possibilité de profiter des animations sans subir d'effets secondaires négatifs. Les utilisateurs qui n'éprouvent aucune difficulté à visualiser des animations, etc. peuvent avoir du mal à comprendre la signification des paramètres d'accessibilité dans Windows.

Salut @verelpode , @predavid dirigera le travail autour du nouveau ScrollViewer, je m'en remets donc à elle.

17 Fournissez une UX par défaut pour prendre en charge un clic du milieu de la souris et un défilement. (Devrait)
18 Prend en charge un mode pour cliquer et faire un panoramique via la souris (par exemple, déplacer du contenu dans une visionneuse PDF). (Devrait)

Je suggère d'envisager de supprimer la prise en charge de 17, car 18 fonctionne beaucoup mieux que 17, et pratiquement personne n'utilise 17 dans la pratique. Certes, je pourrais penser que 17 signifie autre chose que le sens que vous souhaitez (la description de 17 n'est qu'une phrase et je ne suis pas sûr à 100% que cela signifie ce que je pense que cela signifie). N'est-il pas exact de dire que 18 est très convivial et facile à utiliser, alors que 17 est une chose maladroite que tout le monde a du mal à utiliser et évite d'utiliser ?
(Ce problème change si 17 est nécessaire pour des raisons d'accessibilité, mais jusqu'à présent, je n'ai jamais entendu personne dire que 17 est un problème d'accessibilité.)

@verelpode Je pense que vous comprenez bien 17 et je suis d'accord que 18 est plus important et généralement plus intuitif que 17. Mais il peut y avoir des scénarios où "clic gauche et glisser" est déjà utilisé à d'autres fins, telles que la sélection ou le glisser-déposer . Pour de tels scénarios, il serait bon que l'application puisse permettre le défilement par clic du milieu (17).

Personnellement, j'utilise de temps en temps le défilement par clic central dans les navigateurs, où le clic gauche + glisser provoque soit une sélection de texte, soit un glisser-déposer d'images. Un navigateur UWP en aurait 18 désactivés et 17 activés. Les deux doivent être activés via des propriétés distinctes.

@lukasf

Personnellement, j'utilise de temps en temps le défilement par clic central dans les navigateurs, où le clic gauche + glisser provoque soit une sélection de texte, soit un glisser-déposer d'images.

Bon point concernant la nécessité d'éviter de provoquer l'arrêt de la sélection de texte, etc. Que pensez-vous de cette solution possible : faites un clic du milieu pour démarrer le mode panoramique basé sur la souris (18) au lieu de démarrer le mode 17 maladroit.

Lorsque je regarde ce que font d'autres applications, certaines applications vous permettent de démarrer le panoramique à l'aide de la souris en maintenant enfoncée la touche de la barre d'espace tout en cliquant avec le bouton gauche dans la zone de contenu déroulante. Cette solution permet au clic gauche de fonctionner normalement dans la zone de contenu déroulante car le mode panoramique ne démarre que via la barre d'espace + clic. Je trouve le panoramique via la barre d'espace + clic très confortable, pratique et rapide.

Cependant, cette solution barre d'espace + clic est plus facile à implémenter dans les applications qui n'ont pas besoin d'afficher de zones de texte modifiables dans la zone de contenu déroulante. S'il existe des zones de texte modifiables dans la zone de contenu défilant, la fonction de panoramique empêche les utilisateurs de saisir des espaces dans les zones de texte. Par conséquent, comme vous l'avez dit, cette fonctionnalité devrait être activée via une propriété. Alternativement, n'utilisez pas la barre d'espace et faites plutôt un clic du milieu pour démarrer le mode panoramique basé sur la souris (18), ce qui élimine le problème des utilisateurs incapables de taper des espaces dans les zones de texte.

@verelpode
Avec tous les navigateurs et aussi un tas d'autres applications (Word, Adobe Reader, Outlook,...) prenant en charge le mode 17, je pense toujours que cela devrait être implémenté. Ce n'est pas parce que vous le trouvez personnellement gênant qu'il n'est pas utile aux autres. Les deux modes doivent bien sûr être activés, les développeurs peuvent alors décider ce qui doit être utilisé dans leur application. Cela permettrait également à votre comportement barre d'espace + clic, si cela a du sens pour une application : activez le mode 18 sur la barre d'espace vers le bas, désactivez-le à nouveau sur la barre d'espace vers le haut.

@lukasf -- OK,

Merci pour vos commentaires @verelpode et @lukasf , je suis d'accord avec vous pour dire qu'en fin de compte, nous voudrons que les boutons publics de la commande Scroller activent /désactivent les numéros 17 et 18. Appelons-les 17 = déplacement à vitesse constante basé sur la souris et 18 = panoramique basé sur la souris.

Je viens d'envoyer un PR https://github.com/microsoft/microsoft-ui-xaml/pull/1472 pour ajouter du travail d'enquête que j'ai fait. Je voulais voir à quel point je pouvais me rapprocher de la prise en charge des 17 et 18 en utilisant le Scroller d'aujourd'hui.

Les choses se sont plutôt bien passées pour 18 = panoramique basé sur la souris, bien que la solution soit 100% liée à l'interface utilisateur, contrairement aux expériences tactiles ou basées sur la molette de la souris. J'ai utilisé la méthode ScrollTo tout en écoutant l'événement PointerMoved de la souris. En fin de compte, nous voudrions que le composant InteractionTracker sous-jacent gère les mouvements de la souris comme il le fait avec les mouvements des doigts.

Les choses sont beaucoup plus délicates pour 17 = panoramique à vitesse constante basé sur la souris (une expérience que je n'aime pas personnellement). Le prototype est loin d'être idéal et je n'ai pas essayé d'aborder toutes les questions, mais deux en particulier sont préoccupantes :

  • pendant un panoramique à vitesse constante (c'est-à-dire un panoramique 0-velocity-decay), je ne pense pas qu'il y ait un moyen avec le Scroller actuel d'arrêter le mouvement sans revenir à une position plus ancienne (avec ScrollBy (0, 0)) . Il n'est tout simplement pas possible de savoir de manière fiable dans quelle mesure le thread de composition a dépassé le thread d'interface utilisateur. Ainsi, une nouvelle API publique Scroller serait nécessaire pour arrêter la vitesse sans problème.
  • Je n'ai pas pu conserver la capture de la souris après l'événement PointerReleased de la souris. Il semble qu'une nouvelle fonctionnalité de framework Xaml soit nécessaire pour y parvenir.
    Quoi qu'il en soit, cela peut être un autre cas où nous voudrions que l'InteractionTracker sous-jacent gère directement une partie de l'expérience.
    Je garderai cela à l'esprit lorsque je discuterai des futures fonctionnalités de Scroller/InteractionTracker.

@RBrid

Les choses se sont plutôt bien passées pour 18 = panoramique à la souris, …...
Les choses sont beaucoup plus délicates pour 17 = panoramique à vitesse constante basé sur la souris (une expérience que je n'aime pas personnellement).

Résultats intéressants ! Dans ce cas, compte tenu des difficultés avec 17 = vitesse constante, et considérant que 18 peut être mis en œuvre d'une manière qui n'empêche pas l'utilisation normale du clic gauche, alors à mon avis personnel, je pense que la proposition devrait probablement être mise à jour pour abandonnez 17 et supportez 18 à la place, mais je ne sais pas combien d'utilisateurs se plaindraient si 17=constant-velocity était abandonné.

Merci @RBrid pour ces enquêtes. Super d'entendre que le mode 18 fonctionne déjà ! Je suis d'accord qu'il devrait idéalement être géré par InteractionTracker, pour permettre un fonctionnement fluide même pendant le chargement du fil d'interface utilisateur.

@verelpode Les deux modes sont définis comme "devraient". Donc, si l'effort est trop important pour réaliser le mode 17, il peut simplement être omis (et peut-être ajouté plus tard, si nécessaire). Mais peut-être que quelqu'un trouve un moyen de le réaliser sans trop de changements.

J'espère que le nouveau ScrollViewer a la capacité de désactiver ou de personnaliser le zoom avec la touche "Ctrl".

Cette page vous a été utile?
0 / 5 - 0 notes