So, once again, I'm publishing an article about a custom painted Winforms control. I guess I ought to change my profile name to "Control Freak"... But what the heck: I love programming custom controls! Nothing to do about that.
This time I needed a ToggleSwitch or ToggleButton - something that can show a binary On/Off Checked/Unchecked state in a more interesting way than the standard checkbox. As usual, I started checking out the existing controls, because who wants to spend a lot of time reinventing something that has already been invented??? The thing that normally happens to me also happened this time: I found existing controls that could have been so great, but was leaving me wanting in some way. The best I could find (and very good looking too at that) is this one by IKalai (here on CP of course). But I had some problems with Ikalai's control. The most important one was that it doesn't allow you to change the size of the control as you want, and I couldn't make it the size I needed for my application... :-( I really don't understand why he has chosen to impose such a strange limitation on his control!
I also found this one, which is even better looking - but alas, it is built for WPF/Silverlight, so I couldn't use it.
So i did what I so often do: I started rolling my own control. And as usual, I went completely overboard and made it much more advanced than I really needed. Instead of merely doing a control that looked as I wanted and behaved as I wanted, I was inspired by the above two articles to make a control that could be made to look in a lot of different ways. In the end I wound up with NINE different styles plus the possibility to customize those styles further - AND create own renderers to add even more styles if one should feel the need to do that.
The articles didn't just inspire me, I must admit that I even stole some ideas from them... But the code is 100% mine!
In the end - after finishing the control - I got a completely different idea. So I'm not even sure I'm going to use my ToggleSwitch in the application I created it for. But here it is, nevertheless, for the benefit of y'all to use.
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
2. Different styles:
I planked two styles from IKalai's control and seven from the WPF control. The style is changed by merely setting the
Style property to the style you want.
3. Image and text support:
You can customize the control pretty much as you like. A text or image can be inserted in each side of the control (if the selected renderer supports it). You can only have either text OR image. If you have set the image property, it will take precedence and the text property will not be used.
You can also insert an image in the button itself (again if the selected renderer supports it). I didn't implement the text option for the slider button because my judgment was that the text that could fit in the button would be so small anyway that it is of no use.
4. Other cool properties:
AllowUserChange - There may be situations where you don't want the user to be able to change the value of the control, but you don't want to disable it either. If you set
AllowUserChange = false, the user cannot change the value, but you can still change it in code behind.
UseAnimation - When the slider moves from one state to another, it can be animated. When
UseAnimation = true, the two other values determine how fast the button moves. Generally, it is moved the number of pixels set in
AnimationStep for each number of milliseconds set in
AnimationInterval. If you want the button to change state immediately, just set
UseAnimation = false.
GrayWhenDisabled - Determines if the control is painted in grayscale when it is disabled or retains the same look as when it is enabled.
ThresholdPercentage - Determines how far you have to drag the button before it snaps to the other side. Default is 50%, so if you pull the slider button more than halfways across the control, it will automatically snap to the other side. The percentage is always calculated from the side where the button currently resides.
ToggleOnSideClick - The user can of course always drag the slider button to change the value of the switch. But using these properties, you can also decide if the control should toggle if it is clicked by the user. If
ToggleOnButtonClick = true, clicking the slider button will toggle the switch. If
ToggleOnSideClick = true, you can even click the areas beside the button and get the switch to toggle. If both are true, clicking anywhere on the control will toggle it, and if both properties are false, clicking the control will have no effect at all.
5. Customizable renderers:
Each style has its own renderer. Some of the basic properties such as the image properties are so common that they can be found directly on the ToggleSwitch control. Not all renderes accept all these properties, though. But most of them do. Other properties such as colors, I've chosen to attach to the specific renderers. Because they all paint the controls in so different ways, it was hard to make general color properties in the ToggleSwitch control that would make sense in all the different scenarios.
By making the color properties part of the renderers, it's possible to customize the look of a specific style much more than it would otherwise have been. The drawback is that in order to do that, you have to create a new instance of the renderer, change the properties and assign the new renderer to the control. But it's not that hard and the demo program shows exactly how to do that.
In a future version of the control, I might consider passing the renderer instance in a
RendererChanged event so that the properties can be set directly without creating a new instance first. But I thought that that was overkill for this first version.
Points of Interest
The look I originally wanted for my application was the one with the brushed metal knob. All the controls I have seen with brushed metal knobs before have done them in the same way: By embedding an image file created in PhotoShop in the resources and paint that on the surface of the control. I found a PhotoShop tutorial on how to create such an image...
But it disturbed me and my touch of Asperger. Embedding a file and painting that is not so "clean" as if you paint it directly on the control yourself.
Even with the PhotoShop tutorial, I didn't really know how to paint it in C#. I asked some people on CP, but I didn't really get much help there. Pete told me that I had to use a gradient brush - which I already knew, but I couldn't really see how i could do it anyway. So I Googled some more. I found a lot of examples on how to fill a circle with a radial gradient using a
PathGradientBrush. But all of those examples showed how to make a gradient that went from the inner center of the circle to the outer border (or the other way around). And I of course needed to make a gradient that went around the inside of the circle.
It wasn't easy, but FINALLY, I found this example that does more or less what I needed:
So I started dividing the image of the metal knob into gradient pie slices and paint them one by one. That worked out, But strangely enough, I discovered that if I painted all the gradients using the actual center of the knob as center for the brushes, I didn't get the look I had expected. To get the correct look, I had to offset the center of each brush slightly. I don't know why, but I got it to work in the end, I think.
"My" painted brushed metal knob may not look EXACTLY as good as a PhotoShopped one, but I was quite pleased with the result anyway. More so that I didn't have to include resources in the control library to get it to work.
Version 1.0 (2015-09-11)