-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Align (scrolling) TabControl with MD spec #3981
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
base: master
Are you sure you want to change the base?
Conversation
Adds a behavior to handle tab selection changes intended to adjust scrolling for better user experience. Introduces a custom panel to hijack the BringIntoView event, allowing for offsetting the Rect being scrolled to based on the tab selection direction.
There is no need for the padding if all tabs can fit inside the visible region.
The intention is to eventually add some assertions, but we'll need to figure out what is relevant to assert on.
Without this hack, if the user double-clicks on a tab that is partially out of view, it will not scroll fully into view (with the desired additional "padding"). It seems to stop the animation prematurely and leave the TabControl headers in a undesirable state. This minor hack prevents this behavior.
Defaulted to True in a style setter allowing for easy override at the call site.
Default value (40, although spec says 52) is set in style to allow easy override at the call site.
Setting a duration of TimeSpan.Zero, effectively disables the animated scrolling.
|
I tested it tonight and my mouse does have a tilt scroll. It works as I would expect. Each tilt of the wheel applies the small scroll amount of the scroll viewer. Effectively the same as clicking the arrow buttons of the scroll view if they were visible. This does mean that you can scroll your tab view to a state where a tab is right on the horizon line. However, I think that is the expected behavior. For the naming, how about something like For the hack stuff, I sort of anticipated it would be something a little ugly like that. At least with my slow fingers I couldn't really break it in any meaningful way so perhaps it is fine at least for a V1. One final thing, I think we will want this on by default, but I think we will probably want a simple AttachedProperty to allow people to turn it off to get more default tab control behavior. |
|
@Keboo Thanks for testing the tilt 👍 I'm glad that you didn't find any obvious conflicts between the 2 features.
Since this AP lives on I will rename the |
| DependencyProperty.Register(nameof(UseHeaderPadding), typeof(bool), typeof(PaddedBringIntoViewStackPanel), new PropertyMetadata(false)); | ||
|
|
||
| public PaddedBringIntoViewStackPanel() | ||
| => AddHandler(FrameworkElement.RequestBringIntoViewEvent, new RoutedEventHandler(OnRequestBringIntoView), false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Keboo We may want to handle the AddHandler()/RemoveHandler() on loaded/unloaded instead, to avoid a potential handler leak. Thoughts? Could also consider if registering a class handler is the better approach?
|
This is starting to look great.
I assume this has to do with the scrolling animation not having finished yet? tabControlSpamArrowKey.mp4 |
|
@corvinsz Thanks. That is exactly why. The same thing could happen with quick mouse clicks, but I "fixed" that with a hack of temporarily making it not hit test visible. Perhaps some similar short-circuit hack needs to be added for they keyboard case... Ideas are welcome. |
Maybe the following could be a possible solution for the above problem? protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.ScrollChanged += AssociatedObject_ScrollChanged;
AssociatedObject.SizeChanged += AssociatedObject_SizeChanged;
+ TabControl.AddHandler(Keyboard.PreviewKeyDownEvent, (KeyEventHandler)TabControl_PreviewKeyDown, handledEventsToo: true);
Dispatcher.BeginInvoke(() => AddPaddingToScrollableContentIfWiderThanViewPort());
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject is { } ao)
{
ao.ScrollChanged -= AssociatedObject_ScrollChanged;
ao.SizeChanged -= AssociatedObject_SizeChanged;
}
+ TabControl.RemoveHandler(Keyboard.PreviewKeyDownEvent, (KeyEventHandler)TabControl_PreviewKeyDown);
}
+private void TabControl_PreviewKeyDown(object sender, KeyEventArgs e)
+{
+ if (_isAnimatingScroll == false)
+ {
+ return;
+ }
+
+ if (e.Key is Key.Left or
+ Key.Right or
+ Key.Home or
+ Key.End or
+ Key.PageUp or
+ Key.PageDown)
+ {
+ e.Handled = true;
+ }
+}Problem with focusable content@nicolaihenriksen I think there is also still a fundamental problem (which I haven't looked into myself). If a tab contains a focusable element (e.g. |
@corvinsz I have added the tasks to the PR description. Thanks for finding the issues! |

Fixes #3976
This PR aligns the
TabControl(scrolling) style with the MD spec.It contains the following changes:
TabControlHeaderScrollBehaviorin order to react the tab/scroll events.StackPanel(PaddedBringIntoViewStackPanel) in order to hijack and re-raise theRequestBringIntoViewevent.ScrollViewerand the containedTabItems. It "hijacks" theFrameworkElement.RequestBringIntoViewEventby marking it as handled, and subsequently issue a newFrameworkElement.BringIntoView(Rect)call with aRectmatching the original event, but offset left/right based on scroll direction.ScrollViewer.ScrollToHorizontalOffset()- thanks @Keboo for that idea!TabAssist.ScrollDurationAP to allow consumers to control the animation duration.TimeSpan.Zeroeffectively disables the animation.Styleto allow easy override at the call site.TabAssist.HeaderPaddingAP to allow consumers to modify the offset (i.e. "padding" from above).Styleto allow easy override at the call site.TabAssist.UseHeaderPaddingAP to allow consumers to modify enable/disable the new behavior. The default value is "on".Styleto allow easy override at the call site.Tested the following
FlowDirection=RightToLeftworks as expected.TabControl.TabStripPlacement=Left|Top|Right|Bottomall work as expected.Still missing the following (thanks @corvinsz for finding the issues):
I have left a couple of UI tests that don't currently assert anything in there. Feel free to have a stab at them if you wish. If not, I will get around to it eventually... X-mas is a busy time 😄
Preview of the feature