-
Notifications
You must be signed in to change notification settings - Fork 693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A more flexible ScrollViewer #108
Comments
In my mind, the need to have content that can be scrolled and content that can be zoomed is very different. I understand that when content is zoomed it often also needs to be scrolled, but I'm curious if there was any consideration to making these separate controls? Possibly with a zoomable control extending from a scrolling one. Additionally, within the proposal, I'd like to see the ability to scroll to a specific element within the scroll viewer. In theory, this could be done by a developer by measuring all the items before it and then scrolling to the appropriate offset, but this can be difficult to do with virtualized lists and is (to my mind) a common enough scenario that many apps (and developers) would benefit from this being built-in. Similarly, it would also be useful to be able to ensure that an item stays in the visible area while other items within the scrollable areas are removed from (or added to) the visual tree. I'm not sure what the best way to do this would be, but it's very bad when the selected item within a scrollable area moves because a background (or off-UI thread) action removes or adds other items to the scrollable area. |
Thanks @mrlacey. I can see how zooming functionality would be less discoverable in the existing ScrollViewer as well as this proposal. I've added this as an open question for discussion as it would be another point of departure from the existing UWP ScrollViewer (perhaps for the better?). Virtualization makes things tricky since first the specific element might need to be created and run through the layout system to have a position to which you can then scroll. This is why in the past certain controls (i.e. ListView) have had their own ScrollIntoView method that handles the above to make it easy. Is that the type of method you're envisioning? Alternatively, every UIElement has a StartBringIntoView method that can be used to very easily scroll to an item and has a number of options to provide fine-grained control over where in the viewport it lands when brought into view. The StartBringIntoView approach works even in scenarios with nested scrolling surfaces since it generates a request that bubbles up for each scrolling control to respond. This is essentially what happens when an item receives keyboard/GamePad/Narrator focus. Of course, it requires that you first have a UIElement which you may not in a virtualizing scenario without a way to explicitly trigger an element to be realized. Would it be useful to have a way to explicitly trigger the realization of an element? Among other things you could then use it to trigger a StartBringIntoView. Re: maintaining the position of an item as other content is added/removed or changes in size... Totally agree that doing nothing isn't good enough. This is exactly why the scroll anchoring feature exists. :) It's a little-known capability that in the past has been built-in to the virtualizing Panels for ListView/GridView. In recent releases we've been making an explicit effort to decouple things like this from only being available in ListView. |
If the idea of an expanded ScrollViewer is to be explored, perhaps the Fluent Design teams could advise you about handling parallax and items with z-depth values set by default. As XAML moves into 3D representations for MR apps, and as Depth and Shadows come to 2D XAML - how the scrolling would move items of different depth values, how the shadows may render above or behind the scroll indicators, and other issues that may crop up in the XAML team's 3D experiments - could be built into what could become the new default, as Windows UI Library slowly becomes the new default. |
Except in web browsers, graphics design apps, office apps, PDF viewers..... anything that shows a content document. |
Agree with @mrlacey that bringing a specific element into view is important as well as specifying where in the view it is displayed (top, center, bottom, etc...). There also needs to be a way to detect if the element is entirely in view or clipped. This way I don't have to scroll the view to show an element if I know the element is already entirely or even partially in view. |
Thanks all! We gathered some telemetry regarding which properties apps tend to set in their markup and the ZoomMode shows up right after the *ScrollMode and *ScrollBarVisibility. Most of the time apps aren't changing any properties. What I've observed when the ScrollMode and ScrollBarVisibility properties are being set is that its often to enable horizontal scrolling on the content. The introduction of the ContentOrientation property is intended to make that easier. After horizontal scrollling, the next most common scenario is zooming. Rather than introducing a separate (derived) control to address discoverability of zooming I believe the right documentation / samples could be more effective. |
What about adding a Behaviour or Context Enum?
|
|
I probably should have prefaced the suggestion as addressing comments which think there should be a derived control for Zooming content, rather than Scrolling |
To me it actually makes sense to have zoom functionality built in with @micahl, I assume with zooming and
this scenario would be supported natively in the future? |
If there is a need to have more control on Zooming and Scrolling...
But on touch devices, it feels natural to allow pinching and sliding of fingers. Zooming without touch however would need [ + ] and [ − ] buttons to appear. Or perhaps if it was Zoom only, then the scrollbar thumb dragging would zoom in and out. If you look at the Microsoft Word UI, there is a Zoom control in the Status Bar. And scrollbars for the document area. Is mouse zoom support something that should be built into the new ScrollViewer control? So when ZoomMode is on, or a Behaviour Property allows Zoom - then Plus and Minus buttons appear? |
@JustinXinLiu Supporting rotation gestures would likely require support from InteractionTracker (@likuba). @mdtauk The new ScrollViewer would support mousewheel zooming via a Ctrl + mousewheel similar to the existing ScrollViewer. It's unclear to me whether the control should have a default plus/minus button when zoom is enabled. My current impression is that the kinds of apps that commonly enable zooming (e.g. a content document app) choose to incorporate those buttons into their app experience in varied ways. For example, a - / + in a status bar that is separated by a slider (i.e. Office) or as just a simple -/+ that appears vertically below other commands (i.e. Maps). |
@micahl I don't understand why ScrollFrom is called that way. Isn't it just like ScrollBy but with a velocity parameter? |
@adrientetar are you asking why not have another overload of ScrollBy with a different set of parameters instead of naming it ScrollFrom? |
Yea I guess 😃 but I imagine the concept behind ScrollFrom is to move in a direction with a given velocity? as opposed to ScrollBy which is just move to a given point. |
That's right. We had 3 situations in mind:
A scenario for the latter might be something like the mousewheel-click to pan experience. Depending on the position of the cursor relative to when it was clicked, some inertia is inserted scroll from the current position (whatever that is). |
Thanks, that clears things up. I think I'm more looking to use ScrollBy for my own mousewheel-click experience, but I can see ScrollFrom being useful in specific cases. |
In the current ScrollViewer control, it is possible to bind to the ViewportWidth and ViewportHeight properties, but this does not seem currently possible in the new ScrollViewer control. Will this be added? |
@lhak, what would you bind those values to? Adding them wouldn't be a big change. Depending on the context for your scenario there might be a different approach we'd recommend rather than binding. While we want to be sensitive to differences that make it difficult to move from one to the other we also want to promote alternatives if they're better. |
@micahl I'm pretty sure I've used those values in the past too for some calculations. Not sure if I've bound to them. |
@michael-hawker to be clear, the properties will be available on the ScrollViewer. However, in the proposed API they're exposed as regular properties rather than as DependencyProperties. These same values will be available as part of the ExpressionAnimationSources property for situations where someone is building input-driven animations based on Composition. |
I currently use some code similar to the following in my 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> This makes it possible to scroll/zoom the inner xaml element (which has a fixed size) while keeping its aspect ratio. The behavior is similar to the photos app showing an image, with the exception that the xaml element is never smaller than the viewport of the scrollviewer. |
@lhak, good scenario! Thinking out loud on how this scenario might be accommodated in the current proposal... I think we could add another option for the ContentOrientation property. The current options in the proposal:
A fourth option would be to constrain both the height and width during layout to the size of the viewport. For the moment I'll call this a "Viewport" orientation, although I have mixed reactions about naming it as such. <ScrollViewer ContentOrientation="Viewport">
<Image Stretch="Uniform" .../>
</ScrollViewer> For more complex content you'd be able to wrap it in a Viewbox and still skip the bindings. <ScrollViewer ContentOrientation="Viewport">
<Viewbox>
<TheXamlElement Width=X Height=Y/>
</Viewbox>
</ScrollViewer> |
@micahl -- Functional Requirement number 3 includes this subpoint:
That subpoint refers to the preexisting event
MS Edge renders parts of the zoomed-in PDF page on-demand, meaning when the user scrolls those parts into view. This technique avoids the long delay before the page is displayed. I suggest that the Functional Requirements explicitly mention this scenario, but also with indirect support for This issue is not only about the high cost of rendering at a high zoom. It is also applicable to content that is retrieved on-demand. For example, imagine a map or satellite image displayed in ScrollViewer. Parts of the map are only downloaded from the server when the user scrolls them into view.
Although I much appreciated the addition of
I suggest considering whether to remove support for 17, because 18 works much better than 17, and hardly anybody uses 17 in practice. Admittedly I might be thinking that 17 means something else than your intended meaning (the description of 17 is only one sentence and I'm not 100% certain that it means what I think it means). Isn't it correct to say that 18 is very user-friendly and easy-to-use, whereas 17 is an awkward thing that everybody struggles to use and avoids using?
Number 4 is an accessibility-related issue. I'd like to request that ScrollViewer (and all other Controls in WinUI) respect the accessibility settings: Windows 10 -> Start -> Settings -> Ease of Access -> Display -> Show animations in Windows (On or Off). Unfortunately, over the years, I've noticed many examples where Microsoft apps ignore the Windows accessibility settings. Animations are switched off but Microsoft apps display animations anyway. This causes real difficulties for the users who rely upon these accessibility settings. Not everyone has the ability to enjoy animations without suffering negative side-effects. Users who experience no difficulties viewing animations etc can find it difficult to understand the significance of the accessibility settings in Windows. |
Hi @verelpode, @predavid will be driving the work around the new ScrollViewer so I'll defer to her. |
@verelpode I think you understand 17 correctly and I agree that 18 is more important and usually more intuitive than 17. But there can be scenarios where "left click and drag" is already used for other purposes, such as selection, or drag+drop. For such scenarios, it would be good if the app can enable scrolling through middle click (17). I personally use middle click scrolling in browsers from time to time, where left click + drag causes either text selection or drag+drop of images. A UWP browser would have 18 disabled and 17 enabled. Both should be opt-in via separate properties. |
Good point re the need to avoid causing text selection etc to stop working. What do you think of this possible solution: Make middle-click start the mouse-based panning mode (18) instead of starting the awkward 17 mode. When I look at what other apps do, some apps allow you to start mouse-based panning by holding down the spacebar key while left-clicking in the scrollable content area. This solution allows left-click to operate normally within the scrollable content area because the panning mode only starts via spacebar+click. I find panning via spacebar+click very comfortable and convenient and fast. However, this spacebar+click solution is easier to implement in apps that don't need to display any editable textboxes within the scrollable content area. If editable textboxes do exist in the scrollable content area, then it'd be a problem the panning feature makes users unable to type spaces in textboxes. Therefore, as you said, this feature should be opt-in via a property. Alternatively, don't use the spacebar, and instead make middle-click start the mouse-based panning mode (18), and this eliminates the problem of users being unable to type spaces in textboxes. |
@verelpode |
@lukasf -- OK, sounds good. I thought you meant that if the panning mode (18) wouldn't prevent text selection or drag+drop of images etc, then you would stop using mode 17 and switch to 18, but now I see your preference is actually for both modes to be supported. |
Thank you for your feedback @verelpode and @lukasf, I agree with you that ultimately we will want public knobs on the Scroller control to turn on/off numbers 17 and 18. Let's call them 17=mouse-based constant-velocity-panning and 18=mouse-based panning. I just sent out a PR #1472 for adding some investigative work I did. I wanted to see how close I could get to supporting both 17 and 18 using today's Scroller. Things went pretty well for 18=mouse-based panning, although the solution is 100% UI-thread-bound, unlike the touch-based or mouse-wheel-based experiences. I used the ScrollTo method while listening to the mouse's PointerMoved event. Ultimately we would want the underlying InteractionTracker component to handle mouse moves like it does with finger moves. Things are much trickier for 17=mouse-based constant-velocity-panning (an experience which I personally dislike). The prototype is far from ideal and I did not try to address all issues, but two in particular are concerning:
|
Interesting results! In that case, considering the difficulties with 17=constant-velocity, and considering that 18 can be implemented in a way that doesn't prevent normal usage of left-click, then in my personal opinion I think the proposal should probably be updated to abandon 17 and support 18 instead, but admittedly I don't know how many users would complain if 17=constant-velocity is abandoned. |
Thank you @RBrid for these investigations. Great to hear that mode 18 already works! I agree it should ideally be handled by InteractionTracker, to allow smooth operation even during UI thread load. @verelpode Both modes are set as "Should". So if effort is too high to realize mode 17, it could just be left out (and maybe added later, if required). But maybe someone finds a way to realize it without too many changes. |
I hope the new ScrollViewer has ability to disable or customize Zoom with "Ctrl" key. |
I would really like to see a feature for panning with mouse. I have already looked into the source code and in my opinion you just have to change if from PointerType.Touch to a dynamic Variable. Please Mircosoft, be so kind and implement that. |
three years, any updates? |
Another ping on this one, any updates on supporting this? |
It isn't well documented, but I just found an example in the Xaml Controls Gallery that shows this already working: https://github.com/microsoft/Xaml-Controls-Gallery/blob/master/XamlControlsGallery/ControlPages/ScrollViewerPage.xaml . Does this mean the work is completed and this item just hasn't been closed out yet? |
I noticed in the docs:
But that seems annoying as mouse wheel is the most common way to scroll a page? So, when scrolling generally an inner ScrollViewer goes under the mouse and then stops the scrolling once it's reached its bottom... You can see this in the WinUI Gallery samples. Would this proposal resolve this issue with chaining then as well? |
ContentOrientation="Horizontal" not effect in sdk 1.4.1,I try all method, but only can vertical scrolling |
Proposal: ScrollViewer
Summary
tl:dr; Provide a flexible, yet easy-to-use, scrolling and zooming control in XAML that can still be leveraged when targeting downlevel releases of Win10.
The ScrollViewer control plays a critical role in any UI because of how fundamental scrolling is for applications and controls (platform and non-platform alike). The control provides panning and zooming capabilities along with default policies and UX (e.g. conscious scroll bars, focus/keyboard interaction, accessibility, default scroll animation, etc). It must also be flexible enough that it can go from making everyday app scenarios easy to being a key component in scroll-based controls and servicing very advanced use cases. The current control does not provide this level of flexibility.
Rationale
Bringing the ScrollViewer control to the repo in a way that is layered over the core platform's public APIs is an important stepping stone that:
The existing control was authored in Win8 based on the DirectManipulation APIs which 1) are not available to use directly within UWP, 2) do not allow the level of customization that developers have come to expect to create unique scrolling experiences, and 3) are superceded by UWP's newer InteractionTracker APIs.
Extracting the existing ScrollViewer control as-is would be a non-starter. The control must instead be a re-implementation with a near identical API surface based on the newer (and already available) capabilities of InteractionTracker.
High-level Plan
The existing ScrollViewer in the Windows.UI.Xaml.Controls namespace will remain untouched (other than fixing critical bugs).
The new ScrollViewer will live in the Microsoft.UI.Xaml.Controls namespace. It's API will be mostly identical to the Windows version. We will make targeted adjustments to the control's API where there is clear benefit (simplify a common scenario, introduce new capabilities, etc).
In combination with the new ScrollViewer we will introduce a new type, Scroller, that will live in the Microsoft.UI.Xaml.Controls.Primitives namespace. The Scroller will provide the core functionality for scrolling and zooming in XAML based on the capabilities of InteractionTracker.
The initial goal for both controls will be on getting core functionality to ship quality to be part of the next official release. Non-finalized APIs will remain in 'preview' and only be available as part of the pre-release packages.
Functional Requirements
For example:
- correctly renders system focus rects
- automatically brings focused elements into view (keyboard, GamePad, screen readers)
- participates in effective viewport changes
- supports scroll anchoring
Terminology
Usage Examples
Foreword
By default the ScrollViewer supports panning over its content when the size of the content is larger than its viewport. The size of the content is determined by XAML's layout system.
The examples here are intended to highlight the main API difference between the current ScrollViewer and the new ScrollViewer.
Vertical Scrolling (No difference)
The width of the content is automatically constrained to be the same as the viewport. The height is unconstrained. If it exceeds the height of the viewport then the user can pan via various input modalities.
Horizontal Scrolling
This is an intentional departure from the existing ScrollViewer which previously required changing the HorizontalScrollBarVisibility. It has confused many developers.
Horizontal-only scrolling is enabled by setting the ContentOrientation property. It determines what constraints are used on the content during layout. A value of Horizontal implies that the content is unconstrained horizontally (allowed infinite size) and vertically constrained to match the viewport. Similarly, Vertical (the default) implies its unconstrained vertically and constrained horizontally.
Scrolling a Large Image
A ContentOrientation of Both implies the content is unconstrained both horizontally and vertically.
Changing the ScrollBar Visibility
This example always hides both scrollbars. The user can still pan the content in either direction.
Turning off built-in support for specific kinds of input
In this example a developer is creating a canvas-based app that will perform custom processing on mouse wheel input and support a lasso selection experience via Pen. It can configure the ScrollViewer to ignore those input kinds while still accepting others such as Touch.
Customize the animation of a programmatic scroll
The developer listens to a Slider's ValueChanged event and animates a ScrollViewer's VerticalOffset using a custom duration on the default animation.
Detailed Feature Design
High-policy and Low-policy Scrolling
Scroller (low-policy)
The Scroller is a chrome-less, low-level building block that provides all the essential panning and zooming logic. It wraps the platform's even lower-policy InteractionTracker as a XAML markup-friendly element.
In a very literal way the Scroller is "the viewport" for ScrollViewer and takes the place of the ScrollContentPresenter. However, the Scroller, unlike the ScrollContentPresenter, does much more than simply clipping its content.
Scroller provides the flexibility of using InteractionTracker directly along with the following advantages:
ScrollViewer (high-policy)
The new ScrollViewer wraps the Scroller and sets its properties to common values. For example, a
<ScrollViewer/>
configures its Scroller to support both horizontal and vertical scrolling interactions, but constrain the width of its content such that the default user experience appears to be vertical-only scrolling (the common case).ScrollViewer provides the following advantages over using a Scroller:
Which one to use?
The default choice for apps and many control authors should be to use the ScrollViewer. It provides greater ease-of-use and a default UX that is consistent with the platform. The Scroller is appropriate when the default UX / policies are not required - for example, creating an improved FlipView control or a chrome-less scrolling surface.
ScrollViewer-only APIs
HorizontalScrollBarVisibility / VerticalScrollBarVisibility
The default value for both horizontal and vertical scroll bars is Auto. They are automatically shown or hidden based on whether the content is wider/taller than the viewport or not.
The 'Disabled' option will not exist in the enum options available for the new ScrollViewer. Instead, configuring the control to respond to user interactions that pan horizontally or vertically will be based solely on the HorizontalScrollMode and VerticalScrollMode properties.
In the picture below the mouse cursor is over the vertical scroll bar. It is the only one visible because the content is the same width as the viewport.
When the content is larger than the viewport in both dimensions then both conscious scroll bars can be seen as well as their separator in the bottom right corner.
Scroller-only APIs
HorizontalScrollController / VerticalScrollController
The Scroller can be connected to an interactive "widget" that controls scrolling by setting its HorizontalScrollController and VerticalScrollController to a type that implements an IScrollController interface. ScrollBars are familiar examples of such widgets. The ScrollViewer supplies its Scroller with two such widgets. For example, a developer could create a custom IScrollController implementation that relies on a Composition.Visual for UI-thread independent input.
Downlevel Limitations of ScrollViewer / Scroller
The framework should have all the necessary hooks exposed to build a custom scrolling control as of the Windows 10 April 2018 Update. When targeting earlier releases there may be limitations:
Proposed API
ScrollViewer
Open Questions
The text was updated successfully, but these errors were encountered: