TabControl, Surface et look Metro
Avec l'apparition du SDK Surface 2, je me suis demandé ce que je pouvais réaliser. Parallèlement j'ai un projet personnel NeoBD qui me permet de gérer ma collection de B.D. Pour donner le contexte, ce projet utilise une base centralisée exposée par des services WCF.
Après le téléchargement du SDK je me suis posé la question : Qu'est ce que je peux faire ? Alors j'ai créé une petite maquette rapide et voilà le résultat :

La maquette n'est pas complète et ne représente pas le fonctionnement entier de l'application. Mais j'ai décidé de faire un menu de taille fixe sur la gauche et un espace libre qui prend le reste de l'écran. Le but, pouvoir glisser plusieurs fiches de Séries. Je vais parler dans ce poste du menu de gauche où j'utilise un TabControl pour gérer les modules "mes B.D.", "manquantes", "menu".
Comment faire pour customiser le TabControl pour qu'il ressemble au menu de gauche.
Son utilisation :
1: <TabControl x:Name="tabcontrol">
2: <TabItem Header="mes B.D.">
3: </TabItem>
4: <TabItem Header="manquantes">
5: </TabItem>
6: <TabItem Header="menu">
7: </TabItem>
8: </TabControl>
On vas changer le template du TabControl :
1: <Style TargetType="TabControl">
2: <Setter Property="Template">
3: <Setter.Value>
4: <ControlTemplate TargetType="TabControl">
5: <Grid KeyboardNavigation.TabNavigation="Local">
6: <mesbdControl:MenuTabPanel
7: Panel.ZIndex="1"
8: Margin="0,0,4,-1"
9: IsItemsHost="True"
10: KeyboardNavigation.TabIndex="1"
11: Background="Transparent" />
12: <Border Margin="70,75,0,0" Panel.ZIndex="100">
13: <ContentPresenter
14: x:Name="PART_SelectedContentHost"
15: Margin="4"
16: ContentSource="SelectedContent" />
17: </Border>
18: </Grid>
19: </ControlTemplate>
20: </Setter.Value>
21: </Setter>
22: </Style>
23:
Dans ce template j'ai ajouté un mesbdControl:MenuTabPanel qui est un contrôle héritant du MenuTabPanel de WPF. Et j'ai ajouté un ContentPresenter pour le contenu du TabItem.
Que fait le contrôle MenuTabPanel ? Il place les onglets. Ce contrôle ressemble à un contrôle Panel, j'ai donc override les méthodes "MeasureOverride" et "ArrangeOverride"
1: protected override Size MeasureOverride(Size constraint)
2: {
3: double height = 50;
4: foreach (UIElement item in this.Children)
5: {
6: item.Measure(constraint);
7:
8: if (item is TabItem)
9: {
10: var tabItem = item as TabItem;
11: if (!tabItem.IsSelected)
12: {
13: height += tabItem.DesiredSize.Width;
14: }
15: }
16: }
17: constraint.Height = height;
18: return constraint;
19: }
MeasureOverride : Je calcul la hauteur nécessaire a l'affichage correcte des éléments.
1: protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
2: {
3: double height = 75;
4:
5: foreach (var item in this.Children)
6: {
7: if (item is TabItem)
8: {
9: var tabItem = item as TabItem;
10: if (tabItem.IsSelected)
11: {
12: tabItem.Arrange(new Rect(new Size(this.DesiredSize.Width, 50)));
13: Animate(tabItem, 50, 0, 0);
14: }
15: else
16: {
17: height += tabItem.DesiredSize.Width;
18: tabItem.Arrange(new Rect(new Size(tabItem.DesiredSize.Width, 50)));
19: Animate(tabItem, 0, height, -90);
20: }
21: }
22: }
23:
24: return arrangeSize;
25: }
ArrangeOverride : Je place et modifie l'angle des TabItem correctement. Pour cela lorsque le TabItem est sélectionné je mets un angle de 0 (Ce qui correspond à un affichage horizontal) et je mets la position sur une valeur fixe x:50 et y:0.
Pour tous les autres TabItem je mets un angle de -90 (Ce qui correspond à un affichage vertical) et je mets la position sur une valeur variable en hauteur calculée sur la somme des largeurs des TabItem non sélectionnées.
La méthode Animate va ajouter une petite animation des TabItems ce qui va donner un effet de changement.
1: private void Animate(TabItem item, double x, double y, double angle)
2: {
3: DoubleAnimation animAngle = new DoubleAnimation(angle, new Duration(TimeSpan.FromMilliseconds(500)));
4: DoubleAnimation animX = new DoubleAnimation(x, new Duration(TimeSpan.FromMilliseconds(500)));
5: DoubleAnimation animY = new DoubleAnimation(y, new Duration(TimeSpan.FromMilliseconds(500)));
6:
7: TransformGroup tc = null;
8: RotateTransform rt = null;
9: TranslateTransform tt = null;
10:
11: if (item.RenderTransform is TransformGroup)
12: {
13: tc = item.RenderTransform as TransformGroup;
14: rt = tc.Children[0] as RotateTransform;
15: tt = tc.Children[1] as TranslateTransform;
16: }
17: else
18:
19: {
20: tc = new TransformGroup();
21: rt = new RotateTransform();
22: tt = new TranslateTransform();
23: tc.Children.Add(rt);
24: tc.Children.Add(tt);
25: item.RenderTransform = tc;
26: }
27:
28: tt.BeginAnimation(TranslateTransform.XProperty, animX);
29: tt.BeginAnimation(TranslateTransform.YProperty, animY);
30: rt.BeginAnimation(RotateTransform.AngleProperty, animAngle);
31: }
En une demie seconde je vais faire un déplacement et une rotation de mon TabItem. Attention si les transformations existent déjà je les réutilise, si non votre control ne s'animera pas de son emplacement précédent.
Il reste a modifier le template du TabItem :
1: <Style TargetType="TabItem">
2: <Setter Property="Template">
3: <Setter.Value>
4: <ControlTemplate TargetType="TabItem">
5: <Grid>
6: <VisualStateManager.VisualStateGroups>
7: <VisualStateGroup x:Name="SelectionStates">
8: <VisualState x:Name="Unselected">
9: </VisualState>
10: <VisualState x:Name="Selected">
11: </VisualState>
12: </VisualStateGroup>
13: <VisualStateGroup x:Name="CommonStates">
14: <VisualState x:Name="Normal" />
15: <VisualState x:Name="MouseOver" />
16: <VisualState x:Name="Disabled">
17: </VisualState>
18: </VisualStateGroup>
19: </VisualStateManager.VisualStateGroups>
20: <TextBlock FontSize="40" FontFamily="Segoe WP"
21: VerticalAlignment="Top"
22: HorizontalAlignment="Left" Foreground="White">
23: <ContentPresenter x:Name="ContentSite"
24: ContentSource="Header"
25: Margin="20,0,0,0"
26: RecognizesAccessKey="True" />
27: </TextBlock>
28: </Grid>
29: <ControlTemplate.Triggers>
30: <Trigger Property="IsSelected" Value="True">
31: <Setter Property="Panel.ZIndex" Value="100" />
32: </Trigger>
33: </ControlTemplate.Triggers>
34: </ControlTemplate>
35: </Setter.Value>
36: </Setter>
37: </Style>
38:
Et pour le moment j'obtiens quelque chose qui ressemble à cela :
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :