Microsoft-ui-xaml: Un ScrollViewer más flexible

Creado en 19 dic. 2018  ·  61Comentarios  ·  Fuente: microsoft/microsoft-ui-xaml

Esta es una plantilla para nuevas funciones o propuestas de API. Por ejemplo, puede usar esto para proponer una nueva API en un tipo existente o una idea para un nuevo control de IU. Está bien si no tiene todos los detalles: puede comenzar con el Resumen y la justificación. Este enlace describe el proceso de propuesta de la función / API de WinUI: https://github.com/Microsoft/microsoft-ui-xaml-specs/blob/master/docs/feature_proposal_process.md Agregue un título para su propuesta de función o API. Sea breve y descriptivo

Propuesta: ScrollViewer

Resumen

tl: dr; Proporcione un control de desplazamiento y zoom flexible, pero fácil de usar, en XAML que aún se puede aprovechar al apuntar a versiones de nivel inferior de Win10.

El control ScrollViewer juega un papel fundamental en cualquier interfaz de usuario debido a lo fundamental que es el desplazamiento para las aplicaciones y los controles (tanto de plataforma como de no plataforma). El control proporciona capacidades de panorámica y zoom junto con políticas predeterminadas y UX (por ejemplo, barras de desplazamiento conscientes, interacción de enfoque / teclado, accesibilidad, animación de desplazamiento predeterminada, etc.). También debe ser lo suficientemente flexible como para que pueda pasar de facilitar los escenarios cotidianos de las aplicaciones a convertirse en un componente clave en los controles basados ​​en desplazamiento y dar servicio a casos de uso muy avanzados. El control actual no proporciona este nivel de flexibilidad.

Razón fundamental

Describa POR QUÉ se debe agregar la función a WinUI para todos los desarrolladores y usuarios. Si corresponde, también puede describir cómo se alinea con la hoja de ruta y las prioridades actuales de WinUI: https://github.com/Microsoft/microsoft-ui-xaml-specs/blob/master/docs/roadmap.md

Llevar el control ScrollViewer al repositorio de una manera que se superpone a las API públicas de la plataforma central es un trampolín importante que:

  1. permite incorporar más controles al repositorio (mayor agilidad),
  2. brinda a los desarrolladores un control fácil de usar que muestra la flexibilidad de las capacidades de la plataforma de nivel inferior combinadas con el beneficio de los servicios de marco de nivel superior (por ejemplo, accesibilidad), y
  3. permite que las características más nuevas relacionadas con el desplazamiento en XAML se iluminen en versiones anteriores de Win10.

El control existente fue creado en Win8 basado en las API de DirectManipulation que 1) no están disponibles para usar directamente dentro de UWP, 2) no permiten el nivel de personalización que los desarrolladores esperan para crear experiencias de desplazamiento únicas, y 3) son reemplazadas por las API de InteractionTracker más nuevas de UWP.

Extraer el control ScrollViewer existente tal como está no sería nada sencillo. En cambio, el control debe ser una reimplementación con una superficie de API casi idéntica basada en las capacidades más nuevas (y ya disponibles) de InteractionTracker .

Plan de alto nivel

El ScrollViewer existente en el espacio de nombres _Windows_.UI.Xaml.Controls permanecerá intacto (aparte de corregir errores críticos).

El nuevo ScrollViewer vivirá en el espacio de nombres _Microsoft_.UI.Xaml.Controls. Su API será en su mayoría idéntica a la versión de _Windows_. Haremos ajustes específicos a la API del control donde exista un beneficio claro (simplificar un escenario común, introducir nuevas capacidades, etc.).

En combinación con el nuevo ScrollViewer, presentaremos un nuevo tipo, Scroller, que vivirá en el espacio de nombres Microsoft.UI.Xaml.Controls._Primitives_. El Scroller proporcionará la funcionalidad principal para desplazarse y hacer zoom en XAML según las capacidades de InteractionTracker .

El objetivo inicial de ambos controles será lograr que la funcionalidad principal para que la calidad del envío sea parte del próximo lanzamiento oficial. Las API no finalizadas permanecerán en 'vista previa' y solo estarán disponibles como parte de los paquetes preliminares.

-------------------- Las siguientes secciones son opcionales al enviar una idea o propuesta. Todas las secciones son obligatorias antes de que aceptemos un RP para dominar, pero no son necesarias para iniciar la discusión. ----------------------

Requerimientos funcionales


| # | Característica | | Prioridad |
|: -: |: - | - |: -: |
| 1 | Proporciona un control de panorámica y zoom _no sellado_ con una UX predeterminada que coincide con el ScrollViewer existente. || Debe |
| 2 | Demuestra las capacidades de la plataforma confiando únicamente en API públicas compatibles con la plataforma. || Debe |
| 3 | Se integra con servicios XAML de nivel de marco.
Por ejemplo:
- renderiza correctamente los rectos de enfoque del sistema
- trae automáticamente elementos enfocados a la vista (teclado, GamePad, lectores de pantalla)
- participa en cambios efectivos de la ventana gráfica
- admite anclaje de desplazamiento || Debe |
| 4 | Capaz de realizar animaciones basadas en entradas || Debe |
| 5 | Se puede usar en combinación con los controles dependientes del desplazamiento existentes que ya están en el repositorio (es decir, ParallaxView, RefreshContainer, SwipeControl). || Debe |
| 6 | Capaz de controlar la curva para cambios de vista inercial (es decir, desplazamiento animado personalizado o zoom). || Debe |
| 7 | Capaz de cambiar la vista en función de compensaciones absolutas o relativas (por ejemplo, desplazarse a un punto de interés) || Debe |
| 8 | Capaz de cambiar la vista en función de un impulso / velocidad adicional (por ejemplo, desplazamiento suave automático durante la función de arrastrar y soltar o selección múltiple mediante un rectángulo de selección del mouse) || Debe |
| 9 | Capaz de detectar overpan y por cuánto || Debe |
| 10 | Capaz de observar y reaccionar a los cambios en el estado del desplazamiento || Debe |
| 11 | Soporte para desplazarse y hacer zoom en los puntos de ajuste. || Debería |
| 12 | Capaz de utilizar un control personalizado de "barra de desplazamiento" para impulsar el desplazamiento (por ejemplo, el depurador de la línea de tiempo de las fotos). || Debería |
| 13 | Capaz de configurar fácilmente el control para ignorar tipos de entrada específicos (por ejemplo, responder al tacto o al lápiz, pero ignorar la entrada de la rueda del mouse). || Debería |
| 14 | Capaz de guardar y restaurar la posición de desplazamiento (por ejemplo, en una lista de virtualización) || Debería |
| 15 | Soporte para encabezados / elementos fijos. || Debería |
| 16 | Soporte para desplazamiento acelerado cuando un usuario realiza gestos repetidos en rápida sucesión. || Debería |
| 17 | Proporcione una experiencia de usuario predeterminada para admitir un clic central del mouse y un desplazamiento . |mousewheel panning | Debería |
| 18 | Admite un modo para hacer clic y desplazarse mediante el mouse (por ejemplo, mover contenido dentro de un visor de PDF). |mouse hand cursor for panning | Debería |
| 19 | Capaz de utilizar un control personalizado para impulsar el zoom (por ejemplo, un control deslizante de zoom). || Debería |
| 20 | Capaz de imprimir contenido contenido en el área desplazable. || Debería |
| 21 | Admite gestos de rotación. || Podría |
| 22 | Capaz de sincronizar dos o más áreas con un desplazamiento sin parpadeos ni tirones (por ejemplo, un escenario de diferencia de texto). || Podría |
| 23 | Capaz de personalizar la curva de animación para cambios de vista impulsados ​​por la entrada (es decir, definir comportamientos con el dedo hacia abajo, como pozos de gravedad). || Podría |
| 24 | Admite un gesto de desplazamiento hacia arriba o hacia abajo . || Podría |
| 25 | Capaz de deshabilitar el overpanning . || Podría |
| 26 | Capaz de manipular de forma selectiva el contenido dentro de ScrollViewer en función de la propiedad ManipulationMode de un UIElement . || Podría |
| 27 | Capaz de apagar la inercia y / o rebotar por inercia. || Podría |
| 28 | Capaz de deshabilitar el recorte del contenido (es decir, CanContentRenderOutsideBounds ). || Podría |
| 29 | Capaz de determinar el motivo de finalización al final de un cambio de vista. || Podría |

Terminología

  • _Overpan_ es cuando el usuario intenta desplazarse o hacer zoom más allá del tamaño del contenido y da como resultado el efecto de banda elástica / elástica.
  • _Railing_ es cuando el desplazador bloquea automáticamente el movimiento en un eje "preferido" según la dirección del gesto inicial.
  • _Chaining_ es cuando hay una superficie scrollabe anidada dentro de otra y cuando el movimiento de scroll del interior alcanza su extensión se promueve para continuar en el exterior.
  • _Snap points_ son ubicaciones dentro del contenido desplazable donde la ventana se detendrá al final del desplazamiento inercial (es decir, un desplazamiento animado después de que el usuario levantó el dedo). Se requieren puntos de ajuste para un control como FlipView.
  • _Scroll anchoring_ es donde la ventana
  • _Los pozos de gravedad_ son ubicaciones dentro del contenido desplazable que afectan el comportamiento de desplazamiento mientras el usuario manipula el contenido (es decir, el dedo del usuario está hacia abajo). Por ejemplo, los pozos de gravedad podrían usarse para alterar la fricción percibida del desplazamiento donde el movimiento parece ralentizarse o acelerarse a medida que el usuario hace una panorámica o zoom de forma activa.

Ejemplos de uso

Incluya uno o más ejemplos que muestren cómo utilizaría la función. Puede incluir ejemplos de código o capturas de pantalla si eso ayuda

Prefacio

De forma predeterminada, ScrollViewer admite desplazarse por su contenido cuando el tamaño del contenido es mayor que su ventana gráfica. El tamaño del contenido lo determina el sistema de diseño de XAML.

Los ejemplos aquí están destinados a resaltar la principal diferencia de API entre el ScrollViewer actual y el nuevo ScrollViewer.

Desplazamiento vertical (sin diferencia)

El ancho del contenido se restringe automáticamente para que sea el mismo que el de la ventana gráfica. La altura no está restringida. Si excede la altura de la ventana gráfica, el usuario puede desplazarse mediante varias modalidades de entrada.

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

Desplazamiento horizontal

Esta es una desviación intencional del ScrollViewer existente que anteriormente requería cambiar HorizontalScrollBarVisibility. Ha confundido a muchos desarrolladores.

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

El desplazamiento solo horizontal se habilita estableciendo la propiedad _ContentOrientation_ . Determina qué restricciones se utilizan en el contenido durante el diseño. Un valor de Horizontal implica que el contenido no está restringido horizontalmente (tamaño infinito permitido) y verticalmente restringido para coincidir con la ventana gráfica. De manera similar, Vertical (el valor predeterminado) implica que no está restringido verticalmente y restringido horizontalmente.

Desplazamiento de una imagen grande

Una orientación de contenido de _Ambos_ implica que el contenido no está restringido tanto horizontal como verticalmente.

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

Cambiar la visibilidad de la barra de desplazamiento

Este ejemplo siempre oculta ambas barras de desplazamiento. El usuario aún puede desplazar el contenido en cualquier dirección.

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

Desactivación del soporte integrado para tipos específicos de entrada

En este ejemplo, un desarrollador está creando una aplicación basada en lienzo que realizará un procesamiento personalizado en la entrada de la rueda del mouse y admitirá una experiencia de selección de lazo a través de Pen. Puede configurar ScrollViewer para ignorar esos tipos de entrada y al mismo tiempo aceptar otros como Touch.

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

Personaliza la animación de un scroll programático

El desarrollador escucha el evento ValueChanged de Slider y anima VerticalOffset de ScrollViewer usando una duración personalizada en la animación predeterminada.

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

Diseño de características detalladas

Incluya todos los detalles de diseño importantes. Esto podría incluir uno o más de: - una propuesta de API (cualquier lenguaje compatible o pseudocódigo está bien) - maquetas de diseño para una nueva experiencia de usuario - detalles sobre el cumplimiento de la accesibilidad para la nueva interfaz de usuario - otras notas de implementación

Desplazamiento de política alta y política baja

Scroller (política baja)

El Scroller es un bloque de construcción de bajo nivel sin cromo que proporciona toda la lógica esencial de panorámica y zoom. Envuelve InteractionTracker de política aún más baja de la plataforma como un elemento compatible con el marcado XAML.
De una manera muy literal, Scroller es "la ventana gráfica" de ScrollViewer y ocupa el lugar del ScrollContentPresenter. Sin embargo, el Scroller, a diferencia del ScrollContentPresenter, hace mucho más que simplemente recortar su contenido.

Scroller ofrece la flexibilidad de usar InteractionTracker directamente junto con las siguientes ventajas:

  • Soporte de accesibilidad
  • Sintaxis amigable con el marcado y API fácil de usar
  • Integración con el sistema de diseño de XAML y las capacidades de virtualización

ScrollViewer (alta política)

El nuevo ScrollViewer envuelve el Scroller y establece sus propiedades en valores comunes. Por ejemplo, un <ScrollViewer/> configura su Scroller para admitir interacciones de desplazamiento horizontal y vertical, pero restringe el ancho de su contenido de modo que la experiencia del usuario predeterminada parezca ser un desplazamiento solo vertical (el caso común).

ScrollViewer ofrece las siguientes ventajas sobre el uso de Scroller:

  • Una UX predeterminada (por ejemplo, el indicador de desplazamiento, barras de desplazamiento conscientes para el mouse, una curva de animación predeterminada)
  • Soporte predeterminado para la entrada vinculada a subprocesos de la interfaz de usuario no manejada por Scroller / InteractionTracker (es decir, teclado y GamePad)
  • Movimiento de enfoque predeterminado para GamePad (página arriba / abajo y ajuste automático del enfoque en respuesta a los disparadores)
  • Conocimiento predeterminado de la configuración del sistema del usuario (por ejemplo, ocultar automáticamente las barras de desplazamiento en Windows)
  • (Futuro) Opciones de configuración sencillas para puntos de ajuste
  • (Futuro) Soporte para más modos de desplazamiento del mouse (por ejemplo, hacer clic y arrastrar contenido con una mano abierta / cerrada, desplazamiento del mouse mediante la rueda del mouse / clic en el botón central que muestra un cursor de "brújula")

Cual usar?

La opción predeterminada para las aplicaciones y muchos autores de controles debería ser utilizar ScrollViewer. Proporciona una mayor facilidad de uso y una experiencia de usuario predeterminada que es coherente con la plataforma. El Scroller es apropiado cuando no se requieren las políticas / UX predeterminadas, por ejemplo, creando un control FlipView mejorado o una superficie de desplazamiento sin cromo.

API solo de ScrollViewer

HorizontalScrollBarVisibility / VerticalScrollBarVisibility

El valor predeterminado para las barras de desplazamiento horizontal y vertical es _Auto_. Se muestran u ocultan automáticamente en función de si el contenido es más ancho / alto que la ventana gráfica o no.

La opción 'Deshabilitado' no existirá en las opciones de enumeración disponibles para el nuevo ScrollViewer. En cambio, la configuración del control para responder a las interacciones del usuario que se desplazan horizontal o verticalmente se basará únicamente en las propiedades _HorizontalScrollMode_ y _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
    }
}

En la imagen de abajo, el cursor del mouse está sobre la barra de desplazamiento vertical. Es el único visible porque el contenido tiene el mismo ancho que la ventana gráfica.

Cuando el contenido es más grande que la ventana gráfica en ambas dimensiones, se pueden ver ambas barras de desplazamiento conscientes, así como su separador en la esquina inferior derecha.

API solo de desplazamiento

HorizontalScrollController / VerticalScrollController

El Scroller se puede conectar a un "widget" interactivo que controla el desplazamiento estableciendo su _HorizontalScrollController_ y _VerticalScrollController_ en un tipo que implemente una interfaz _IScrollController_. ScrollBars son ejemplos familiares de este tipo de widgets. ScrollViewer proporciona a su Scroller dos de estos widgets. Por ejemplo, un desarrollador podría crear una implementación de IScrollController personalizada que se base en un Composition.Visual para la entrada independiente del subproceso de la interfaz de usuario.

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

Limitaciones de nivel inferior de ScrollViewer / Scroller

El marco debe tener todos los ganchos necesarios expuestos para crear un control de desplazamiento personalizado a partir de la actualización de Windows 10 de abril de 2018. Al apuntar a versiones anteriores, puede haber limitaciones:
| Lanzamiento | Limitación | Razón |
|: -: |: - |: -: |
| Actualización de Windows 10 Fall Creators (compilación 16299) y versiones anteriores | Los elementos no se mostrarán automáticamente al recibir el enfoque. | El evento BringIntoViewRequested de UIElement no está disponible |
| | El control no puede participar en eventos EffectiveViewportChanged. Los rectos de enfoque representados por el sistema no se recortarán a los límites de la ventana gráfica. | RegisterAsScrollPort de UIElement no está disponible |

API propuesta

ScrollViewer

`` C #
clase pública Microsoft.UI.Xaml.Controls.ScrollViewer: Control
{
ScrollViewer ();

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

/ *

  • Propiedades centradas en el diseño
    * /
    // Valor predeterminado: nulo
    Contenido de UIElement {get; colocar; };
// 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; };

/ *

  • Propiedades centradas en la interacción del usuario
    * /
    // Valor predeterminado: habilitado
    Microsoft.UI.Xaml.Controls.ScrollMode HorizontalScrollMode {get; colocar; };
// 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étodos
    * /
    // Se desplaza de forma asincrónica hasta los desplazamientos especificados. Permite la animación,
    // respeta los puntos de ajuste. Devuelve una estructura ScrollInfo.
    Microsoft.UI.Xaml.Controls.ScrollInfo ScrollTo (
    desplazamiento horizontal doble,
    doble 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);

/ *

  • Reenviado a la implementación de IScrollAnchorProvider de Scroller interno
    * /
    void RegisterAnchorCandidate (elemento UIElement);
    void UnregisterAnchorCandidate (elemento UIElement);

/ *

  • Eventos
    * /
    // Elevado siempre que cualquiera de HorizontalOffset, VerticalOffset y ZoomFactor
    // la propiedad de dependencia cambió.
    evento TypedEventHandlerViewChanged;
// 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;

/ *

  • Propiedades de dependencia
    * /
    DependencyProperty estática ContentProperty {get; };
    DependencyProperty estática ContentOrientationProperty {get; };
    DependencyProperty estática ComputedHorizontalScrollBarVisibilityProperty {get; };
    DependencyProperty estática ComputedVerticalScrollBarVisibilityProperty {get; };
    DependencyProperty estática HorizontalScrollBarVisibilityProperty {get; };
    DependencyProperty estática 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; };

}
''

Preguntas abiertas

  • ¿Tener una propiedad ContentOrientation (o una con un nombre diferente) que afecte la forma en que se aplican las restricciones de diseño al contenido haría las cosas más complicadas o más fáciles?
  • ¿Deberían separarse las API relacionadas con el zoom en un control derivado (por ejemplo, ZoomViewer) de modo que ScrollViewer se trate estrictamente de desplazamiento?
Epic area-Scrolling feature proposal proposal-NewControl team-Controls

Comentario más útil

Eso es correcto. Teníamos 3 situaciones en mente:

  1. No me importa cuál sea mi puesto actual. Quiero desplazarme / hacer zoom a un destino específico.
  2. Me importa cuál es mi posición actual y quiero desplazarme / hacer zoom POR una cantidad específica en relación con esa posición.
  3. No me importa cuál sea el destino final. Solo quiero desplazarme / hacer zoom DESDE mi posición actual.

Un escenario para este último podría ser algo así como la experiencia de hacer clic con la rueda del mouse para desplazarse. Dependiendo de la posición del cursor con respecto a cuando se hizo clic en él, se inserta cierta inercia en el desplazamiento desde la posición actual (cualquiera que sea).

Todos 61 comentarios

En mi opinión, la necesidad de tener contenido que se pueda desplazar y contenido que se pueda ampliar es muy diferente. Entiendo que cuando se hace zoom en el contenido, a menudo también es necesario desplazarlo, pero tengo curiosidad por saber si hubo alguna consideración para hacer estos controles separados. Posiblemente con un control ampliable que se extiende desde uno de desplazamiento.
Mis preocupaciones son: 1) el ScrollViewer se vuelve demasiado complejo al agregar funciones relacionadas con el zoom que la mayoría no necesitará; 2) no es obvio para alguien que busque un control para hacer zoom en el contenido que el ScrollViewer lo admite.


Además, dentro de la propuesta, me gustaría ver la capacidad de desplazarse a un elemento específico dentro del visor de desplazamiento. En teoría, esto podría hacerlo un desarrollador midiendo todos los elementos anteriores y luego desplazándose hasta el desplazamiento apropiado, pero esto puede ser difícil de hacer con listas virtualizadas y es (en mi opinión) un escenario bastante común que muchas aplicaciones ( y desarrolladores) se beneficiarían de esto integrado.
Considere dónde se agrega un elemento a una "lista" y el desarrollador quiere asegurarse de que el elemento sea visible. O considere una página con una vista maestra / detallada donde la página se abre en un elemento de detalle hacia abajo en la lista, pero el desarrollador quiere asegurarse de que se muestre el elemento seleccionado en la lista maestra, por lo que proporciona contexto para lo que se muestra en la Vista de detalles.

De manera similar, también sería útil poder asegurarse de que un elemento permanezca en el área visible mientras que otros elementos dentro de las áreas desplazables se eliminan (o se agregan) al árbol visual. No estoy seguro de cuál sería la mejor manera de hacer esto, pero es muy malo cuando el elemento seleccionado dentro de un área desplazable se mueve porque una acción de fondo (o subproceso fuera de la interfaz de usuario) elimina o agrega otros elementos al área desplazable.
Hacer que un elemento se mueva a medida que hace clic / toca en él conduce a una experiencia de usabilidad terrible. El hecho de que los elementos seleccionados o enfocados se muevan fuera del área visible también puede causar problemas para las herramientas de accesibilidad y los lectores de pantalla.
Soy consciente de que existen problemas potenciales al tratar de mantener un elemento en la misma posición mientras los que lo rodean se mueven (especialmente si ese elemento se elimina de otra parte), pero no parece que hacer nada sea lo suficientemente bueno.

Gracias @mrlacey. Puedo ver cómo la funcionalidad de zoom sería menos detectable en el ScrollViewer existente, así como en esta propuesta. Agregué esto como una pregunta abierta para discusión, ya que sería otro punto de partida del ScrollViewer de UWP existente (¿quizás para mejor?).

La virtualización complica las cosas, ya que es posible que primero sea necesario crear el elemento específico y ejecutarlo en el sistema de diseño para tener una posición a la que luego pueda desplazarse. Esta es la razón por la que en el pasado ciertos controles (es decir, ListView) han tenido su propio método ScrollIntoView que maneja lo anterior para que sea más fácil. ¿Es ese el tipo de método que está imaginando? Alternativamente, cada UIElement tiene un método StartBringIntoView que se puede usar para desplazarse muy fácilmente a un elemento y tiene una serie de opciones para proporcionar un control detallado sobre dónde aterriza en la ventana gráfica cuando se

Re: mantener la posición de un elemento a medida que se agrega / elimina otro contenido o cambia de tamaño ... Estoy totalmente de acuerdo en que no hacer nada no es lo suficientemente bueno. Esta es exactamente la razón por la que existe la función de anclaje de desplazamiento . :) Es una capacidad poco conocida que en el pasado se ha incorporado a los paneles de virtualización para ListView / GridView. En lanzamientos recientes, hemos estado haciendo un esfuerzo explícito para desacoplar cosas como esta de solo estar disponibles en ListView.

Si se va a explorar la idea de un ScrollViewer expandido, tal vez los equipos de Fluent Design puedan asesorarlo sobre cómo manejar el paralaje y los elementos con valores de profundidad z establecidos de forma predeterminada.

A medida que XAML pasa a representaciones 3D para aplicaciones de RM, y a medida que la profundidad y las sombras llegan a XAML 2D, cómo el desplazamiento movería elementos de diferentes valores de profundidad, cómo se pueden representar las sombras por encima o detrás de los indicadores de desplazamiento y otros problemas que pueden surgir. en los experimentos 3D del equipo XAML, podría integrarse en lo que podría convertirse en el nuevo predeterminado, ya que la biblioteca de la interfaz de usuario de Windows se convierte lentamente en el nuevo predeterminado.

En mi opinión, la necesidad de tener contenido que se pueda desplazar y contenido que se pueda ampliar es muy diferente.

Excepto en navegadores web, aplicaciones de diseño gráfico, aplicaciones de oficina, visores de PDF ... cualquier cosa que muestre un documento de contenido.

Esté de acuerdo con

¡Gracias a todos! Recopilamos algunos datos de telemetría con respecto a las propiedades que las aplicaciones tienden a establecer en su marcado y ZoomMode aparece justo después de * ScrollMode y * ScrollBarVisibility. La mayoría de las veces, las aplicaciones no cambian ninguna propiedad. Lo que he observado cuando se establecen las propiedades ScrollMode y ScrollBarVisibility es que a menudo permite el desplazamiento horizontal en el contenido. La introducción de la propiedad ContentOrientation tiene como objetivo facilitar las cosas. Después del desplazamiento horizontal, el siguiente escenario más común es el zoom.

En lugar de introducir un control separado (derivado) para abordar la capacidad de descubrimiento del zoom, creo que la documentación / muestras correctas podrían ser más efectivas.

¿Qué hay de agregar una enumeración de comportamiento o contexto?

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

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom; embargo, ZoomMode debería quedarse como está.

ScrollViewer.ScrollBehaviour = ScrollBehaviour.Zoom; embargo, ZoomMode debería quedarse como está.

Probablemente debería haber precedido la sugerencia para abordar los comentarios que creen que debería haber un control derivado para el contenido de Zoom, en lugar de Desplazamiento

Para mí, en realidad tiene sentido tener la funcionalidad de zoom incorporada con ScrollViewer , especialmente en dispositivos táctiles. Actualmente, no existe una manera fácil de hacer panorámico / zoom / rotación de estilo libre en una imagen / control en UWP, y se han hecho muchas preguntas relacionadas en StackOverflow y GitHub (por ejemplo, Agregar control de contenido arrastrable ).

@micahl , supongo que con zoom y

Admite gestos de rotación

este escenario sería soportado de forma nativa en el futuro?

Para mí, en realidad tiene sentido tener la funcionalidad de zoom incorporada con ScrollViewer , especialmente en dispositivos táctiles. Actualmente, no existe una manera fácil de hacer panorámico / zoom / rotación de estilo libre en una imagen / control en UWP, y se han hecho muchas preguntas relacionadas en StackOverflow y GitHub (por ejemplo, Agregar control de contenido arrastrable ).

Si es necesario tener más control sobre el zoom y el desplazamiento ...

ScrollViewer.ScrollBehaviour = ScrollBehaviour.ZoomAndScroll;

Pero en los dispositivos táctiles, es natural permitir pellizcar y deslizar los dedos. Sin embargo, al hacer zoom sin tocar, es necesario que aparezcan los botones [+] y [-]. O tal vez si fuera solo Zoom, entonces el pulgar de la barra de desplazamiento arrastrando el zoom se acercaría y alejaría.

Si observa la interfaz de usuario de Microsoft Word, hay un control de zoom en la barra de estado. Y barras de desplazamiento para el área del documento.

¿Es la compatibilidad con el zoom del mouse algo que debería integrarse en el nuevo control ScrollViewer? Entonces, cuando ZoomMode está activado, o una propiedad de comportamiento permite Zoom, ¿aparecen los botones Más y Menos?

@JustinXinLiu El apoyo a los gestos de rotación probablemente requiera el apoyo de InteractionTracker (@likuba).

@mdtauk El nuevo ScrollViewer admitiría el zoom con la rueda del mouse a través de Ctrl + rueda del mouse similar al ScrollViewer existente. No me queda claro si el control debería tener un botón más / menos predeterminado cuando el zoom está habilitado. Mi impresión actual es que los tipos de aplicaciones que comúnmente permiten hacer zoom (por ejemplo, una aplicación de documento de contenido) eligen incorporar esos botones en su experiencia de aplicación de diversas maneras. Por ejemplo, un - / + en una barra de estado que está separada por un control deslizante (es decir, Office) o simplemente un - / + que aparece verticalmente debajo de otros comandos (es decir, Mapas).

@micahl No entiendo por qué ScrollFrom se llama así. ¿No es como ScrollBy pero con un parámetro de velocidad?

@adrientetar, ¿estás preguntando por qué no tener otra sobrecarga de ScrollBy con un conjunto diferente de parámetros en lugar de nombrarlo ScrollFrom?

Sí, supongo 😃 pero me imagino que el concepto detrás de ScrollFrom es moverse en una dirección con una velocidad determinada. a diferencia de ScrollBy, que es simplemente moverse a un punto determinado.

Eso es correcto. Teníamos 3 situaciones en mente:

  1. No me importa cuál sea mi puesto actual. Quiero desplazarme / hacer zoom a un destino específico.
  2. Me importa cuál es mi posición actual y quiero desplazarme / hacer zoom POR una cantidad específica en relación con esa posición.
  3. No me importa cuál sea el destino final. Solo quiero desplazarme / hacer zoom DESDE mi posición actual.

Un escenario para este último podría ser algo así como la experiencia de hacer clic con la rueda del mouse para desplazarse. Dependiendo de la posición del cursor con respecto a cuando se hizo clic en él, se inserta cierta inercia en el desplazamiento desde la posición actual (cualquiera que sea).

Gracias, eso aclara las cosas. Creo que busco más usar ScrollBy para mi propia experiencia de hacer clic con la rueda del mouse, pero puedo ver que ScrollFrom es útil en casos específicos.

En el control ScrollViewer actual, es posible enlazar las propiedades ViewportWidth y ViewportHeight, pero esto no parece posible actualmente en el nuevo control ScrollViewer. ¿Se agregará esto?

@lhak , ¿a qué vincularías esos valores? Agregarlos no sería un gran cambio. Dependiendo del contexto de su escenario, podría haber un enfoque diferente que recomendaríamos en lugar de vinculante. Si bien queremos ser sensibles a las diferencias que dificultan el paso de uno a otro, también queremos promover alternativas si son mejores.

@micahl Estoy bastante seguro de que también he usado esos valores en el pasado para algunos cálculos. No estoy seguro de si me he atado a ellos.

@ michael-hawker para que quede claro, las propiedades estarán disponibles en ScrollViewer. Sin embargo, en la API propuesta, se exponen como propiedades regulares en lugar de DependencyProperties. Estos mismos valores estarán disponibles como parte de la propiedad ExpressionAnimationSources para situaciones en las que alguien está creando animaciones controladas por entradas basadas en Composition.

Actualmente utilizo un código similar al siguiente en mi aplicación:

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

Esto hace posible desplazar / hacer zoom en el elemento xaml interno (que tiene un tamaño fijo) manteniendo su relación de aspecto. El comportamiento es similar al de la aplicación de fotos que muestra una imagen, con la excepción de que el elemento xaml nunca es más pequeño que la ventana gráfica del visor de desplazamiento.

@lhak , buen escenario! Pensando en voz alta en cómo este escenario podría adaptarse a la propuesta actual ... creo que podríamos agregar otra opción para la propiedad ContentOrientation. Las opciones actuales en la propuesta:

  • Ninguno = Sin orientación preferida. Dale al contenido un tamaño infinito disponible en ambas direcciones.
  • Horizontal = Dar al contenido un tamaño disponible infinito solo horizontalmente y restringir la altura de la ventana gráfica durante el diseño.
  • Vertical = Transposición de horizontal

Una cuarta opción sería restringir tanto la altura como el ancho durante el diseño al tamaño de la ventana gráfica. Por el momento, llamaré a esto una orientación "Viewport", aunque tengo reacciones encontradas acerca de nombrarlo como tal.
Al menos en el caso (¿más común?) De una imagen, creo que haría que el Viewbox y los enlaces fueran innecesarios, ya que "lo correcto" debería suceder como parte del proceso de diseño.

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

Para contenido más complejo, podría envolverlo en un Viewbox y aún omitir los enlaces.

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

@micahl Tengo otra pregunta: me gustaría cambiar el manejo de la rueda del mouse de Scroller para hacer que el zoom sea más rápido (incrementos más grandes) y eliminar la animación de suavizado. Parece que no hay API para ajustar el zoom de la rueda, ¿necesito manejar el evento de la rueda del mouse manualmente?

Es posible que tenga que manejar la rueda del mouse manualmente, pero sería desafortunado si terminara necesitando volver a implementar lo que hace el control. Creemos que puede haber una manera de lograr lo que busca. La esencia de esto sería establecer ZoomInertiaDecayRate en un valor cercano a 1.0 y luego definir un punto de ajuste de zoom repetido. Es algo para probar una vez que agregamos la propiedad ZoomInertiaDecayRate y solucionamos algunos problemas relacionados con los puntos de ajuste obligatorios.

Si eso no funciona, puede volver a intentar manejarlo usted mismo configurando IgnoredInputKind = "Mousewheel" y luego subclasificando el Scroller para anular el OnPointerWheelChanged, o agregando un controlador de eventos para el evento PointerWheelChanged en Scroller.

@micahl ¡Gracias! No estoy seguro de en qué escenario calibraste el comportamiento de la rueda del mouse, personalmente me gusta cómo es, por ejemplo, en Adobe XD: parece que hay un poco de animación acelerada, pero es ágil y tiene incrementos de zoom lo suficientemente grandes. La animación de zoom de la rueda del mouse de desplazamiento se siente un poco lenta y tiene incrementos de zoom realmente pequeños en mi opinión.

@adrientetar , buenos comentarios. Podemos considerar ajustarlo.

Cualquier tipo de variable que controle incrementos y valores, podría convertirse en propiedades reemplazables

Derecha. Un desafío para nosotros aquí es que en Win10 versión 1809 y posteriores, el control transfiere la entrada de la rueda del mouse al InteractionTracker subyacente para permitir que se procese fuera del hilo de la interfaz de usuario para un movimiento más suave. Esas propiedades anulables tendrían que exponerse primero en el nivel inferior. Para las versiones de Win10 anteriores a 1809, estamos agregando lógica para que el proceso de control de la entrada de la rueda del mouse en el subproceso de la interfaz de usuario.

Podríamos considerar tener algún comportamiento en el que expongamos algunas propiedades anulables y, si alguna se establece explícitamente, recurrimos a tener la rueda del mouse del proceso de control en el hilo de la interfaz de usuario. Si / Cuando esas propiedades existen en el nivel inferior, entonces podríamos pasar a confiar en ellas y devolverlo al compositor. El desplazamiento probablemente no sería tan suave para la rueda del mouse. ¿Quizás eso esté bien aquí?

Entonces, ¿el comportamiento de zoom actual está integrado en InteractionTracker sin forma de anularlo? Y supongo que ahora no hay forma de cambiarlo porque rompería la compatibilidad con versiones anteriores 🤔 cc @likuba

Creo que hay algunas opciones en InteractionTracker que podrían ayudar aquí 😊. Específicamente, creamos características como PointerWheelConfig y DeltaScaleModifier para habilitar este tipo de personalizaciones. Vamos a sincronizar y ver si usarlos dentro del control podría permitir que algo suceda aquí sin los problemas / riesgos que mencionaste anteriormente.

