Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Enhanced Scrollbar

0.00/5 (No votes)
2 Oct 2014 1  
WinForms Scrollbar with enhanced properties, graphical bookmarks and value tooltips.

Introduction

In one of my recent projects, I needed to scroll large number of items. To be precise, I wanted to scroll contents of entire physical drive. It is easy to guess that out-of-the-box WinForms ScrollBar with Maximum, Minimum, and Value properties defined as Integer (Int32) is not very useful for this purpose.

In the reality, limitation is even stronger. In design mode: setting Maximum to anything more than 1,000,000,000 and Minimum to anything less than -1,000,000,000 results in design time exception ("Property value is not valid" message). On the other hand, you can set Minimum and Maximum from the code to any valid integer (Int32) value. No exception is thrown. But when expression Maximum - Minimum evaluates to more than int.MaxValue -1, you can observe strange behavior. Value of Maximum suddenly changes to something different than you wanted it to be. Better don't even try to play with Microsoft's ScrollBar and big numbers. For this, you have ScrollBarEnhanced described in this article.

As I said above:

  • I wanted ScrollBar to accept at least long values for Maximum, Minimum and Value properties.
  • I also wanted to be able to bookmark certain ScrollBar values and be able to see bookmarked values on the ScrollBar itself as colored little dots, squares or bars.
  • And finally, I wanted the ability to display dynamic tooltips when mouse moves over bookmark or over not marked track area.

If you remember Vertical ScrollBar in Visual Studio with multicolored graphical decorations, this is what I wanted to achieve.

Background

A quick search of CodeProject revealed a great number of Scrollbar related articles but none of them fully addressed my needs.

In the first attempt to solve my problem, I built a custom control inherited from VScrollBar (I didn't care about the horizontal scrollbar at this time, all I needed was vertical scrollbar). I was able to overwrite Maximum, Minimum, and Value properties to accept long values. But first, it was quite ugly code (translating long values from the wrapper to int values for underlying VScrollBar and vice versa and I still could not show a graphical decoration for bookmarks, and dynamic tooltips were not easy to implement as well.

What inspired me to write my own ScrollBar (I decided to call it ScrollBarEnhanced) was the CodeProject article by How to skin scrollbars for Panels, in C#. This article showed to me that building a ScrollBar from scratch is not so difficult and that writing my own ScrollBar gives me the way for even more customization and more functionality if I ever needed it.

After I decided to code ScrollBarEnhanced from scratch, I quickly upgraded my requirements to use decimal instead of long for Maximum, Minimum, SmallChage, LargeChange and Value properties.

Decimal has 28-29 significant digit precision and an approximate values range of ±1.0 × 10-28 to ±7.9 × 1028. This is much better than long with values in range –9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ("only" 19 significant digits). There is the reason for greater accuracy of decimal, decimal is 128-bit data type while long is 64-bit "only". And the final argument for using decimal was fact that WPF version of ScrollBar does use decimal so why not have similar behavior in WinForms version of it.

To code my own ScrollBarEnhanced implementation, I need to program the following:

  • Drawing of scrollbar's elements (arrows, thumb, trackbar, and grip). It turned out be very easy by using ScrollBarRenderer static methods of ScrollBar control. All drawing should be coded in overwritten OnPaint handler.
  • Drawing of bookmark markers graphical representation also in OnPaint handler. Not difficult as well as I just needed to draw rectangles (for squares and bars) and ovals (for dots).
  • Detecting mouse movements and clicks (in OnMouseMove, OnMouseDown, OnMouseUp, OnMouseLeave and OnMouseClick handlers) and define few enhanced events to pass information back the host.
  • Define Maximum, Minimum. SmallChage, and LargeChage and Value properties as decimal.

Overview

For start, I created in Visual Studio ScrollBarEnhanced custom control inherited from UserControl.

public class ScrollbarEnhanced : UserControl 

This control encapsulates functionality of both Vertical and Horizontal ScrollBar and adds several enhancements to satisfy my needs.

All drawing happens in OnPaint. Below I'm showing simplified version that paints basic ScrollBar. The "real" code has a little bit more lines in order to address different way of painting hot or pressed elements of ScrollBar. I removed them here to show how simple is the basic drawing of ScrollBarEnhanced: just few calls to ScrollBarRenderer draw methods - that's all.

protected override void OnPaint(PaintEventArgs e) 
{ 
   int ArrowHeight = SystemInformation.VerticalScrollBarArrowHeight;
 
   //Draw top arrow
   ScrollBarRenderer.DrawArrowButton(e.Graphics,rec, ScrollBarArrowButtonState.UpNormal);
           
   //Draw top track
   int ThumbPos = Value2ThumbTopPosition(Value);
   rec = new Rectangle(0, ArrowHeight + 1, ClientSize.Width, ThumbPos - ArrowHeight);
   ScrollBarRenderer.DrawUpperVerticalTrack(e.Graphics, rec, ScrollBarState.Normal);
 
   //draw thumb
   int nThumbHeight =ThumbHeight;
   rec = new Rectangle(0, ThumbPos, ClientSize.Width, nThumbHeight);
   ScrollBarRenderer.DrawVerticalThumb(e.Graphics, rec, ScrollBarState.Normal);
 
   //draw thumb grip
   ScrollBarRenderer.DrawVerticalThumbGrip(e.Graphics, rec, ScrollBarState.Normal);
 
   //Draw bottom track
   rec = new Rectangle(0, ThumbPos + nThumbHeight, 
             ClientSize.Width, TrackHeight - (ThumbPos + nThumbHeight));
   ScrollBarRenderer.DrawLowerVerticalTrack(e.Graphics, rec, ScrollBarState.Normal);
 
   //Draw bottom arrow 
   rec = new Rectangle(0, ClientSize.Height - ArrowHeight, ClientSize.Width, ArrowHeight);  
   ScrollBarRenderer.DrawArrowButton(e.Graphics, rec, ScrollBarArrowButtonState.DownNormal);
 
   //Draw  bookmarks (on top of everything)
   if (Bookmarks != null)
   {
      foreach (ScrollBarBookmark bk in Bookmarks)
      {
         DrawBookmark(e.Graphics, bk);
      }
  }
}

DrawBookmark method called from OnPaint is used to display bookmark markers on the ScrollBarEnhanced control. They are three flavors of bookmarks: ImageScrollBarBookmark, BasicShapeScrollBarBookamrk and ValueRangeScrollBarBookmark. All three are derived from Abstract class ScrollBarBoookmark.

  • ImageScrollBarBookmark as the name indicates is a bookmark drawn as a small image. The center of the image is located at the Value property of the bookmark.
  • BasicShapeScrollBarBookmark is a colored rectangle or square drawn over scrollbar body. The center of the shape is located in the Value property of the bookmark.
  • ValueRangeScrollBarBookmark is a variation of BasicShapeScrollBarBookmark. But while former two bookmarks are "pont bookmarks": they are associated with exact value of scrollbar, ValueRangeScrollBarBookmark is bookmark associated with range of values. It has one extra property EndValue, that together with Value defines range. ValueRangeScrollBarBookmark could be used to mark range of elements scrollbar is associated with. For example if you want to use ScrollBarEnhanced to scroll lines of text in text editor, you can use range scrollbar to mark selected text lines. Screenshot from demo programs has a sample of two ValuRangeScrollBarBookmars: one is drawn as right justified orange line and and another one as green (lime), left justified line.
private void DrawBookmark(Graphics Graphics, ScrollBarBookmark Bookmark)
{
    BasicShapeScrollBarBookmark shapeBookmark = null;
    ImageScrollBarBookmark imageBookmark=null;
    if (Bookmark is BasicShapeScrollBarBookmark)
        shapeBookmark = (BasicShapeScrollBarBookmark)Bookmark;
    else
        imageBookmark = (ImageScrollBarBookmark)Bookmark;

    //Make sure that brush needed for drawing is ready to use
    if (shapeBookmark != null)
    {
        if (shapeBookmark.Brush == null)  //this bookmark is used to the first time
        {
            if (!m_BrushesCache.ContainsKey
            (shapeBookmark.Color))  //this color is used for the first time
                m_BrushesCache.Add(shapeBookmark.Color, new SolidBrush(shapeBookmark.Color));

            shapeBookmark.Brush = m_BrushesCache[shapeBookmark.Color];
        }
        //Make sure that pen needed for drawing is ready to use
        if (shapeBookmark.Pen == null)  //this bookmark is used to the first time
        {
            if (!m_PensCache.ContainsKey
            (shapeBookmark.Color))  //this color is used for the first time
            {
                m_PensCache.Add(shapeBookmark.Color, new Pen(shapeBookmark.Color));
            }
            shapeBookmark.Pen = m_PensCache[shapeBookmark.Color];
        }

        if (shapeBookmark.Stretch)
        {
            Bookmark.X = 0;
            shapeBookmark.Width = this.ClientSize.Width;
        }
        else
        {
            CalculateBookmarkXPosition(Bookmark);
        }
    }
    else  //Calculate X for ImageBookmark
    {
        CalculateBookmarkXPosition(Bookmark);
    }

    //Calculate top Y position of bookmark 
    Bookmark.Y = (int)(TrackHeight * Bookmark.Value / Maximum + 
      SystemInformation.VerticalScrollBarArrowHeight - Bookmark.Height / 2);

    if (imageBookmark != null)
    {
        Graphics.DrawImage(imageBookmark.Image, new Point(Bookmark.X, Bookmark.Y)); 
    }
    else  if (shapeBookmark.FillBookmarkShape)
    {
        if (shapeBookmark.Shape == ScrollbarBookmarkShape.Oval)
            Graphics.FillEllipse(shapeBookmark.Brush, new Rectangle(Bookmark.X, 
               Bookmark.Y, shapeBookmark.Width, shapeBookmark.Height));
        else
            Graphics.FillRectangle(shapeBookmark.Brush, new Rectangle(Bookmark.X, 
               Bookmark.Y, shapeBookmark.Width, shapeBookmark.Height));
    }
    else  
    {
        if (shapeBookmark.Shape == ScrollbarBookmarkShape.Oval)
            Graphics.DrawEllipse(shapeBookmark.Pen, new Rectangle(Bookmark.X, 
              Bookmark.X, shapeBookmark.Width, shapeBookmark.Height));
        else
            Graphics.DrawRectangle(shapeBookmark.Pen, new Rectangle(Bookmark.X, 
              Bookmark.X, shapeBookmark.Width, shapeBookmark.Height));
    }
}
private void CalculateBookmarkXPosition(ScrollBarBookmark Bookmark)
{
    switch (Bookmark.Alignment)
    {
        case ScrollBarBookmarkAlignment.Left:
            Bookmark.X = 0;
            break;
        case ScrollBarBookmarkAlignment.Right:
            Bookmark.X = this.ClientSize.Width - Bookmark.Width;
            break;
        case ScrollBarBookmarkAlignment.Center:
            Bookmark.X = (this.ClientSize.Width - Bookmark.Width) / 2;
            break;
    }
}

ScrollBarBookmark class mentioned above requires a little bit more attention. You will be using only if you want to display bookmarks on your scrollbar. ScrollBarBookmark is an abstract class that contains basic bookmark properties defined as follows:

public abstract class ScrollBarBookmark
{
    [DefaultValue(0)]
    public decimal Value { set; get; }

    [DefaultValue(ScrollBarBookmarkAlignment.Left)]
    public ScrollBarBookmarkAlignment Alignment { set; get; }

    //e.g. Bookmark object from host can contain any sort of info
    //can be handy when bookmark is clicked; moved over etc.
    [DefaultValue(null)]
    public object Tag { set; get; }

    public virtual int Height { set; get; }
    public abstract int Width { set; get; }

    //Every time bookmark is repainted the following two values are 
    //Populated with X, Y of bookmark center
    protected internal int X { set; get; }
    protected internal int Y { set; get; }
}  

"Real" bookmarks is stored in instance of one of three classes inherited from ScrollBarBookmark:

  • ImageScrollBarBookmark - This class allows to define bookmark with Image as its graphical representation
  • BasicShapeScrollBarBookmark - This class allows to define bookmark as basic shapes (rectangles or ovals)
  • ValueRangeScrollBarBookmark - Technically this class id derived from BasicShapeScrollBarBookmark and it has only one extra property defined: EndRange. It allows to draw bookmark not as a point at provided value but as a range. Typically you will use rectangle shape for this purpose.

ImageScrollBarBookmark is defined below:

public class ImageScrollBarBookmark : ScrollBarBookmark
{
    public ImageScrollBarBookmark()
    {
        this.Image = null;
        this.Value = 0;
        this.Alignment = ScrollBarBookmarkAlignment.Left;
        this.Tag = null;
    }
    public ImageScrollBarBookmark(decimal Value, Image Image, 
            ScrollBarBookmarkAlignment Alignment, object Tag)
    {
        this.Image = Image;
        this.Value = Value;
        this.Alignment = Alignment;
        this.Tag = Tag;
    }
    public Image Image { set; get; }
    public override int Width 
    {
        get { return Image.Width; }
        set {  ;}
    }
    public override  int Height
    {
         get { return Image.Height; }
        set {;}
    }
} 

And here goes BasicShapeScrollBarBookmark:

public BasicShapeScrollBarBookmark()
{
    Value = 0;
    Alignment = ScrollBarBookmarkAlignment.Left;
    Height = 5;
    Width = 5;
    Shape = ScrollbarBookmarkShape.Rectangle;
    Color = Color.Orange;
    FillBookmarkShape = true;
    Stretch = false;
    Tag = null;
}

public BasicShapeScrollBarBookmark(decimal ScrollValue, 
       ScrollBarBookmarkAlignment Alignment, int Height, 
       int Width, bool Stretch, object Tag)
{
    this.Value = ScrollValue;
    this.Alignment = Alignment;
    this.Height = Height;
    this.Width = Width;
    this.Stretch = Stretch;  //Stretch to smallest scrollbar dimension
    this.Tag = Tag;
    this.Shape = ScrollbarBookmarkShape.Rectangle;
    this.Color = Color.Empty;
    this.FillBookmarkShape = true;
}

public BasicShapeScrollBarBookmark(decimal ScrollValue, 
       ScrollBarBookmarkAlignment Position, int Height, int Width, 
       ScrollbarBookmarkShape Shape, Color Color, bool FillBookmarkShape, 
       bool StretchToScrollBarWidth, object Tag)
{
    this.Value = ScrollValue;
    this.Alignment = Position;
    this.Height = Height;
    this.Width = Width;
    this.Shape = Shape;
    this.Color = Color;
    this.FillBookmarkShape = FillBookmarkShape;
    this.Stretch = StretchToScrollBarWidth;
    this.Tag = Tag;
}

int m_Width = 0;
[DefaultValue(4)]
public override int Width
{ 
     set { m_Width = value; } 
     get { return m_Width; } 
}

int m_Height = 0;
[DefaultValue(4)]
public override int Height 
{ 
    set { m_Height = value; }
    get { return m_Height; } 
}

[DefaultValue(false)]
public bool Stretch { set; get; }

[DefaultValue(ScrollbarBookmarkShape.Rectangle)]
public ScrollbarBookmarkShape Shape { set; get; }

[DefaultValue(true)]
public bool FillBookmarkShape { set; get; }

private Color m_Color = Color.Empty;
[DefaultValue(typeof(Color), "Orange")]
public Color Color 
{
    set
    {
        if (m_Color != value)
        {
            m_Color = value;
            Pen = null;
            Brush = null;
        }
    }
    get { return m_Color; }
}
 
[NonSerialized]
private Pen m_Pen;
protected internal Pen Pen 
{
    set { m_Pen = value; }
    get { return m_Pen; }
}

[NonSerialized]
private Brush m_Brush;
protected internal Brush Brush
{
    set { m_Brush = value; }
    get { return m_Brush;  }
}

ValueRangeScrollBarBookmark adds one extra property to BasicShapeScrollBarbookmark: EndValue.

public class ValueRangeScrollBarBookmark : BasicShapeScrollBarBookmark
{

    public ValueRangeScrollBarBookmark()
    {
        Name = "";
        Value = 0;
        EndValue = 0;
        Alignment = ScrollBarBookmarkAlignment.Left;
        Height = 5;
        Width = 5;
        Shape = ScrollbarBookmarkShape.Rectangle;
        Color = Color.Orange;
        FillBookmarkShape = true;
        Stretch = false;
        Tag = null;
    }



    public ValueRangeScrollBarBookmark(string Name, decimal StartValue, 
        decimal EndValue, ScrollBarBookmarkAlignment Alignment, int Depth, 
        Color Color, bool FillBookmarkShape, bool StretchToScrollBarWidth, object Tag):
        base(Name, StartValue, Alignment, 0, Depth, ScrollbarBookmarkShape.Rectangle, 
        Color, FillBookmarkShape, StretchToScrollBarWidth, Tag)
    {
        this.EndValue = EndValue;
    }

    public decimal EndValue { set; get; }

    int m_Width = 0;
    [DefaultValue(4)]
    [Description("ValueRangeScrollBarBookmark Height (for vertical) 
      or Width (for horizontal) are calculated every time scrollbar is repainted. 
      Value will change every time Minimum, Maximum or scrollbar size change.")]
     public override int Width
    {
        set { m_Width = value; }
        get { return m_Width; }
    }

    int m_Height = 0;
    [DefaultValue(4)]
    [Description("ValueRangeScrollBarBookmark Height (for vertical) 
      or Width (for horizontal) are calculated every time scrollbar is repainted.
      Value will change every time Minimum, Maximum or scrollbar size chage.")]
    public override int Height
    {
        set { m_Height = value; }
        get { return m_Height; }
    }
}

When ValueRangeScrollBookmark is drawn in OnPaint method its length (height for Vertical or width for horizontal scrollbar) is calculated:

int bookmarkLength =(Bookmark.EndValue - Bookmark.Value) / (Maximum - Minimum)) * TrackLength);

And finally goes definition of enums used in above code:

public enum ScrollbarBookmarkShape
{
    Rectangle,
    Oval,
}
public enum ScrollBarBookmarkAlignment
{
    Left,
    Right,
    Center
}

Typically, you would assign bookmarks from the code. For example, user of your program clicks something on the page with attached scrollbar and wants to mark this position for quick reference.

Here is the short description of BasicShapeScrollBarBookmark properties

  • Value - Value property of ScrollBarEnhanced corresponding to the center of bookmark graphical representation.
  • Height -Height of the bookmark graphical element; default value is 5 pixels.
  • Width - Width of the bookmark graphical element; default value is 5 pixels.
  • Stretch - If this property is set to true, Width and Position properties are disregarded and bookmark is shown as a rectangle or oval (depending on Shape property value) with Width equal to the width of the scrollbar control instance.
  • Shape - Bookmark shape enumerator: can be Rectangle or Oval
  • FillBookmarkShape - If set to true, bookmark body is filled with the same color as the bookmark border, otherwise only border line of bookmark is displayed. Since bookmark markers are usually small, most likely you would like to keep this property set to true to make marker more visible.
  • Color - Bookmark color; color used to draw bookmark border and the body.
  • Alignment - Bookmark position enumerator. Can be Left, Center, or Right
  • Tag - Anything can go here. It can be useful if you want to assign more information to the bookmark object than list of predefined properties can offer. ScrollBarEnhanced doesn't make any use if it, but since some enhanced events return original bookmark object to the host (for example, list of bookmarks mouse is moving over), host can find Tag values handy in some situations.

ImageScrollBarBookmark has a little bit different set of properties:

  • Value - Value property of ScrollBarEnhanced corresponding to the center of bookmark graphical representation.
  • Image - Graphical element to be displayed as bookmark.
  • Height - Height of the bookmark graphical element (height of the Image). This is read-only property.
  • Width - Width of the bookmark graphical element (width of the Image). This is read-only property.
  • Alignment - Bookmark position enumerator. Can be Left, Center, or Right
  • Tag - Anything can go here. It can be useful if you want to assign more information to the bookmark object than list of predefined properties can offer. ScrollBarEnhanced doesn't make any use if it, but since some enhanced events return original bookmark object to the host (for example list of bookmarks mouse is moving over), host can find Tag values handy in some situations.

ValueRangeScrollBarBookmark has identical properties as BasicShapeScrollBarBookmark plus one extra:

  • EndValue - Together with Value it defines range of values and effectively the size of shape used to draw the bookmark.

Besides OnPaint few more methods play key role in the code. Below they are briefly mentioned. If you want more details please view ScrollBarEnhanced source code attached to this article.

  • OnMouseDown - saves current mouse position. Fires proper OnScroll events. If mouse down was on up or down arrow, decrements or increments Value by SmallChange amount. If mouse was down on upper or lower track area, decrements or increments Value by LargeChage.
  • OnMouseMove - If mouse is down and over the thumb, performs thumb dragging. Otherwise fires MouseMove event with extended list values passed as an argument.
  • OnMouseUp -Fires Scroll event with extended list values passed as an argument.
  • OnMouseClick - Fires MouseClick event with extended list values passed as an argument.
  • OnMouseLeave - Cleanups after other mouse actions.

Using the Code

You can use ScrollBarEnhanced control exactly same way you are using regular WinForms VScrollBar or HScrollBar. ScrollBarEnhanced has an Orientation property that makes scrollbar to behave like vertical or horizontal scrollbar appropriately. You just have to drop it on desired Form, set up needed properties, hook up events you want to intercept and you are ready to go.

Enhanced Properties You Can Use Are:

  • Bookmarks - Collection of ScrolBarBookmark objects. Each element defines where and how to display bookmark graphical marker. You can define value, location, size, color and shape.
  • BookmarksOnTop - This property defines if bookmark are drawn as the topmost items (when set to true). This means that scrollbar thumb might be partially cover with bookmark. If this property is set to false, topmost item is scrollbar thumb.
  • EndValue - Together with Value property it defines range for which scrollbar is defined. EndValue is defined of ValueRangeScrollBarBookmark only.
  • ContextMenuStrip - This property is inherited from, the base control but unlike context menu of standard VScrollBar and HSrollBar is fully customizable. You can intercept Opening events and modify context menu before display or simply replace context menu with your own.
  • InitialDelay -Standard VScrollBar has one cool feature I wanted to reproduce. When you keep the left mouse button in down position it repeats click action without you actually clicking. This behavior starts after holding mouse down for interval defined in milliseconds in InitialDelay property. Value of InitialDelay is usually bigger than RepeatInterval. Default value is 400 milliseconds.
  • Orientation - enumerator defining if ScrollBarEnhanced behaves like vertical or horizontal scrollbar.
  • QuickToolbarNavigtion - if set to true, allows quick navigation to bookmarked values by clicking bookmark graphical representation
  • RepeatInterval- After auto-repeat behavior is triggered by holding mouse down for InitialDelay milliseconds, all subsequent auto-generated clicks occur in intervals defined in RepeatInerval. Default value is 62 milliseconds
  • ShowTooltipOnMouseMove - If this property set to true, every time when user is moving mouse over scrolbar track, tooltip will be displayed. By default tooltip text shows value mouse is passing over. Tolltip text can be overwritten by host code by handling TooltipNeeded event by changing ToolTip property of event arguments object passed to the event handler.
  • Value, Minimum, Maximum, SmallChage, LargeChage - they have the same meaning as in standard VScrollBar or HScrollBar control but now all have type of decimal.

Enhanced Events:

There three new events defined in ScrollBarEnhanced that are not present is standard VScrollBar and HScrollBar:

  • OrienationChanged - fires every time Orientation property of ScrollBarEnhanced changes.
  • OrientationChanging - fires every time value of Orientation property is about to change. This is cancellable event.
  • ToolTipNeeded - I wanted to be able to capture all situations when scrollbar needs to display tooltip, and be able to either use default tooltip value or change it on the fly.

Here is a short description of all events defined in ScrollBarEnhanced:

  • MouseClick - This event occurs every time mouse is clicked in the scrollbar trackbar area. This event is identical to MouseClick of VScrollBar or HScrollBar.
  • MouseMove - This event occurs every time mouse moves over ScrollBarEnhanced control . See EnhancedMouseEventArgs for detailed information about argument passed to the event handler.
  • OrientationChanging - This event occurs every tine orientation of ScrollBarEnhanced is about to change. User has the opportunity to cancel change of Orientation property value.
  • OrientaionChange - This notification happens every time after orientation property changes.
  • TooltipNeeded - This event is fired every time mouse moves over trackbar area or over bookmark marker (in order for this event to be fired, ShowToooltipOnMouseMove property has to be set to true). By handling this event, you can modify default tooltip value. See ToolTipNeededEventArgs for detailed information about argument passed to the event handler.
  • Scroll - Mimics Scroll event of standard VScrollBar and HScrollBar. See EnhancedScrollEventArgs for detailed information about argument passed to the event handler.
  • ValueChanged - Occurs every time scrollbar value changes. This event is identical to standard VScrollBar or HScrollBar ValueChanged event.

Enhanced Event Arguments

EnhancedMouseEventArgs - This object is used to pass information to MouseMove and MouseClick event handlers. Please take a look at Bookmarks property. It is collection. Reason for this is the fact that one single vertical pixel of ScrollBarEnhanced can be mapped to multiple bookmarks. First, nothing stops you from defining multiple Boookmarks for the same ScrollBarEnhanced value. Second, even if you would be protected from assigning multiple bookmarks for the same value, you still might end up with multiple bookmarks displayed over same ScrollBar pixel. Bookmarks on the list are displayed in the same order as they occur on the list of Bookmarks property. Last element is drawn last so on the display it appears to be on the top. Let's consider the following (a little bit extreme) example. You want to scroll through the entire content of physical drive of 1Tb (Terabyte) size (~1012 bytes). Let's assume that you are going to show 100 bytes in one line of display. This means that you need to set ScrollbarEnhanced Maximum to 1012/100=1010) - that how many lines you want to scroll through. Finally, assume that your ScrollBarEnhanced control is displayed with 1000 vertical pixels on the track bar. That means that you have to map <0, 1010> ScrollBarEnhanced address space to <0, 1000> pixels of display. Therefore, 1 pixel will be mapped to 1010/1000= 107 (10 Millions) values. So even if you set one bookmark at 0 value and another one at 5,000,000, both bookmarks will be displayed at the same point on the screen.

EnhancedScrollEventArgs - This definition is almost identical to ScrollEventArs used in VScrollBar. Only difference is that OldValue and NewValue are decimal not int.

public class EnhancedMouseEventArgs:MouseEventArgs
{
    public EnhancedMouseEventArgs(decimal Value, MouseEventArgs MouseArgs,
           List<ScrollBarBookmark> Bookmarks,
           EnhancedScrollBarMouseLocation ScrollBarSection )
           :base(MouseArgs.Button, MouseArgs.Clicks, MouseArgs.X, MouseArgs.Y,
           MouseArgs.Delta)
    {
        this.Value = Value;
        this.Bookmarks = Bookmarks;
        this.ScrollBarSection = ScrollBarSection;
    }
    public decimal Value {private set; get;}
    public List<ScrollBarBookmark> Bookmarks {private set; get;}
    public EnhancedScrollBarMouseLocation ScrollBarSection { private set; get; }
}

ToolTipNeededEventArgs - used for ToolTipNeeded event. It passes extended information about point of the ScrollBarEnhanced that needs tooltip. ToolTipNeededEventArgs defines the following properties:

  • Value - ScrollBarEnhanced Value property that requires tooltip text.
  • ToolTip - Default tooltip value ScrollBarEnhanced wants to display. This value can be changed.
  • Bookmarks - List of bookmarks mouse is hovering over. Last bookmark on the list represents topmost bookmark on the screen, by having this list available, you can display tooltip based on bookmark context.
public class TooltipNeededEventArgs : EventArgs
{
    public TooltipNeededEventArgs(decimal Value, string ToolTip,
    List<ScrollBarBookmark> Bookmarks)
    {
        this.Value=Value;
        this.ToolTip=ToolTip;
        this.Bookmarks = Bookmarks;
    }
    public decimal Value { private set; get; }
    public string ToolTip {set; get;}
    public List<ScrollBarBookmark> Bookmarks { set; get; }
}
public decimal OldValue {private set; get;}
public decimal NewValue {private set; get;}
    public ScrollOrientation ScrollOrientation {private set; get;}
    public ScrollEventType Type {private set; get;}
}

Notes

  1. I decided to "fix" default VScrollBar and HScrollBar behavior, that limits Value to range <Minimum, Maximum-LargeChange +1>. Microsoft's design assumes that LargeChange is equal to the size of the page you are scrolling, so when Value is equal Maximum - LargeChange +1 you still should be able to see all end elements of the page you are scrolling. Perhaps it makes sense, but I prefer simpler interpretation of Maximum and no relation of Maximum to the page size. ScrollBarEnhanced can be scrolled from Minimum to Maximum (inclusive). If you prefer default ScrollBar behavior as conceived by Microsoft engineers, you can always modify source code to support your needs.
  2. Minimum and Maximum are both defined as decimal values. So theoretically, they both should accept any decimal value. While the former statement is essentially true, there is one extra constraint on Maximum and Minimum. Value of Maximum - Minimum cannot be greater than decimal.MaxValue. For example:
//Following are valid assignments for Minimum and Maximum
Minimum = 0;
Maximum  = decimal.MaxValue;
//Following is valid too
Minimum = deximal.MinValue/2+1;
Maximum = decimal.MaxValue/2;
//The following will throw OverflowException (because decimal.MaxValue+1>decimal.MaxValue)
Minimum = -1;
Maximum= decimal.MaxValue; 

Demo Program

The downloadable demo program illustrates how to use ScrollBarEnhanced control in your code. It doesn't require much explanation. From the program GUI, you can modify all properties of instance of ScrollBarEnhanced control (scrollbar is displayed on the central part of the form). All added and modified properties are grouped in Enhanced category. I did it only to make the demo program easier to navigate since all enhanced properties will be displayed next to each other on the property grid. You can of course change property definition and change category attribute to more logical value. In fact, they all should belong to Behavior category. GUI also captures and displays all enhanced events fired by the instance of ScrollBarEnhanced instance.

Downloadable zip file contains also help file (chm). Please note, that since you are downloading this file from the Internet, in order to use it you have to unblock it first. You can do it from Windows explorer: right_click on the help file, select 'Properties', click 'Unblock' button.

History

July 31, 2013 - Initial version of the article.

August 1, 2013 - Fixed source code bug: modification of Bookmark's Color property didn't work due to Brush and Pen caching.

August 3, 2013 - A few changes:

  • Removed BackgroundColor property. It was mistake to have it in the first place.
  • Added new bookmark type ImageScrollBarBookmark - new bookmark type that allows to display tiny images (icons, bitmaps) as the bookmark graphical representation.
  • Added QuickBookmarkNavigation property. It allows to instantaneous navigation to the bookmark value when bookmark is clicked.

August 6, 2013 - Bug fixes and functional enhancements:

  • Bug fix: divide by zero exception when Maximum == LargeChange was fixed
  • Added Orientation property. This property enhances scope of entire article. Initially it was about Vertical ScrollBar enhancements, now it covers both Vertical and Horizontal ScrollBar functionality.

August 7, 2013 - Source code correction to fix the issue of incorrect drawing when Minimal value is different than zero. I also changed meaning of Value argument passed to OnMouseClick event. Previously it contained current Value of ScrolBarEnhanced instance. Now it is HotValue (value under mouse position).

August 9, 2013 - Few more additions and changes:

  • Added customizable context menu strip similar to context menu of VScrollBar and HScrollBar.
  • Added support for mouse wheel movement
  • Added BookmarksOnTop Boolean property to control order scrollbar elements are painted.
  • Changed EnhancedMouseEventArgs to inherit from MouseArgs, to pass more detailed information to the event handler.

August 12, 2013 - Added ValueRangeScrollBarBookmark that allows to create bookmarks based on range of values.

September 17th, 2013 - Changes in downloadable file: added help (chm); source code changes: added comments, corrected Dispose override.

December 12th, 2013 - Code review and cleanup. Adding bookmarks property editor for design mode. 

October 2nd, 2014 - Bug fix;  Argument of Scroll event :EnhancedScrollEventArgs was showning oldValue instedad of new value and vice versa. The bug was found by  altiealtie thank you for showing it to me. Source code was updated.

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here