Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions components/Extensions/samples/SliderExtensions.md
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();
}
}
110 changes: 110 additions & 0 deletions components/Extensions/src/Slider/SliderExtensions.cs
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..],
typeof(double),
typeof(SliderExtensions),
new PropertyMetadata(double.NaN));
Comment on lines +19 to +35

/// <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;
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;
}
}
Loading