Today we’re going to further develop our user interface with the integration of volume controls. This is the final stretch before we start implementing the core of our music player, so hang on!
The art of reproduction
Let’s go back to the mock-up, here is what we will have to reproduce in the app:
As you can see, it’s nothing more than a mute button and a volume control bar. It shouldn’t take long, just start by downloading the different images used to display the volume status.Volume_Tracker_-_Images.zip
After you unzipped this file, you will see 4 new images:
Here, we have an image for each volume level: when it is very loud, medium or very low. Moreover, there is even one for when the sound will be muted.
Now that you have the images, all you have to do is including them in the Resources/Images folder, just like last time!
On your marks, get set! Code!
All set? Let’s go to the code!
For this, we need an ImageButton to mute the sound and a Slider to control the sound volume precisely.
That’s right! Technically, it’s all déjà vu, so try to reproduce these controls yourself before looking at the following code:
So we have defined an ImageButton with a default image, and a Slider to control the volume from 0% to 100%. As you may have noticed, the Slider is composed of a black bar representing the current volume, and a gray bar for the upper volume available.
Now, all you have to do is adding the controls to the BottomLayout:
So, did you remember to apply a ColumnSpan? 😛 We need it to display the volume bar across three columns in our Grid.
Come on, it’s time to relaunch the app! Let’s see what it looks like:
# Switching images when the sound is muted It's getting easier with all this training, are you starting to get the hang of it? 🙂
Well, this time there were only two components to define, so we’ll go a little further. Remember the different images we have for our volume? It’s time make use for them!
The idea is that our
MuteButton changes its appearance according to the requested volume level. We will therefore have a direct reference to the
VolumeTracker in order to find the right image to associate with the
MuteButton. To do this, we will use a DataTrigger which allows us to modify the property of an object when a target value is detected.
Let’s take the simplest case where the detected volume is 0 :
First of all, we specify the type of object to which we want to apply the changes. In our case, it will be an ImageButton since we want to change the image of the
Then, with the help of a Setter, we ask the
VolumeOffTrigger to change the image source of the
MuteButton with the corresponding icon for the volume being turned off:
However, the change should only apply if the
VolumeTracker value reaches 0!
This is possible with the Binding technique. So, we create a link to this component to monitor the Value property evolution:
Finally, the target value to be reached is defined in the
VolumeOffTrigger as follows:
In summary, we have a trigger that will change the icon the moment the user lowers the Slider value to zero.
Hehe, good call! Indeed, it wasn’t a typo 😄
However, if you remove the “d” and move your mouse over the “0”, you will see that it is no longer considered a double, but an int!
And as the Value property of our trigger is of type object, it potentially accepts any type of value. We must therefore explicitly tell it how to consider this “0”: as a double!
So the “d” must be added just after:
All that remains is attaching this trigger to our
MuteButton component. Moreover, as it will be subject to additional configurations, we will isolate its initialization in an
InitMuteButton() method. This will keep our code clear:
That’s it! Now try dragging the value of the Slider to the far left:
A button in all its forms!
Now that you know how triggers work, let’s create some more to handle all the different states of the button.
Functionally, here is what we would like to put in place:
the low volume icon will appear for all values between 1 and 15,
between 16 and 50, the moderate volume icon will be displayed,
and for the high volume icon it will be between 51 and 100.
For all these cases, the trigger no longer depends on a single specific value, but rather on a whole range of values. We will therefore call upon a new specific trigger, the MultiTrigger. It’s the same principle as for the DataTrigger, with the difference that the MultiTrigger will instead depend on the result of several conditions. I’ll explain it right after.
For now, let’s declare our three new possible states:
All we did was to define the aspect changes using Setters, for low, medium or high volume.
We can now define the trigger conditions for these three states. Modify the
InitMuteButton() method as shown below:
Yes I know, it can be a lot at once, but it’s not that hard to understand. Actually, you can see that some operations are repeated in this piece of code!
At this stage, some errors will probably have been pointed out by Visual Studio. To resolve them, declare the following headers at the very top of the file:
Now it’s time for an explanation. Let’s break down a bit this
InitMuteButton() method, starting with this piece of code:
It seems simpler that way, right? All we are doing here is to add two conditions for each of the triggers that are necessary to change the
For example, if you look at the
VolumeLowTrigger, you’ll see that the first trigger condition is tied to a minimum value of 1, while the other condition depends on a maximum value of 15. Does this sound familiar now?
Absolutely! And the same logic applies for the
Okay, but that’s not magic either! The creation of these conditions is based on the
CreateMinRangeCondition(double value) and
CreateMaxRangeCondition(double value) methods:
The first method represents the minimum value for triggering the new condition, and the second the maximum value. To create these conditions, we need a target value, and a type of operator:
These parameters are taken into account by a basic method defined at the beginning of the
InitMuteButton(). It follows the same principle as for the DataTrigger which is used for the
The only change here is that a numerical target value is no longer desired (as we did for “0”). Instead, it is based on the result of a comparison.
The purpose of the
CreateRangeCondition(OperatorType comparison, double value) method is to create a trigger condition based on a standard value and a comparison type. And if you look closely, you’ll see that it defines a Binding on the VolumeTracker value while applying a CompareConverter to it.
The idea is simple, we want to define conditions so that they are only met if the result of the comparison between the standard value and the detected value is true.
It’s coming, don’t worry! Suppose we have created a lambda condition, and that this condition is only met when the detected value is greater than or equal to 80.
If the current volume is 50, would you agree that it won’t matter? Well, now imagine that you raise the volume to 88… Boom, that’s it! Your condition is now satisfied and that will logically trigger something 🙂
You’re almost there! All that’s left is to add our three new triggers to the
MuteButton. And I know you can do that! So let’s proceed as follows:
That’s it! Go ahead and try the app again!
The mobile app is seriously starting to take shape, be proud of yourself!
Except… all we have at the moment is visual. Nothing really happens when you manipulate all the buttons!
But don’t worry, you’re now ready to develop the key features of the app, starting with the next chapter!
More articles in the series: