Download MultiSlider_W10_.zip
(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 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.
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. 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
- (g
et
/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.
Methods
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
}.
Events
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);
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
.