Skip to content

Instantly share code, notes, and snippets.

@ahmedosama007
Last active May 22, 2024 19:10
Show Gist options
  • Save ahmedosama007/c1b0cd327d395a5698c1e17e96d0f8f9 to your computer and use it in GitHub Desktop.
Save ahmedosama007/c1b0cd327d395a5698c1e17e96d0f8f9 to your computer and use it in GitHub Desktop.
Flat ScrollBar control for Windows Forms apps based on https://www.codeproject.com/Articles/41869/Custom-Drawn-Scrollbar
'Copyright (c) Smart PC Utilities, Ltd.
'All rights reserved.
#Region "References"
Imports System.ComponentModel
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D
#End Region
Namespace Controls
<Designer(GetType(FlatScrollBarDesigner))> <ToolboxBitmap(GetType(ScrollBar))> <DefaultEvent("Scroll")> <DefaultProperty("Value")> Public Class FlatScrollBar
Inherits Control
Implements ITheme
#Region "Private Members"
Private _isDrawing As Boolean 'Indicates many changes to the scrollbar are happening, so stop painting till finished.
Private _barOrientation As ScrollBarOrientation = ScrollBarOrientation.Vertical 'The scrollbar orientation - horizontal / vertical.
Private _scrollOrientation As ScrollOrientation = ScrollOrientation.VerticalScroll 'The scroll orientation in scroll events.
Private _rectClickBar As Rectangle 'The clicked channel rectangle.
Private _rectThumb As Rectangle
Private _rectTopArrow As Rectangle
Private _rectBottomArrow As Rectangle
Private _rectChannel As Rectangle
Private _isTopArrowClicked As Boolean 'Indicates if top arrow was clicked.
Private _isBottomArrowClicked As Boolean 'Indicates if down arrow was clicked.
Private _isTopBarClicked As Boolean 'Indicates if channel rectangle above the thumb was clicked.
Private _isBottomBarClicked As Boolean 'Indicates if channel rectangle under the thumb was clicked.
Private _isThumbClicked As Boolean 'Indicates if the thumb was clicked.
Private _thumbState As ScrollBarState = ScrollBarState.Normal 'The state of the thumb.
Private _topArrowButtonState As ScrollBarArrowButtonState = ScrollBarArrowButtonState.UpNormal 'The state of the top arrow.
Private _bottomArrowButtonState As ScrollBarArrowButtonState = ScrollBarArrowButtonState.DownNormal 'The state of the bottom arrow.
Private _minimum As Integer
Private _maximum As Integer = 100
Private _smallChange As Integer = 1
Private _largeChange As Integer = 10
Private _value As Integer
Private _thumbWidth As Integer = 10
Private _thumbHeight As Integer
Private _arrowWidth As Integer = 18
Private _arrowHeight As Integer = 18
Private _thumbBottomLimitBottom As Integer 'The bottom limit for the thumb bottom.
Private _thumbBottomLimitTop As Integer 'The bottom limit for the thumb top.
Private _thumbTopLimit As Integer 'The top limit for the thumb top.
Private _thumbPosition As Integer 'The current position of the thumb.
Private _trackPosition As Integer 'The track position.
Private ReadOnly scrollTimer As New Timer() 'The progress timer for moving the thumb.
Private _theme As UITheme = UITheme.VS2019LightBlue
Private _backColor As Color = Color.FromArgb(242, 242, 242)
Private _borderColor As Color = Color.FromArgb(242, 242, 242)
Private _borderColorDisabled As Color = Color.FromArgb(242, 242, 242)
Private ReadOnly _thumbColors As Color() = New Color(3) {}
Private ReadOnly _arrowColors As Color() = New Color(3) {}
#End Region
#Region "Constants"
Private Const SETREDRAW As Integer = 11 ' Redraw constant
Private Const MINIMUM_SIZE As Integer = 10
#End Region
#Region "Constructor"
Public Sub New()
SetStyle(ControlStyles.OptimizedDoubleBuffer Or ControlStyles.ResizeRedraw Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint, True)
SetStyle(ControlStyles.Selectable, False)
SetUpScrollBar() 'Setup the ScrollBar
AddHandler scrollTimer.Tick, AddressOf ScrollTimer_Tick
_thumbColors(0) = Color.FromArgb(194, 195, 201) 'Normal state
_thumbColors(1) = Color.FromArgb(104, 104, 104) 'Hover state
_thumbColors(2) = Color.FromArgb(91, 91, 91) 'Pressed state
_arrowColors(0) = Color.FromArgb(134, 137, 153) 'Normal state
_arrowColors(1) = Color.FromArgb(70, 181, 255) 'Hover state
_arrowColors(2) = Color.FromArgb(0, 122, 204) 'Pressed state
End Sub
#End Region
#Region "Events"
''' <summary>
''' Raised when the ScrollBar control is scrolled.
''' </summary>
<Category("Behavior")> <Description("Raised when the ScrollBar control is scrolled.")> Public Event Scroll As ScrollEventHandler
#End Region
#Region "Public Properties"
<Category("Layout")> <Description("Gets or sets the ScrollBar orientation.")> <DefaultValue(ScrollBarOrientation.Vertical)> Public Property Orientation As ScrollBarOrientation
Get
Return _barOrientation
End Get
Set
If Value <> _barOrientation Then
_barOrientation = Value
_scrollOrientation = If(Value = ScrollBarOrientation.Vertical, ScrollOrientation.VerticalScroll, ScrollOrientation.HorizontalScroll)
If DesignMode Then 'only in DesignMode switch width and height
Size = New Size(Height, Width)
End If
SetUpScrollBar()
End If
End Set
End Property
<Category("Behavior")> <Description("Gets or sets the ScrollBar minimum value.")> <DefaultValue(0)> Public Property Minimum As Integer
Get
Return _minimum
End Get
Set
If _minimum = Value OrElse Value < 0 OrElse Value >= _maximum Then
Return
End If
_minimum = Value
'Current large change value invalid - adjust
If _largeChange > _maximum - _minimum Then
_largeChange = _maximum - _minimum
End If
SetUpScrollBar()
If _value < Value Then 'Current value less than new minimum value - adjust
Me.Value = Value
Else
ChangeThumbPosition(GetThumbPosition()) 'Current value is valid - adjust thumb position
Refresh()
End If
End Set
End Property
<Category("Behavior")> <Description("Gets or sets the ScrollBar maximum value.")> <DefaultValue(100)> Public Property Maximum As Integer
Get
Return _maximum
End Get
Set
If Value = _maximum OrElse Value < 1 OrElse Value <= _minimum Then
Return
End If
_maximum = Value
If _largeChange > _maximum - _minimum Then
_largeChange = _maximum - _minimum
End If
SetUpScrollBar()
If _value > _maximum Then
Me.Value = _maximum
Else
ChangeThumbPosition(GetThumbPosition())
Refresh()
End If
End Set
End Property
<Category("Behavior")> <Description("Gets or sets the ScrollBar small change value.")> <DefaultValue(1)> Public Property SmallChange As Integer
Get
Return _smallChange
End Get
Set
If Value = _smallChange OrElse Value < 1 OrElse Value >= _largeChange Then
Return
End If
_smallChange = Value
SetUpScrollBar()
End Set
End Property
<Category("Behavior")> <Description("Gets or sets the ScrollBar large change value.")> <DefaultValue(10)> Public Property LargeChange As Integer
Get
Return _largeChange
End Get
Set
If Value = _largeChange OrElse Value < _smallChange OrElse Value < 2 Then
Return
End If
_largeChange = If(Value > _maximum - _minimum, _maximum - _minimum, Value)
SetUpScrollBar()
End Set
End Property
<Category("Behavior")> <Description("Gets or sets the ScrollBar current value.")> <DefaultValue(0)> Public Property Value As Integer
Get
Return _value
End Get
Set
If _value = Value OrElse Value < _minimum OrElse Value > _maximum Then
Return
End If
_value = Value
ChangeThumbPosition(GetThumbPosition())
OnScroll(New ScrollEventArgs(ScrollEventType.ThumbPosition, -1, _value, _scrollOrientation))
Refresh()
End Set
End Property
<Category("Appearance")> <Description("The theme to apply to the Flat ScrollBar control.")> <DefaultValue(GetType(UITheme), "1")> Public Property Theme As UITheme Implements ITheme.Theme
Get
Return _theme
End Get
Set
_theme = Value
If _theme = UITheme.VS2019DarkBlue Then
_backColor = Color.FromArgb(62, 62, 66)
_borderColor = Color.FromArgb(62, 62, 66)
_borderColorDisabled = Color.FromArgb(62, 62, 66)
_thumbColors(0) = Color.FromArgb(104, 104, 104)
_thumbColors(1) = Color.FromArgb(158, 158, 158)
_thumbColors(2) = Color.FromArgb(239, 235, 239)
_arrowColors(0) = Color.FromArgb(153, 153, 153)
_arrowColors(1) = Color.FromArgb(28, 151, 234)
_arrowColors(2) = Color.FromArgb(0, 122, 204)
ElseIf _theme = UITheme.VS2019LightBlue Then
_backColor = Color.FromArgb(245, 245, 245)
_borderColor = Color.FromArgb(245, 245, 245)
_borderColorDisabled = Color.FromArgb(245, 245, 245)
_thumbColors(0) = Color.FromArgb(194, 195, 201) 'Normal state
_thumbColors(1) = Color.FromArgb(104, 104, 104) 'Hover state
_thumbColors(2) = Color.FromArgb(91, 91, 91) 'Pressed state
_arrowColors(0) = Color.FromArgb(134, 137, 153) 'Normal state
_arrowColors(1) = Color.FromArgb(28, 151, 234) 'Hover state
_arrowColors(2) = Color.FromArgb(0, 122, 204) 'Pressed state
ElseIf _theme = UITheme.Custom Then
ParentTheme = False
End If
Invalidate()
End Set
End Property
<Category("Appearance")> <Description("True to allow the control to inherit the parent control style.")> <DefaultValue(True)> Public Property ParentTheme As Boolean = True Implements ITheme.ParentTheme
#End Region
#Region "Public Methods"
Public Sub BeginUpdate()
NativeMethods.SendMessage(Handle, SETREDRAW, False, 0)
_isDrawing = True
End Sub
Public Sub EndUpdate()
NativeMethods.SendMessage(Handle, SETREDRAW, True, 0)
_isDrawing = False
SetUpScrollBar()
Refresh()
End Sub
#End Region
#Region "Overridden Methods"
Protected Overridable Sub OnScroll(e As ScrollEventArgs)
RaiseEvent Scroll(Me, e)
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
If e Is Nothing Then Return
Dim g = e.Graphics
g.SmoothingMode = SmoothingMode.AntiAlias
DrawBackground(g, ClientRectangle)
DrawThumb(g, _rectThumb, _thumbState)
DrawArrowButton(g, _rectTopArrow, _topArrowButtonState, True, _barOrientation)
DrawArrowButton(g, _rectBottomArrow, _bottomArrowButtonState, False, _barOrientation)
If _isTopBarClicked Then
If _barOrientation = ScrollBarOrientation.Vertical Then
_rectClickBar.Y = _thumbTopLimit
_rectClickBar.Height = _rectThumb.Y - _thumbTopLimit
Else
_rectClickBar.X = _thumbTopLimit
_rectClickBar.Width = _rectThumb.X - _thumbTopLimit
End If
ElseIf _isBottomBarClicked Then
If _barOrientation = ScrollBarOrientation.Vertical Then
_rectClickBar.Y = _rectThumb.Bottom + 1
_rectClickBar.Height = _thumbBottomLimitBottom - _rectClickBar.Y + 1
Else
_rectClickBar.X = _rectThumb.Right + 1
_rectClickBar.Width = _thumbBottomLimitBottom - _rectClickBar.X + 1
End If
End If
'Draw border
Using p As New Pen(If(Enabled, _borderColor, _borderColorDisabled))
e.Graphics.DrawRectangle(p, 0, 0, Width - 1, Height - 1)
End Using
End Sub
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
MyBase.OnMouseDown(e)
Focus()
If e.Button = MouseButtons.Left Then
Dim mouseLocation As Point = e.Location
If _rectThumb.Contains(mouseLocation) Then
_isThumbClicked = True
_thumbPosition = If(_barOrientation = ScrollBarOrientation.Vertical, mouseLocation.Y - _rectThumb.Y, mouseLocation.X - _rectThumb.X)
_thumbState = ScrollBarState.Pressed
Invalidate(_rectThumb)
ElseIf _rectTopArrow.Contains(mouseLocation) Then
_isTopArrowClicked = True
_topArrowButtonState = ScrollBarArrowButtonState.UpPressed
Invalidate(_rectTopArrow)
ProgressThumb(True)
ElseIf _rectBottomArrow.Contains(mouseLocation) Then
_isBottomArrowClicked = True
_bottomArrowButtonState = ScrollBarArrowButtonState.DownPressed
Invalidate(_rectBottomArrow)
ProgressThumb(True)
Else
_trackPosition = If(_barOrientation = ScrollBarOrientation.Vertical, mouseLocation.Y, mouseLocation.X)
If _trackPosition < If(_barOrientation = ScrollBarOrientation.Vertical, _rectThumb.Y, _rectThumb.X) Then
_isTopBarClicked = True
Else
_isBottomBarClicked = True
End If
ProgressThumb(True)
End If
ElseIf e.Button = MouseButtons.Right Then
_trackPosition = If(_barOrientation = ScrollBarOrientation.Vertical, e.Y, e.X)
End If
End Sub
Protected Overrides Sub OnMouseUp(e As MouseEventArgs)
MyBase.OnMouseUp(e)
If e.Button = MouseButtons.Left Then
If _isThumbClicked Then
_isThumbClicked = False
_thumbState = ScrollBarState.Normal
OnScroll(New ScrollEventArgs(ScrollEventType.EndScroll, -1, _value, _scrollOrientation))
ElseIf _isTopArrowClicked Then
_isTopArrowClicked = False
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
scrollTimer.[Stop]()
ElseIf _isBottomArrowClicked Then
_isBottomArrowClicked = False
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
scrollTimer.[Stop]()
ElseIf _isTopBarClicked Then
_isTopBarClicked = False
scrollTimer.[Stop]()
ElseIf _isBottomBarClicked Then
_isBottomBarClicked = False
scrollTimer.[Stop]()
End If
Invalidate()
End If
End Sub
Protected Overrides Sub OnMouseLeave(e As EventArgs)
MyBase.OnMouseLeave(e)
RefreshScrollBar()
End Sub
Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
MyBase.OnMouseMove(e)
If e.Button = MouseButtons.Left Then 'Moving and holding the left mouse button
If _isThumbClicked Then
Dim oldValue As Integer = _value
Dim pos As Integer = If(_barOrientation = ScrollBarOrientation.Vertical, e.Location.Y, e.Location.X)
If pos <= (_thumbTopLimit + _thumbPosition) Then 'The thumb is all the way to the top
ChangeThumbPosition(_thumbTopLimit)
_value = _minimum
ElseIf pos >= (_thumbBottomLimitTop + _thumbPosition) Then 'The thumb is all the way to the bottom
ChangeThumbPosition(_thumbBottomLimitTop)
_value = _maximum
Else 'The thumb is between the ends of the track.
ChangeThumbPosition(pos - _thumbPosition)
Dim pixelRange, thumbPos, arrowSize As Integer
'Calculate the value - first some helper variables dependent on the current orientation
If _barOrientation = ScrollBarOrientation.Vertical Then
pixelRange = Height - (2 * _arrowHeight) - _thumbHeight
thumbPos = _rectThumb.Y
arrowSize = _arrowHeight
Else
pixelRange = Width - (2 * _arrowWidth) - _thumbWidth
thumbPos = _rectThumb.X
arrowSize = _arrowWidth
End If
Dim perc As Single = 0F
If pixelRange <> 0 Then
perc = CSng(thumbPos - arrowSize) / CSng(pixelRange)
End If
_value = Convert.ToInt32((perc * (_maximum - _minimum)) + _minimum)
End If
If oldValue <> _value Then
OnScroll(New ScrollEventArgs(ScrollEventType.ThumbTrack, oldValue, _value, _scrollOrientation))
Refresh()
End If
End If
ElseIf Not ClientRectangle.Contains(e.Location) Then
RefreshScrollBar()
ElseIf e.Button = MouseButtons.None Then 'Only moving the mouse
If _rectTopArrow.Contains(e.Location) Then
_topArrowButtonState = ScrollBarArrowButtonState.UpHot
Invalidate(_rectTopArrow)
ElseIf _rectBottomArrow.Contains(e.Location) Then
_bottomArrowButtonState = ScrollBarArrowButtonState.DownHot
Invalidate(_rectBottomArrow)
ElseIf _rectThumb.Contains(e.Location) Then
_thumbState = ScrollBarState.Hot
Invalidate(_rectThumb)
Else
_thumbState = ScrollBarState.Normal
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
Refresh()
End If
End If
End Sub
Protected Overrides Sub OnMouseWheel(e As MouseEventArgs)
MyBase.OnMouseWheel(e)
Dim oldValue = _value
Dim scrollType As ScrollEventType
If e.Delta >= 0 Then 'Decrease value
_value = GetScrollValue(False, True)
If _value = _minimum Then
scrollType = ScrollEventType.First
ChangeThumbPosition(_thumbTopLimit)
Else
scrollType = ScrollEventType.LargeDecrement
ChangeThumbPosition(Math.Max(_thumbTopLimit, GetThumbPosition()))
End If
Else 'Increase value
_value = GetScrollValue(False, False)
If _value = _maximum Then
scrollType = ScrollEventType.Last
ChangeThumbPosition(_thumbBottomLimitTop)
Else
scrollType = ScrollEventType.SmallIncrement
ChangeThumbPosition(Math.Min(_thumbBottomLimitTop, GetThumbPosition()))
End If
End If
If oldValue <> _value Then
OnScroll(New ScrollEventArgs(scrollType, oldValue, _value, _scrollOrientation))
Invalidate(_rectChannel)
End If
End Sub
Protected Overrides Sub SetBoundsCore(x As Integer, y As Integer, width As Integer, height As Integer, specified As BoundsSpecified)
If DesignMode Then
If _barOrientation = ScrollBarOrientation.Vertical Then
Dim minHeight = (2 * _arrowHeight) + MINIMUM_SIZE
If height < minHeight Then height = minHeight
width = SystemInformation.VerticalScrollBarWidth
Else
Dim minWidth = (2 * _arrowWidth) + MINIMUM_SIZE
If width < minWidth Then width = minWidth
height = SystemInformation.VerticalScrollBarWidth
End If
End If
MyBase.SetBoundsCore(x, y, width, height, specified)
If DesignMode Then SetUpScrollBar()
End Sub
Protected Overrides Sub OnSizeChanged(e As EventArgs)
MyBase.OnSizeChanged(e)
SetUpScrollBar()
End Sub
Protected Overrides Function ProcessDialogKey(keyData As Keys) As Boolean
Dim isHandled As Boolean
Dim oldValue = _value
Dim scrollType As ScrollEventType
Dim keyUp As Keys = Keys.Up
Dim keyDown As Keys = Keys.Down
If _barOrientation = ScrollBarOrientation.Horizontal Then
keyUp = Keys.Left
keyDown = Keys.Right
End If
Select Case keyData
Case keyUp
_value = GetScrollValue(True, True)
If _value = _minimum Then
scrollType = ScrollEventType.First
ChangeThumbPosition(_thumbTopLimit)
Else
scrollType = ScrollEventType.SmallDecrement
ChangeThumbPosition(Math.Max(_thumbTopLimit, GetThumbPosition()))
End If
isHandled = True
Case keyDown
_value = GetScrollValue(True, False)
If _value = _maximum Then
scrollType = ScrollEventType.Last
ChangeThumbPosition(_thumbBottomLimitTop)
Else
scrollType = ScrollEventType.SmallIncrement
ChangeThumbPosition(Math.Min(_thumbBottomLimitTop, GetThumbPosition()))
End If
isHandled = True
Case Keys.PageUp
_value = GetScrollValue(False, True)
If _value = _minimum Then
scrollType = ScrollEventType.First
ChangeThumbPosition(_thumbTopLimit)
Else
scrollType = ScrollEventType.LargeDecrement
ChangeThumbPosition(Math.Max(_thumbTopLimit, GetThumbPosition()))
End If
isHandled = True
Case Keys.PageDown
_value = GetScrollValue(False, False)
If _value = _maximum Then
scrollType = ScrollEventType.Last
ChangeThumbPosition(_thumbBottomLimitTop)
Else
scrollType = ScrollEventType.SmallIncrement
ChangeThumbPosition(Math.Min(_thumbBottomLimitTop, GetThumbPosition()))
End If
isHandled = True
Case Keys.Home
_value = _minimum
scrollType = ScrollEventType.First
ChangeThumbPosition(_thumbTopLimit)
isHandled = True
Case Keys.End
_value = _maximum
scrollType = ScrollEventType.Last
ChangeThumbPosition(_thumbBottomLimitTop)
isHandled = True
End Select
If isHandled AndAlso oldValue <> _value Then
OnScroll(New ScrollEventArgs(scrollType, oldValue, _value, _scrollOrientation))
Invalidate(_rectChannel)
End If
Return isHandled
Return MyBase.ProcessDialogKey(keyData)
End Function
Protected Overrides Sub OnEnabledChanged(e As EventArgs)
MyBase.OnEnabledChanged(e)
If Enabled Then
_thumbState = ScrollBarState.Normal
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
Else
_thumbState = ScrollBarState.Disabled
_topArrowButtonState = ScrollBarArrowButtonState.UpDisabled
_bottomArrowButtonState = ScrollBarArrowButtonState.DownDisabled
End If
Refresh()
End Sub
#End Region
#Region "Overriden Properties"
Protected Overrides ReadOnly Property DefaultSize As Size
Get
Return New Size(SystemInformation.VerticalScrollBarWidth, 200)
End Get
End Property
#End Region
#Region "Private Methods"
Private Sub SetUpScrollBar()
If _isDrawing Then Return
If _barOrientation = ScrollBarOrientation.Vertical Then
_arrowHeight = 18
_arrowWidth = 18
_thumbWidth = 9
_thumbHeight = GetThumbSize()
_rectClickBar = ClientRectangle
_rectClickBar.Inflate(-1, -1)
_rectClickBar.Y += _arrowHeight
_rectClickBar.Height -= _arrowHeight * 2
_rectChannel = _rectClickBar
_rectThumb = New Rectangle(CInt(ClientRectangle.Right / 2) - CInt(_thumbWidth / 2), ClientRectangle.Y + _arrowHeight, _thumbWidth, _thumbHeight)
_rectTopArrow = New Rectangle(CInt(ClientRectangle.Right / 2) - CInt(_arrowWidth / 2) + 1, ClientRectangle.Y + 1, _arrowWidth, _arrowHeight)
_rectBottomArrow = New Rectangle(CInt(ClientRectangle.Right / 2) - CInt(_arrowWidth / 2), ClientRectangle.Bottom - _arrowHeight - 1, _arrowWidth, _arrowHeight)
_thumbPosition = CInt(_rectThumb.Height / 2)
_thumbBottomLimitBottom = ClientRectangle.Bottom - _arrowHeight - 2
_thumbBottomLimitTop = _thumbBottomLimitBottom - _rectThumb.Height
_thumbTopLimit = ClientRectangle.Y + _arrowHeight + 2
Else
_arrowHeight = 18
_arrowWidth = 18
_thumbHeight = 9
_thumbWidth = GetThumbSize()
_rectClickBar = ClientRectangle
_rectClickBar.Inflate(-1, -1)
_rectClickBar.X += _arrowWidth
_rectClickBar.Width -= _arrowWidth * 2
_rectChannel = _rectClickBar
_rectThumb = New Rectangle(ClientRectangle.X + _arrowWidth, CInt(ClientRectangle.Bottom / 2) - CInt(_thumbHeight / 2), _thumbWidth, _thumbHeight)
_rectTopArrow = New Rectangle(ClientRectangle.X + 2, CInt(ClientRectangle.Bottom / 2) - CInt(_arrowHeight / 2), _arrowWidth, _arrowHeight)
_rectBottomArrow = New Rectangle(ClientRectangle.Right - _arrowWidth - 2, CInt(ClientRectangle.Bottom / 2) - CInt(_arrowHeight / 2) + 1, _arrowWidth, _arrowHeight)
_thumbPosition = CInt(_rectThumb.Width / 2)
_thumbBottomLimitBottom = ClientRectangle.Right - _arrowWidth - 3
_thumbBottomLimitTop = _thumbBottomLimitBottom - _rectThumb.Width
_thumbTopLimit = ClientRectangle.X + _arrowWidth + 3
End If
ChangeThumbPosition(GetThumbPosition())
Refresh()
End Sub
Private Sub RefreshScrollBar()
Dim pt As Point = PointToClient(Cursor.Position)
If ClientRectangle.Contains(pt) Then
If _rectThumb.Contains(pt) Then
_thumbState = ScrollBarState.Hot
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
ElseIf _rectTopArrow.Contains(pt) Then
_thumbState = ScrollBarState.Normal
_topArrowButtonState = ScrollBarArrowButtonState.UpActive
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
ElseIf _rectBottomArrow.Contains(pt) Then
_thumbState = ScrollBarState.Normal
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
_bottomArrowButtonState = ScrollBarArrowButtonState.DownActive
Else
_thumbState = ScrollBarState.Normal
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
End If
Else
_thumbState = ScrollBarState.Normal
_topArrowButtonState = ScrollBarArrowButtonState.UpNormal
_bottomArrowButtonState = ScrollBarArrowButtonState.DownNormal
End If
_isTopArrowClicked = False
_isBottomArrowClicked = False
_isTopBarClicked = False
_isBottomBarClicked = False
scrollTimer.[Stop]()
Refresh()
End Sub
Private Function GetScrollValue(isSmallChange As Boolean, isDecreaseValue As Boolean) As Integer
Dim newValue As Integer
If isDecreaseValue Then
newValue = _value - If(isSmallChange, _smallChange, _largeChange)
If newValue < _minimum Then newValue = _minimum
Else
newValue = _value + If(isSmallChange, _smallChange, _largeChange)
If newValue > _maximum Then newValue = _maximum
End If
Return newValue
End Function
Private Function GetThumbPosition() As Integer
Dim pixelRange = If(_barOrientation = ScrollBarOrientation.Vertical, _rectChannel.Height, _rectChannel.Width)
Dim realRange As Integer = _maximum - _minimum
Dim perc As Single = 0F
If realRange <> 0 Then
perc = CSng((_value - _minimum) / realRange)
End If
Return Math.Max(_thumbTopLimit, Math.Min(_thumbBottomLimitTop, Convert.ToInt32(perc * pixelRange)))
End Function
Private Function GetThumbSize() As Integer
Dim trackSize As Integer = If(_barOrientation = ScrollBarOrientation.Vertical, Height, Width)
If _maximum = 0 OrElse _largeChange = 0 Then
Return trackSize
End If
Dim thumbSize As Single = CSng(_largeChange * trackSize / _maximum)
Return Convert.ToInt32(Math.Min(trackSize, Math.Max(thumbSize, 10.0F)))
End Function
Private Sub ChangeThumbPosition(position As Integer)
If _barOrientation = ScrollBarOrientation.Vertical Then
_rectThumb.Y = position
Else
_rectThumb.X = position
End If
Dim pt As Point = PointToClient(Cursor.Position)
If _rectThumb.Contains(pt) Then
_thumbState = ScrollBarState.Hot
Invalidate(_rectThumb)
End If
End Sub
Private Sub ProgressThumb(isContinousScroll As Boolean)
Dim oldValue = _value
Dim type As ScrollEventType = ScrollEventType.First
Dim thumbSize, thumbPos As Integer
If _barOrientation = ScrollBarOrientation.Vertical Then
thumbPos = _rectThumb.Y
thumbSize = _rectThumb.Height
Else
thumbPos = _rectThumb.X
thumbSize = _rectThumb.Width
End If
If _isBottomArrowClicked OrElse (_isBottomBarClicked AndAlso (thumbPos + thumbSize) < _trackPosition) Then
type = If(_isBottomArrowClicked, ScrollEventType.SmallIncrement, ScrollEventType.LargeIncrement)
_value = GetScrollValue(_isBottomArrowClicked, False)
If _value = _maximum Then
ChangeThumbPosition(_thumbBottomLimitTop)
type = ScrollEventType.Last
Else
ChangeThumbPosition(Math.Min(_thumbBottomLimitTop, GetThumbPosition()))
End If
ElseIf _isTopArrowClicked OrElse (_isTopBarClicked AndAlso thumbPos > _trackPosition) Then
type = If(_isTopArrowClicked, ScrollEventType.SmallDecrement, ScrollEventType.LargeDecrement)
_value = GetScrollValue(_isTopArrowClicked, True)
If _value = _minimum Then
ChangeThumbPosition(_thumbTopLimit)
type = ScrollEventType.First
Else
ChangeThumbPosition(Math.Max(_thumbTopLimit, GetThumbPosition()))
End If
ElseIf Not ((_isTopArrowClicked AndAlso thumbPos = _thumbTopLimit) OrElse (_isBottomArrowClicked AndAlso thumbPos = _thumbBottomLimitTop)) Then
RefreshScrollBar()
Return
End If
If oldValue <> _value Then
OnScroll(New ScrollEventArgs(type, oldValue, _value, _scrollOrientation))
Invalidate(_rectChannel)
If isContinousScroll Then StartScrollTimer()
End If
End Sub
#End Region
#Region "Timer Methods"
Private Sub ScrollTimer_Tick(sender As Object, e As EventArgs)
ProgressThumb(True)
End Sub
Private Sub StartScrollTimer()
If Not scrollTimer.Enabled Then
scrollTimer.Interval = 50 'Initial delay
scrollTimer.Start()
Else
scrollTimer.Interval = 10
End If
End Sub
#End Region
#Region "Drawing Methods"
Private Sub DrawBackground(g As Graphics, rect As Rectangle)
If g Is Nothing OrElse rect.IsEmpty OrElse g.IsVisibleClipEmpty OrElse Not g.VisibleClipBounds.IntersectsWith(rect) Then Return
Using sb = New SolidBrush(_backColor)
g.FillRectangle(sb, rect)
End Using
End Sub
Private Sub DrawThumb(g As Graphics, rect As Rectangle, state As ScrollBarState)
If g Is Nothing OrElse rect.IsEmpty OrElse g.IsVisibleClipEmpty OrElse Not g.VisibleClipBounds.IntersectsWith(rect) OrElse state = ScrollBarState.Disabled Then
Return
End If
Dim index = 0
Select Case state
Case ScrollBarState.Hot
index = 1
Case ScrollBarState.Pressed
index = 2
End Select
Using sb As New SolidBrush(_thumbColors(index))
g.FillRectangle(sb, rect)
End Using
End Sub
Private Sub DrawArrowButton(g As Graphics, rect As Rectangle, state As ScrollBarArrowButtonState, isUpArrow As Boolean, orient As ScrollBarOrientation)
If g Is Nothing OrElse rect.IsEmpty OrElse g.IsVisibleClipEmpty OrElse Not g.VisibleClipBounds.IntersectsWith(rect) Then
Return
End If
If orient = ScrollBarOrientation.Vertical Then
DrawVerticalArrowButton(g, rect, state, isUpArrow)
Else
DrawHorizontalArrowButton(g, rect, state, isUpArrow)
End If
End Sub
Private Sub DrawVerticalArrowButton(g As Graphics, rect As Rectangle, state As ScrollBarArrowButtonState, arrowUp As Boolean)
Using img As Image = GetDownArrowButtonImage(state)
If arrowUp Then img.RotateFlip(RotateFlipType.Rotate180FlipNone)
g.DrawImage(img, rect)
End Using
End Sub
Private Sub DrawHorizontalArrowButton(g As Graphics, rect As Rectangle, state As ScrollBarArrowButtonState, arrowUp As Boolean)
Using img As Image = GetDownArrowButtonImage(state)
If arrowUp Then
img.RotateFlip(RotateFlipType.Rotate90FlipNone)
Else
img.RotateFlip(RotateFlipType.Rotate270FlipNone)
End If
g.DrawImage(img, rect)
End Using
End Sub
Private Function GetDownArrowButtonImage(state As ScrollBarArrowButtonState) As Image
Dim rect As New Rectangle(0, 0, _arrowWidth, _arrowHeight)
Dim bitmap As New Bitmap(_arrowWidth, _arrowHeight, PixelFormat.Format32bppArgb)
Dim g As Graphics = Graphics.FromImage(bitmap)
g.SmoothingMode = SmoothingMode.None
g.InterpolationMode = InterpolationMode.HighQualityBicubic
Dim index = 0
Select Case state
Case ScrollBarArrowButtonState.UpHot, ScrollBarArrowButtonState.DownHot
index = 1
Case ScrollBarArrowButtonState.UpActive, ScrollBarArrowButtonState.DownActive
index = 1
Case ScrollBarArrowButtonState.UpPressed, ScrollBarArrowButtonState.DownPressed
index = 2
End Select
Using sb = New SolidBrush(_arrowColors(index))
g.FillPolygon(sb, GetDownArrowPos(rect))
End Using
g.Dispose()
Return bitmap
End Function
Private Shared Function GetDownArrowPos(r As Rectangle) As Point()
Dim middle = New Point(r.Left + CInt(r.Width / 2), r.Top + CInt(r.Height / 2))
Return {New Point(middle.X - 4, middle.Y - 3), New Point(middle.X + 4, middle.Y - 2), New Point(middle.X, middle.Y + 2)}
End Function
#End Region
#Region "Enumerations"
Private Enum ScrollBarArrowButtonState
''' <summary>
''' Indicates the up arrow is in normal state.
''' </summary>
UpNormal
''' <summary>
''' Indicates the up arrow is in hot state.
''' </summary>
UpHot
''' <summary>
''' Indicates the up arrow is in active state.
''' </summary>
UpActive
''' <summary>
''' Indicates the up arrow is in pressed state.
''' </summary>
UpPressed
''' <summary>
''' Indicates the up arrow is in disabled state.
''' </summary>
UpDisabled
''' <summary>
''' Indicates the down arrow is in normal state.
''' </summary>
DownNormal
''' <summary>
''' Indicates the down arrow is in hot state.
''' </summary>
DownHot
''' <summary>
''' Indicates the down arrow is in active state.
''' </summary>
DownActive
''' <summary>
''' Indicates the down arrow is in pressed state.
''' </summary>
DownPressed
''' <summary>
''' Indicates the down arrow is in disabled state.
''' </summary>
DownDisabled
End Enum
Private Enum ScrollBarState
''' <summary>
''' Indicates a normal scrollbar state.
''' </summary>
Normal
''' <summary>
''' Indicates a hot scrollbar state.
''' </summary>
Hot
''' <summary>
''' Indicates an active scrollbar state.
''' </summary>
Active
''' <summary>
''' Indicates a pressed scrollbar state.
''' </summary>
Pressed
''' <summary>
''' Indicates a disabled scrollbar state.
''' </summary>
Disabled
End Enum
#End Region
End Class
End Namespace
'Copyright (c) Smart PC Utilities, Ltd.
'All rights reserved.
#Region "References"
Imports System.ComponentModel
Imports System.Windows.Forms.Design
#End Region
Namespace Controls
Friend Class FlatScrollBarDesigner
Inherits ControlDesigner
#Region "Overridden Properties"
Public Overrides ReadOnly Property SelectionRules As SelectionRules
Get
Dim propDescriptor As PropertyDescriptor = TypeDescriptor.GetProperties(Component)("Orientation")
If propDescriptor IsNot Nothing Then
Dim orientation As ScrollBarOrientation = CType(propDescriptor.GetValue(Component), ScrollBarOrientation)
Return If(orientation = ScrollBarOrientation.Vertical,
SelectionRules.Visible Or SelectionRules.Moveable Or SelectionRules.BottomSizeable Or SelectionRules.TopSizeable,
SelectionRules.Visible Or SelectionRules.Moveable Or SelectionRules.LeftSizeable Or SelectionRules.RightSizeable)
End If
Return MyBase.SelectionRules
End Get
End Property
#End Region
#Region "Overridden Methods"
Protected Overrides Sub PreFilterProperties(properties As IDictionary)
properties.Remove("Text")
properties.Remove("BackgroundImage")
properties.Remove("ForeColor")
properties.Remove("ImeMode")
properties.Remove("Padding")
properties.Remove("BackgroundImageLayout")
properties.Remove("BackColor")
properties.Remove("Font")
properties.Remove("RightToLeft")
MyBase.PreFilterProperties(properties)
End Sub
#End Region
End Class
End Namespace
'Copyright (c) Smart PC Utilities, Ltd.
'All rights reserved.
Public Interface ITheme
#Region "Properties"
''' <summary>
''' Get or set the control style
''' </summary>
Property Theme As UITheme
''' <summary>
''' Get or set whether to allow the control to inherit the parent control style
''' </summary>
Property ParentTheme As Boolean
#End Region
End Interface
'Copyright (c) Smart PC Utilities, Ltd.
'All rights reserved.
#Region "References"
Imports System.Runtime.InteropServices
#End Region
Friend Class NativeMethods
<DllImport("user32.dll")> Friend Shared Function SendMessage(wnd As IntPtr, msg As Integer, param As Boolean, lparam As Integer) As Integer
End Function
End Class
'Copyright (c) Smart PC Utilities, Ltd.
'All rights reserved.
Namespace Controls
Public Enum ScrollBarOrientation
''' <summary>
''' Horizontal ScrollBar
''' </summary>
Horizontal
''' <summary>
''' Vertical ScrollBar
''' </summary>
Vertical
End Enum
End Namespace
'Copyright (c) Smart PC Utilities, Ltd.
'All rights reserved.
Public Enum UITheme As Integer
''' <summary>
''' User defined style
''' </summary>
Custom = -1
''' <summary>
''' Visual Studio 2019 Dark Blue style
''' </summary>
VS2019DarkBlue = 0
''' <summary>
''' Visual Studio 2019 Light Blue style
''' </summary>
VS2019LightBlue = 1
End Enum
@Dragon-0609
Copy link

Dragon-0609 commented Sep 6, 2022

Hi. It's great that you made this control.
But in my project it's buggy.
When I scroll, the colored scrollbar hides, and the system scrollbar is shown. Please, fix it.
I want to use this scrollbar in my project, so I'm looking forward to an improved version.

@ahmedosama007
Copy link
Author

ahmedosama007 commented Sep 7, 2022

Hi. It's great that you made this control. But in my project it's buggy. When I scroll, the colored scrollbar hides, and the system scrollbar is shown. Please, fix it. I want to use this scrollbar in my project, so I'm looking for to an improved version.

@Dragon-0609 The code above only aims to create a standalone ScrollBar control that supports custom themes. If you want to use this control to replace the default Control ScrollBars, see my answer on StackOverflow https://stackoverflow.com/a/73613569/5514131 of how to replace the Panel default ScrollBars with this custom ScrollBar control.

@ahmedosama007
Copy link
Author

  • Improved the Flat ScrollBar control to handle custom ScrollBar width and height.

@Dragon-0609
Copy link

Dragon-0609 commented Sep 7, 2022

Tested the example code that you wrote in StackOverflow. The default scrollbars were still there in the panel. I copied FlatPanel and added it to my form. The system scrollbars are still shown.
System info:
Windows 10
.NET Framework 4.5.2

@ahmedosama007
Copy link
Author

  • Fixed an issue with the Flat ScrollBar control Width property.

@ahmedosama007
Copy link
Author

@Dragon-0609 I created a demo project to showcase the Flat ScrollBar control and how to use it to replace the default Panel ScrollBars.
https://1drv.ms/u/s!AvvS0P5fH-wMjVuEG2uH5bKYcmpK?e=Y81inl
https://1drv.ms/u/s!AvvS0P5fH-wMjVxTld5XO0dMU-Op?e=5bag4n

@Dragon-0609
Copy link

It works! I might have done something wrong.
Thanks. I'll try to use it in my project. If I achieve that, I'll notify you.

@ahmedosama007
Copy link
Author

@Dragon-0609 There is no issue with your code, I have just fixed an issue with the Flat ScrollBar control.

@Dragon-0609
Copy link

Dragon-0609 commented Sep 10, 2022

There is no issue with your code

No, there's an issue with my code.

I'm trying to swap places of VScrollBar(winforms) with FlatScrollBar. I inherited VScrollBar in FlatScrollBar in order to swap places. It works well if I scroll with Mouse Wheels, but if I scroll by holding the left button, windows scroll popups up and hides FlatScrollBar.

Scrolling

@ahmedosama007
Copy link
Author

ahmedosama007 commented Sep 11, 2022

@Dragon-0609 I have improved the FlatPanel control:

  1. Used the WndProc instead of OnPaint to show/update the custom scrollbars, and handled the WM_NCCALCSIZE message to show/hide the custom scrollbars, this will prevent the default scrollbars from appearing.
  2. Used the Parent property instead of the FindForm function to add the custom scrollbar controls, this will improve the positioning of the custom scrollbars.

See the updated FlatPanel control https://gist.github.com/ahmedosama007/39f8b76e65300e5969110b753fe0a654

@Dragon-0609
Copy link

Dragon-0609 commented Sep 11, 2022

@ahmedosama007 I saw the updated code, but it isn't what I'm looking for. I don't use FlatPanel. I use FlatScrollBar.
Here's the VScrollBar that I want to swap places.

I can just swap places via a plugin, not that code itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment