General Purpose MultiSlider





5.00/5 (9 votes)
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.
Introduction
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 isfalse
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 istrue
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.
Comments
When tabbing through the main window's controls using the Tab key or Modifier+Cursor:
- If an arrow is selected, Shift+Cursor may be used to tab over any adjacent arrows.
- The Tab key will NOT tab over any adjacent arrows, but only over the Controls.
- When a
MultiSlider
control is tabbed to, depressing the Space key will activate the prior selected arrow. - Control+Cursor acts as per normal when tabbing over controls.
There are a few multi-slider tools out there:
- https://www.codeproject.com/Articles/626132/WPF-MultiRangeSlider-Control
This is dated 2013 and uses Windows 7 arrows, so is out of date for W10 style. Limited functionality although can add new arrows.
- http://avaloncontrolslib.codeplex.com/
This is even older (2007).
- http://www.telerik.com/products/wpf/slider.aspx?utm_source=google&utm_medium=cpc&gclid=CjwKEAjwl9DIBRCG_e3DwsKsizsSJADMmJ11IH1yTbk4LCKPBdn0Q9rsFBB0G-P1IpyNywq5hAi4_BoCpYfw_wcB&gclsrc=aw.ds&dclid=CKqYk9fy59MCFcKg7QodaLcK5g
This looks as if it is for W7. I haven't investigated this one.
- http://wpftoolkit.codeplex.com/wikipage?title=RangeSlider&referringTitle=Home
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.
Usage
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
Properties
AdornerColor
- (get
/set
) - The color to use for the adorning circle of an arrow. Ifnull
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. Ifnull
is passed, the ratio is reset.ArrowAtIndex(int index)
- Returns theArrowData
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
orRect
.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 ifN > 0
and takes precedence overAutoToolTipPrecision
.CanDelAddArrows
- (get
/set
) - Whether the user can Add/Delete arrows with the mouse.HelpText
- (get
) - Astring
containing basic formatted text giving the user instructions. Suitable for adding to and displaying in aMessageBox
.Minimum
- (g
et
/set
) - Minimum value of an arrow in thetrackbar
. The setter deletes all existing arrows and theArrowDeleted
event is not fired.Maximum
- (get
/set
) - Maximum value of an arrow in thetrackbar
. The setter deletes all existing arrows and theArrowDeleted
event is not fired.Orientation
- (get
/set
) - Specifies either a vertical or horizontal axis for thetrackbar
.ReverseDirection
- (get
/set
) - WhetherMaximum
is at the maximum or minimum end of the trackbar (vice versa forMinimum
).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 thetrackbar
. If the trackbar is empty, you getDoubleNaN
; or thetrackbar
is ignored for set. This is useful for a non-deletable single-arrow slider.
Methods
CreateArrow(double value, bool isUser = false, bool isMoveable = true)
- Create and show a new arrow. Returns anArrowData
.- "
value
">A value withinMinimum
andMaximum
properties of theTrackBar
. - "
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 theMinimum
end). If'fireEvent' = true
, then theArrowDeleted
event is fired.DeleteAllArrows(bool fireEvent)
- Removes all arrows from thetrackbar canvas
. If'fireEvent' = true
, then theArrowDeleted
event is fired.IsValidValue(value)
- Whether the arg falls within the multi slider'sMinimum
/Maximum
values.ValueAtIndex(int index)
- Return the Value at the valid arrow index (base 0 starting from theMinimum
end) on the trackbar.
For the arrows (type ArrowData
sent by the event handlers):
Index
- (get
) - Base 0. The nth arrow on thetrackbar
starting from theMinimum
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
toMaximum
}.
Events
ArrowCreated
ArrowRightClicked
- If the Alt key is used for deleting an arrow, this will not be firedArrowScrolled
ArrowDeleted
-ArrowSelected
will fire also after this if the focus changes to an adjacent arrowArrowLeftClicked
ArrowSelected
- Fired when an arrow receives keyboard focus (e.g. is clicked on or tabbed to). All pass their senderarg
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
tofalse
. - Write event handler,
TrackBar_MouseLeftButtonUp()
- sets_mouseUp
totrue
.
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);
threadBW.RunWorkerAsync();
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
.