-
Notifications
You must be signed in to change notification settings - Fork 152
Add SliderExtensions (mouse-wheel attached properties) #851
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: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| --- | ||
| title: SliderExtensions | ||
| author: niels9001 | ||
| description: SliderExtensions provides attached properties that let a Slider be adjusted by mouse-wheel input. | ||
| keywords: windows 10, windows 11, windows community toolkit, winui, Slider, extensions, mouse wheel | ||
| dev_langs: | ||
| - csharp | ||
| category: Extensions | ||
| subcategory: Controls | ||
| discussion-id: 0 | ||
| issue-id: 0 | ||
| icon: Assets/Extensions.png | ||
| --- | ||
|
|
||
| The `SliderExtensions` static class provides attached properties for the [`Slider`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.slider) control. | ||
|
|
||
| > **Platform APIs:** `SliderExtensions` | ||
|
|
||
| ## IsMouseWheelEnabled | ||
|
|
||
| Setting `SliderExtensions.IsMouseWheelEnabled` to `true` opts a slider in to mouse-wheel input: scrolling the wheel while the pointer is over the slider increments or decrements `Value`. The wheel event is marked handled, so an enclosing [`ScrollViewer`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.scrollviewer) is not also scrolled. | ||
|
|
||
| ```xaml | ||
| <Page xmlns:ui="using:CommunityToolkit.WinUI"> | ||
| <Slider Minimum="0" | ||
| Maximum="100" | ||
| ui:SliderExtensions.IsMouseWheelEnabled="True" /> | ||
| </Page> | ||
| ``` | ||
|
|
||
| ## MouseWheelChange | ||
|
|
||
| `SliderExtensions.MouseWheelChange` is the amount added to or subtracted from `Value` per wheel notch. The naming mirrors the built-in [`Slider.SmallChange`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.primitives.rangebase.smallchange) / [`LargeChange`](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.primitives.rangebase.largechange) properties. When unset (the default of `double.NaN`), the slider's own `SmallChange` is used — so a slider already configured for arrow-key input "just works" for the wheel too. | ||
|
|
||
| ```xaml | ||
| <Page xmlns:ui="using:CommunityToolkit.WinUI"> | ||
| <Slider Minimum="0" | ||
| Maximum="100" | ||
| ui:SliderExtensions.IsMouseWheelEnabled="True" | ||
| ui:SliderExtensions.MouseWheelChange="5" /> | ||
| </Page> | ||
| ``` | ||
|
|
||
| > [!SAMPLE SliderWheelSample] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <Page x:Class="ExtensionsExperiment.Samples.SliderExtensions.SliderWheelSample" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
| xmlns:ui="using:CommunityToolkit.WinUI" | ||
| Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" | ||
| mc:Ignorable="d"> | ||
|
|
||
| <StackPanel MaxWidth="320" | ||
| Spacing="20"> | ||
| <StackPanel Spacing="4"> | ||
| <TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" | ||
| Text="Hover and scroll the wheel" /> | ||
| <TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" | ||
| Style="{ThemeResource CaptionTextBlockStyle}" | ||
| Text="Each notch changes the value by MouseWheelChange. Try the controls in the sample pane to tweak it." /> | ||
| </StackPanel> | ||
|
|
||
| <Slider Maximum="100" | ||
| Minimum="0" | ||
| ui:SliderExtensions.IsMouseWheelEnabled="{x:Bind IsMouseWheelEnabled, Mode=OneWay}" | ||
| ui:SliderExtensions.MouseWheelChange="{x:Bind MouseWheelChange, Mode=OneWay}" | ||
| Value="50" /> | ||
| </StackPanel> | ||
| </Page> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| namespace ExtensionsExperiment.Samples.SliderExtensions; | ||
|
|
||
| /// <summary> | ||
| /// A sample showing how the <c>SliderExtensions</c> attached properties make a | ||
| /// <see cref="Microsoft.UI.Xaml.Controls.Slider"/> respond to mouse-wheel input. | ||
| /// </summary> | ||
| [ToolkitSampleBoolOption("IsMouseWheelEnabled", true, Title = "Enable mouse-wheel scrolling")] | ||
| [ToolkitSampleNumericOption("MouseWheelChange", 5, 1, 25, Title = "Value change per wheel notch")] | ||
| [ToolkitSample(id: nameof(SliderWheelSample), "Slider Mouse Wheel", description: "An extension that lets a Slider be adjusted by mouse-wheel scrolling.")] | ||
| public sealed partial class SliderWheelSample : Page | ||
| { | ||
| public SliderWheelSample() | ||
| { | ||
| this.InitializeComponent(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,110 @@ | ||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||
| // See the LICENSE file in the project root for more information. | ||||||
|
|
||||||
| namespace CommunityToolkit.WinUI; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Provides attached dependency properties for the <see cref="Slider"/> control. | ||||||
| /// </summary> | ||||||
| public static class SliderExtensions | ||||||
| { | ||||||
| /// <summary> | ||||||
| /// Attached <see cref="DependencyProperty"/> that, when <c>true</c>, lets the | ||||||
| /// target <see cref="Slider"/> respond to mouse-wheel input by adjusting its | ||||||
| /// <see cref="RangeBase.Value"/>. Each notch changes the value by | ||||||
| /// <see cref="MouseWheelChangeProperty"/>; the wheel event is marked handled so | ||||||
| /// an enclosing <see cref="ScrollViewer"/> does not also scroll. | ||||||
| /// </summary> | ||||||
| public static readonly DependencyProperty IsMouseWheelEnabledProperty = DependencyProperty.RegisterAttached( | ||||||
| nameof(GetIsMouseWheelEnabled)[3..], | ||||||
| typeof(bool), | ||||||
| typeof(SliderExtensions), | ||||||
| new PropertyMetadata(false, OnIsMouseWheelEnabledChanged)); | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Attached <see cref="DependencyProperty"/> for the value added to or subtracted | ||||||
| /// from <see cref="RangeBase.Value"/> per mouse-wheel notch. Defaults to | ||||||
| /// <see cref="double.NaN"/>, which means "use the slider's own | ||||||
| /// <see cref="RangeBase.SmallChange"/>". | ||||||
| /// </summary> | ||||||
| public static readonly DependencyProperty MouseWheelChangeProperty = DependencyProperty.RegisterAttached( | ||||||
| nameof(GetMouseWheelChange)[3..], | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| typeof(double), | ||||||
| typeof(SliderExtensions), | ||||||
| new PropertyMetadata(double.NaN)); | ||||||
|
Comment on lines
+19
to
+35
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. This is kind of clever, but weird. I think it makes the code less readable, so I'm against it |
||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets the value of <see cref="IsMouseWheelEnabledProperty"/>. | ||||||
| /// </summary> | ||||||
| /// <param name="obj">The <see cref="Slider"/> to read the property value from.</param> | ||||||
| /// <returns><c>true</c> if mouse-wheel scrolling is enabled on the slider; otherwise <c>false</c>.</returns> | ||||||
| public static bool GetIsMouseWheelEnabled(Slider obj) => (bool)obj.GetValue(IsMouseWheelEnabledProperty); | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Sets the value of <see cref="IsMouseWheelEnabledProperty"/>. | ||||||
| /// </summary> | ||||||
| /// <param name="obj">The <see cref="Slider"/> to set the property on.</param> | ||||||
| /// <param name="value"><c>true</c> to enable mouse-wheel scrolling on the slider; otherwise <c>false</c>.</param> | ||||||
| public static void SetIsMouseWheelEnabled(Slider obj, bool value) => obj.SetValue(IsMouseWheelEnabledProperty, value); | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets the value of <see cref="MouseWheelChangeProperty"/>. | ||||||
| /// </summary> | ||||||
| /// <param name="obj">The <see cref="Slider"/> to read the property value from.</param> | ||||||
| /// <returns>The per-notch delta, or <see cref="double.NaN"/> to inherit from <see cref="RangeBase.SmallChange"/>.</returns> | ||||||
| public static double GetMouseWheelChange(Slider obj) => (double)obj.GetValue(MouseWheelChangeProperty); | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Sets the value of <see cref="MouseWheelChangeProperty"/>. | ||||||
| /// </summary> | ||||||
| /// <param name="obj">The <see cref="Slider"/> to set the property on.</param> | ||||||
| /// <param name="value">The per-notch delta to apply to <see cref="RangeBase.Value"/>.</param> | ||||||
| public static void SetMouseWheelChange(Slider obj, double value) => obj.SetValue(MouseWheelChangeProperty, value); | ||||||
|
|
||||||
| private static void OnIsMouseWheelEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | ||||||
| { | ||||||
| if (d is not Slider slider) | ||||||
| { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| slider.PointerWheelChanged -= OnPointerWheelChanged; | ||||||
|
|
||||||
| if (e.NewValue is true) | ||||||
| { | ||||||
| slider.PointerWheelChanged += OnPointerWheelChanged; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private static void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e) | ||||||
| { | ||||||
| if (sender is not Slider slider) | ||||||
| { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| int delta = e.GetCurrentPoint(slider).Properties.MouseWheelDelta; | ||||||
| if (delta == 0) | ||||||
| { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| double step = GetMouseWheelChange(slider); | ||||||
| if (double.IsNaN(step) || step <= 0) | ||||||
|
|
||||||
| { | ||||||
| step = slider.SmallChange; | ||||||
| } | ||||||
|
|
||||||
| double notches = delta / 120.0; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Let's get a comment for this magic constant |
||||||
| double newValue = Math.Clamp(slider.Value + (notches * step), slider.Minimum, slider.Maximum); | ||||||
|
|
||||||
| if (newValue != slider.Value) | ||||||
| { | ||||||
| slider.Value = newValue; | ||||||
| } | ||||||
|
|
||||||
| // Claim the gesture so an enclosing ScrollViewer does not also scroll. | ||||||
| e.Handled = true; | ||||||
| } | ||||||
| } | ||||||
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.