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.ViewportSize
property, but it in turn is related to the Scrollbar.Minimum
and Scrollbar.Maximum
values.
What I have discovered so far is:
For a Minimum and Maximum of 0 and 10, a ViewportSize of:
0 - Thumb minimum size
5 - Thumb approximately 25% of the available track
10 - Thumb approximately 50% of the available track
100 - Thumb approximately 75% of the available track
1000 - Thumb approximately 90% of the available track
10000 - Thumb fills the available track.
[note: these figures are only from my rough trial and error!]
Ideally I'd like to be able to have an algorithm where given the minimum and maximum values for the Scrollbar I can set the thumb size to be exactly x% of the available track.
Can anyone help with this?
Thanks.
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.
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
}
}
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!
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;
}
Here's a method that will override the thumb minimum width for all ScrollBar
s. There's 2 important reasons for using this setup.
1) This will not resize the ScrollBar
RepeatButton
s. (Why the style overrides Track
)
2) This will only resize the thumbs for Track
controls that are used in ScrollBar
s. (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>