Introduction
I'm currently writing a media player application. Basically, I want it to look exactly like the Classic Winamp UI - only with more specialized functionality. And without the hassle of trying to implement skinning of the entire application. I am not going for a 100% look-alike, but perhaps 98%...
Winamp's UI - This is what I'm aiming at for my application
So I started making a form that mimics the Winamp user interface. It went quite well and reasonably quick even though I encountered a lot of interesting "challenges" along the way (something I thought I should write an article series about when I get the time some day). The only real obstacle came when I was designing the player and needed a trackbar/slider for the playback progress showing and the volume control.
The standard Windows Forms trackbar wouldn't cut it of course, and even though I browsed both The CodeProject and the rest of the internet, I couldn't find a trackbar that could be made to look like the Winamp trackbar in the least. I really didn't want to user paint my own trackbar if I didn't absolutely have to, so I was willing to make an exception and compromise on the look of the trackbar if only I could find one that resembled just a little bit.
The best one I found was the MediaSlider control by John Underhill (formerly known as Steppenwolfe). It looks great, and the look is just close enough for me to be able to live with it. I have used it before and like the control and the look. Unfortunately, I have also discovered that there are some issues with the control. The most important is that it flickers a lot when the form is resized. There are also some issues with the painting of the control, plus the fact that it doesn't support the Anchor
and Dock
properties.
I have reported most (but not all) of these issues in the article forum, but it doesn't look like the article is being updated anymore, and John himself hasn't replied to any comments since 2010. So I was left with the choice of either fixing the bugs myself, finding a different control or creating one of my own. Well, I had already tried finding another control and failed. As I mentioned before, the last option was really not what I was keen on doing, but in the end I decided that that was the best solution. I thought it was nicer to bugfix my own code than somebody else's.
So that's what I did. And it resulted in this article.
The control is quite straightforward and if you know how to paint controls in GDI+, there is really nothing interesting in the code. So I am not going to paste a lot of code. Actually, I just wanted to share the control with you so that you can use it in your own projects if you should want to.
Nitpickers will say that without code samples, it's not a real article and should be posted as a tip/trick. I don't agree. A tip/trick in my opinion is more like a short "do this to accomplish that". Also if you tip people about something, you relay information from some other third party source that you have found useful and want to share with others. Not code you have produced yourself. Just my opinion. You can disagree if you want to, but I'm not going to change it to a tip...
Instead, I thought that I would go through some of the functionality I put in the control so that you can see what it's like.
Control Feature Overview
1. Basic Control Goodies
The control is of course doublebuffered to minimize flicker. It is also derived from the Control
class so it supports both the Anchor
and Dock
properties. AND it supports Transparent
BackColor
if you should wish for that!
2. Orientation
Both of Winamp's trackbars are horizontal, so theroretically, I could just have made the control horizontal. But because I also needed a vertical trackbar for my special functionality, I had to implement both orientations.
Horizontal orientation was no problem, but because of the way .NET calculates X and Y coordinates, it was a little bit of a hassle implementing the Vertical one. If it had its minimum value at the top and maximum value at the bottom, it had basically just been a matter of switching X and Y and Width and Height, but most people prefer it the other way around, so it required a little more magic to get the Vertical trackbar to work properly. I hope that I have managed getting it right.
3. Slider Button
The size of the slider button can be changed using the SliderButtonSize
property. There is a default OnHover
functionality that makes the button light up when the mouse is over it. That can be turned off by setting UseHoverEffect = false
if you don't want that.
There are also 3 settings for when the slider button should be visible: Always
, Never
and OnHover
. That property is called ShowSlider
.
The thought behind it was that as a default, the button is always visible. But if you're implementing a progress bar that shows the playback progress, then maybe you don't want any button at all.
With OnHover
, the button is shown when the mouse hovers over the trackbar. That is not something Winamp makes use of, but the effect can be found in Windows Media Player, so I thought I would implement it here as well...
4. Track Style
With Track Style, I mean the way the track is colorized when you move the slider button and/or the Value
changes. There are 4 different values for the TrackStyle
property: None
, FromLeftOrTop
, FromRightOrBottom
, FromZero
.
None
means of course that there is no filling in the track at all.
FromLeftOrTop
means that the track is filled from the left side of the trackbar to the selected value of the control (for a horizontal trackbar; a vertical trackbar is filled from the top to the current value). FromRightOrBottom
means that the track is filled from the right side of the trackbar to the selected value of the control (for a horizontal trackbar; a vertical trackbar is filled from the bottom to the current value). FromZero
: Means that the track is filled from 0 to the currently selected value.
OBSERVE: Zero is not necessarily the end of the trackbar. You could for instance have a trackbar with Minimum = -20
and Maximum = 20
. That would put 0 in the middle, and the track will therefore be filled from the middle.
5. Track Color
The look of the control is not as customizable as it is in MediaSlider
. I had no need for that in this project. The only customizable property is the color of the progress track. There is a property called EmptyTrackColor
that determines the color of the track when it is not filled. When the track IS filled, it's a little more complicated because a very simple gradient using two colors is applied. Those colors are set using the TrackLowerColor
and TrackUpperColor
properties.
6. Seeking
The Winamp GUI is a strange thing. There are a lot of controls that look exactly the same but work in entirely different ways. The two trackbars for instance. If you move the slider of the volume trackbar, then the value is changed instantly.
If you move the slider of the player progress trackbar, that is not the case. There, a semi-transparent "ghost" slider button shows you where the slider will end up when you release the mouse button. But the slider button itself is not moved (and thus, the value is not changed) until you actually DO release the mouse button. Winamp seems to refer to this as "Seeking", because that's what it writes in the display when you do it.
I created a property called UseSeeking
that implements that exact functionality. Remember that when that is set, the ValueChanged
event will not be fired until you release the button. However, there is a Seeking
event that is raised to give you information about the "ghost" button value.
The transparency of the "ghost" slider button can be set using the SeekSliderTransparency
property.
7. The Scale
In Winamp, there are no tick markings along the trackbars, which is common in other programs. Instead, there is a very cool volume scale above the volume slider. I wanted that as well, so instead of implementing Ticks, I implemented Scale Fields.
The scale is the most customizable part of the control. You can change the size of the scale fields using the ScaleFieldWidth
and ScaleFieldMaxHeight
properties, the spacing between them with the ScaleFieldSpacing
and the color with the ScaleFieldColor
property. The number of fields is calculated from the aforementioned size properties.
You can also position the scale on the desired side of the trackbar with the ScaleFieldPosition
property. If you don't want to have any scale at all, just set ScaleFieldPosition = Hidden
.
One last semi-important property concerning the scale: ScaleFieldEqualizeHeights
. That property is used in the case where Minimum < 0
, Maximum > 0
and Minimum
and Maximum
has different absolute value. You can see the difference below:
By the way: There is a small surprise in the source code for y'all concerning the scale: There is a separate ScalePanel control - a panel that paints only the scale. It is a little more crudely coded than the slider control, and I had first thought that I would use that in conjunction with the MediaSlider control I mentioned in the beginning.
But when I started doing the trackbar control, I chose to incorporate the scale. I just kept the "old" scale panel for the heck of it...
8. AutoSize
For your convenience, I implemented an AutoSize
function. When AutoSize = true
, the control sizes so that there is room for the scale and the trackbar with slider, plus it sizes so that it will contain precisely a whole number of scale fields.
9. User Interaction
If you are using the trackbar to show some progress and do not want the user to be able to change it manually, just set AllowUserValueChange = false
. You will still be able to set the value in code, but the user will not be able to change the value in the GUI.
If you want the user to be able to change the value using the arrow keys when the control has focus, you can set the KeyChangeOption
property. It has 3 possible values: NoKeyChange
, LeftAndRightArrowKeys
and UpAndDownArrowKeys
.
NoKeyChange
means that the user can't use the keys to change the value.
LeftAndRightArrowKeys
means that the user can increase/decrease the value using the Left and Right arrow keys.
UpAndDownArrowKeys
means that the user can increase/decrease the value using the Left and Right arrow keys.
When the arrow keys are pressed, the value is increased/decreased with the SmallChange
property value.
OBSERVE: This behavior doesn't necessarily have anything to do with the orientation of the trackbar. In Winamp, for instance, you increase/decrease the volume using the Up/Down keys even though the trackbar is horizontal. It's a tad illogical, but you have the possibility of mimicking the same response with this control if you want to.
No matter if KeyChangeOption = LeftAndRightArrowKeys
or KeyChangeOption = UpAndDownArrowKeys
(but NOT if KeyChangeOption = NoKeyChange
!), pressing the PageUp, PageDown, Home and End buttons will have these effects:
PageUp
: Increase the value with the LargeChange
property value PageDown
: Decrease the value with the LargeChange
property value Home
: Set value to Maximum
property value End
: Set value to Minimum
property value
The control also supports changing the value using the mouse wheel on the mouse. If you don't want your user to be able to do that, just set AllowMouseWheelChange = false
.
10. Tick Marks (New in Version 1.2)
OK, so I was wrong when I claimed before that there are no trackbars with tickmarks in Winamp. The Equalizer actually has tick marks along the trackbars:
So I had to implement it anyway, much to my dismay. I also had to make some changes to the code that breaks backwards compatibility, and for that I'm very sorry. But I had no choice.
In version 1.2, there's a new ScaleType
property:
That means that I had to remove the Hidden
option from the ScalePosition
property (which I also renamed to ScaleFieldPosition
for consistency). So from this version, you should set ScaleType = None
instead of ScalePosition = Hidden
.
Anyway, when ScaleType = Ticks
, you can customize the tick marks using the appropriate properties: TickAlignment
, TickColor
, TickEmphasizedColor
, TickEmphasizedHeight
, TickEmphasizeMinMaxAndZero
, TickHeight
, TickPosition
, TickSpacing
and TickWidth
.
Please note that even though all the above examples show ticks on BOTH sides of the trackbar, that is not the only way it can look. You can, of course, position them on one side of the trackbar only if you should feel a strong urge to do that.
11. Built-in ToolTip (New in Version 1.2)
I mentioned at the beginning that one of my options was to use the MediaSlider
control instead of developing my own. So I really set myself up to having my control compared to that one...
And as Member 11773069 has discovered in the comments below, the MediaSlider
control has quite a nice feature called Flyout
, which basically lets you display control information in a tooltip/balloon style.
The problem with that feature is that the Flyout
itself is a painted part of the control rather than a separate window. That means that the control has to be big enough to accommodate the flyout as well as the slider. The flyout cannot transcend the control or form boundaries. Which again means that if you have two sliders next to each other on the form, you have to make space between them to accommodate the flyout of one or both of them. At least the way I see it. There might be other options, but I haven't really used it that much.
Anyway, as I see it, a popup/flyout should be a true window so that it can overlap other controls. There are plenty of examples on the internet about how to do that, but it can be quite tricky to get it to work properly.
I didn't really feel like doing such an elaborate solution, and there is actually a control in the Toolbox already that does an excellent job of this - and looks good as well: The ToolTip
control.
I could just have used a ToolTip
control on my form in collaboration with the WinampTrackBar
, but the ToolTip
control works on the entire trackbar surface, and I wanted the possibility to show the tooltip over the slider button only.
So I incorporated a ToolTip
component in the trackbar code and created two properties: ToolTipText
and ToolTipTextSliderButton
. The first one shows a tooltip when the mouse pointer is hovered over any part of the trackbar control, and the other only shows the tooltip if the mouse pointer is hovered over the slider button.
For convenience, I chose to expose all of the ToolTip
controls properties so that they are easily customizable in the property grid.
You can see from the black background color that the ToolTip
can transcend the control boundaries without any problems.
History
Version 1.3 (2015-08-07)
- BUGFIX: Fixed missing implementation of
TickAlignment
property (Stupid me!) - BUGFIX: Fixed pixel error in
GetTickLayoutRectangle
method of both renderers
Version 1.2 (2015-08-05)
- NEW: Ticks
ScaleType
implementation - NEW Built-in
ToolTip
with possibility to set tooltip for the entire control or only the slider button - NEW:
ScaleType
property. Can be None
, ScaleFields
or Ticks
- NEW:
ScalePosition
property renamed to ScaleFieldPosition
for consistency (OBS: This change breaks compatibility with previous versions) - NEW:
WinampTrackBarScalePosition
enumeration renamed to WinampTrackBarScaleFieldPosition
for consistency (OBS: This change breaks compatibility with previous versions.) - NEW:
ScalePosition = Hidden
option removed. Use ScaleType = None
instead (OBS: This change breaks compatibility with previous versions) - CHANGE: Change in the way the different layout rectangles are calculated in order to keep the scale fields or ticks near the track even if the control is bigger
- BUGFIX: Check for out of bounds
Value
Property values
Version 1.1 (2015-06-30)
- NEW:
SeekDone
event - NEW:
SliderButtonDoubleClick
event - NEW:
ValueChanging
event, including the possibility of cancelling the change - NEW: The
EventArgs
in the ValueChanged
event now gives information about how and why the value was changed (OBS: This change breaks compatibility with previous versions.) - BUGFIX: Fixed minor calculation errors in
Trackbar
renderers - ADDED: Unit Tests for
ValueToPixelValue
and PixelValueToValue
methods in renderers to avoid more bugs like the one above
Version 1.0 (2015-06-02)