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
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
Dock properties. AND it supports
BackColor if you should wish for that!
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:
OnHover. That property is called
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.
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
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
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
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
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
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...
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 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
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.
ScaleType = Ticks, you can customize the tick marks using the appropriate properties:
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
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:
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.
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
- NEW Built-in
ToolTip with possibility to set tooltip for the entire control or only the slider button
ScaleType property. Can be
ScalePosition property renamed to
ScaleFieldPosition for consistency (OBS: This change breaks compatibility with previous versions)
WinampTrackBarScalePosition enumeration renamed to
WinampTrackBarScaleFieldPosition for consistency (OBS: This change breaks compatibility with previous versions.)
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)
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
- ADDED: Unit Tests for
PixelValueToValue methods in renderers to avoid more bugs like the one above
Version 1.0 (2015-06-02)