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
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
}
}