• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 29/04/2014

Gestion des états visuels

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Maintenant que nous connaissons les templates, nous allons revenir sur un point qui vous sera sûrement utile dans le développement de vos applications. Il s’agit de la gestion des états visuels. Mais qu’est-ce donc ?
Prenons un bouton par exemple, il possède plusieurs états visuels. Il y a l'état quand il est cliqué, l'état quand il est désactivé et l'état lorsqu'il est au repos.

C'est la même chose pour une ListBox, nous avons par exemple vu un état où un élément est sélectionné, qui est d'ailleurs mis en avant grâce à la couleur d'accentuation du téléphone.
Tous les contrôles ont potentiellement plusieurs représentations visuelles en fonction de leurs états. Voyons voir comment cela fonctionne.

Les états d’un contrôle

Observons les états de notre bouton plus en détail. À la figure suivante, vous pouvez le voir dans son état normal, au repos.

Etat du bouton au repos
Etat du bouton au repos

À la figure suivante, vous voyez son état quand il est cliqué.

Etat du bouton cliqué
Etat du bouton cliqué

Et sur la figure suivante, lorsqu’il est désactivé et donc non cliquable.

Etat du bouton désactivé
Etat du bouton désactivé

Ils correspondent respectivement aux états :

  • Normal

  • Pressed

  • Disabled

Chaque contrôle dispose de différents états. Les états changent automatiquement en fonction d’une action utilisateur ou d’une propriété. Par exemple, c’est en cliquant sur le bouton que celui-ci passe de l’état Normal à Pressed et en relâchant le clic que celui-ci passe de Pressed à Normal, changeant au passage l’apparence du contrôle. Pour l’état désactivé, ce changement se fait quand on passe la propriété IsEnabled à false.
Le bouton possède d’autres états qui ne sont pas utilisés pour Windows Phone, comme l’état Focused et l’état Unfocused qui correspondent au fait que le bouton ait le focus ou pas, ce qui ne sert pas vraiment dans une application pour Windows Phone, ainsi que l’état MouseOver qui correspond au passage de la souris sur le bouton.

Ces trois états du bouton sont un héritage de Silverlight pour PC, ils ne servent pas avec le XAML pour Windows Phone.
Un contrôle peut être dans plusieurs états à la fois, par exemple si c’était valable on pourrait envisager qu’il soit dans un état Pressed et un état où il ait le focus. Par contre, d’autres états sont exclusifs, il n’est bien sûr pas possible que le bouton soit dans l’état Pressed et dans l’état Normal… Pour représenter ceci, les états appartiennent à des groupes. Le bouton ne peut avoir qu’un seul état par groupe. En l’occurrence, notre bouton possède deux groupes avec les états suivants :

Groupe FocusStates :

  • Focused

  • Unfocused

Groupe CommonStates :

  • Pressed

  • Disabled

  • Normal

  • MouseOver

À chaque état donc son apparence… ce qui implique que nous pouvons modifier les apparences de chaque état via des templates que nous allons définir dans un style.
Pour comprendre, le plus simple est d’utiliser Blend. Ajoutons un bouton dans notre XAML :

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <Button x:Name="But" Content="Cliquez-moi !" />
    </StackPanel>
</Grid>

Puis démarrons Blend en faisant un clic droit sur le projet, puis ouvrir dans expression blend. Une fois ouvert, cliquez sur le bouton pour le sélectionner et faites un clic droit pour modifier une copie du modèle, comme indiqué à la figure suivante.

Modification du modèle du bouton dans Blend
Modification du modèle du bouton dans Blend

Cela permet de créer un style automatiquement (voir la figure suivante).

Création du style par défaut du bouton
Création du style par défaut du bouton

Maintenant, nous pouvons voir dans l’onglet Etats, les différents états du bouton, comme vous pouvez le constater sur la figure suivante.

Les différents états du bouton
Les différents états du bouton

Remarquons que le XAML est désormais :

<phone:PhoneApplicationPage
    x:Class="DemoEtatVisuel.MainPage"
    …>
	<phone:PhoneApplicationPage.Resources>
		<Style x:Key="ButtonStyle1" TargetType="Button">
			<Setter Property="Background" Value="Transparent"/>
			<Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
			<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
			<Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
			<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
			<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
			<Setter Property="Padding" Value="10,5,10,6"/>
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="Button">
						<Grid Background="Transparent">
							<VisualStateManager.VisualStateGroups>
								<VisualStateGroup x:Name="CommonStates">
									<VisualState x:Name="Normal"/>
									<VisualState x:Name="MouseOver"/>
									<VisualState x:Name="Pressed">
										<Storyboard>
											<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
												<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneButtonBasePressedForegroundBrush}"/>
											</ObjectAnimationUsingKeyFrames>
											<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonBackground">
												<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneAccentBrush}"/>
											</ObjectAnimationUsingKeyFrames>
										</Storyboard>
									</VisualState>
									<VisualState x:Name="Disabled">
										<Storyboard>
											<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
												<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
											</ObjectAnimationUsingKeyFrames>
											<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="ButtonBackground">
												<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
											</ObjectAnimationUsingKeyFrames>
											<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonBackground">
												<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent"/>
											</ObjectAnimationUsingKeyFrames>
										</Storyboard>
									</VisualState>
								</VisualStateGroup>
								<VisualStateGroup x:Name="FocusStates"/>
							</VisualStateManager.VisualStateGroups>
							<Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0" Margin="{StaticResource PhoneTouchTargetOverhang}">
								<ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
							</Border>
						</Grid>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>
	</phone:PhoneApplicationPage.Resources>

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MON APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="nom de la page" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <Button x:Name="But" Content="Cliquez-moi !" Style="{StaticResource ButtonStyle1}" />
            </StackPanel>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

Beaucoup de choses, mais ce qu’il faut regarder précisément c’est que Blend a déclaré un nouveau template du bouton, celui-ci est simplement :

<Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0" Margin="{StaticResource PhoneTouchTargetOverhang}">
	<ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>

Bref, un contrôle Border contenant un contrôle ContentControl, ce qui est précisément le look du bouton tel que nous le connaissons.

Ensuite, il faut regarder l’intérieur de la balise <VisualStateManager.VisualStateGroups>. À l’intérieur sont définis tous les groupes d’états du contrôle :

<VisualStateManager.VisualStateGroups>
	<VisualStateGroup x:Name="CommonStates">
		<VisualState x:Name="Normal"/>
		<VisualState x:Name="MouseOver"/>
		<VisualState x:Name="Pressed">
			<Storyboard>
				…
			</Storyboard>
		</VisualState>
		<VisualState x:Name="Disabled">
			<Storyboard>
				…
			</Storyboard>
		</VisualState>
	</VisualStateGroup>
	<VisualStateGroup x:Name="FocusStates"/>
</VisualStateManager.VisualStateGroups>

Ce qu’on peut voir c’est que les états définissent une animation qui permet de changer la valeur de certaines propriétés lorsque le contrôle change d’état. Par exemple, en passant à l’état Pressed, nous pouvons constater que la propriété Foreground passe à la valeur de PhoneButtonBasePressedForegroundBrush.

Voilà comment sont définis les états, dans des modèles.

Modifier un état

Ceci nous permet de faire ce que nous voulons avec les états des contrôles afin d’améliorer le look de nos boutons. Rappelez-vous dans la première partie, nous avions modifié l’apparence d’un bouton en modifiant sa propriété Content :

<Button x:Name="But">
    <Button.Content>
        <StackPanel>
            <Image Source="rouge.png" Width="100" Height="100" />
            <TextBlock Text="Cliquez-moi !" />
        </StackPanel>
    </Button.Content>
</Button>

Comme on peut s’en rendre compte maintenant que nous avons vu le modèle original du contrôle, nous n’avons modifié que ce qui correspondait au contenu du ContentControl. Le cadre est donc conservé, mais plus encore, la même animation sur le fond du cadre existe toujours.
Ceci ne correspond peut-être pas à ce que nous souhaitons avoir lorsque le bouton est cliqué. En l’occurrence, moi ce que je voudrais, c’est que le rond rouge devienne vert et que le texte passe à « cliqué ». Pour cela, il suffit de modifier le template de l’état Pressed de notre contrôle… Reprenons donc notre bouton :

<Button x:Name="But" Content="Cliquez-moi !" Style="{StaticResource ButtonStyle1}" />

et modifions ses templates à l’intérieur de son style :

<ControlTemplate TargetType="Button">
    <Grid Background="Transparent">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver"/>
                <VisualState x:Name="Pressed">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Source" Storyboard.TargetName="MonRond">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="vert.png"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Text" Storyboard.TargetName="MonTexte">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Cliqué :)"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Disabled">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="MonRond">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Text" Storyboard.TargetName="MonTexte">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Pas touche ..."/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0" Margin="{StaticResource PhoneTouchTargetOverhang}">
            <StackPanel>
                <Image x:Name="MonRond" Source="rouge.png" Width="100" Height="100" />
                <TextBlock x:Name="MonTexte" Text="Cliquez-moi !" />
            </StackPanel>
        </Border>
    </Grid>
</ControlTemplate>

Vous pouvez voir que j’ai modifié l’apparence du contrôle pour qu’il contienne notre image et notre texte :

<Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0" Margin="{StaticResource PhoneTouchTargetOverhang}">
    <StackPanel>
        <Image x:Name="MonRond" Source="rouge.png" Width="100" Height="100" />
        <TextBlock x:Name="MonTexte" Text="Cliquez-moi !" />
    </StackPanel>
</Border>

Puis, dans l’état Pressed, j’ai animé les propriétés Source de l’image et Text du TextBlock pour charger une nouvelle image et changer le texte :

