I am attempting to work out the algorithm associated with sizing of the WPF Scrollbar thumb element.
The thumb element can be sized using the Scrollbar.ViewportSi
Scrollbar thumb size for UWP:
static void SetViewportSize(ScrollBar bar, double size)
{
var max = (bar.Maximum - bar.Minimum);
bar.ViewportSize = size / (max - size) * max;
bar.IsEnabled = (bar.ViewportSize >= 0 &&
bar.ViewportSize != double.PositiveInfinity);
InvalidateScrollBar(bar);
}
static void InvalidateScrollBar(ScrollBar bar)
{
var v = bar.Value;
bar.Value = (bar.Value == bar.Maximum) ? bar.Minimum : bar.Maximum;
bar.Value = v;
}
If you're looking for how to set a minimum height for the scrollbar thumb:
From Ian (da real MVP) here:
scrollBar1.Track.ViewportSize = double.NaN;
scrollBar1.Track.Thumb.Height = Math.Max(minThumbHeight, DataScrollBar.Track.Thumb.ActualHeight);
Or you know, add 100+ lines of xaml code cause omgDATABINDING!!1!
From: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.track(VS.90).aspx
thumbSize = (viewportSize/(maximum–minimum+viewportSize))×trackLength
or re-arranging for viewportSize:
viewportSize = thumbSize×(maximum-minimum)/(trackLength-thumbSize)
You've prob found this already but thought I'd post in case others end up here.
Here's a method that will override the thumb minimum width for all ScrollBars. There's 2 important reasons for using this setup.
1) This will not resize the ScrollBar RepeatButtons. (Why the style overrides Track)
2) This will only resize the thumbs for Track controls that are used in ScrollBars. (Why the Track style is contained in a ScrollBar style.
<!-- Override for all styles -->
<Style TargetType="{x:Type ScrollBar}" BasedOn="{StaticResource {x:Type ScrollBar}}">
<Style.Resources>
<Style TargetType="{x:Type Track}">
<Style.Resources>
<System:Double x:Key="{x:Static SystemParameters.VerticalScrollBarButtonHeightKey}">48</System:Double>
<System:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}">48</System:Double>
</Style.Resources>
</Style>
</Style.Resources>
</Style>
<!-- Override for a certain control -->
<!-- The ScrollBar Style part in the middle can be safely ommited
if you can guarantee the control only uses Tracks for ScrollBars -->
<SomeControl>
<SomeControl.Resources>
<Style TargetType="{x:Type ScrollBar}" BasedOn="{StaticResource {x:Type ScrollBar}}">
<Style.Resources>
<Style TargetType="{x:Type Track}">
<Style.Resources>
<System:Double x:Key="{x:Static SystemParameters.VerticalScrollBarButtonHeightKey}">48</System:Double>
<System:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}">48</System:Double>
</Style.Resources>
</Style>
</Style.Resources>
</Style>
</SomeControl.Resources>
</SomeControl>
On my side, I preserved a minimum thumb length because touch inputs require a thumb of a minimum size to be touch optimized.
You can define a ScrollViewer ControlTemplate that will use the TouchScrollBar as its horisontal and vertical ScrollBar.
See UpdateViewPort method for the math.
Sorry, I don't see the use case for explicitly setting the scrollbar thumb to cover a percentage of the track length
public class TouchScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
#region Fields
#region Dependency properties
public static readonly DependencyProperty MinThumbLengthProperty =
DependencyProperty.Register
("MinThumbLength", typeof(double), typeof(TouchScrollBar), new UIPropertyMetadata((double)0, OnMinThumbLengthPropertyChanged));
#endregion
private double? m_originalViewportSize;
#endregion
#region Properties
public double MinThumbLength
{
get { return (double)GetValue(MinThumbLengthProperty); }
set { SetValue(MinThumbLengthProperty, value); }
}
#endregion
#region Constructors
public TouchScrollBar()
{
SizeChanged += OnSizeChanged;
}
private bool m_trackSubscribed;
void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
SubscribeTrack();
}
private void SubscribeTrack()
{
if (!m_trackSubscribed && Track != null)
{
Track.SizeChanged += OnTrackSizeChanged;
m_trackSubscribed = true;
}
}
#endregion
#region Protected and private methods
#region Event handlers
#region Dependency properties event handlers
private void OnMinThumbLengthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TouchScrollBar instance = d as TouchScrollBar;
if(instance != null)
{
instance.OnMinThumbLengthChanged(e);
}
}
#endregion
protected void OnTrackSizeChanged(object sender, SizeChangedEventArgs e)
{
SubscribeTrack();
UpdateViewPort();
}
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
base.OnMaximumChanged(oldMaximum, newMaximum);
SubscribeTrack();
UpdateViewPort();
}
protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
{
base.OnMinimumChanged(oldMinimum, newMinimum);
SubscribeTrack();
UpdateViewPort();
}
protected void OnMinThumbLengthChanged(DependencyPropertyChangedEventArgs e)
{
SubscribeTrack();
UpdateViewPort();
}
#endregion
private void UpdateViewPort()
{
if(Track != null)
{
if(m_originalViewportSize == null)
{
m_originalViewportSize = ViewportSize;
}
double trackLength = Orientation == Orientation.Vertical ? Track.ActualHeight : Track.ActualWidth;
double thumbHeight = m_originalViewportSize.Value / (Maximum - Minimum + m_originalViewportSize.Value) * trackLength;
if (thumbHeight < MinThumbLength && !double.IsNaN(thumbHeight))
{
ViewportSize = (MinThumbLength * (Maximum - Minimum)) / (trackLength + MinThumbLength);
}
}
}
#endregion
}
}