@micahl Tengo una situación interesante en un proyecto en el que estoy trabajando y tuve que escribir mi propio visor de desplazamiento para el PoC, pero me gustaría saber si tiene algún plan (o si este control ya lo admite) para el siguiente escenario:
Supongamos que tiene un lienzo gigante, 10000x5000, de modo que para moverse por él, lo encapsula en un visor de desplazamiento.
Ahora en el lienzo tiene 2 InkCanvases (o más) y le gustaría escribir en ambos (o más), con diferentes dedos o con 1 dedo y 1 puntero del mouse. ¿Cómo lograrías eso (o incluso es posible) con este control? :)
Lo que he visto es el comportamiento predeterminado tan pronto como tocas cualquier otra cosa mientras dibujas en un InkCanvas, ese InkCanvas en particular dispara "PointerLost" y eso es todo, nada más que hacer.
Obviamente, mi scrollviewer está lejos de ser perfecto como lo escribí yo mismo, así que estoy buscando ver si esto podría ser posible con este control.

Gracias por su amabilidad !

Hola @stefangavrilasengage , escenario interesante. :) Desde su PoC, ¿cuál es el comportamiento esperado cuando un dedo baja sobre un InkCanvas y luego comienza a moverse? ¿Dibuja o sacude?

Hola @micahl , ¡gracias por responder! Supongo que a estas alturas ya habrá adivinado que es algo así como una aplicación de pizarra digital.
Ahora les presentaré lo que sucede actualmente:

  • ScrollViewer (predeterminado): dibujar con un dedo en un InkCanvas está bien. El segundo dedo hace que el primer InkCanvas pierda entrada.
  • ScrollViewerEx (control personalizado): use tantos dedos y tantos InkCanvases para dibujar y funciona.

¿Esto ayuda? :)
Gracias !

@stefangavrilasengage Estaba preguntando qué

@micahl Mis disculpas. Olvidé mencionar cómo resolví el conflicto.
Los objetos que tenemos en el lienzo tienen su tinta dentro de un contenedor Win2D. Solo si ingresa un modo de edición para cada objeto, se presentará un InkCanvas en la parte superior para su edición (con secado personalizado).
Por lo tanto, en mi situación (no estoy seguro de si esto se aplica a alguien más), si tiene un "dibujable" activo de InkCanvas, entonces nada debería subir al ScrollViewer.
Tendría curiosidad por saber de un caso de uso en el que cree que esto no se aplica, si corresponde.

Gracias !

¡Es difícil de creer que esta es la primera vez que @micahl y yo nos hemos mezclado en un problema de github!

¡Es difícil de creer que esta es la primera vez que @micahl y yo nos hemos mezclado en un problema de github!

Lo siento por eso ! Editado

@micahgodbolt puede que no sea el último. ;)
@stefangavrilasengage gracias! Eso responde a mi pregunta. Me di cuenta de que cambiar un ScrollViewer con algo más como un borde como padre de un lienzo que contiene varios controles InkCanvas no tiene el mismo problema. Por lo tanto, me comuniqué con algunas personas internamente para comprender qué puede estar causando que la funcionalidad de múltiples entradas / múltiples InkCanvas se descomponga cuando sucede dentro de un ScrollViewer.

@stefangavrilasengage , averiguar por qué no funciona llevará algún tiempo / investigación. Hagamos un seguimiento de eso por separado de esta propuesta. ¿Abrirás un problema para ello?

@micahl Tengo un problema con Scroller en el que desplazarme y hacer zoom alrededor de mi contenido en OnControlLoaded me pone en una posición incorrecta. Si solo me desplazo, termino en la posición correcta, pero si hago zoom justo después de desplazarme, estoy en una posición totalmente fuera de lugar, aunque especifiqué centerPoint = null, por lo que el valor predeterminado es el centro de la ventana gráfica (de mirar el código porque hay aún no hay documentación disponible). Con este código .

¡Gracias por probar cosas, @adrientetar! Mantengamos este hilo centrado en la propuesta. ¿Puede abrir una edición separada para lo que está viendo? Dependiendo de dónde salgamos de esa discusión, es posible que deseemos volver a este hilo para discutir si amerita cambios en la propuesta.

@micahl ¿Por qué los eventos ExtentChanged / StateChanged / ViewChanged tienen object como argumento y no, por ejemplo, StateChangedEventArgs con un miembro de State? Dado que InteractionTracker se ejecuta en un hilo diferente, ¿no se prefiere el patrón de paso de mensajes (evento con un argumento) a consultar la propiedad de control en el controlador de eventos?

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

No es gran cosa, pero la primera parte de este repositorio muestra cómo implementar el zoom incluso cuando las barras de desplazamiento horizontal / vertical están bloqueadas (deshabilitadas),

En palabras más simples:
Permite desplazarse por un contenido ampliado sin dejar de forzar el ajuste
Ventana de visualización vertical / horizontal efectiva.

En el momento de publicar esto, es obligatorio habilitar la propiedad HorizontalScrollbarVisibility en un ScrollViewer para obtener una operación de Zoom funcional, y aunque podría haber mejores formas de evitar esto, creo que la solución de visor de desplazamiento dual que deseché podría ser suficiente suficiente.

@micahl Veo que Scroller no envía eventos StateChanged cuando se realiza un zoom no animado, ¿es eso por diseño?
Establezco mi escala de CanvasVirtualControl cuando Scroller se vuelve inactivo, como sugirió en https://github.com/microsoft/microsoft-ui-xaml/issues/541#issuecomment -488749469, de modo que el control no termine rasterizándose demasiado grande partes del lienzo en relación con el factor de zoom. Pero con un zoom no animado, necesito hacer eso en el momento en que llamo al método ZoomTo, aunque el cambio de zoom no se haya realizado (afaict).

@adrientetar , hay algunos gastos generales para crear argumentos de eventos para pasar entre el marco y la aplicación. Debido a que el evento arg solo proporcionaría una copia de las propiedades expuestas por el remitente, nuestro diseño actual es no tener el evento arg.

Sí, el diseño actual es que en un zoom no animado que invoca mediante programación, StateChanged no se activa. El evento ZoomCompleted y ScrollCompleted se generaría en respuesta a cambios programáticos. IIRC no se activan por cambios del usuario. Podría usar uno de esos, quizás en combinación con StateChanged. Para su información, el evento ViewChanged se genera cada vez que cambia la vista (usuario o programática), lo que la convierte en una ruta de código muy sensible al rendimiento y no es lo que le gustaría usar.

Todavía estamos escuchando comentarios, así que si las cosas parecen obtusas, háganoslo saber.

Sí, el diseño actual es que en un zoom no animado que invoca mediante programación, StateChanged no se activa.

Ah, está bien, ahora lo entiendo. Tal vez el evento podría dispararse siempre y contener un parámetro de origen, como SetFocus, pero tal vez haya algo de sobrecarga para hacer eso.

El evento ZoomCompleted y ScrollCompleted se generaría en respuesta a cambios programáticos. IIRC no se activan por cambios del usuario.

Bien, entonces para mi caso de uso tengo que manejar ZoomCompleted y StateChanged iff State == Idle, dependiendo de si el cambio es programático o no. ¿No es raro eso? Quiero decir, tener diferentes eventos con diferentes nombres para lo mismo solo se desencadenó de una manera diferente 🤔

@RBrid y yo tuvimos una breve charla sobre la introducción de un nuevo estado "Transición" y hacer algunos cambios de nombre menores. Háganos saber si esto tendría más sentido para su caso de uso.

La idea discutida fue que cuando un cambio en la posición de desplazamiento / zoom está a punto de ser reparado (ya sea iniciado por el usuario o programático), generaríamos el evento StateChanged e informaríamos el estado actual como Transición. Así es como se vería la secuencia para un desplazamiento programático (animado y no animado):

// solicitud programática, animada
myScrollInfo = ScrollTo (100, animado = verdadero);
// pocas garrapatas
StateChanged elevado (Scroller.State == Transición)
// pocas garrapatas
StateChanged elevado (Scroller.State == Animating)
// muchas garrapatas
StateChanged levantado (Scroller.State == Inactivo)

// solicitud programática, sin animación
myScrollInfo = ScrollTo (100, animate = false);
// pocas garrapatas
StateChanged elevado (Scroller.State == Transición)
// pocas garrapatas
StateChanged levantado (Scroller.State == Inactivo)

Probablemente nos gustaría cambiar el nombre de la enumeración InteractionState a algo como ScrollState, ya que representaría más que solo las interacciones.

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

@micahl Sí, ¡eso se parece mucho a lo que esperaría! Alguien que quiera manejar solo los cambios animados puede interceptar el estado Animating. Para los nombres, mantendría inactivo, no inactivo ("Scroller está inactivo" / "Scroller está animando"), Transitioning → Stirring? (La transición es muy confusa, preferiría que transmitamos que está al comienzo del movimiento, por lo que la agitación de imo podría funcionar mejor ), Inercial podría ser Drifting 😛 pero supongo que API Review también intervendrá.

Agitar inicialmente me hizo pensar en mezclar. :) La revisión de API querrá intervenir cuando finalicemos los nombres y tiende a buscar precedentes. Siempre es bueno tener algunas opciones. Otras alternativas pueden ser Desconocidas o Pendientes. Hay un precedente en las API existentes para ambos junto con Idle, lo que significa que probablemente no haríamos Idling.

Guay guay. Pendiente sgtm, imo Unknown es incluso más confuso que Transitioning.

@micahl - El requisito funcional número 3 incluye este subpunto:

participa en cambios efectivos de la ventana gráfica (debe)

Ese subpunto se refiere al evento preexistente FrameworkElement.EffectiveViewportChanged . ¿Es ese subpunto suficiente para decir (indirectamente) que la carga parcial asíncrona bajo demanda de contenido también es un requisito / prioridad? Me refiero, por ejemplo, al equivalente de cómo funciona ScrollViewer cuando MS Edge muestra una página ampliada de un documento PDF (o SVG u otro documento complejo). Cuando el zoom es del 900% (por ejemplo), no procesa previamente toda la página PDF con un tamaño del 900%, debido a estas 2 razones:

  • Esta tarea de renderizado (con un alto porcentaje de zoom) requiere aproximadamente 3 ... 20 segundos, dependiendo de la complejidad del documento (cantidad de elementos vectoriales, efectos, etc.), tamaño del documento y porcentaje de zoom. Este retraso es demasiado para que lo acepten los usuarios finales.
  • Una imagen renderizada (mapa de bits) de toda la página PDF puede consumir cientos de megabytes de RAM cuando el porcentaje de zoom es alto, porque equivale a renderizar a un DPI muy alto.

MS Edge procesa partes de la página PDF ampliada a pedido, es decir, cuando el usuario desplaza esas partes para verlas. Esta técnica evita la gran demora antes de que se muestre la página. Sugiero que los Requisitos funcionales mencionen explícitamente este escenario, pero también con soporte indirecto para async Task . Cuando una parte no renderizada (o no inmediatamente disponible) se desplaza a la vista, ScrollViewer puede llenar temporalmente ese espacio con un Brush configurable y luego activar un evento, y el controlador de eventos puede iniciar un async Task para renderizar (o recuperar / cargar) la pieza. Más tarde, cuando se completa Task , la parte renderizada / recuperada / cargada se muestra en ScrollViewer, cubriendo el espacio que se llenó temporalmente con Brush .

Este problema no se trata solo del alto costo de renderizar con un zoom alto. También es aplicable al contenido que se recupera bajo demanda. Por ejemplo, imagine un mapa o una imagen de satélite que se muestra en ScrollViewer. Partes del mapa solo se descargan del servidor cuando el usuario las desplaza para verlas.

¿Deberían separarse las API relacionadas con el zoom en un control derivado (por ejemplo, ZoomViewer) de modo que ScrollViewer se trate estrictamente de desplazamiento?

Aunque aprecié mucho la adición de ScrollViewer.ZoomFactor , me sorprendió ver que se agregó directamente a ScrollViewer. Supuse que sería más simple y más confiable poner zoom en una clase separada (no necesariamente una clase derivada). Son posibles al menos 3 formas:

  1. Una clase llamada quizás ZoomViewer que NO hereda ScrollViewer , pero usa una instancia ScrollViewer como elemento secundario (en su ControlTemplate ).
  2. Una clase llamada quizás ZoomableScrollViewer que hereda ScrollViewer .
  3. Soporte directo de zoom dentro de ScrollViewer , si no es excesivamente complicado o desordenado.

17 Proporcione una experiencia de usuario predeterminada para admitir un clic central del mouse y un desplazamiento. (Deberían)
18 Admite un modo para hacer clic y desplazarse mediante el mouse (por ejemplo, mover contenido dentro de un visor de PDF). (Deberían)

Sugiero considerar si eliminar el soporte para 17, porque 18 funciona mucho mejor que 17, y casi nadie usa 17 en la práctica. Es cierto que podría estar pensando que 17 significa algo más que el significado deseado (la descripción de 17 es solo una oración y no estoy 100% seguro de que signifique lo que creo que significa). ¿No es correcto decir que 18 es muy fácil de usar y fácil de usar, mientras que 17 es una cosa incómoda que todo el mundo tiene dificultades para usar y evita usar?
(Este problema cambia si se necesita 17 por razones de accesibilidad, pero hasta ahora nunca he escuchado a nadie decir que 17 es un problema de accesibilidad).

4 Capaz de realizar animaciones impulsadas por entradas

El número 4 es un problema relacionado con la accesibilidad. Me gustaría solicitar que ScrollViewer (y todos los demás controles en WinUI) respeten la configuración de accesibilidad:

Windows 10 -> Inicio -> Configuración -> Accesibilidad -> Pantalla -> Mostrar animaciones en Windows (Activado o Desactivado).

Desafortunadamente, a lo largo de los años, he notado muchos ejemplos en los que las aplicaciones de Microsoft ignoran la configuración de accesibilidad de Windows. Las animaciones están desactivadas, pero las aplicaciones de Microsoft muestran animaciones de todos modos. Esto provoca verdaderas dificultades para los usuarios que dependen de estos ajustes de accesibilidad. No todo el mundo tiene la capacidad de disfrutar de las animaciones sin sufrir efectos secundarios negativos. Los usuarios que no experimentan dificultades para ver animaciones, etc. pueden tener dificultades para comprender la importancia de la configuración de accesibilidad en Windows.

Hola @verelpode , @predavid conducirá el trabajo en torno al nuevo ScrollViewer, así que me referiré a ella.

17 Proporcione una experiencia de usuario predeterminada para admitir un clic central del mouse y un desplazamiento. (Deberían)
18 Admite un modo para hacer clic y desplazarse mediante el mouse (por ejemplo, mover contenido dentro de un visor de PDF). (Deberían)

Sugiero considerar si eliminar el soporte para 17, porque 18 funciona mucho mejor que 17, y casi nadie usa 17 en la práctica. Es cierto que podría estar pensando que 17 significa algo más que el significado deseado (la descripción de 17 es solo una oración y no estoy 100% seguro de que signifique lo que creo que significa). ¿No es correcto decir que 18 es muy fácil de usar y fácil de usar, mientras que 17 es una cosa incómoda que todo el mundo tiene dificultades para usar y evita usar?
(Este problema cambia si se necesita 17 por razones de accesibilidad, pero hasta ahora nunca he escuchado a nadie decir que 17 es un problema de accesibilidad).

@verelpode Creo que entiendes 17 correctamente y estoy de acuerdo en que 18 es más importante y generalmente más intuitivo que 17. Pero puede haber escenarios en los que "hacer clic con el botón izquierdo y arrastrar" ya se usa para otros fines, como la selección o arrastrar y soltar. . Para tales escenarios, sería bueno si la aplicación pudiera permitir el desplazamiento a través del clic central (17).

Personalmente, uso el desplazamiento con el clic central en los navegadores de vez en cuando, donde el clic izquierdo + arrastrar provoca la selección de texto o arrastrar + soltar imágenes. Un navegador para UWP tendría 18 deshabilitados y 17 habilitados. Ambos deben habilitarse a través de propiedades independientes.

@lukasf

Personalmente, uso el desplazamiento con el clic central en los navegadores de vez en cuando, donde el clic izquierdo + arrastrar provoca la selección de texto o arrastrar + soltar imágenes.

Un buen punto es la necesidad de evitar que la selección de texto, etc. deje de funcionar. ¿Qué piensa de esta posible solución? Haga que el clic central inicie el modo de panorámica basado en el mouse (18) en lugar de iniciar el incómodo modo 17.

Cuando miro lo que hacen otras aplicaciones, algunas aplicaciones le permiten iniciar la panorámica basada en el mouse manteniendo presionada la tecla de la barra espaciadora mientras hace clic con el botón izquierdo en el área de contenido desplazable. Esta solución permite que el clic izquierdo funcione normalmente dentro del área de contenido desplazable porque el modo de panorámica solo se inicia mediante la barra espaciadora + clic. Encuentro el desplazamiento a través de la barra espaciadora + clic muy cómodo, conveniente y rápido.

Sin embargo, esta solución de barra espaciadora + clic es más fácil de implementar en aplicaciones que no necesitan mostrar cuadros de texto editables dentro del área de contenido desplazable. Si existen cuadros de texto editables en el área de contenido desplazable, entonces sería un problema que la función de panorámica haga que los usuarios no puedan escribir espacios en los cuadros de texto. Por lo tanto, como dijiste, esta función debe habilitarse a través de una propiedad. Alternativamente, no use la barra espaciadora y, en su lugar, haga que el clic central inicie el modo de panorámica basado en el mouse (18), y esto elimina el problema de que los usuarios no puedan escribir espacios en los cuadros de texto.

@verelpode
Con todos los navegadores y también un montón de otras aplicaciones (Word, Adobe Reader, Outlook, ...) que admiten el modo 17, todavía creo que esto debería implementarse. El hecho de que personalmente lo encuentre incómodo no significa que no sea útil para otras personas. Ambos modos deben ser opcionales, por supuesto, luego los desarrolladores pueden decidir qué se usará en su aplicación. Esto también permitiría el comportamiento de la barra espaciadora + clic, si tiene sentido para una aplicación: habilite el modo 18 en la barra espaciadora hacia abajo, deshabilítelo nuevamente en la barra espaciadora hacia arriba.

@lukasf -

Gracias por sus comentarios @verelpode y @lukasf , estoy de acuerdo con usted en que, en última instancia, querremos que las perillas públicas del control Scroller enciendan / apaguen los números 17 y 18. Llamémoslos 17 = movimiento panorámico de velocidad constante basado en el mouse y 18 = panorámica basada en el ratón.

Acabo de enviar un PR https://github.com/microsoft/microsoft-ui-xaml/pull/1472 para agregar un trabajo de investigación que hice. Quería ver qué tan cerca podía llegar a dar soporte tanto a 17 como a 18 usando el Scroller de hoy.

Las cosas salieron bastante bien para la panorámica 18 = basada en el mouse, aunque la solución está 100% ligada a la interfaz de usuario, a diferencia de las experiencias basadas en el tacto o en la rueda del mouse. Usé el método ScrollTo mientras escuchaba el evento PointerMoved del mouse. En última instancia, querríamos que el componente InteractionTracker subyacente manejara los movimientos del mouse como lo hace con los movimientos de los dedos.

Las cosas son mucho más complicadas para 17 = panorámica a velocidad constante basada en el mouse (una experiencia que personalmente no me gusta). El prototipo está lejos de ser ideal y no intenté abordar todos los problemas, pero dos en particular son preocupantes:

  • durante un desplazamiento panorámico de velocidad constante (es decir, un desplazamiento panorámico de caída de velocidad 0), no creo que haya una forma con el Scroller actual de detener el movimiento sin volver a una posición anterior (con ScrollBy (0, 0)) . Simplemente no es posible saber de manera confiable qué tan lejos ha ido el hilo de composición por delante del hilo de la interfaz de usuario. Por lo tanto, se necesitaría una nueva API de Scroller pública para detener la velocidad sin fallas.
  • No pude mantener la captura del mouse después del evento PointerReleased del mouse. Parece que se necesitaría una nueva función de marco Xaml para lograr esto.
    De todos modos, este puede ser otro caso en el que nos gustaría que el InteractionTracker subyacente manejara parte de la experiencia directamente.
    Los tendré en cuenta con seguridad cuando analice las funciones futuras de Scroller / InteractionTracker.

@RBrid

Las cosas salieron bastante bien para 18 = paneo basado en el mouse, ... ...
Las cosas son mucho más complicadas para 17 = panorámica a velocidad constante basada en el mouse (una experiencia que personalmente no me gusta).

¡Resultados interesantes! En ese caso, considerando las dificultades con 17 = velocidad constante, y considerando que 18 se puede implementar de una manera que no impida el uso normal del clic izquierdo, entonces, en mi opinión personal, creo que la propuesta probablemente debería actualizarse a abandono 17 y soporte 18 en su lugar, pero es cierto que no sé cuántos usuarios se quejarían si se abandona 17 = velocidad constante.

Gracias @RBrid por estas investigaciones. ¡Es genial saber que el modo 18 ya funciona! Estoy de acuerdo en que idealmente debería ser manejado por InteractionTracker, para permitir un funcionamiento sin problemas incluso durante la carga de subprocesos de la interfaz de usuario.

@verelpode Ambos modos están configurados como "Debería". Entonces, si el esfuerzo es demasiado alto para realizar el modo 17, podría omitirse (y tal vez agregarse más adelante, si es necesario). Pero tal vez alguien encuentre la manera de realizarlo sin demasiados cambios.

Espero que el nuevo ScrollViewer tenga la capacidad de deshabilitar o personalizar el zoom con la tecla "Ctrl".

¿Fue útil esta página
0 / 5 - 0 calificaciones