WPF Development Notes Collection - Solution for ScrollViewer Slider Too Small

WPF Development Notes Collection - Solution for ScrollViewer Slider Too Small

When the content inside ScrollViewer is too long, the slider of the scrollbar becomes very small, making it less friendly to click.

Last updated 5/13/2022 7:13 AM
流浪g
4 min read
Category
WPF
Tags
.NET WPF

1. Introduction

In WPF development, ScrollViewer is a commonly used control. In a project I worked on, we received feedback that when the content inside ScrollViewer is too long, the scrollbar's thumb becomes very small, making it difficult to click. Initially, I tried setting a minimum size for the thumb in the style, but it didn't work. Finally, I took a different approach: hide the original thumb and use a custom control as a replacement to indirectly control the scrolling of ScrollViewer.

2. Main Content

  1. Here I will directly use the curve chart control I previously wrote for demonstration. When there is a large amount of curve data, the thumb appears very small. The following shows the default style; it would be even smaller under a custom style.

  2. Here, I placed a Canvas on top of the curve chart and added a Border to act as the thumb. Note that the entire Canvas is overlaid on the curve chart because I also added the functionality to drag and move the chart by clicking. Then, I hide the ScrollViewer’s thumb, set the minimum width and height for the custom thumb, and hide it by default.

    <Grid>
        <local:CruveDrawingVisual x:Name="curve" Margin="0,10,0,15" />
        <ScrollViewer
            Name="scroll"
            HorizontalScrollBarVisibility="Hidden"
            ScrollChanged="ScrollViewer_ScrollChanged"
            VerticalScrollBarVisibility="Disabled">
            <Canvas x:Name="canvas" />
        </ScrollViewer>
        <Canvas x:Name="CurvePanel" Background="Transparent">
            <Border
                x:Name="border"
                Canvas.Left="0"
                Canvas.Bottom="0"
                Height="15"
                MinWidth="80"
                Background="Green"
                PreviewMouseLeftButtonDown="Border_PreviewMouseLeftButtonDown"
                Visibility="Collapsed" />
        </Canvas>
    </Grid>
    
  3. Next, add the corresponding logic code in the code-behind. Detailed explanations are already included as comments in the code, so I won't elaborate further here.

    public partial class MainWindow : Window
    {
        private bool isAdd = true;
        private List<int> lists = new List<int>();
    
        private Point point_border;
    
        private double offset = -1;
    
        public MainWindow()
        {
            InitializeComponent();
    
            CurvePanel.MouseMove += delegate (object sender, MouseEventArgs e)
            {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    if (Mouse.Captured == null) Mouse.Capture(CurvePanel);
    
                    // Drag the thumb
                    if (isBorder)
                    {
                        Point point = e.GetPosition(this);
                        // Mouse is beyond the left edge of the control
                        if (point.X - point_border.X <= 0)
                        {
                            scroll.ScrollToHorizontalOffset(0);
                        }
                        // Mouse is beyond the right edge of the control
                        else if (point.X - point_border.X >= CurvePanel.ActualWidth - border.ActualWidth)
                        {
                            scroll.ScrollToHorizontalOffset(lists.Count - CurvePanel.ActualWidth);
                        }
                        // Mouse is within the control area
                        else if (point.X - point_border.X > 0 && point.X - point_border.X < CurvePanel.ActualWidth - border.ActualWidth)
                        {
                            double left = point.X - point_border.X;
                            scroll.ScrollToHorizontalOffset((lists.Count - CurvePanel.ActualWidth) / (CurvePanel.ActualWidth - border.ActualWidth) * left);
                        }
                    }
                    // Drag the canvas
                    else
                    {
                        if (offset >= 0 && offset <= CurvePanel.ActualWidth)
                        {
                            scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - (e.GetPosition(this).X - offset));
                        }
                        offset = e.GetPosition(this).X;
                    }
                }
                else
                {
                    offset = -1;
                    isBorder = false;
                    Mouse.Capture(null); // Release mouse capture
                }
            };
        }
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            int temp = 20;
            for (int i = 0; i < 24 * 60 * 60 * 2; i++)
            {
                if (isAdd)
                {
                    lists.Add(temp);
                    temp += 2;
                }
                else
                {
                    lists.Add(temp);
                    temp -= 2;
                }
    
                if (temp == 280) isAdd = false;
                if (temp == 20) isAdd = true;
            }
            canvas.Width = lists.Count;
            // Determine whether to show the thumb
            if (canvas.Width > CurvePanel.ActualWidth)
            {
                border.Visibility = Visibility.Visible;
                // Calculate the thumb width based on the ratio of ScrollViewer content
                border.Width = CurvePanel.ActualWidth * CurvePanel.ActualWidth / canvas.Width;
            }
            else
            {
                border.Visibility = Visibility.Collapsed;
            }
            curve.SetupData(lists);
        }
    
        private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            curve.OffsetX(scroll.HorizontalOffset);
    
            Canvas.SetLeft(border, scroll.HorizontalOffset / ((lists.Count - CurvePanel.ActualWidth) / (CurvePanel.ActualWidth - border.ActualWidth)));
        }
    
        private bool isBorder = false;
        private void Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isBorder = true;
            // Get the mouse position relative to the thumb
            point_border = e.GetPosition(border);
        }
    }
    
  4. The runtime effect is as follows.

Keep Exploring

Related Reading

More Articles
Same category / Same tag 9/13/2025

Migration Series from WPF to Avalonia: Why I Must Migrate My WPF Application to Avalonia

In the past few years, our host computer software has mainly been developed using WPF and WinForm . These technologies work well on the Windows platform and have accompanied us from small-scale trial production to the current stage of large-scale delivery. However, with business development and changes in customer requirements, the single Windows technology stack has gradually become a hurdle we must overcome.

Continue Reading
Same category / Same tag 1/26/2025

Implementing Internationalization in WPF Using Custom XML Files

This article details the method of implementing internationalization in WPF applications using custom XML files, including installing the necessary NuGet packages, dynamically retrieving the language list, dynamically switching languages, using translated strings in code and XAML interfaces, and provides a source code link to help developers easily achieve internationalization in WPF applications.

Continue Reading