Click here to Skip to main content
15,748,270 members
Articles / Programming Languages / C# 7.0
Posted 17 May 2017


10 bookmarked

General Purpose MultiSlider

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
26 May 2017CPOL7 min read
Comprehensive Multi-Slider(-Range) that can add, delete and move arrows (thumbs)


(Visual Studio 2017)
Note: Only minor changes would be needed to convert for earlier versions.


As its name suggests, MultiSlider is a UserControl capable of displaying more than one arrow in a track bar. I have kept most of the behaviour and appearance of the stock Slider control and also added minor enhancements.

  • New arrows may be added (optional) by the user by double-clicking on the trackbar.
  • Arrows may be removed (optional) by the user by Alt+Right-clicking on them.
  • Arrows have an IsMoveable property; if it is false then they can neither be moved nor deleted (visually denoted by a colored adorner circle on the arrow).
  • Arrows have an IsUser property; if it is true then the brushes used for coloring the plain arrow will be different. The user arrow and the standard arrow are otherwise treated the same.
  • AutoToolTipPlacement is catered for.


When tabbing through the main window's controls using the Tab key or Modifier+Cursor:

  1. If an arrow is selected, Shift+Cursor may be used to tab over any adjacent arrows.
  2. The Tab key will NOT tab over any adjacent arrows, but only over the Controls.
  3. When a MultiSlider control is tabbed to, depressing the Space key will activate the prior selected arrow.
  4. Control+Cursor acts as per normal when tabbing over controls.

There are a few multi-slider tools out there:


    This is dated 2013 and uses Windows 7 arrows, so is out of date for W10 style. Limited functionality although can add new arrows.


    This is even older (2007).


    This looks as if it is for W7. I haven't investigated this one.


    This is fixed at just 2 arrows so is not as flexible as desired, but looks quite good for what it is.

None of them are comprehensive enough for my use so I developed my own:

  • Event handling for the programmer is comprehensively catered for (see specs below).
  • I haven't included 'tick' graphics and capabilities (may be for a future date) but there is AutoToolTip capability.


There are two projects in the solution that will demonstrate the MultiSlider in action. The first, TestSimple, merely displays all the arrow types and use of overlay coloring with basic functionality. The second, TestMultiSlider, is more thorough, with:

  • Facilities for the user to manipulate arrows in all ways
  • Switches to change the states of the multi-sliders
  • A vertical and a horizontal oriented multi-slider
  • Readout of user actions taken in a TextBlock

Image 1


  • AdornerColor - (get/set) - The color to use for the adorning circle of an arrow. If null is passed, the color is reset.
  • AdornerRatio - (get/set) - Set/get the ratio of the adorner circle's diameter relative to the Up arrow width. If null is passed, the ratio is reset.
  • ArrowAtIndex(int index) - Returns the ArrowData at position 'index' (base=0 starting from the Minimum end).
  • ArrowCount - (get) - The number of arrows in the trackbar.
  • ArrowType - (get/set) - MultiSlider.ArrowTypes enum. Values: LeftRight, UpDown or Rect.
  • AutoToolTipPlacement - (get/set) - Where to place the auto tool tip relative to the arrow.
  • AutoToolTipPrecision - (get/set) - Prints the arrow's Value to N decimal places.
  • AutoToolTipSigFigs - (get/set) - Prints the arrow's Value in N significant figures if N > 0 and takes precedence over AutoToolTipPrecision.
  • CanDelAddArrows - (get/set) - Whether the user can Add/Delete arrows with the mouse.
  • HelpText - (get) - A string containing basic formatted text giving the user instructions. Suitable for adding to and displaying in a MessageBox.
  • Minimum - (get/set) - Minimum value of an arrow in the trackbar. The setter deletes all existing arrows and the ArrowDeleted event is not fired.
  • Maximum - (get/set) - Maximum value of an arrow in the trackbar. The setter deletes all existing arrows and the ArrowDeleted event is not fired.
  • Orientation - (get/set) - Specifies either a vertical or horizontal axis for the trackbar.
  • ReverseDirection - (get/set) - Whether Maximum is at the maximum or minimum end of the trackbar (vice versa for Minimum).
  • SchemeOverlayColor - (get,set) - Overlay color to use on top of the trackbar and plain arrows (suggested alpha=10%).
  • SmallChange - (get/set) - The small incremental Value to add/subtract to/from the current arrow's value when using the keyboard cursor keys to move with.
  • LargeChange - (get/set) - The large incremental Value to add/subtract to/from the current arrow's value when using the keyboard cursor keys (+Alt key) to move with.
  • Value - (get/set) - The value for the first arrow on the trackbar. If the trackbar is empty, you get DoubleNaN; or the trackbar is ignored for set. This is useful for a non-deletable single-arrow slider.


  • CreateArrow(double value, bool isUser = false, bool isMoveable = true) - Create and show a new arrow. Returns an ArrowData.
    • "value">A value within Minimum and Maximum properties of the TrackBar.
    • "isUser">true: Use the arrow's User state brushes.
    • "isMoveable">false: The arrow can neither be moved nor deleted.
  • DeleteArrow(int index, bool fireEvent) - Removes the arrow at 'index' (from the trackbar canvas also. base=0 starting from the Minimum end). If 'fireEvent' = true, then the ArrowDeleted event is fired.
  • DeleteAllArrows(bool fireEvent) - Removes all arrows from the trackbar canvas. If 'fireEvent' = true, then the ArrowDeleted event is fired.
  • IsValidValue(value) - Whether the arg falls within the multi slider's Minimum/Maximum values.
  • ValueAtIndex(int index) - Return the Value at the valid arrow index (base 0 starting from the Minimum end) on the trackbar.

For the arrows (type ArrowData sent by the event handlers):

  • Index - (get) - Base 0. The nth arrow on the trackbar starting from the Minimum end.
  • IsMoveable - (get/set) - Whether an arrow can be moved/deleted. May be done on the fly, example in the event handlers below.
  • IsUser - (get/set) - Only the brushes to use when setting the 'plain' arrow brushes are different. May be done on the fly, for example, in the event handlers below.
  • Value - (get/set) - Value between {Minimum to Maximum}.


  • ArrowCreated
  • ArrowRightClicked - If the Alt key is used for deleting an arrow, this will not be fired
  • ArrowScrolled
  • ArrowDeleted - ArrowSelected will fire also after this if the focus changes to an adjacent arrow
  • ArrowLeftClicked
  • ArrowSelected - Fired when an arrow receives keyboard focus (e.g. is clicked on or tabbed to). All pass their sender arg as an (object)ArrowData

Implementation Notes

If Minimum==Maximum => Behaviour is a little odd but acceptable.

There is a Conditional Compilation Symbol, MULTISLIDER, in the MultiSlider properties window; change this to prevent Console output when running the Debug version outside of the Debugger.

Construction of the MultiSlider Control

The control firstly has a Canvas representing the trackbar (as a Border) and its size is set to that of the user control. To this is first added a trackbar Border control. This Border's size is set relative to the width or height property of the MultiSlider. Then composite arrows, having been created and initialized, are added to the main trackbar Canvas.

An arrow consists of a Canvas which is added to the main trackbar; to this Canvas are added firstly an initialized Polygon describing the arrow, then an Ellipse (which acts as an adorner) and lastly a Label (which displays the AutoToolTip text). Whenever the arrow's visual position needs to be changed, it is the Canvas that is placed relative to the main trackbar Canvas; thus the Polygon's points are never accessed or changed.

Arrow Keyboard Focus using Shift+Cursor_key

Using Shift+Cursor key for tabbing over the arrows inside the control is achieved in method MultiSlider.Arrow_KeyDown().

Arrow Moving by Holding Mouse Down on the Trackbar

This is all done in the trackbar MouseLeftButtonDown/Up handlers. Inside TrackBar_MouseLeftButtonDown(). The predicate if(Mouse.LeftButton == MouseButtonState.Released) can't be used inside a loop because the reported Mouse button state remains unaltered when the mouse-button is released. The only technique that works is the following:

  • Deploy an iVar flag _mouseUp.
  • In TrackBar_MouseLeftButtonDown() set _mouseUp to false.
  • Write event handler, TrackBar_MouseLeftButtonUp() - sets _mouseUp to true.

Processing continuous holding mouse button down:

Write a BackgroundWorker.DoWork event handler, ProcessHoldingMouseDown(), with args set to the desired types to be passed by TrackBar_MouseLeftButtonDown(); this will do the mouse-button state detection and perform the arrow-moving operations.

In TrackBar_MouseLeftButtonDown() write the following code where the arrow moving is to be done:

BackgroundWorker threadBW = new BackgroundWorker();
threadBW.DoWork += (obj, e2) => ProcessHoldingMouseDown(mouseCoords, arrow);

And in ProcessHoldingMouseDown() the GUI main thread invoker is deployed several times, e.g., Dispatcher.Invoke(() => TrackBar.InitBounds(arrow));

Schematic Wiring

The behaviour of the MultiSlider requires quite intricate 'tuning' of the internal event handlers. Modify them at your own peril!

A Shape.Polygon

RenderBounds() doesn't take into account any Transforms (e.g., Scaling, Rotating) on the shape. Just includes the StrokeThickness, etc., so when the arrow’s Polygon is rendered, it exactly fits its containing Canvas.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Written By
CEO Future Developments Ltd
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

QuestionFeature Request Pin
Member 138635657-Aug-19 16:36
Member 138635657-Aug-19 16:36 
NewsUPDATE: not VS2015update3 compatible. Pin
John Trinder26-May-17 0:51
professionalJohn Trinder26-May-17 0:51 
QuestionArrowCursor issues in VS2015 Community (yes, Update 3) Pin
Joe Pizzi22-May-17 17:56
Joe Pizzi22-May-17 17:56 
I opened this in VS 2015 Community, Update 3, and get over 200 errors. Most of them are in ArrowCursor.cs. Did you by chance program this using C# 7 features, so it cannot be used in VS 2015?

On line 71, it looks like you are creating a Tuple, but that syntax doesn't seem to be valid in VS 2015. The first error message is
CS1519, Invalid token '(' in class, struct, or interface member declaration.

Then, there are messages for the comma, the close paren, and the equals.
Joe Pizzi

SuggestionRe: ArrowCursor issues in VS2015 Community (yes, Update 3) Pin
John Trinder26-May-17 0:23
professionalJohn Trinder26-May-17 0:23 
GeneralMy vote of 5 Pin
BillWoodruff21-May-17 13:07
professionalBillWoodruff21-May-17 13:07 
NewsUPDATE: very minor focus bug Pin
John Trinder19-May-17 5:52
professionalJohn Trinder19-May-17 5:52 
QuestionUPDATE: WindowUtils Pin
John Trinder19-May-17 1:31
professionalJohn Trinder19-May-17 1:31 
QuestionNice one Pin
Member 244330618-May-17 9:24
Member 244330618-May-17 9:24 
AnswerRe: Nice one Pin
John Trinder18-May-17 13:21
professionalJohn Trinder18-May-17 13:21 
GeneralRe: Nice one Pin
BillWoodruff18-May-17 20:50
professionalBillWoodruff18-May-17 20:50 
GeneralRe: Nice one Pin
John Trinder19-May-17 1:33
professionalJohn Trinder19-May-17 1:33 
GeneralMy vote of 5 Pin
Franc Morales17-May-17 22:55
Franc Morales17-May-17 22:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.