windows 10 uwp app development ebook
TRANSCRIPT
1
Windows 10 Universal Windows Platform (UWP)
I. CONTROLES.............................................................................................................................................. 3
1. RELATIVEPANEL .............................................................................................................................................. 3 2. SPLITVIEW ..................................................................................................................................................... 5
a. SplitView de base et Adaptive Triggers ................................................................................................. 5 b. Bouton Rechercher et AutoSuggestBox dans SplitView ......................................................................... 8 c. « Page Header » : Contrôle utilisateur « Barre de titre » avec marge adaptable selon le mode
d’affichage du SplitView ............................................................................................................................... 10 3. NAVIGATION MODEL ...................................................................................................................................... 13 4. BACKBUTTON ............................................................................................................................................... 16 5. COMMANDBAR, SYMBOLICON, … .................................................................................................................... 17 6. MEDIA ELEMENT ET CUSTOM MEDIA TRANSPORT CONTROLS ................................................................................. 20 7. CONTENT DIALOG ......................................................................................................................................... 21 8. AUTOSUGGESTBOX (« REMPLAÇANT DE SEARCHBOX ») ....................................................................................... 22 9. POPUP ........................................................................................................................................................ 23 10. EXTENDED SPASHSCREEN ........................................................................................................................... 23 11. VARIABLESIZEDWRAPGRID ......................................................................................................................... 25
II. ACCENT ET THEME (DARK, LIGHT) .......................................................................................................... 27
DÉFINIR SON PROPRE THÈME .................................................................................................................................... 27
III. DESIGN TIME.......................................................................................................................................... 28
IV. DATATEMPLATESELECTOR ................................................................................................................. 29
DERNIER ELEMENT « AFFICHER PLUS » POUR LISTVIEW OU GRIDVIEW .................................................................... 31
V. HEADERGROUP ...................................................................................................................................... 34
DATATEMPLATES ........................................................................................................................................... 36
POUR LISTVIEW..................................................................................................................................................... 36 POUR GRIDVIEW ................................................................................................................................................... 38
VI. ADAPTIVE UI ...................................................................................................................................... 40
1. ADAPTIVE TRIGGERS ...................................................................................................................................... 41 2. MASTER DETAILS ................................................................................................................................................ 43 3. CUSTOM ADAPTIVE TRIGGERS .......................................................................................................................... 45 4. DEVICEFAMILY ............................................................................................................................................. 48 5. ASTUCE : REACTIVER LES ANIMATIONS, TRANSITIONS WINDOWS ............................................................................ 48
VII. X :BIND « COMPILED BINDING » ........................................................................................................ 49
1. BINDING DE COLLECTION ................................................................................................................................ 49 2. AVEC DATATEMPLATE.................................................................................................................................... 50 3. AVEC DICTIONNAIRE DE RESSOURCES (RESOURCEDICTIONARY) ............................................................................... 50 4. « RELATIVESOURCE » ET « ELEMENTNAME » … LIER AU NOM DE L’ELEMENT ........................................................... 51 5. « SOURCE » ET « DATACONTEXT ».. AJOUTER UNE PROPRIETE DU VIEWMODEL DANS LE CODE-BEHIND ....................... 51 6. BINDING EVENTS .......................................................................................................................................... 52 7. DEFER LOADING ............................................................................................................................................ 53
2
VIII. APPLICATION LIFECYCLE..................................................................................................................... 53
1. HISTORIQUE DE NAVIGATION (« APP ») ............................................................................................................ 54 2. ETATS DE LA PAGE ET DONNEES ........................................................................................................................ 54
IX. TILES, TOASTS ........................................................................................................................................ 56
TILE .................................................................................................................................................................... 56 Adaptive Tiles ............................................................................................................................................... 59
TOAST ................................................................................................................................................................. 60 Adaptive toast .............................................................................................................................................. 60
Documentation, exemples (page de tous les exemples de codes)
Modèles d’application (Visual Studio 2015)
Avec Windows 10, le modèle d’application universelle permet de créer à partir d’un seul projet une
application pour le Windows Store (PC, tablettes et ordinateurs portables) et une application pour le
Windows Phone Store.
Modèle « Universel » Windows 10 (UWP)
Modèles d’applications Windows, Windows Phone et
« Universel » (avec projet « Shared ») Windows 8
Modèle « Universel Windows 8 » :
3 projets (un projet Windows Store,
un Windows Phone et un « Shared »)
Modèle « Universel Windows 10 »
(UWP) : 1 seul projet
3
I. Contrôles Liste des contrôles et exemples
Voir l’exemple « XamlUIBasics »
1. RelativePanel Permet de positionner les éléments les uns par rapport aux autres et de gérer le repositionnement
des éléments avec des AdaptiveTriggers.
Propriétés de placement
Placement par rapport à un élément
Above : Au-dessus de l’élément
Below : En-dessous de l’élément
LeftOf : A gauche de l’élément
RightOf : A droite de l’élément
Plus
AlignHorizontalCenterWith
AlignVerticalCenterWith
AlignBottomWith
AlignTopWith
AlignLeftWith
AlignRightWith
Exemple
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Rectangle x:Name="rectangle1" Fill="Red" Width="300" Height="150" /> <Rectangle x:Name="rectangle2" Fill="Blue" RelativePanel.RightOf="rectangle1" Width="300" Height="150" /> <Rectangle x:Name="rectangle3" Fill="Green" RelativePanel.Below="rectangle2" RelativePanel.AlignHorizontalCenterWith="rectangle2" Width="300" Height="150" /> </RelativePanel>
Rectangle bleu placé à droite du
rectangle rouge
Rectangle vert placé en-dessous du
rectangle bleu (Below) et aligné avec
celui-ci (AlignHorizontalCenterWith)
4
Placement par rapport au RelativePanel
AlignTopWithPanel : En haut (à gauche si pas précisé) du RelativePanel
AlignBottomWithPanel : En bas (et à gauche si pas précisé) du RelativePanel
AlignLeftWithPanel : A gauche (et en haut si pas précisé) du RelativePanel
AlignRightWithPanel : A droite (et en haut si pas précisé) du RelativePanel
Plus pour centrer horizontalement et verticalement un élément par rapport au RelativePanel
AlignHorizontalCenterWithPanel
AlignVerticalCenterWithPanel
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Rectangle x:Name="rectangle1" Fill="Red" Width="300" Height="150" /> <Rectangle x:Name="rectangle2" RelativePanel.AlignRightWithPanel="True" Fill="Blue" Width="300" Height="150" /> <Rectangle x:Name="rectangle3" Fill="Green" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignRightWithPanel="True" Width="300" Height="150" /> </RelativePanel>
Rectangle bleu placé à droite du
RelativePanel (AlignRightWithPanel)
Par défaut les éléments sont placés
à gauche en haut du RelativePanel
Rectangle vert placé à droite
(AlignRightWithPanel) et en bas
(AlignBottomWithPanel)du RelativePanel
5
2. SplitView
a. SplitView de base et Adaptive Triggers
Exemple
DisplayMode
- Overlay : par-dessus le contenu quand ouvert et caché fermé
- CompactOverlay : par-dessus le contenu quand ouvert et avec une barre quand fermé
- Inline : ancré ouvert, caché fermé
- CompactInline : ancré ouvert et avec une barre quand fermé
<SplitView> <SplitView.Pane> <!-- menu --> </SplitView.Pane> <SplitView.Content> <!-- content--> </SplitView.Content> </SplitView>
De 720 à 1024,
« CompactInline » et
panel fermé
Les « Adaptive Triggers »
permettent de basculer le mode
d’affichage du splitview selon la
taille de la page.
Plus de1024 « CompactInline »,
panel ouvert
En-dessous 720, Mode
d’affichage « Overlay »
Le bouton hamburger permet en
plus d’ouvrir, fermer le panel. Il est
placé en dehors du splitview pour ne
pas être caché quand le panel est
fermé en mode « Overlay »
6
SplitView
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="VisualStateMin0"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="0" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="splitView.DisplayMode" Value="Overlay"/> <Setter Target="splitView.IsPaneOpen" Value="False"/> </VisualState.Setters> </VisualState> <VisualState x:Name="VisualStateMin720"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="720" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="splitView.DisplayMode" Value="CompactInline"/> <Setter Target="splitView.IsPaneOpen" Value="False"/> </VisualState.Setters> </VisualState> <VisualState x:Name="VisualStateMin1024"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="1024" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="splitView.DisplayMode" Value="CompactInline"/> <Setter Target="splitView.IsPaneOpen" Value="True"/> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <SplitView x:Name="splitView" DisplayMode="CompactInline" IsPaneOpen="True" OpenPaneLength="320"> <SplitView.Pane> <ListView Margin="0,48,0,0" VerticalAlignment="Stretch" ItemsSource="{x:Bind ViewModel.MenuItems}" ItemTemplate="{StaticResource MenuItemTemplate}" SelectionMode="None" IsItemClickEnabled="True"/> </SplitView.Pane> <SplitView.Content> <Frame x:Name="mainFrame"></Frame> </SplitView.Content> </SplitView> <Button Name="splitViewButton" Style="{StaticResource Square48x48ButtonStyle}" VerticalAlignment="Top" Click="splitViewButton_Click"> <FontIcon FontFamily="{ThemeResource ContentControlThemeFontFamily}" Glyph="≡" FontSize="32" Margin="0,-8,0,0"/> </Button> </Grid>
On peut définir ce que l’on veut en contenu .De
plus on peut omettre « SplitView.Content »
Bouton hamburger
placé en dernier
Marge de 48 par rapport au top
pour laisser la place au bouton
hamburger
Adaptive Triggers, on
change le mode
d’affichage du splitview
selon la taille de la page
7
Template
<DataTemplate x:Key="MenuItemTemplate" x:DataType="local:MenuItem"> <StackPanel Orientation="Horizontal" Margin="2,0,0,0"> <SymbolIcon Symbol="{x:Bind Symbol}"/> <TextBlock Text="{x:Bind Title}" Margin="24,0,0,0" VerticalAlignment="Center"/> </StackPanel> </DataTemplate>
Code-behind
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); ViewModel = new MainPageViewModel(); } public MainPageViewModel ViewModel { get; set; } private void splitViewButton_Click(object sender, RoutedEventArgs e) { splitView.IsPaneOpen = !splitView.IsPaneOpen; } }
Classe utilisée pour le menu
public class MenuItem { public Symbol Symbol { get; set; } public string Title { get; set; } public MenuItem(string title, Symbol symbol) { Title = title; Symbol = symbol; } }
On remplit la collection
public class MainPageViewModel { public ObservableCollection<MenuItem> MenuItems { get; set; } public MainPageViewModel() { MenuItems = new ObservableCollection<MenuItem>(); MenuItems.Add(new MenuItem("Accueil", Symbol.Home)); MenuItems.Add(new MenuItem("Vidéos", Symbol.Video)); MenuItems.Add(new MenuItem("Musiques", Symbol.Audio)); } }
8
b. Bouton Rechercher et AutoSuggestBox dans SplitView
<SplitView x:Name="splitView" DisplayMode="CompactInline" IsPaneOpen="True" OpenPaneLength="320"> <SplitView.Pane> <StackPanel Margin="0,48,0,0"> <Grid Height="48"> <AutoSuggestBox x:Name="autoSuggestBox" Margin="12,0" PlaceholderText="Rechercher" VerticalAlignment="Center" QueryIcon="Find" Visibility="{Binding IsPaneOpen, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=splitView}" /> <Button x:Name="searchButton" Style="{StaticResource Square48x48ButtonStyle}" Visibility="{Binding IsPaneOpen, Converter={StaticResource BooleanToCollapsedConverter}, ElementName=splitView}" Click="searchButton_Click"> <SymbolIcon Symbol="Find" /> </Button> </Grid> <ListView ItemsSource="{x:Bind ViewModel.MenuItems}" ItemTemplate="{StaticResource MenuItemTemplate}" SelectionMode="None" IsItemClickEnabled="True"/> </StackPanel> </SplitView.Pane> <SplitView.Content> <Frame x:Name="mainFrame"></Frame> </SplitView.Content> </SplitView>
Il suffit en plus de permettre au clic sur le bouton rechercher d’ouvrir le panel
private void searchButton_Click(object sender, RoutedEventArgs e) { splitView.IsPaneOpen = true; }
Style du bouton
On lie la visibilité de
l’AutoSuggestBox et du bouton
rechercher à SplitView
IsPaneOpen et on utilise des
converters
AutoSuggestBox quand le
panel est ouvert
Bouton quand le panel est
fermé
On fait en sorte que chaque
élément fasse 48px de haut
9
<Style x:Key="Square48x48ButtonStyle" TargetType="Button"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" /> <Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" /> <Setter Property="Height" Value="48" /> <Setter Property="Width" Value="48" /> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Grid x:Name="RootGrid" Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListLowBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListMediumBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter x:Name="Content" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" AutomationProperties.AccessibilityView="Raw" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
Bouton 48x48 avec
contenu centré
10
c. « Page Header » : Contrôle utilisateur « Barre de titre » avec marge adaptable selon le
mode d’affichage du SplitView Création d’un UserControl « PageHeader » qui pourra être glissé sur chaque page de contenu
(affichée dans la zone de contenu du SplitView) . On pourra personnaliser le contenu de cette barre
de titre pour chaque page, mais surtout permettra de gérer la marge entre le titre et le bouton
hamburger du splitview. En effet, en mode d’affichage Overlay, le titre (s’il est aligné à gauche) et le
bouton hamburger risqueraient de se chevaucher, il faut donc gérer la marge.
<UserControl x:Class="UWPNavigationDemo.Controls.PageHeader" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UWPNavigationDemo.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Height="48" d:DesignHeight="300" d:DesignWidth="400"> <Grid> <Grid x:Name="titleBar"> <ContentPresenter x:Name="content" VerticalAlignment="{x:Bind VerticalContentAlignment}" HorizontalAlignment="{x:Bind HorizontalContentAlignment}" Margin="{x:Bind Padding}" Content="{x:Bind HeaderContent}"/> </Grid> </Grid> </UserControl>
Code-behind du contrôle
public sealed partial class PageHeader : UserControl { public PageHeader() { this.InitializeComponent(); EasyMessenger.Default.Subscribe<bool>("SplitView-DisplayMode-Overlay", (isOverlay)=> { if (isOverlay) { this.titleBar.Margin = new Thickness(overlayMargin, 0, 0, 0); } else { this.titleBar.Margin = new Thickness(defaultMargin, 0, 0, 0); } }); } private const int defaultMargin = 12; private const int overlayMargin = 48; public UIElement HeaderContent { get { return (UIElement)GetValue(HeaderContentProperty); } set { SetValue(HeaderContentProperty, value); } } public static readonly DependencyProperty HeaderContentProperty = DependencyProperty.Register("HeaderContent", typeof(UIElement), typeof(PageHeader), new PropertyMetadata(DependencyProperty.UnsetValue)); }
Hauteur de 48 pour être aligné avec
le bouton hamburger
J’utilise un Messenger pour m’abonner au changement
de « DisplayMode » du SplitView. SI le DisplayMode est
« Overlay » . Je mets une grosse marge (48px) pour que
le bouton hamburger et le titre ne se chevauchent pas
Dependency Property
permettant de personnaliser le
contenu du contrôle selon
chaque page
11
Voir l’exemple XamlNavigation, qui n’utilise pas tout à fait la même méthode, mais il m’a
semblé observer quelques soucis
Dans le code-behind de MainPage
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); ViewModel = new MainPageViewModel(); SizeChanged += (s, e) => { CheckDisplayMode(); }; mainFrame.Navigated += (s, e) => { CheckDisplayMode(); }; // navigation mainFrame.Navigate(typeof(PageOne)); } private void CheckDisplayMode() { var displayMode = splitView.DisplayMode; if (displayMode == SplitViewDisplayMode.Overlay) { // messenger EasyMessenger.Default.Publish("SplitView-DisplayMode-Overlay", true); } else { EasyMessenger.Default.Publish("SplitView-DisplayMode-Overlay", false); } } // etc.
}
PageOne
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> </Grid.RowDefinitions> <Controls:PageHeader HorizontalAlignment="Left"> <Controls:PageHeader.HeaderContent> <TextBlock Text="Titre de la page" Style="{StaticResource TitleTextBlockStyle}" /> </Controls:PageHeader.HeaderContent> </Controls:PageHeader> <Grid Background="#ccc" Grid.Row="1"> </Grid> </Grid>
Abonnement au changement de taille de la page
et en fin de navigation de la frame
« mainFrame ».
On vérifie le mode d’affichage du SPlitView et
notifie par Messenger
Utilisation du contrôle, ajout
d’un simple TextBlock avec un
titre aligné à gauche
12
Marge de 12 en « CompactInline »
(panel ouvert et fermé)
Marge de 48 en mode « Overlay » pour éviter que
le titre et le bouton hamburger se chevauchent
13
3. Navigation model Documentation
Voir l’exemple XamlNavigation
La navigation doit se faire dans deux sens. La barre de navigation doit être « synchronisée »
(navigation retour) avec les pages affichées.
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var MenuItems = new ObservableCollection<MenuItem>(); MenuItems.Add(new MenuItem("HomePage", "Accueil", Symbol.Home)); MenuItems.Add(new MenuItem("VideosPage", "Vidéos", Symbol.Video)) ; MenuItems.Add(new MenuItem("MusicsPage", "Musiques", Symbol.Audio)); menusListView.ItemsSource = MenuItems; mainFrame.Navigated += (s, e) => { // affiche le bouton if (mainFrame.CanGoBack) { SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible; } else { SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed; } // sélectionne le menu correspondant à la page if (e.NavigationMode == NavigationMode.Back) { string pageName = e.SourcePageType.Name; foreach (var item in menusListView.Items) { var menu = item as MenuItem; if (menu.Id == pageName) { menusListView.SelectedItem = item; return; } } } }; // Gère la navigation retour SystemNavigationManager.GetForCurrentView().BackRequested += (s, a) => { if (mainFrame.CanGoBack) { mainFrame.GoBack(); a.Handled = true; } }; }
Navigation « Retour »
14
private void ListView_ItemClick(object sender, ItemClickEventArgs e) { var menu = e.ClickedItem as MenuItem; if (menu.Id == "HomePage") { mainFrame.Navigate(typeof(HomePage)); } if (menu.Id == "VideosPage") { mainFrame.Navigate(typeof(VideosPage)); } if (menu.Id == "MusicsPage") { mainFrame.Navigate(typeof(MusicsPage)); } } protected override void OnNavigatedTo(NavigationEventArgs e) { // navigation mainFrame.Navigate(typeof(HomePage)); menusListView.SelectedIndex = 0; } }
public class MenuItem { public string Id { get; set; } public Symbol Symbol { get; set; } public string Title { get; set; } public MenuItem(string id,string title, Symbol symbol) { Id = id; Title = title; Symbol = symbol; } }
La ListView modifiée en sélection « Single »
<ListView x:Name="menusListView" ItemTemplate="{StaticResource MenuItemTemplate}" SelectionMode="Single" IsItemClickEnabled="True" ItemClick="ListView_ItemClick"/>
Navigation « Aller »
Ajout d’un paramètre « Id »
permettant de retrouver la page
correspondante au menu
15
Navigation « Aller »
Navigation « Retour », après avoir cliqué sur le bouton retour de la barre de titre, le menu est
sélectionné et synchronisé par rapport à la page affichée.
Il faut garder en tête ici que c’est un scénario simple. On pourrait par exemple imaginer créer un
paramètre spécifique à la navigation avec des propriétés permettant de mieux gérer celle-ci
(exemple la page doit elle être prise en compte dans la navigation par la barre de menus?)
On peut également observer Template10, qui crée un contrôle « HamburgerMenu ».
16
4. BackButton
Voir l’exemple BackButton
Affichage d’un bouton dans la barre de titre (Desktop)
Dans « App » pour « rootFrame » (ou dans le code de la page pour une Frame particulière)
protected override void OnLaunched(LaunchActivatedEventArgs e) { // etc. Window.Current.Activate(); // Affiche ou masque le bouton retour de la barre de titre rootFrame.Navigated += (s, a) => { if (rootFrame.CanGoBack) { SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible; } else { SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed; } }; // Gère la navigation retour SystemNavigationManager.GetForCurrentView().BackRequested += (s, a) => { if (rootFrame.CanGoBack) { rootFrame.GoBack(); a.Handled = true; } }; }
Page d’accueil
Navigation vers une autre page, le bouton retour apparait
dans la barre de titre. La couleur correspond à l’accent
sélectionné des paramètres de personnalisation de
Windows 10
17
5. CommandBar, SymbolIcon, …
Voir l’exemple XamlCommanding et XamlUIBasics
CommandBar
<CommandBar> <CommandBar.Content> <TextBlock Text="Titre" /> </CommandBar.Content> <AppBarButton Icon="Like" Label="Like" /> <!--etc.--> <!-- Separator --> <AppBarSeparator/> <!-- MenuFlyout --> <AppBarButton Icon="OpenWith" Label="Show Flyout"> <AppBarButton.Flyout> <MenuFlyout> <MenuFlyoutItem Text="Option 1"/> <MenuFlyoutItem Text="Option 2"/> </MenuFlyout> </AppBarButton.Flyout> </AppBarButton> <!-- AppBarToggleButton --> <AppBarToggleButton Icon="Contact" Label="Contact" IsChecked="True"/> <!-- Secondary --> <CommandBar.SecondaryCommands> <AppBarButton Icon="Setting" Label="Settings"/> </CommandBar.SecondaryCommands> </CommandBar>
Menu Flyout (apparait au-dessus ou en dessous la barre selon l’espace disponible)
On peut définir « IsOpen » pour
afficher ou non les labels
Les commandes secondaires et labels apparaissent
quand on clique sur le bouton « … »
« Content » à gauche
de la barre
Commandes « primaires »
placées à droite de la barre
Commandes « secondaires »
placées dans menu ouvert
avec le bouton « … »
18
« IconElement » (SymbolIcon, FontIcon, ou PathIcon)
<AppBarButton Icon="Like" Label="Like" />
SymbolIcon Liste des symboles disponibles
<AppBarButton Label="Dislike"> <AppBarButton.Icon> <SymbolIcon Symbol="Dislike" Foreground="Red"/> </AppBarButton.Icon> </AppBarButton>
FontIcon permet de définir un symbole qui n’est pas inclus dans SymbolIcon (on peut s’aider avec la
« table des caractères »
<AppBarButton Label="FontIcon"> <AppBarButton.Icon> <FontIcon FontFamily="Candara" Glyph="Σ"/> </AppBarButton.Icon> </AppBarButton>
PathIcon
<AppBarButton Label="PathIcon"> <AppBarButton.Icon> <PathIcon Data="F1 M 16,12 20,2L 20,16 1,16" HorizontalAlignment="Center"/> </AppBarButton.Icon> </AppBarButton>
BitmapIcon
<AppBarButton Label="BitmapIcon"> <AppBarButton.Icon> <BitmapIcon UriSource="ms-appx:///Assets/YouTube.png" /> </AppBarButton.Icon> </AppBarButton>
Custom
<AppBarButton Label="Custom" HorizontalContentAlignment="Center"> <Grid Width="48" Height="48" Margin="0,-8,0,-4"> <SymbolIcon Symbol="Memo"/> <TextBlock Text="2" Margin="0,2,0,0" Style="{StaticResource CaptionTextBlockStyle}" HorizontalAlignment="Center"/> </Grid> </AppBarButton>
19
Top et bottom AppBar
(Peuvent être ajoutées rapidement depuis le panneau « Structure du document »)
<Page.TopAppBar> <CommandBar x:Name="topBar" ClosedDisplayMode="Compact"> <CommandBar.Content> <StackPanel Orientation="Horizontal"> <AppBarButton Icon="Home" /> <!--etc.--> </StackPanel> </CommandBar.Content> <AppBarButton Icon="Accept" Label="Valider"/> <!--etc.--> </CommandBar> </Page.TopAppBar> <Page.BottomAppBar> <CommandBar> <AppBarButton Icon="Accept" Label="Valider"/> <!--etc.--> </CommandBar> </Page.BottomAppBar>
On peut changer le mode d’affichage :
- Compact (par défaut)
- Minimal (n’affiche plus que le bouton « … » permettant d’afficher les éléments de la barre)
- Hidden la barre est complètement cachée.
TopAppBar
BottomAppBar
20
6. Media Element et Custom media transport controls Documentation
Source
<MediaElement x:Name="mediaElement" Source="/Assets/dhany.mp4" AreTransportControlsEnabled="True" />
SetSource
<MediaElement x:Name="mediaElement" AreTransportControlsEnabled="True" />
Ouverture d’une boite de dialogue
private async void button_Click(object sender, RoutedEventArgs e) { var picker = new FileOpenPicker(); picker.FileTypeFilter.Add(".wmv"); picker.FileTypeFilter.Add(".mp4"); picker.FileTypeFilter.Add(".mp3"); picker.FileTypeFilter.Add(".wma"); picker.SuggestedStartLocation = PickerLocationId.VideosLibrary; var file = await picker.PickSingleFileAsync(); if (file != null) { IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read); mediaElement.SetSource(stream, file.ContentType); } }
Il est possible de customiser les contrôles, en ajouter (un bouton like en plus par exemple)
Voir l’exemple XamlCustomMediaTransportControls
21
7. Content Dialog Menu « Ajouter » … « Nouvel élément » … « Boite de dialogue de contenu »
Desktop Mobile
Exemple de « ContentDialog »
<ContentDialog x:Class="UWPContentDialogDemo.MyContentDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UWPContentDialogDemo" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="Ecrire une note" PrimaryButtonText="Envoyer" SecondaryButtonText="Annuler" PrimaryButtonClick="ContentDialog_PrimaryButtonClick" SecondaryButtonClick="ContentDialog_SecondaryButtonClick"> <StackPanel> <TextBox Header="Titre"></TextBox> <TextBox TextWrapping="Wrap" AcceptsReturn="True" Header="Contenu" Height="100"></TextBox> </StackPanel> </ContentDialog>
Afficher la boite de dialogue et gérer le résultat
private async void Button_Click(object sender, RoutedEventArgs e) { var dialog = new MyContentDialog(); var dialogResult = await dialog.ShowAsync(); if(dialogResult == ContentDialogResult.Primary) { } else if (dialogResult == ContentDialogResult.Secondary) { } }
La boite de dialogue s’adapte
selon la taille de la page.
Par défaut la boite occupe toute la
page mais on peut la
redimensionner depuis le designer
Contenu
Titre et boutons de la
boite de dialogue
22
8. AutoSuggestBox (« remplaçant de SearchBox »)
Voir les exemples XamlUIBasics et XamlAutoSuggestBox
<AutoSuggestBox x:Name="autoSuggestBox" Margin="0,8,12,0" Width="270" PlaceholderText="Entrer le nom d'une ville Française" QueryIcon="Find" TextChanged="autoSuggestBox_TextChanged" SuggestionChosen="autoSuggestBox_SuggestionChosen" QuerySubmitted="autoSuggestBox_QuerySubmitted" RelativePanel.AlignRightWithPanel="True" />
private void autoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) { var suggestions = GetSuggestions(sender.Text); sender.ItemsSource = suggestions; } } private void autoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { var selectedItem = args.SelectedItem.ToString(); suggestionChosenTextBlock.Text = "Suggestion choisie : " + selectedItem; } private void autoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (args.ChosenSuggestion != null) { } else if (!string.IsNullOrEmpty(args.QueryText)){ } } private List<string> GetSuggestions(string query) { var suggestions = cities.FindAll(c => c.ToLower().StartsWith(query.ToLower())); return suggestions; }
On change la liste des
suggestions selon le texte
saisi
Déclenché lorsque l’utilisateur sélectionne une suggestion de la liste proposée
23
private List<string> cities = new List<string> { "Lyon", "Marseille", "Nantes", "Nice", "Strasbourg", "Toulouse", "Paris" };
9. Popup Exemple popup « chargement » affichée pendant le chargement des données
<Popup IsOpen="{Binding IsBusy}" VerticalAlignment="Center" HorizontalAlignment="Center"> <Grid Background="{ThemeResource ContentDialogBorderThemeBrush}" Height="100" Width="200"> <Grid.RenderTransform> <TranslateTransform X="-100" Y="-50" /> </Grid.RenderTransform> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"> <ProgressRing VerticalAlignment="Center" IsActive="True" Foreground="{ThemeResource ContentDialogDimmingThemeBrush}" /> <TextBlock x:Uid="loadingTextBlock" Foreground="{ThemeResource ContentDialogDimmingThemeBrush}" Text="Loading" FontSize="22" FontWeight="Light" Margin="12,0,0,0" /> </StackPanel> </Grid> </Popup>
10. Extended SpashScreen Ajout dans la méthode « OnLaunched » de « App »
if (e.PreviousExecutionState != ApplicationExecutionState.Running) { bool loadState = (e.PreviousExecutionState == ApplicationExecutionState.Terminated); var splash = new ExtendedSplash(e.SplashScreen, loadState); rootFrame.Content = splash; Window.Current.Content = rootFrame; }
Création d’une page avec image et progress ring.
<Grid Background="#D03133" > <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="180"/> </Grid.RowDefinitions> <Canvas Grid.RowSpan="2"> <Image x:Name="extendedSplashImage" Source="Assets/SplashScreen.png"/> </Canvas> <ProgressRing IsActive="True" Grid.Row="1" Width="80" Height="80" Foreground="White" HorizontalAlignment="Center" /> </Grid>
Place correctement au centre de la page
24
La taille de l’image et les éléments sont repositionnés selon la taille de la page
public sealed partial class ExtendedSplash { internal Rect splashImageRect; private SplashScreen splash; private double ScaleFactor; public ExtendedSplash(SplashScreen splashscreen, bool loadState) { InitializeComponent(); Window.Current.SizeChanged += new WindowSizeChangedEventHandler(OnResize); ScaleFactor = (double)DisplayInformation.GetForCurrentView().ResolutionScale / 100; splash = splashscreen; if (splash != null) { splash.Dismissed += new TypedEventHandler<SplashScreen, Object>(OnDismissed); splashImageRect = splash.ImageLocation; PositionImage(); } } private void PositionImage() { extendedSplashImage.SetValue(Canvas.LeftProperty, splashImageRect.Left); extendedSplashImage.SetValue(Canvas.TopProperty, splashImageRect.Top); extendedSplashImage.Height = splashImageRect.Height / ScaleFactor; extendedSplashImage.Width = splashImageRect.Width / ScaleFactor; } private void OnResize(Object sender, WindowSizeChangedEventArgs e) { if (splash != null) { splashImageRect = splash.ImageLocation; PositionImage(); } } private async void OnDismissed(SplashScreen sender, object e) { // on peut effectuer un chargement, essayer de connecter l’utilisateur, etc. par exemple puis naviguer vers la page principale await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { WNavigationService.Default.Navigate(typeof(MainPage)); }); } }
25
11. VariableSizedWrapGrid <VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="4" ItemHeight="100" ItemWidth="100"> <Rectangle Fill="Green" Width="200" Height="200" VariableSizedWrapGrid.RowSpan="2" VariableSizedWrapGrid.ColumnSpan="2" Margin="4"/> <Rectangle Fill="Gray" Width="200" VariableSizedWrapGrid.ColumnSpan="2" Margin="4"/> <Rectangle Fill="Red" Margin="4"/> <Rectangle Fill="Blue" Margin="4"/> </VariableSizedWrapGrid>
La même chose pour un GridView
public class VariableGridView : GridView { protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { var variableItem = item as VariableSizedItem; if (variableItem != null) { var gridViewItem = element as GridViewItem; if (gridViewItem != null) { VariableSizedWrapGrid.SetColumnSpan(gridViewItem, variableItem.ColumnSpan); VariableSizedWrapGrid.SetRowSpan(gridViewItem, variableItem.RowSpan); } } base.PrepareContainerForItemOverride(element, item); } }
Le modèle
public class VariableItem { public string Title { get; set; } public SolidColorBrush BackgroundColor { get; set; } public int Height { get; set; } public int Width { get; set; } public int ColumnSpan { get; set; } public int RowSpan { get; set; } // etc. }
On peut changer la largeur,
hauteur et ainsi définir
combien de colonnes, lignes
sont occupées
Propriétés liées pour définir l’élément
dans le GridView, il pourrait avoir
d’autres propriétés avec des données
récupérées
On dérive le GridView et redéfinit
« PrepareContainerForItemOverride »
On change le nombre de colonnes
et lignes en rapport à l’élément
« VariableItem » (modèle défini)
Chaque élément de base a une taille de
100x100. Ils s’empilent horizontalement
(orientation) dans la limite de 4
26
Ajout à la page
<local:VariableGridView x:Name="variableGridView"> <local:VariableGridView.ItemTemplate> <DataTemplate> <Grid Background="{Binding BackgroundColor}" Height="{Binding Height}" Width="{Binding Width}" Margin="4"> <TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </DataTemplate> </local:VariableGridView.ItemTemplate> <local:VariableGridView.ItemsPanel> <ItemsPanelTemplate> <VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="4" ItemHeight="100" ItemWidth="100"/> </ItemsPanelTemplate> </local:VariableGridView.ItemsPanel> </local:VariableGridView>
Dans le code-behind de la page
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); Items = new List<VariableItem>(); Items.Add(new VariableItem { Title = "First", BackgroundColor = new SolidColorBrush(Colors.Green), Height = 200, Width = 200, ColumnSpan = 2, RowSpan = 2 }); Items.Add(new VariableItem { Title = "Second", BackgroundColor = new SolidColorBrush(Colors.Gray), Width = 200, Height = 100, ColumnSpan = 2 }); Items.Add(new VariableItem { Title = "Three", BackgroundColor = new SolidColorBrush(Colors.Red), Height = 100, Width = 100, }); Items.Add(new VariableItem { Title = "Four", BackgroundColor = new SolidColorBrush(Colors.Blue), Height = 100, Width = 100, }); variableGridView.ItemsSource = Items; } public List<VariableItem> Items { get; set; } }
27
II. Accent et Thème (Dark, Light) <Rectangle Fill="{ThemeResource SystemAccentColor}" Grid.Row="1"></Rectangle>
On peut changer le thème (« Light » ou « Dark ») pour toute l’application dans « app »
<Application x:Class="UWPAutoSuggestBoxDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UWPAutoSuggestBoxDemo" RequestedTheme="Light"> </Application>
Ou pour seulement des éléments ou contrôles avec l’attribut « RequestedTheme », exemple
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" RequestedTheme="Dark"> <!--etc--> </RelativePanel>
Définir son propre thème <Application.Resources> <ResourceDictionary> <ResourceDictionary.ThemeDictionaries> <ResourceDictionary x:Key="Default"> <SolidColorBrush x:Key="BackgroundBrush" Color="Red" /> </ResourceDictionary> <ResourceDictionary x:Key="Light"> <SolidColorBrush x:Key="BackgroundBrush" Color="Gray" /> </ResourceDictionary> <ResourceDictionary x:Key="HighContrast"> <SolidColorBrush x:Key="BackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" /> </ResourceDictionary> </ResourceDictionary.ThemeDictionaries> </ResourceDictionary> </Application.Resources>
Thème Dark, la couleur
sera rouge
Thème Light, la couleur
sera gris
28
On peut également définir les thèmes dans des dictionnaires de ressources et les rendre accessibles à
l’application
<Application x:Class="UWPThemesDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UWPThemesDemo" RequestedTheme="Dark"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.ThemeDictionaries> <ResourceDictionary Source="Resources/Dark.xaml" x:Key="Default" /> <ResourceDictionary Source="Resources/Light.xaml" x:Key="Light" /> </ResourceDictionary.ThemeDictionaries> </ResourceDictionary> </Application.Resources> </Application>
Utilisation
<Grid Background="{ThemeResource BackgroundBrush}"> </Grid>
III. Design Time public class MainPageViewModel : ViewModelBase { public MainPageViewModel() { if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) { } } }
29
IV. DataTemplateSelector Permet d’afficher différents Templates dans une ListView ou un GridView pour une même source de
données.
public class SearchResultTemplateSelector : DataTemplateSelector { public DataTemplate VideoTemplate { get; set; } public DataTemplate ChannelTemplate { get; set; } public DataTemplate PlayListTemplate { get; set; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { if(item is Video) { return VideoTemplate; } if (item is Channel) { return ChannelTemplate; } if (item is PlayList) { return PlayListTemplate; } return base.SelectTemplateCore(item, container); } }
Exemple de Templates
<DataTemplate x:Key="YouTube320VideoItemTemplate" x:DataType="models:Video"> <!--etc--> </DataTemplate> <DataTemplate x:Key="YouTubeChannelItemTemplate" x:DataType="models:Channel"> <!--etc--> </DataTemplate> <DataTemplate x:Key="YouTubePlaylistItemTemplate" x:DataType="models:PlayList"> <!--etc--> </DataTemplate>
Models
public class ModelBase { // properties }
public class Video :ModelBase { // } public class Channel :ModelBase { // } public class PlayList :ModelBase { // }
3 propriétés permettant de
définir un Template à retourner
selon le type de l’élément reçu
Les 3 classes peuvent hériter d’une
classe de base. Ainsi la source de
données sera une collection de
« ModelBase »
30
ViewModel
public class SearchPageViewModel : ViewModelBase { private ObservableCollection<ModelBase> _results; public ObservableCollection<ModelBase> Results { get { return _results; } set { SetProperty(ref _results, value); } } // etc. }
Utilisation
En ressources (de la page par exemple)
<local:SearchResultTemplateSelector x:Key="TemplateSelector" VideoTemplate="{StaticResource YouTubeVideoItemTemplate}" ChannelTemplate="{StaticResource YouTubeChannelItemTemplate}" PlayListTemplate="{StaticResource YouTubeSearchPlayListItemTemplate}"> </local:SearchResultTemplateSelector>
Puis définition de l’ « ItemTemplateSelector »
<GridView x:Name="itemsGridView" ItemsSource="{x:Bind ViewModel.Results,Mode=OneWay}" ItemTemplateSelector="{StaticResource TemplateSelector}"> </GridView>
Le GridView affiche un Template
différent selon que c’est une
vidéo, une playlist ou une chaine
par exemple
31
Dernier élément « Afficher plus » pour ListView ou GridView Sur ce même principe on peut imaginer un « last item » template qui afficherait un bouton afficher
plus par exemple.
public class LastTemplateSelector : DataTemplateSelector { public DataTemplate NormalTemplate { get; set; } public DataTemplate LastTemplate { get; set; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { if (item is DataItem) { return NormalTemplate; } if (item is LastItem) { return LastTemplate; } return base.SelectTemplateCore(item, container); } }
Pour les modèles on a deux classes héritant de « ModelBase
public class ModelBase { public string Title { get; set; } } public class LastItem : ModelBase { public LastItem(string title) { Title = title; } } public class DataItem : ModelBase { public int Id { get; set; } public string Subtitle { get; set; } // etc. }
32
ViewModel
public class MainPageViewModel { public ObservableCollection<ModelBase> Items { get; set; } private ICommand _goDetailsPageCommand; public ICommand GoDetailsPageCommand { get { return _goDetailsPageCommand ?? (_goDetailsPageCommand = new RelayCommand<ItemClickEventArgs>((item) => { var selectedItem = item.ClickedItem; if(selectedItem is DataItem) { // navigate to data } if(selectedItem is LastItem) { // load more data } })); } } public MainPageViewModel() { Items = new ObservableCollection<ModelBase>(); Items.Add(new DataItem(1, "Titre 1", "Subtile", "Lorem ipsum dolor sit …", "Images/group.jpg")); Items.Add(new DataItem(2, "Titre 2", "Subtile", "Lorem ipsum dolor sit …", "Images/group_2.jpg")); // etc. // Last Items.Add(new LastItem("Afficher plus")); } }
On ajoute dans le code behind de la page une propriété pour le binding (avec x :Bind)
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); ViewModel = new MainPageViewModel(); } public MainPageViewModel ViewModel { get; set; } }
Quand l’utilisateur clique sur
un élément du GridView soit
on le diriga vers la page détails
soit on charge plus d’éléments.
(Il faudra les insérer avant le
bouton)
33
En ressources de la page
<local:LastTemplateSelector x:Key="LastTemplateSelector" NormalTemplate="{StaticResource ImageOverlayTemplate}" LastTemplate="{StaticResource LastTemplate}"/>
On définit un template simple pour le type « LastItem »
<DataTemplate x:Key="LastTemplate" x:DataType="models:LastItem"> <Grid Width="200" Height="200" Background="{ThemeResource SystemAccentColor}" Margin="4"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </DataTemplate>
Le GridView avec ItemTemplateSelector et behavior
<GridView x:Name="itemsListView" ItemsSource="{x:Bind ViewModel.Items}" ItemTemplateSelector="{StaticResource LastTemplateSelector}" SelectionMode="None" IsItemClickEnabled="True"> <Interactivity:Interaction.Behaviors> <Core:EventTriggerBehavior EventName="ItemClick"> <Core:InvokeCommandAction Command="{x:Bind ViewModel.GoDetailsPageCommand}" CommandParameter="{x:Bind itemsListView.SelectedItem}"/> </Core:EventTriggerBehavior> </Interactivity:Interaction.Behaviors> </GridView>
34
V. HeaderGroup
Models
public sealed class DataSource { public static IEnumerable<DataGroup> GetGroups() { var groups = new List<DataGroup>(); var groupA = new DataGroup("A"); groupA.Items.Add(new DataItem("Aanor")); groupA.Items.Add(new DataItem("Aaricia")); groupA.Items.Add(new DataItem("Aaron")); // etc. groups.Add(groupA); var groupB = new DataGroup("B"); groupB.Items.Add(new DataItem("Babet")); groupB.Items.Add(new DataItem("Babeth")); // etc. groups.Add(groupB); // etc. return groups; } } public class DataGroup { public string TitleGroup { get; private set; } public ObservableCollection<DataItem> Items { get; set; } public DataGroup(string titleGroup) { TitleGroup = titleGroup; Items = new ObservableCollection<DataItem>(); } } public class DataItem { public string TitleItem { get; set; } public DataItem(string titleItem) { TitleItem = titleItem; } }
Switch du nom de groupe au
défilement
35
ViewModel
public class MyDataViewModel { public ObservableCollection<DataGroup> Groups { get; set; } public void LoadData() { var groups = DataSource.GetGroups(); if (groups != null) { Groups = new ObservableCollection<DataGroup>(groups); } } }
Utilisation
Code behind de la page
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); ViewModel = new MyDataViewModel(); } public MyDataViewModel ViewModel { get; set; } protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.LoadData(); } }
<ListView ItemsSource="{x:Bind cvs.View}" ItemTemplate="{StaticResource DataItemTemplate}"> <ListView.GroupStyle> <GroupStyle HeaderTemplate="{StaticResource HeaderTemplate}" /> </ListView.GroupStyle> </ListView>
Templates et source de données groupée
<Page.Resources> <CollectionViewSource x:Name="cvs" Source="{x:Bind ViewModel.Groups}" IsSourceGrouped="True" ItemsPath="Items" /> <DataTemplate x:Key="HeaderTemplate" x:DataType="models:DataGroup"> <TextBlock Text="{x:Bind TitleGroup}" Foreground="{ThemeResource ApplicationForegroundThemeBrush}" Style="{StaticResource SubtitleTextBlockStyle}"/> </DataTemplate> <DataTemplate x:Key="DataItemTemplate" x:DataType="models:DataItem"> <TextBlock Text="{x:Bind TitleItem}" Style="{StaticResource BaseTextBlockStyle}"/> </DataTemplate> </Page.Resources>
Utilisation ici d’une ListView
mais on peut faire la même
chose avec un GridView
36
DataTemplates
Pour ListView Texte seul
<DataTemplate x:Key="TextListTemplate" x:DataType="models:DataItem"> <Grid Width="280"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" Margin="8,0,0,0" HorizontalAlignment="Left" TextWrapping="Wrap"/> </Grid> </DataTemplate>
Icone et texte
<DataTemplate x:Key="IconTextTemplate" x:DataType="models:DataItem"> <StackPanel Orientation="Horizontal" Width="500"> <Image Height="45" Width="45" Margin="0,8,0,8" Source="{x:Bind Image}" Stretch="UniformToFill"/> <StackPanel Orientation="Vertical" VerticalAlignment="Top" Margin="8,8,0,0"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" /> <TextBlock Text="{x:Bind Subtitle}" Margin="0,4,8,0" Style="{StaticResource BodyTextBlockStyle}" /> </StackPanel> </StackPanel> </DataTemplate>
37
Image et texte
<DataTemplate x:Key="ImageTextListTemplate" x:DataType="models:DataItem"> <StackPanel Orientation="Horizontal" Width="500" Height="130"> <Image Height="110" Width="110" Margin="0,8,0,8" Source="{x:Bind Image}" Stretch="UniformToFill"/> <StackPanel VerticalAlignment="Center" Width="380" Margin="8,8,0,0"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" /> <TextBlock Text="{x:Bind Subtitle}" TextWrapping="WrapWholeWords" Style="{StaticResource CaptionTextBlockStyle}" /> <TextBlock Text="{x:Bind Description}" TextWrapping="WrapWholeWords" Margin="0,8,0,0" Style="{StaticResource BodyTextBlockStyle}"/> </StackPanel> </StackPanel> </DataTemplate>
Overlay
<DataTemplate x:Key="ImageOverlayTemplate" x:DataType="models:DataItem"> <Grid Height="110"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="300"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Image Source="{x:Bind Image}" Stretch="Uniform" Grid.Column="1" Grid.RowSpan="2" Margin="0,8,0,8"/> <Border Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" Margin="0,8,0,8"> <TextBlock Text="{x:Bind Title}" Margin="8,8,0,0" TextWrapping="Wrap" HorizontalAlignment="Left" Style="{StaticResource BaseTextBlockStyle}"/> </Border> <TextBlock Text="{x:Bind Subtitle}" Grid.Row="1" Style="{StaticResource BodyTextBlockStyle}" TextWrapping="Wrap" Margin="8,0,0,0"/> </Grid> </DataTemplate> </Page.Resources>
38
Pour GridView Texte seul
<DataTemplate x:Key="TextTemplate" x:DataType="models:DataItem"> <StackPanel Orientation="Horizontal" Width="300"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" Margin="8,0,0,0"/> </StackPanel> </DataTemplate>
Icone et texte
<DataTemplate x:Key="IconTextTemplate" x:DataType="models:DataItem"> <StackPanel Orientation="Horizontal" Width="280"> <Image Source="{x:Bind Image}" Width="45" Height="45" Margin="8" Stretch="UniformToFill"/> <StackPanel VerticalAlignment="Center" Margin="8,0"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" /> <TextBlock Text="{x:Bind Subtitle}" /> </StackPanel> </StackPanel> </DataTemplate>
Image et texte
<DataTemplate x:Key="ImageTextTemplate" x:DataType="models:DataItem"> <StackPanel Orientation="Horizontal" Width="500" Height="130"> <Image Source="{x:Bind Image}" Stretch="Fill" Height="110" Width="110" Margin="8,8,0,8"/> <StackPanel Width="350" Margin="8,8,0,0" VerticalAlignment="Center"> <TextBlock Text="{x:Bind Title}" Style="{StaticResource BaseTextBlockStyle}" /> <TextBlock Text="{x:Bind Subtitle}" Style="{StaticResource CaptionTextBlockStyle}"/> <TextBlock Text="{x:Bind Description}" TextWrapping="Wrap" Margin="0,8,0,0"/> </StackPanel> </StackPanel> </DataTemplate>
39
Overlay
<DataTemplate x:Key="ImageOverlayTemplate" x:DataType="models:DataItem"> <StackPanel Height="130" Width="190" Margin="4,4,4,8"> <TextBlock Text="{x:Bind Title}" Margin="8,4" Width="186" Style="{StaticResource BaseTextBlockStyle}" HorizontalAlignment="Left"/> <Image Source="{x:Bind Image}" Margin="8,0,8,8" Stretch="UniformToFill"/> </StackPanel> </DataTemplate>
40
VI. Adaptive UI Une application UWP doit pouvoir s’exécuter sur différents appareils. Il va falloir adapter la page
pour les différentes résolutions et orientations (Portrait, paysage).
Penser en Pixel effectif (et non en pixel réel)
« Four is the magic number » (tailles, marges multiples de 4)
« 6R »
Reposition
Resize
Reflow (basculer de 2 à 3 colonnes par exemple)
Reveal
Replace
Re-architect
Utilisation de RelativePanel pour pouvoir repositionner les éléments
Utilisation Visual States et AdaptiveTrigger pour déplacer des blocs ou éléments, redimensionner,
cacher, etc.
Snap points :
- 320
- 548 Phone
- 720 Tablet
- 1024 Desktop
Astuce : afficher la taille courante de la page
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.SizeChanged += (s, e) => { textBlock.Text = e.NewSize.Width.ToString(); }; }
41
1. Adaptive Triggers
Voir exemple Responsive Techniques
1. Ouvrir le projet avec Blend
2. Ajouter des Etats Visuels
Panneau Etats : Ajouter un groupe d’états puis des états visuels
3. Changer les propriétés des éléments selon les états
Sélectionner l’état dans le panneau « Etats » et l’élément dans le panneau « Objets et chronologie »,
puis modifier les propriétés de cet élément dans le panneau « Propriétés »
AdaptiveTrigger ou Custom Trigger
Ajout de groupe d’états Ajout d’états viusels
Ajouter un AdaptiveTrigger Réglage des propriétés (MinWindowHeight et
MinWindowWidth pour un AdaptiveTrigger)
42
Repositionnement avec RelativePanel
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="VisualStateMin0"> <VisualState.Setters> <Setter Target="textBlock.(RelativePanel.Below)" Value="image"/> </VisualState.Setters> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="1"/> </VisualState.StateTriggers> </VisualState> <VisualState x:Name="VisualStateMin548"> <VisualState.Setters> <Setter Target="textBlock.(RelativePanel.RightOf)" Value="image"/> </VisualState.Setters> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="548"/> </VisualState.StateTriggers> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Image x:Name="image" Source="Images/breakfast.jpg" Width="300" Margin="8"/> <TextBlock x:Name="textBlock" Text="Une petite faim?" TextWrapping="NoWrap" Style="{StaticResource SubheaderTextBlockStyle}" Margin="8" /> </RelativePanel>
De 0 à 548
> 548
Setters
StateTriggers
De 0 à 548 on place le texte
en-dessous l’image
> 548 on place le texte à
droite de l’image
43
2. Master details > 548
Page divisée avec à gauche la liste, à droite le détail
De 0 à 548
La liste occupe toute la largeur de la page (ColumnSpan=2), la partie détail est cachée (Collapsed) et
lorsque l’on clique sur un élément de la liste on navigue vers une page détails
Une variante consiste donner un nom à chaque colonne de la grille (exemple « masterColumn »
et « detailsColumn ») et changer la taille pour de 0 à 548 (ou 720) passer masterColumn à « * » et
detailsColumn à 0. Exemple XamlMasterDetail
44
<Page …> <Page.Transitions> <TransitionCollection> <NavigationThemeTransition /> </TransitionCollection> </Page.Transitions> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="VisualStateMin0"> <VisualState.Setters> <Setter Target="personDetailsControl.(UIElement.Visibility)" Value="Collapsed"/> <Setter Target="peopleList.(Grid.ColumnSpan)" Value="2"/> </VisualState.Setters> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="1"/> </VisualState.StateTriggers> </VisualState> <VisualState x:Name="VisualStateMin548"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="548"/> </VisualState.StateTriggers> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ListView x:Name="peopleList" ItemsSource="{x:Bind People}" IsItemClickEnabled="True" ItemClick="peopleList_ItemClick" /> <Controls:PersonDetailsControl x:Name="personDetailsControl" Background="#ccc" DataContext="{Binding SelectedItem, ElementName=peopleList}" Grid.Column="1"/> </Grid>
Navigation vers une page Détails si la partie détails est cachée (>548)
private void ListView_ItemClick(object sender, ItemClickEventArgs e) { if (personDetailsControl.Visibility == Visibility.Collapsed) { Frame.Navigate(typeof(PersonDetailsPage), e.ClickedItem); } }
La liste à gauche de la grille
Un contrôle utilisateur affichant le détail à droite
Visual States
On peut ajouter une transition à
page pour effet de retour
45
La page Détails avec un bouton retour un titre et le contrôle utilisateur
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Button x:Name="backButton" Style="{StaticResource NavigationBackButtonSmallStyle}" TabIndex="1" Margin="8" Click="backButton_Click" AutomationProperties.Name="Back" ToolTipService.ToolTip="Back" /> <TextBlock Style="{ThemeResource TitleTextBlockStyle}" Text="{Binding Name, Mode=OneWay}" RelativePanel.RightOf="backButton" RelativePanel.AlignVerticalCenterWith="backButton" Margin="8"/> <Controls:PersonDetailsControl RelativePanel.Below="backButton" /> </RelativePanel>
private void backButton_Click(object sender, RoutedEventArgs e) { Frame.GoBack(new DrillInNavigationTransitionInfo()); }
3. Custom adaptive triggers
voir l’exemple XamlStateTriggers
using Windows.System.Profile; using Windows.UI.Xaml; namespace UWPAdaptiveTriggerDemo.CustomTriggers { public class DeviceFamilyTrigger : StateTriggerBase { private string _deviceFamily; public string DeviceFamily { get { return _deviceFamily; } set { var qualifiers = ResourceContext.GetForCurrentView().QualifierValues; _deviceFamily = value; SetActive(_deviceFamily == qualifiers["DeviceFamily"]); // ou //var currentDeviceFamily = AnalyticsInfo.VersionInfo.DeviceFamily; //_deviceFamily = value; //SetActive(_deviceFamily == currentDeviceFamily); } } } }
Avec Blend, pour un état visuel
Hérite de « StateTriggerBase »
On appelle la méthode « SetActive »
Propriété(s) accessible(s) servant pour
déterminer quand l’état est activé
Sélectionner « Autre type » …
46
On change ensuite les propriétés des éléments de la page selon les états visuels.
Et le « Custom State Trigger »
On définit ensuite les valeurs qui
rendront actif l’état visuel
47
Dans cet exemple, on affiche une image quand on est sur desktop, et une autre quand on est sur
mobile
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="DeviceFamilyStates"> <VisualState x:Name="Desktop"> <VisualState.StateTriggers> <triggers:DeviceFamilyTrigger DeviceFamily="Windows.Desktop" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="desktopImage.Visibility" Value="Visible" /> <Setter Target="mobileImage.Visibility" Value="Collapsed" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Mobile"> <VisualState.StateTriggers> <triggers:DeviceFamilyTrigger DeviceFamily="Windows.Desktop" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="desktopImage.Visibility" Value="Collapsed" /> <Setter Target="mobileImage.Visibility" Value="Visible" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Image x:Name="desktopImage" Source="../Assets/desktop-family.png"></Image> <Image x:Name="mobileImage" Source="../Assets/mobile-family.png"></Image> </Grid>
48
4. DeviceFamily On peut créer une version de page selon les devices
Si on navigue vers la page « Scenario4 », automatiquement la vue pour Mobile sera affichée si on est
sur ce device.
On peut obtenir le device également en code
var qualifiers = ResourceContext.GetForCurrentView().QualifierValues; if (qualifiers["DeviceFamily"] == "DeviceFamily-Xbox") { Frame.Navigate(typeof(MainPage_Xbox)); } else { Frame.Navigate(typeof(MainPage)); }
5. Astuce : réactiver les animations, transitions Windows Régler les effets visuels de Windows. Permet de réactiver les animations, transitions au cas où elles
auraient été désactivées pour améliorer les performances par exemple.
Vue correspondante
pour Mobile
Vue par « défaut »
49
VII. x :Bind « Compiled Binding »
Voir exemple XamlBind
Les +
- Meilleures performances que le binding « classique ». Très performant pour afficher des
listes.
- Va chercher automatiquement les propriétés dans le code-behind de la page sans définir de
DataContext. Pour l’utilisation de ViewModels on définit donc directement une propriété
dans le code-behind.
- Le mode par défaut de binding est « OneTime ». Il faut donc définir un mode OneWay ou
TwoWay si ce n’est pas suffisant.
- On peut utiliser « x :Bind » pour binder des événements, permet d’éviter l’utilisation de
RelayCommand ou de behavior pour des scénarios simples.
Les -
- Problème avec les commandes dans les DataTemplates de fichier de ressources (Besoin
d’aller chercher le DataContext du parent)
- Problème avec SelectedItem (renvoyant un objet) avec ListView / GridView .., besoin de faire
des convertisseurs ?
- Problème avec les styles
- Les attributs ElementName, RelativeSource, Source, UpdateSourceTrigger ne sont pas pris
en charge par « x :Bind »
- Solution : il faudra parfois mélanger le binding classique et binding avec « x :Bind ». On règle
alors le DataContext sur le même ViewModel défini en propriété dans le code-behind de la
page.
1. Binding de collection On définit une propriété dans le code-behind de la page sans définir de DataContext
public sealed partial class Scenario1 : Page { public Scenario1() { this.InitializeComponent(); People = new ObservableCollection<Person> { new Person { Name ="Marie Bellin"}, new Person { Name ="Jérôme Romagny"} }; } public ObservableCollection<Person> People { get; set; } }
(Le modèle utilisé)
public class Person { public string Name { get; set; } public override string ToString() { return Name; } }
50
2. Binding dans la page
<ListView ItemsSource="{x:Bind People}" />
2. Avec DataTemplate On définit le DataTemplate en ressources de la page
<DataTemplate x:Key="personItemTemplate" x:DataType="models:Person"> <StackPanel Orientation="Horizontal"> <SymbolIcon Symbol="People" Margin="8,0,0,0"/> <TextBlock Text="{x:Bind Name}" Margin="8,0,0,0"/> </StackPanel> </DataTemplate>
<ListView ItemsSource="{x:Bind People}" ItemTemplate="{StaticResource personItemTemplate}" />
3. Avec dictionnaire de ressources (ResourceDictionary) 1. Ajouter un dictionnaire de ressources
<ResourceDictionary x:Class="UWPDataBindingDemo.Resources.Templates" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UWPDataBindingDemo.Resources" xmlns:models="using:UWPDataBindingDemo.Models"> <DataTemplate x:Key="personItemTemplateFromResourceDictionary" x:DataType="models:Person"> <StackPanel Orientation="Horizontal"> <SymbolIcon Symbol="People" Margin="8,0,0,0"/> <TextBlock Text="{x:Bind Name}" Margin="8,0,0,0"/> </StackPanel> </DataTemplate> </ResourceDictionary>
2. Ajout d’une classe (partielle) du même nom que le dictionnaire de ressources
namespace UWPDataBindingDemo.Resources { public partial class Templates { public Templates() { InitializeComponent(); } } }
Utilisation du DataTemplate du dictionnaire de resources
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <resources:Templates /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
Si aucun DataTemplate n’est défini c’est la méthode
ToString des éléments qui est appelée
Ajouter un namespace et
définir le DataType
Exemple de template
Ajout d’un nom de classe
Attention au namespace
Référencer le dictionnaire en
ressources de la page ou pour
toute l’application dans « App »
51
<ListView ItemsSource="{x:Bind People}" ItemTemplate="{StaticResource personItemTemplateFromResourceDictionary}" />
4. « RelativeSource » et « ElementName » … lier au nom de l’élément <Slider x:Name="slider" Header="Slider" Maximum="10" Margin="8"/> <TextBlock Text="{x:Bind slider.Value,Mode=OneWay}" Margin="8"/>
5. « Source » et « DataContext ».. Ajouter une propriété du ViewModel dans le
code-behind public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); ViewModel = new MyViewModel(); DataContext = ViewModel; } public MyViewModel ViewModel { get; set; } }
Variantes
public sealed partial class MainPage : Page { private MainViewModel viewModel; public MainPage() { this.InitializeComponent(); this.Loaded += MainPage_Loaded; } void MainPage_Loaded(object sender, RoutedEventArgs e) { viewModel = new MyViewModel(); this.DataContext = viewModel; } } }
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.DataContextChanged += (s, e) => { ViewModel = DataContext as MyViewModel; }; } public MainPageViewModel ViewModel { get; set; } }
Parfois il faudra définir le
DataContext pour mélanger les deux
formes de binding
52
<TextBlock Text="{x:Bind ViewModel.Message}"/>
public class MyViewModel { public string Message { get; set; } public MyViewModel() { Message = "Bonjour!"; } }
6. Binding Events Permet d’éviter l’utilisation de « RelayCommand » (ICommand) ou de behavior
« EventToCommand » pour des scénarios simples, de « déplacer » les événements du code-
behind de la page vers le ViewModel par exemple.
N’accepte pas les paramètres « personnels », pas de support de CanExecute
Exemple
public class MyViewModel { public void DoWork1() { } public void DoWork2(object sender, RoutedEventArgs e) { } public void DoWork3(object sender, object e) { } }
Dans le code-behind de la page
public sealed partial class Scenario4 : Page { public Scenario4() { this.InitializeComponent(); ViewModel = new MyViewModel(); } public MyViewModel ViewModel { get; set; } }
<Button Content="Sans paramètre ... void DoWork1()" Click="{x:Bind ViewModel.DoWork1}"/> <Button Content="void DoWork2(object sender, RoutedEventArgs e)" Click="{x:Bind ViewModel.DoWork2}"/> <Button Content="void DoWork3(object sender, object e)" Click="{x:Bind ViewModel.DoWork3}"/>
Les 3 signatures
acceptées
53
7. Defer loading Dans la page
<Grid x:Name="myGrid" x:DeferLoadStrategy="Lazy" Background="Red" />
Dans le code-behind
private void Button_Click(object sender, RoutedEventArgs e) { FindName("myGrid"); }
VIII. Application Lifecycle Documentation
Il faut donner l’illusion que l’application n’a pas été suspendue quand celle-ci est résumée.
Launch (« OnLaunched » de « App »).
- « PreviousExecutionState » : NotRunning, Running,Suspended, Terminated, ClosedByUser. Si
l’application est fermée par l’utilisateur (« ClosedByUser ») ne pas restaurer.
- « ActivationKind » : Launch, File, Protocol, Cortana, Secondary tile, toast, search contract,etc.
Suspend : Les applications Desktop sont suspendues quand elles sont minimisées dans la barre
de tâches.
- Sauvegarder les états de la page et les données (Local, Roaming,…)
- « App » : Sauvegarder l’historique de navigation
Resume :
- « App » : Restaurer l’historique de navigation, retourner sur la page avant la suspension
- Restaurer les états de la page (champs) et les données (Local, Roaming …)
Note : Les applications UWP n’incluent pas « SuspensionManager » et « NavigationHelper ».
La grille n’est affichée qu’à
l’appel de FindName
54
1. Historique de navigation (« App ») Sauvegarder la navigation
private void OnSuspending(object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); Frame rootFrame = Window.Current.Content as Frame; ApplicationData.Current.LocalSettings.Values["NavigationState"] = rootFrame.GetNavigationState(); deferral.Complete(); }
Restaurer la navigation
protected override void OnLaunched(LaunchActivatedEventArgs e) { // etc. if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { if (ApplicationData.Current.LocalSettings.Values.ContainsKey("NavigationState")) { rootFrame.SetNavigationState((string)ApplicationData.Current.LocalSettings.Values["NavigationState"]); } } }
2. Etats de la page et données Sauver les états de la page courante à la suspension (« code-behind de la page » )
protected override void OnNavigatedFrom(NavigationEventArgs e) { vm.SaveData(); }
ViewModel
public void SaveData() { ApplicationData.Current.RoamingSettings.Values["ValueOne"] = ValueOne; ApplicationData.Current.RoamingSettings.Values["ValueTwo"] = ValueTwo; }
Restaurer les états
protected override void OnNavigatedTo(NavigationEventArgs e) { if(e.NavigationMode == NavigationMode.New) { // clear vm.ClearData(); } else { vm.LoadData(); } }
Exemple sauvegarde de deux
valeurs de champs
55
ViewModel
public void LoadData() { if (ApplicationData.Current.RoamingSettings.Values.ContainsKey("ValueOne")) { ValueOne = ApplicationData.Current.RoamingSettings.Values["ValueOne"].ToString(); } if (ApplicationData.Current.RoamingSettings.Values.ContainsKey("ValueTwo")) { ValueTwo = ApplicationData.Current.RoamingSettings.Values["ValueTwo"].ToString(); } } public void ClearData() { if (ApplicationData.Current.RoamingSettings.Values.ContainsKey("ValueOne")) { ApplicationData.Current.RoamingSettings.Values.Remove("ValueOne"); } if (ApplicationData.Current.RoamingSettings.Values.ContainsKey("ValueTwo")) { ApplicationData.Current.RoamingSettings.Values.Remove("ValueTwo"); } }
56
IX. Tiles, toasts Documentation
Voir exemple Notifications
Tile On définit les Assets toujours dans le manifeste de l’application, onglet « Ressources visuelles »
- Petit : 71x71 (« généré automatiquement » si l’Asset n’est pas présent)
- Moyen : 150x150 (Square150x150Logo.png)
- Large : 310x150 (Wide310x150Logo.png)
- Grand : 310x310 (Wide310x310Logo.png)
Vignette de l’application : Chercher l’application depuis le champ recherche de Windows 10 puis
« Epingler à l’écran de démarrage »
71x71 150x150 310x150
310x310
Seulement pour desktop
57
Note : si on définit la couleur d’arrière-plan des vignettes sur « transparent », c’est alors la couleur
d’accent de Windows qui est appliqué
Vignette secondaire : ajoutée par code. La vignette est désormais ajoutée directement sans boite de
dialogue (comme c’était le cas avec Windows 8.1)
private async void OnPinSecondaryTile(object sender, RoutedEventArgs e) { var isPinned = SecondaryTile.Exists(tileId); if (!isPinned) { await PinSecondaryTile(tileId); } } const string tileId = "DetailsTile "; private async Task<bool> PinSecondaryTile(string tileId) { string tileActivationArguments = " mon argument"; var tile = new SecondaryTile(tileId, "Vignette secondaire", tileActivationArguments, new Uri("ms-appx:///Assets/Square150x150Logo.png", UriKind.Absolute), TileSize.Square150x150); tile.VisualElements.Wide310x150Logo = new Uri("ms-appx:///Assets/Wide310x150Logo.png", UriKind.Absolute); tile.VisualElements.Square310x310Logo = new Uri("ms-appx:///Assets/Square310x310Logo.png", UriKind.Absolute); // détails tile.VisualElements.ShowNameOnSquare150x150Logo = true; tile.VisualElements.ShowNameOnWide310x150Logo = true; tile.VisualElements.ShowNameOnSquare310x310Logo = true; //tile.VisualElements.BackgroundColor = Colors.DarkOrange; tile.VisualElements.ForegroundText = ForegroundText.Light; tile.RoamingEnabled = false; bool isPinned = await tile.RequestCreateAsync(); return isPinned; }
Nom affiché, arguments, uri de la
tile 150x150 et taille désirée de la
vignette
58
Notification de vignette
private void OnUpdateTile(object sender, RoutedEventArgs e) { string image = "ms-appx:///Assets/image_1.jpg"; string title = "Titre!"; string message = "Nouveau message!"; string xml = string.Format(@"<tile> <visual version=""2""> <binding template=""TileSquare150x150PeekImageAndText02"" fallback=""TileSquarePeekImageAndText02""> <image id=""1"" src=""{0}"" alt=""alt text""/> <text id=""1"">{1}</text> <text id=""2"">{2}</text> </binding> <binding template=""TileWide310x150PeekImage01"" fallback=""TileWidePeekImage01""> <image id=""1"" src=""{0}""/> <text id=""1"">{1}</text> <text id=""2"">{2}</text> </binding> </visual> </tile>", image, title, message); var document = new XmlDocument(); document.LoadXml(xml); var notification = new TileNotification(document); TileUpdateManager.CreateTileUpdaterForApplication().Update(notification); }
« Scheduled » (différé)
// etc. var notification = new ScheduledTileNotification(document, DateTimeOffset.Now.AddSeconds(10)); TileUpdateManager. CreateTileUpdaterForSecondaryTile(tileId). AddToSchedule(notification);
« Periodic » (Web Service appelé toutes les 30 minutes, 60 minutes, 6 heures, 12 heures, 24 heures)
// etc. var updater = TileUpdateManager.CreateTileUpdaterForSecondaryTile(tileId); var uri = new Uri("http://localhost:3660/api/Notification/"); updater.StartPeriodicUpdate(uri, PeriodicUpdateRecurrence.Hour);
« Push » (Push Services)
Documentation
Animation « cube 3D »
Défilement
Notification de la vignette principale. On peut également
notifier les vignettes secondaires en passant le « tile id »
59
Adaptive Tiles On peut toujours utiliser les templates de Windows 8.1 ou on peut utiliser les « Adaptive Templates »
qui permettent plus de souplesse et de personnalisation
Adaptive Tile Schema
Exemple
private void OnAdaptiveTile(object sender, RoutedEventArgs e) { string image = "ms-appx:///Assets/image_1.jpg"; string title = "Titre!"; string message = "Nouveau message!"; string xml = string.Format(@"<tile> <visual> <binding template=""TileSmall""> <image src=""{0}"" placement=""background""></image> </binding> <binding template=""TileMedium""> <image src=""{0}"" placement=""background""></image> <text hint-style=""caption"" hint-wrap=""true"">{1}</text> <text hint-style=""captionSubtle"" hint-wrap=""true"">{2}</text> </binding> <binding template=""TileWide""> <image src=""{0}"" placement=""background""></image> <text hint-style=""title"" hint-wrap=""true"">{1}</text> <text hint-style=""subSubtle"" hint-wrap=""true"">{2}</text> </binding> <binding template=""TileLarge"" branding=""nameAndLogo""> <group> <subgroup> <text hint-style=""title"">{1}</text> <text hint-style=""subSubtle"">{2}</text> </subgroup> </group> <image src=""{0}"" placement=""inline""></image> </binding> </visual> </tile>", image, title, message); var document = new XmlDocument(); document.LoadXml(xml); var notification = new TileNotification(document); TileUpdateManager.CreateTileUpdaterForApplication().Update(notification); }
Image en arrière-plan,
texte par-dessus avec styles
Vignette 71x71
Vignette 150x150
Vignette 310x150
Vignette 310x310
Placement de l’image en arrière-plan
(background) , on pourrait la placer en
ligne avec un alignement
On définit le style du texte
(caption, etc.) .« hint-
wrap » place devant
l’image d’arrière-plan
« branding » :
nom et logo
(44x44)
Affichage du nom
et logo
Image « inline »
Groupe et image
en ligne
60
Toast Les toasts servent soit à afficher un message, soit propose une action.
On peut utiliser les templates de toast de Windows 8.1 ou on peut utiliser les adaptives templates.
private void OnSendToast(object sender, RoutedEventArgs e) { string title = "Mon application"; string message = "Nouveau message!"; string xml = string.Format(@"<toast> <visual> <binding template=""ToastText02""> <text id=""1"">{0}</text> <text id=""2"">{1}</text> </binding> </visual> </toast>", title, message); var document = new XmlDocument(); document.LoadXml(xml); var notification = new ToastNotification(document); ToastNotificationManager.CreateToastNotifier().Show(notification); }
Adaptive toast
Toast
Image remplaçant le
logo
Image « inline »
Centre de
notifications
61
private void OnSendAdaptiveToast(object sender, RoutedEventArgs e) { string image = "ms-appx:///Assets/image_1.jpg"; string newLogo = "ms-appx:///Assets/image_2.png"; string title = "Mon application"; string message = "Nouveau message!"; string xml = string.Format(@"<toast> <visual> <binding template=""ToastGeneric""> <image src=""{0}"" placement=""appLogoOverride""/> <text>{1}</text> <text>{2}</text> <image src=""{3}"" placement=""inline""/> </binding> </visual> </toast>",newLogo, title, message,image); var document = new XmlDocument(); document.LoadXml(xml); var notification = new ToastNotification(document); ToastNotificationManager.CreateToastNotifier().Show(notification); }
Scénarios
<toast scenario=""reminder"">
private void OnSendAlarmToast(object sender, RoutedEventArgs e) { string title = "Alarme!"; string message = "Message!"; string xml= string.Format(@"<toast launch='args' scenario='alarm'> <visual> <binding template='ToastGeneric'> <text>{0}</text> <text>{1}</text> </binding> </visual> <actions> <action arguments = 'snooze' content = 'snooze' /> <action arguments = 'dismiss' content = 'dismiss' /> </actions> </toast>",title,message); var document = new XmlDocument(); document.LoadXml(xml); var notification = new ToastNotification(document); ToastNotificationManager.CreateToastNotifier().Show(notification); }
Reminder, alarm ou
incomingCall
62
Historique de notification
ToastNotificationManager.History.Clear();
Il est possible de définir les applications pouvant envoyer des notifications dans les options de
Windows 10 (« Système » … « Notifications et actions » ou depuis le centre de notifications de la
barre de tâches)