Saturday, November 13, 2010

"Selected button" with visual state in silverlight

Lately, I start using silverlight to create some pages in my asp application.

I needed to create a toolbar that had some buttons for different operations for users to use, and to make their experience better I wanted to mark the selected tool in some way.

My first thought was to create "click" events to all my tools buttons and write something like this:

private void MyToolAButton_Click(object sender, RoutedEventArgs e)
{
       UnClickedTool(ToolB);
       UnClickedTool(ToolC);
       ClickedTool(ToolA)
}
……
private void UnClickedTool(Button tool)
{
       //Set unclicked visual appearance
}

private void ClickedTool(Button tool)
{
       //Set clicked visual appearance
}

This solution will work but it felt a bit outdated for silverlight.

Then I remembered hearing stuff about silverlight VisualStateManager that helps you define the visualization for each of the states in your control(such as mouseOver,Focused etc..), and all in simple xaml code!

For example, here is some code to change the color of the button(from red to orange) when mouseOver:

<Button x:Name="ButtonA">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="ButtonSolidBrush"
                                                        Storyboard.TargetProperty="Color"
                                                        To="Orange" Duration="0" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Normal"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="ButtonBorder" >
                            <Border.Background>
                                <SolidColorBrush x:Name="ButtonSolidBrush" Color="Red"/>
                            </Border.Background>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Button.Template>
</Button>

But I wanted to change the color when the button is selected! so at first, I configured the "Pressed" state of the button. but the problem is that when your mouse leaves the button, the state is changed back to "normal" (and return to the old color) because "Pressed" state is when you actually press the button.

The solution for this is simple – use ToggleButton.

What is ToggleButton?

ToggleButton is the base class of checkbox and radiobutton, which means that it's a button with checked and unchecked states.

The state of the control is determined by the IsChecked property.

By replacing my Button control with ToggleButton I was able to achieve my goal!

I've created a control template with "checked" and "unchecked" visual configuration:

 <ControlTemplate TargetType="ToggleButton" x:Name="ToggleTemplate">
            <Grid>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Checked">
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetName="ButtonSolidBrush"
                                                        Storyboard.TargetProperty="Color"
                                                        To="Orange" Duration="0" />
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Unchecked">
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetName="ButtonSolidBrush"
                                                        Storyboard.TargetProperty="Color"
                                                        To="Blue" Duration="0" />
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
                <Border x:Name="ButtonBorder" BorderBrush="Coral">
                    <Border.Background>
                        <SolidColorBrush x:Name="ButtonSolidBrush" Color="Red"/>
                    </Border.Background>
                </Border>
           </Grid>
</ControlTemplate>

Created 2 togglebuttons with this template:

<ToggleButton x:Name="MyButton" Grid.Row="1" Content="Click me.." Checked="MyButton_Checked"  Template="{StaticResource ToggleTemplate}" IsThreeState="False"/>
<ToggleButton x:Name="MyButton2" Grid.Row="2" Content="Click me2.." Checked="MyButton2_Checked"  Template="{StaticResource ToggleTemplate}" IsThreeState="False"/>

*The "IsThreeState" property determine if we had "indeterminate" state or just "checked" and "unchecked" states.

And implemented the events(when checking one button,the other is unchecked):

private void MyButton_Checked(object sender, RoutedEventArgs e)
{
     MyButton2.IsChecked = false;
}

private void MyButton2_Checked(object sender, RoutedEventArgs e)
{
     MyButton.IsChecked = false;
}

You can see that I still had to implement sort of "ClickedTool" and "UnClickedTool" in my events. so what's the difference??

Well, the big difference is that I don't use any visual definition in my code behind! I've just changed the states of the controls and that's it! all the visual definitions is in the xaml,so I can change them without changing any code! so if tomorrow I get new control template I just need to change the "Template" properties of my buttons and that's it!