<VisualState x:Name="Pressed">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Source" Storyboard.TargetName="MonRond">
            <DiscreteObjectKeyFrame KeyTime="0" Value="vert.png"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Text" Storyboard.TargetName="MonTexte">
            <DiscreteObjectKeyFrame KeyTime="0" Value="Cliqué :)"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

Bien sûr, il faudra rajouter les images rouge.png et vert.png à la solution.
De la même façon, dans l’état Disabled, j’ai changé la visibilité de l’image pour la faire disparaitre, puis j’ai animé le texte pour le changer :

<VisualState x:Name="Disabled">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="MonRond">
            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Text" Storyboard.TargetName="MonTexte">
            <DiscreteObjectKeyFrame KeyTime="0" Value="Pas touche ..."/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

Du coup, dans mon XAML, je me retrouve avec mon bouton :

<Button x:Name="But" Content="Cliquez-moi !" Style="{StaticResource ButtonStyle1}" />

Qui, lorsqu’il est au repos, ressemble à ce que vous pouvez voir sur la figure suivante.

Etat du bouton modifié au repos
Etat du bouton modifié au repos

Lorsqu’il est cliqué, il est plutôt ainsi (voir la figure suivante).

Etat du bouton modifié lorsqu'il est cliqué
Etat du bouton modifié lorsqu'il est cliqué

Et lorsqu’il est désactivé, il est comme vous pouvez le voir à la figure suivante.

Etat du bouton modifié lorsqu'il est désactivé
Etat du bouton modifié lorsqu'il est désactivé

Changer d’état

Bon… j’avoue ! Dans le chapitre précédent j’ai triché ! Mais ne le dites à personne :p .
Pour faire mes copies d’écrans, je n’ai pas cherché à appuyer sur la touche de copie d’écran tout en maintenant le clic sur le bouton… trop compliqué, je fais attention à l’état… de mes doigts. :-° J’ai donc pour l’occasion changé l’état du bouton par code.
Et oui, il n’y a pas que les actions de l’utilisateur qui peuvent changer l’état d’un contrôle. Il est très simple de changer l’état d’un contrôle avec une ligne de code. On utilise pour cela le VisualStateManager. Par exemple, pour passer sur l’état Pressed, je peux utiliser :

VisualStateManager.GoToState(But, "Pressed", true);

Il suffit de connaître le nom de l’état à atteindre et le tour est joué.

Créer un nouvel état

Et vous savez quoi ? Il est même possible de rajouter des états à un contrôle. Imaginons par exemple que je souhaite que mon bouton puisse être dans un état « TailleReduite » et un autre « TailleNormale » où vous l’aurez compris notre bouton pourra avoir deux apparences différentes en fonction de son état. Ce nouvel état sera bien sûr cumulatif aux autres états, comme Pressed ou Disabled. Comme nous l’avons vu, il va falloir commencer par créer un nouveau groupe d’état.
Pour le rajouter, il suffit de se placer à l’intérieur de la propriété <VisualStateManager.VisualStateGroups>. Et nous aurons par exemple :

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="TailleStates">
        <VisualState x:Name="TailleNormale"/>
        <VisualState x:Name="TailleReduite">
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Width" Storyboard.TargetName="ButtonBackground">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="100"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
    <VisualStateGroup x:Name="CommonStates">
        <VisualState x:Name="Normal"/>
        <VisualState x:Name="MouseOver"/>
        <VisualState x:Name="Pressed">
            …
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Ici, j’ai simplement choisi de réduire la taille de la propriété Width du contrôle Border afin de réduire la taille du bouton. Le voici à la figure suivante donc dans un état Pressed et TailleReduite.

Le bouton est dans l'état Pressed et TailleReduite
Le bouton est dans l'état Pressed et TailleReduite

Pour obtenir cela, dans le XAML, j’aurai toujours mon bouton, mais j’ai également rajouté une case à cocher pour pouvoir positionner l’état réduit :

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <Button x:Name="But" Content="Cliquez-moi !" Style="{StaticResource ButtonStyle1}" />
        <CheckBox Margin="0 100 0 0" Content="Taille réduite ?" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
    </StackPanel>
</Grid>

Avec dans le code-behind :

private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
    VisualStateManager.GoToState(But, "TailleReduite", true);
}

private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
    VisualStateManager.GoToState(But, "TailleNormale", true);
}
  • Un contrôle peut posséder plusieurs états, comme un bouton qui peut être cliqué ou non cliqué.

  • À chaque état est associée une représentation visuelle différente, qu’il est possible de modifier grâce aux templates.

  • Il est possible de créer un nouvel état ou un nouveau groupe d’état pour un contrôle.

  • On change un état par code grâce à la classe VisualStateManager.

  • Blend peut se révéler très utile dans la création ou la modification d’états.

Exemple de certificat de réussite
Exemple de certificat de réussite