Как устранить скачок большого пальца, во время прокрутки скроллбара?
Подсказка: "большой палец / thumb / скруббер" - это ползунок, расположенный внутри полосы прокрутки. Потянув за который осуществляется прокрутка контента.
Имеются две полосы прокрутки:
Пользовательская, начинает прокрутку содержимого, только если курсор мыши находится посередине Thumb. Если щелкнуть мышью в другом месте, то Thumb перепрыгнет так, чтобы курсор оказался точно посередине и только тогда начнет прокрутку;Стандартная / системная, прокручивает содержимое независимо от того, где находится курсор мыши на Thumb.
Изменение метода "MouseScroll" - дает разные результаты, но прыжок все равно остается.
Пользовательский ScrollBar: скачет, когда пытаешься прокручивать НЕ с середины Thumb
Системный ScrollBar: осуществляет прокрутку независимо от того, с какого места тянешь за Thumb.
Beauty_VScrollBar
namespace WindowsFormsApplication1 {
[DefaultEvent("ValueChanged")]
public class Beauty_VScrollBar : Control {
#region Setting the initial parameters
public Beauty_VScrollBar() {
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw, true); UpdateStyles();
Size = new Size(12, 250); Orientation = ScrollOrientation.Vertical;
}
#endregion
#region Basic properties
private int _value;
public int Value {
get { return _value; }
set {
_value = value;
if (Value < Minimum) { Value = Minimum; }
if (Value > Maximum) { Value = Maximum; }
Refresh(); OnScroll();
}
}
private int minimum;
public int Minimum {
get { return minimum; }
set { minimum = value; Invalidate(); }
}
private int maximum = 100;
public int Maximum {
get { return maximum; }
set { maximum = value; Invalidate(); }
}
public int SmallStep { get; set; } = 1;
[Browsable(false)]
public enum ScrollOrientation {
Horizontal = 0x114, Vertical = 0x115
}
private ScrollOrientation orientation;
public ScrollOrientation Orientation {
get { return orientation; }
set { orientation = value; Invalidate(); }
}
#endregion
#region Properties responsible for design
[Description("Thumb size")]
private int thumbSize = 50;
public int ThumbSize {
get { return thumbSize; }
set { thumbSize = value; Invalidate(); }
}
[Description("Thumb color")]
private Color thumbColor = Color.Gray;
public Color ThumbColor {
get { return thumbColor; }
set { thumbColor = value; Invalidate(); }
}
[Description("Scroll bar fill color")]
private Color backgroundColor = Color.Transparent;
public Color BackgroundColor {
get { return backgroundColor; }
set { backgroundColor = value; Invalidate(); }
}
[Description("Scroll bar borders color")]
private Color borderColor = Color.Silver;
public Color BorderColor {
get { return borderColor; }
set { borderColor = value; Invalidate(); }
}
#endregion
#region Key Events
public event EventHandler ValueChanged;
#endregion
#region Event handlers
private Rectangle thumbRect;
protected override void OnMouseDown(MouseEventArgs e) {
if (e.Button == MouseButtons.Left
&& !thumbRect.Contains(e.Location)) MouseScroll(e);
if (e.Button == MouseButtons.Left && thumbRect.Contains(e.Location)) {
thumbColor = Color.Green; Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e) {
thumbColor = Color.Gray; Invalidate();
}
protected override void OnMouseMove(MouseEventArgs e) {
thumbColor = thumbRect.Contains(e.Location)
? thumbColor = Color.Orange : thumbColor = Color.Gray;
if (e.Button == MouseButtons.Left) {
thumbColor = Color.Green;
MouseScroll(e);
}
Invalidate();
}
private void MouseScroll(MouseEventArgs e) {
Value = Maximum * (e.Y - thumbSize / 2) / (Height - thumbSize) + 1;
}
public virtual void OnScroll(ScrollEventType type = ScrollEventType.ThumbPosition) {
ValueChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Drawing controls
protected override void OnPaint(PaintEventArgs e) {
thumbRect = new Rectangle(2, (Value * (Height - ThumbSize) / Maximum), Width - 4, thumbSize);
e.Graphics.FillRectangle(new SolidBrush(BackgroundColor), new Rectangle(0, 0, Width, Height)); // scroll bar fill
e.Graphics.FillRectangle(new SolidBrush(ThumbColor), thumbRect); // thumb fill
e.Graphics.DrawRectangle(pen, new Rectangle(0, 0, Width - 1, Height - 1)); // drawing the scroll bar borders
#endregion
}
}
Ответы (3 шт):
ОБЪЯСНЕНИЕ ПРОБЛЕМЫ:
Метод MouseScroll делает так, что середина вашего скролбала оказывается в месте клика мыши
private void MouseScroll(MouseEventArgs e) {
Value = Maximum * (e.Y - thumbSize / 2) / (Height - thumbSize) + 1;
}
Давайте предположем, что наш скролбал имеет длину 5 пикселей и его Value = 3, вся полоска - 9 пикселей. После этого мы кликаем на 4й (считая с нуля) пиксель. Вот наглядно что происходит:
Этот метод, вроде как, правильно используется в OnMouseDown, так как тут, если происходит клик вне скролбара, то скролбар "прибегает к мышке":
protected override void OnMouseDown(MouseEventArgs e) {
if (e.Button == MouseButtons.Left
&& !thumbRect.Contains(e.Location)) MouseScroll(e);
....
}
А вот в методе OnMouseMove, который происходит когда мышка двигается и 100% мышка находится в зоне скролбара, такой метод уже делает не то, что хотелось бы.
РЕШЕНИЕ ПРОБЛЕМЫ:
Чтобы решить вашу проблему нужно вне методов создать переменную private Point startMouseClickPosition и сохранить туда e.Location в методе OnMouseDown:
private Point startMouseClickPosition;
protected override void OnMouseDown(MouseEventArgs e) {
startMouseClickPosition = e.Location;
....
}
После этого в методе OnMouseMove достаточно просто сместить Value на разницу между e.Location.Y и startMouseClickPosition.Y (не забывая про максимум):
protected override void OnMouseMove(MouseEventArgs e) {
....
if (e.Button == MouseButtons.Left) {
thumbColor = Color.Green;
//MouseScroll(e);
var newValue = Value + (e.Location.Y - startMouseClickPosition.Y);
Value = newValue > Maximum ? Maximum : newValue;
}
...
}
Полезные материалы:
- Описание ScrollBar: https://webinmind.ru/html/customization-scrollbar-webkit
- Написание Custom ScrollBar: https://www.codeproject.com/Articles/14801/How-to-skin-scrollbars-for-Panels-in-C
Детальная схема полосы прокрутки (ScrollBar)

Код, который позволяет двигать ползунок полосы прокрутки с любого места
private Rectangle thumbRect;
private bool isThumbDown = false;
private bool isThumbDragging = false;
private int oldThumbTop, newThumbTop;
private Point startMouseClickPosition;
protected override void OnMouseDown(MouseEventArgs e) {
// При клике по ScrollBar, отцентрировать Thumb относительно курсора мыши
if (!thumbRect.Contains(e.Location))
Value = Maximum * (e.Y - ThumbSize / 2) / (Height - ThumbSize) + 1;
if (thumbRect.Contains(e.Location)) {
startMouseClickPosition.Y = (e.Y - thumbRect.Top);
isThumbDown = true;
}
}
protected override void OnMouseUp(MouseEventArgs e) {
isThumbDown = false; isThumbDragging = false;
}
protected override void OnMouseMove(MouseEventArgs e) {
if (isThumbDown) isThumbDragging = true;
if (isThumbDragging) MoveThumb(e);
}
private void MoveThumb(MouseEventArgs e) {
int trackPiece = Height - ThumbSize;
if (isThumbDown && Maximum > 0) {
newThumbTop = e.Y - startMouseClickPosition.Y;
if (newThumbTop < 0) {
oldThumbTop = newThumbTop = 0;
} else if (newThumbTop > trackPiece) {
oldThumbTop = newThumbTop = trackPiece;
} else {
oldThumbTop = newThumbTop;
}
float percent = (float)oldThumbTop / (float)trackPiece;
float newValue = percent * (Maximum - SmallStep);
Value = (int)newValue;
}
}
Вариант, который работает без доп. полей (isThumbDown и isThumbDragging):
private Rectangle thumbRect;
private int oldThumbTop, newThumbTop;
private Point startMouseClickPosition;
protected override void OnMouseDown(MouseEventArgs e) {
// When clicking on the ScrollBar, center the Thumb relative to the mouse cursor
if (!thumbRect.Contains(e.Location))
Value = Maximum * (e.Y - ThumbSize / 2) / (Height - ThumbSize) + 1;
if (thumbRect.Contains(e.Location)) {
startMouseClickPosition.Y = (e.Y - thumbRect.Top);
ThumbBackColor = Color.Green;
}
}
protected override void OnMouseUp(MouseEventArgs e) {
ThumbBackColor = Color.Gray;
}
protected override void OnMouseMove(MouseEventArgs e) {
ThumbBackColor = thumbRect.Contains(e.Location)
? ThumbBackColor = Color.Orange : ThumbBackColor = Color.Gray;
if (e.Button == MouseButtons.Left) {
ThumbBackColor = Color.Green; MoveThumb(e);
}
}
private void MoveThumb(MouseEventArgs e) {
int trackPiece = Height - ThumbSize;
if (Maximum > 0) {
newThumbTop = e.Y - startMouseClickPosition.Y;
if (newThumbTop < 0) {
oldThumbTop = newThumbTop = 0;
} else if (newThumbTop > trackPiece) {
oldThumbTop = newThumbTop = trackPiece;
} else {
oldThumbTop = newThumbTop;
}
float percent = (float)oldThumbTop / (float)trackPiece;
float newValue = percent * (Maximum - SmallStep);
Value = (int)newValue;
}
}


