This article describes two behaviors of the native XAML Slider control that reduce the joy of using it in MVVM Windows 8 Store apps. We all agree that XAML is one of the nicest technologies to develop Windows 8 Store apps, right? We all agree that you should use MVVM to build XAML apps, right? No, we don’t: unfortunately some of the native controls strongly disagree with us. The most well-known example of an MVVM-unfriendly XAML control is the radio button. A radio button is cleared by selecting another radio button in the same group. That’s OK, but at that moment it also clears its binding. It breaks the link to the viewmodels to which it was bound. This classic MVVM problem is generally solved by assigning a unique GroupName to each radio button, and deal with the mutual exclusiveness programmatically on the viewmodels’ side. Ouch!
Recently I observed that the native slider control has a similar issue. Even worse: two issues. Ouch! Ouch!
Seeing is believing, so I created a small demo app to illustrate these issues. The app shows two sliders that display temperature values from a viewmodel. The user may switch between Celsius and Fahrenheit scales using a data bound toggle switch. [My shoulder devil tried to convince me to use radio buttons instead of a toggle switch. It was a close fight, but the angel won.]
Here’s a screenshot of the app:
Here are the two issues, and their impact on the MVVM app:
A Slider dies when its Minimum becomes greater than its Maximum
Whenever a slider control –even temporarily- reaches a state where its Minimum value becomes greater than is Maximum, it simply blocks. So when you create a slider of which the range is bound to values in the viewmodel, you could be in trouble. The sliders in the sample app show a range from 0 °C to 28 °C, that’s (more or less) from 32 °F to 82 °F. These values are provided by the viewmodel, and they’re updated when the settings change. Here are the bindings for one of the sliders:
<Slider Value="{Binding SomeTemperature, Mode=TwoWay}"
Minimum="{Binding MinimumTemperature}"
Maximum="{Binding MaximumTemperature}" />
In a regular MVVM app, we would safely rely on the data binding engine to propagate the modifications from the viewmodels to the controls. Well, that does not work if there are sliders involved: whenever the sample app reaches a sub state where the MinimumValue is in Fahrenheit (32) and the MaximumValue is still or already in Celsius (28), the sliders simply die with an unmovable thumb docked to the maximum end, like this:
As a work around, you can take control of the order in which the property changes are notified, just to make sure there’s always a valid range. Here’s an example; this is part of the code that is triggered by the temperature unit toggle switch:
// Workaround: control change propagation order.
if (this.isCelsius)
{
this.OnPropertyChanged("MinimumTemperature");
this.OnPropertyChanged("MaximumTemperature");
}
else
{
this.OnPropertyChanged("MaximumTemperature");
this.OnPropertyChanged("MinimumTemperature");
}
If we switch to Celsius, we first change the MinimumValue (from 32 to 0) and then the MaximumValue (from 82 to 28), and the other way around when we switch to Fahrenheit. We should never be forced to write this kind of code, but it does the trick.
Let’s move to another issue:
A Slider modifies its Value to keep it in its Range
While the first issue is just an inconvenience, I perceive the second issue as a real disaster: while you are changing the boundaries of the slider range, the slider will update its Value property to keep it in this range. Here’s a scenario. Start the app and set both temperatures to 57°F (that’s in the middle of the range).
Then switch to Celsius and observe the value in the top slider:
The slider Value went down to 0 instead of the expected 14 °C. This is what happened: when the range moved from (32 -> 82) to (0 -> 28), the slider ‘adjusted’ the value before the viewmodel had a chance to calculate the new value. So the slider changes the value, and then the viewmodel used that wrong value to apply the conversion to. Somewhere in that process, the slider also broke its binding. So its value is at zero, where the viewmodel is at -2. Both are wrong. Staggering, isn’t it? And by the way: unit tests on your viewmodel will NOT reveal these bugs.
On a more positive note: you can circumvent the problem by making a backup of the value on the start of the change propagation, like this:
var temp = this.anotherTemperature;
// Obsolete algorithm to update slider range
// ...
// This would be standard code. Unfortunately it does not work.
if (this.isCelsius)
{
this.SomeTemperature = UnitConverter.FahrenheitToCelsius(this.SomeTemperature);
}
else
{
this.SomeTemperature = UnitConverter.CelsiusToFahrenheit(this.SomeTemperature);
}
// Workaround: backup value.
if (this.isCelsius)
{
this.AnotherTemperature = UnitConverter.FahrenheitToCelsius(temp);
}
else
{
this.AnotherTemperature = UnitConverter.CelsiusToFahrenheit(temp);
}
Can you imagine how the complexity would increase if we had multiple sliders, some of them with more than two possible ranges (e.g. liters, US gallons, and UK gallons)? Well I can, here’s an example of such a page. This view will be part of the next release of this app. It does what it's supposed to do, but its viewmodel is bloated with logic to keep all sliders up and running:
Call to action
This small sample shows that even one simple slider in a view could dramatically increase the complexity of your code, even up to a point where it threatens the MVVM architecture. It really should not be necessary to apply modifications to the viewmodels (and/or models), just to work around the inappropriate behavior of one single type of control. I strongly believe that XAML developers deserve a better solution, in the form of more MVVM-friendly native controls. So Microsoft: please fix this. And while you’re there: fix the radio button too.
Maybe I should make an appeal to the community? Why not:
Dear open source MVVM framework builders,
We have enough property change propagators, dependency injectors, service locators and event aggregators. Could you please spend some time in creating or fixing some controls to actually work with it?
Thanks,
a XAML developer
That almost sounded like a rant, didn't it? Don't worry: I'm fine.
Code
Here’s the sample project: U2UConsult.WinRT.MVVMSlider.zip (117.08 kb).
Enjoy!
Diederik