Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

Custom Winamp-Style TrackBar (Slider)

5.00/5 (9 votes)
2 Jun 2015CPOL14 min read 30.3K   2.5K  
A custom drawn TrackBar that looks like the one in the classic Winamp skin

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%...

Image 1

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

Image 2

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. 

Image 3

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...

Image 4

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, FromRightOrBottomFromZero.

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.

Image 5

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.

Image 6

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.

Image 7

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.

Image 8

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:

Image 9

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.

Image 10

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:

Image 11

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:

Image 12

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.

Image 13

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.

Image 14

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)

  • Initial release

License

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