Windows 10. Quelques nouveautés intéressantes

Bonjour à tous;

Voici les sources du projet, qui tourne avec Visual Studio 2015 : https://github.com/Mimetis/Windows10Sample 

Aujourd'hui présentation d'une petite application d'exemple Windows 10 permettant d'appréhender les nouveaux contrôles visuels et quelques astuces diverses :)

L'idée c'est de se rapprocher d'une application existante, Xbox music, et d'essayer de la recréer en mode Windows 10 compliant.

Voici ce vers quoi on veut tendre :

Application Windows 10 Musique

Et voici les thèmes que nous allons aborder :

  1. SplitView
  2. ApiInformation
  3. Adaptive Triggers
  4. RelativePanel
  5. Binding Static

Avant de commencer par le SplitView, j'ai essayé de "coller" au thème et voici le rendu final de la page qui correspond au module Découvrir de l'application xbox musique :

Application exemple Windows 10 Application exemple Windows Phone 10

SplitView

Le SplitView introduit un concept de navigation via un menu en overlay et appelable depuis un bouton "hamburger"

Le SplitView est intéressant, notamment sur la navigation car il contient lui même la frame navigable, dans sa propriété Content.

Vous trouverez le code nécessaire dans la vue Views/Shell.xaml

On y aborde :

  • L'utilisation des glyphs Segoe MDL2 Assets.
  • Les différents modes de visualisation (Overlay, CompactOverlay ...)
  • Un mécanisme sommaire de navigation, permettant de "binder" la propriété "Content" du SplitView à la frame en cours.

Voici le code du SplitView coté XAML :

 <SplitView x:Name="SplitView" Background="Black"
        OpenPaneLength="240" CompactPaneLength="60" PaneClosed="SplitView_PaneClosed"
        DisplayMode="CompactOverlay" IsPaneOpen="False"
        PaneBackground="#343434"
        >
    <SplitView.Content>
        <ContentControl Content="{Binding}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"  Background="#EBEBEB" />
    </SplitView.Content>
    <SplitView.Pane>
        <StackPanel x:Name="SplitViewPanePanel">
            <RadioButton x:Name="BackRadioButton"
                Background="Green" Visibility="Visible"
                Tag="" Click="BackRadionButton_Click"
                Style="{StaticResource NavRadioButtonStyle}"
                Content="Back"/>

            <RadioButton x:Name="HamburgerRadioButton" Click="HamburgerRadioButton_Click"
                Style="{StaticResource NavRadioButtonStyle}"
                Tag=""
            Content="Menu" />

            <RadioButton x:Name="HomeRadioButton"
                Tag="" Click="HomeRadioButton_Click"
                Style="{StaticResource NavRadioButtonStyle}"
                Content="Home" />

            <RadioButton x:Name="FriendsRadioButton"
                Tag="" Click="FriendsRadioButton_Click"
                Style="{StaticResource NavRadioButtonStyle}"
                Content="Friends" />
        </StackPanel>
    </SplitView.Pane>
</SplitView>

Notez que l'initialisation de l'objet Shell est faite dans depuis la class App (App.xaml.cs)

 Shell shell = Window.Current.Content as Shell;

if (shell == null)
{
    shell = new Shell();

    if (rootFrame == null)
    {
        rootFrame = new Frame();
        rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];

        rootFrame.NavigationFailed += OnNavigationFailed;

        if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
        }
    }

    shell.DataContext = rootFrame;

    Window.Current.Content = shell;
}

Lorsque vous passez en mode Overlay, le SplitView disparaissant complètement, il est judicieux de placer un bouton hamburger cliquable pour pouvoir le rappeler :

Icone Hamburger Windows Phone 10

C'est ce qui est fait à l'aide d'un ToggleButton:

        <ToggleButton
                x:Name="HamburguerButton"
                Click="HamburguerButton_Click"
                Style="{StaticResource SymbolButton}"
                VerticalAlignment="Top"
                Foreground="Green" 

                Margin="0,5,0,0">
            <ToggleButton.Content>
                <Border Background="Transparent"
                        Width="40"
                        Height="40">
                    <FontIcon Foreground="Green" x:Name="Hamburger"
                              FontFamily="Segoe MDL2 Assets"
                              Glyph="&#xE700;" />
                </Border>
            </ToggleButton.Content>
        </ToggleButton>

ApiInformation

Une grosse nouveauté concernant les capacités différentes entre application tablette et phone.

L'appel de l'ApiInformation va permettre de déterminer si une capacité est présente ou pas sur la plateforme où s'exécute le code.
Pratique quand on veut savoir si on utilise un WP et si le bouton Back est présent :

 if (ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
{
    // Evènement présent
    HardwareButtons.BackPressed += (s, e) => { };
    // Label
    Text2.Text = "Windows Phone Platform";
    Text1.FontSize = 18;
    Text2.FontSize = 18;
}
else
{
    Text2.Text = "Windows Platform";
}

AdaptiveTrigger

Ici Nous allons utiliser les AdaptiveTriggers pour cacher / diminuer / remplacer des élments du layouts pour s'adapter au différents formats de périphériques.

Dans l'exemple, vous trouverez beaucoup d'AdaptiveTriggers :

  1. MainPage.xaml : Disparition du bandeau haut et remplacement par un logo Xbox musique
  2. MainPage.xaml : Disparition du logo Universal App et diminution de la font pour que le message reste lisible sur Phone
  3. Shell.xaml : Disparition de la flèche retour et changement de mode de fonctionnement du SplitView (passage en mode Overlay)

Adaptive Triggers

Voici le VisualStateGroup tiré de la page MainPage.xaml

 <VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="Min">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="1" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="XboxMusicImage.Visibility" Value="Visible" />
                <Setter Target="XboxMusicImage.Height" Value="100" />
                <Setter Target="XboxMusicImage.Stretch" Value="Uniform" />
                <Setter Target="FlipViewImages.Visibility" Value="Collapsed" />
                <Setter Target="UAPImage.Visibility" Value="Collapsed" />
                <Setter Target="TxtOne.FontSize" Value="14" />
                <Setter Target="HLinkOne.FontSize" Value="14" />
            </VisualState.Setters>
        </VisualState>

        <VisualState x:Name="Moy">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="400" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="XboxMusicImage.Visibility" Value="Visible" />
                <Setter Target="FlipViewImages.Visibility" Value="Collapsed" />
                <Setter Target="UAPImage.Visibility" Value="Collapsed" />
                <Setter Target="TxtOne.FontSize" Value="14" />
                <Setter Target="HLinkOne.FontSize" Value="14" />
            </VisualState.Setters>
        </VisualState>

        <VisualState x:Name="Max">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="600" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="XboxMusicImage.Visibility" Value="Collapsed" />
                <Setter Target="FlipViewImages.Visibility" Value="Visible" />
                <Setter Target="UAPImage.Visibility" Value="Visible" />
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Note Importante : Dans mes tests, j'ai remarqué que les VisualState triggers ne fonctionnent que s'ils sont placés immédiatement aprés le premier conteneur de votre page (dans mon cas, la première Grid).
Si vous le placez aprés, ça a tendance à ne pas marcher au poil...

Pour aller plus loin :

Vous pouvez aussi créer vos propres AdaptiveTriggers en héritant de StateTriggerBase :

 public class IsTypePresentStateTrigger : StateTriggerBase
{
}

Vous trouverez un exemple dans WindowsStateTriggers/IsTypePresentStateTrigger.cs

RelativePanel

Une de mes features préférées.

Le RelativePanel simplifie grandement l'agencement des éléments dans votre UI.

Voici le rendu final du bandeau publicitaire :

RelativePanel

Voici un code possible, que j'aurai écrit précédemment, avec utilisation de StackPanel + Grid :

 <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="0,10,0,0">
    <Image VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="None" Source="Assets/UAPIcon.jpg" />
    <Grid VerticalAlignment="Center">
        <Grid.RowDefinitions>
            <RowDefinition Height="0.5*" />
            <RowDefinition Height="0.5*" />
        </Grid.RowDefinitions>
        <TextBlock VerticalAlignment="Center" FontSize="20" Text="Rien que de la musique, toute la musique, sans publicité, à écouter partout" />
        <HyperlinkButton VerticalAlignment="Center" FontSize="20" Grid.Row="1" NavigateUri="https://www.bing.com" Content="Commencer un essai gratuit" />
    </Grid>
</StackPanel>

Et Voici un version à base de RelativePanel:

 <RelativePanel Grid.Row="1" Margin="10,10,0,0" HorizontalAlignment="Stretch">
    <Image VerticalAlignment="Center" Visibility="Collapsed" Name="UAPImage" HorizontalAlignment="Left" Stretch="None" Source="Assets/UAPIcon.jpg" />
    <TextBlock Name="TxtOne" RelativePanel.RightOf="UAPImage"  Foreground="#222222"  VerticalAlignment="Center" FontSize="20" Text="Rien que de la musique, toute la musique, sans publicité, à écouter partout" />
    <HyperlinkButton Name="HLinkOne"  RelativePanel.AlignHorizontalCenterWith="TxtOne" RelativePanel.Below="TxtOne" VerticalAlignment="Center" FontSize="20" Grid.Row="1" NavigateUri="https://www.bing.com" Content="Commencer un essai gratuit" />
</RelativePanel>

On voit vite l'intéret en terme de simplification de l'arbre visuel de votre code XAML :)

Binding Static

Une autre nouveauté apporté aux applications Windows 10 : La possibilité de créer des Binding statiques, permettant d'avoir une liaison forte entre votre source de données et votre ItemsControl :

GridView avec Binding statique

Le code est assez classique mais remarquez l'utilisation de la syntaxe "ItemsSource="{x:Bind Albums, Mode=OneTime} " :

 <GridView Grid.Row="3"
x:Name="GridAlbums"
Padding="10"
ItemsSource="{x:Bind Albums, Mode=OneTime}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True">
    <GridView.ItemTemplate>
        <DataTemplate>
            <RelativePanel>
                <Image  Source="{Binding Icon}" Name="Icon" Width="120" Height="120" Margin="10" Stretch="UniformToFill" />
                <TextBlock RelativePanel.Below="Icon" RelativePanel.AlignLeftWith="Icon" Name="Name" Text="{Binding Name}" Margin="10,8,0,0"  Foreground="#222222"/>
                <TextBlock RelativePanel.Below="Name" RelativePanel.AlignLeftWith="Icon" Text="{Binding Author}"  TextWrapping="NoWrap" Margin="10,8,0,10" Foreground="#646464"/>
            </RelativePanel>
        </DataTemplate>
    </GridView.ItemTemplate>

</GridView>

Un petit test à faire est de récupérer le code généré dans le fichier MainPage.g.cs où l'on peut voir, entre autre :

 // Fields for each control that has bindings.
internal global::Windows.UI.Xaml.Controls.GridView obj6;

// Update method for each path node used in binding steps.
internal void Update_Albums(global::App1.Model.Albums obj, bool isInitialUpdate = false)
{
    if (isInitialUpdate)
    {
        this.SetValue_obj6_ItemsSource(obj);
    }
}

private void SetValue_obj6_ItemsSource(global::App1.Model.Albums obj)
{
    try
    {
        this.obj6.ItemsSource = obj;
    }
    catch {}
}

Pretty Awesome non ? :)

Voilà pour un premier petit tour sur les applications modernes Windows 10.
Bien-sûr tout ceci est sujet à évolution, et je mettrais à jour tout ça aprés la //Build