Étude de cas Silverlight pour Windows Phone vers UWP : Bookstore2
Cette étude de cas, qui s’appuie sur les informations fournies dans Bookstore1, commence par une application Silverlight Windows Phone qui affiche des données groupées dans un LongListSelector. Dans le modèle d’affichage, chaque instance de la classe Author représente le groupe des livres écrits par cet auteur, et dans LongListSelector, nous pouvons afficher la liste des livres regroupés par auteur ou nous pouvons effectuer un zoom avant pour afficher une liste de raccourcis d’auteurs. Grâce à cette liste, vous pouvez vous déplacer beaucoup plus rapidement que si vous faisiez défiler la liste des ouvrages. Nous passons en revue les étapes de portage de l’application vers une application Windows 10 plateforme Windows universelle (UWP).
Remarque Lors de l’ouverture de Bookstore2Universal_10 dans Visual Studio, si vous voyez le message « Visual Studio update required », suivez les étapes de définition de la version de la plateforme cible dans TargetPlatformVersion.
Téléchargements
Téléchargez l’application Silverlight De Bookstore2WPSL8 Windows Phone.
Téléchargez l’application Bookstore2Universal_10 Windows 10.
Application Silverlight Windows Phone
L’illustration ci-dessous montre ce que Bookstore2WPSL8 , l’application que nous allons porter, ressemble. Il s’agit d’un LongListSelector à défilement vertical des livres regroupés par auteur. Vous pouvez effectuer un zoom arrière vers la liste de raccourcis et, à partir de là, vous pouvez revenir en arrière dans n’importe quel groupe. Il existe deux éléments principaux à cette application : le modèle de vue qui fournit la source de données groupée et l’interface utilisateur qui se lie à ce modèle de vue. Comme nous le verrons, ces deux éléments sont facilement portés de la technologie Silverlight windows Phone vers le plateforme Windows universelle (UWP).
Portage vers un projet Windows 10
Il s’agit d’une tâche rapide pour créer un projet dans Visual Studio, copier des fichiers vers celui-ci à partir de Bookstore2WPSL8 et inclure les fichiers copiés dans le nouveau projet. Commencez par créer un projet d’application vide (Windows Universel). Nommez-le Bookstore2Universal_10. Il s’agit des fichiers à copier à partir de Bookstore2WPSL8 vers Bookstore2Universal_10.
- Copiez le dossier contenant les fichiers PNG de l’image de couverture du livre (le dossier est \Assets\CoverImages). Après avoir copié le dossier, dans Explorateur de solutions, vérifiez que l’option Afficher tous les fichiers est activée. Cliquez avec le bouton droit sur le dossier que vous avez copié, puis cliquez sur Inclure dans le projet. Cette commande est ce que nous entendons par « inclure » des fichiers ou des dossiers dans un projet. Chaque fois que vous copiez un fichier ou un dossier, cliquez sur Actualiser dans Explorateur de solutions, puis incluez le fichier ou le dossier dans le projet. Il n’est pas nécessaire de procéder ainsi pour les fichiers que vous remplacez dans la destination.
- Copiez le dossier contenant le fichier source du modèle d’affichage (le dossier est \ViewModel).
- Copiez MainPage.xaml et remplacez le fichier dans la destination.
Nous pouvons conserver App.xaml et App.xaml.cs que Visual Studio a généré pour nous dans le projet Windows 10.
Modifiez le code source et les fichiers de balisage que vous venez de copier et de modifier les références à l’espace de noms Bookstore2WPSL8 en Bookstore2Universal_10. Pour ce faire, utilisez la fonctionnalité Remplacer dans les fichiers . Dans le code impératif du fichier source du modèle d’affichage, ces modifications de portage sont nécessaires.
- Passez
System.ComponentModel.DesignerProperties
àDesignMode
la commande Résoudre, puis utilisez la commande Résoudre . Supprimez laIsInDesignTool
propriété et utilisez IntelliSense pour ajouter le nom de propriété approprié :DesignModeEnabled
. - Utilisez la commande Resolve sur
ImageSource
. - Utilisez la commande Resolve sur
BitmapImage
. - Supprimer
using System.Windows.Media;
etusing System.Windows.Media.Imaging;
. - Remplacez la valeur retournée par la propriété Bookstore2Universal_10.BookstoreViewModel.AppName de « BOOKSTORE2WPSL8 » par « BOOKSTORE2UNIVERSAL ».
- Comme nous l’avons fait pour Bookstore1, mettez à jour l’implémentation de la propriété BookSku.CoverImage (voir Liaison d’une image à un modèle d’affichage).
Dans MainPage.xaml, ces modifications de portage initiales sont nécessaires.
- Passez
phone:PhoneApplicationPage
àPage
(y compris les occurrences dans la syntaxe de l’élément de propriété). - Supprimez les déclarations de préfixe et
shell
d’espacephone
de noms. - Remplacez « clr-namespace » par « using » dans la déclaration de préfixe d’espace de noms restante.
- Supprimez et
Orientation="Portrait"
configurezSupportedOrientations="Portrait"
Portrait dans le manifeste du package d’application dans le nouveau projet. - Supprimez
shell:SystemTray.IsVisible="True"
. - Les types des convertisseurs d’éléments de liste de raccourcis (qui sont présents dans le balisage en tant que ressources) ont été déplacés vers l’espace de noms Windows.UI.Xaml.Controls.Primitives . Ajoutez donc la déclaration de préfixe d’espace de noms Windows_UI_Xaml_Controls_Primitives et mappez-la à Windows.UI.Xaml.Controls.Primitives. Dans les ressources du convertisseur d’élément de liste de raccourcis, remplacez le préfixe par
phone:
Windows_UI_Xaml_Controls_Primitives:
. - Tout comme nous l’avons fait pour Bookstore1, remplacez toutes les références au
PhoneTextExtraLargeStyle
style TextBlock par une référence àSubtitleTextBlockStyle
, remplacezPhoneTextSubtleStyle
par , remplacez parSubtitleTextBlockStyle
, remplacezPhoneTextNormalStyle
parCaptionTextBlockStyle
et remplacezPhoneTextTitle1Style
parHeaderTextBlockStyle
. - Il existe une exception dans
BookTemplate
. Le style du deuxième TextBlock doit référencerCaptionTextBlockStyle
. - Supprimez l’attribut FontFamily de TextBlock à l’intérieur
AuthorGroupHeaderTemplate
et définissez l’arrière-plan de la bordure à référencerSystemControlBackgroundAccentBrush
au lieu dePhoneAccentBrush
. - En raison des modifications liées aux pixels d’affichage, parcourez le balisage et multipliez toute dimension de taille fixe (marges, largeur, hauteur, etc.) de 0,8.
Remplacement de LongListSelector
Le remplacement de LongListSelector par un contrôle SemanticZoom prendra plusieurs étapes. Nous allons donc commencer à cela. Un LongListSelector se lie directement à la source de données groupée, mais un SemanticZoom contient des contrôles ListView ou GridView, qui se lient indirectement aux données via un adaptateur CollectionViewSource. CollectionViewSource doit être présent dans le balisage en tant que ressource. Nous allons donc commencer par l’ajouter au balisage dans MainPage.xaml à l’intérieur <Page.Resources>
.
<CollectionViewSource
x:Name="AuthorHasACollectionOfBookSku"
Source="{Binding Authors}"
IsSourceGrouped="true"/>
Notez que la liaison sur LongListSelector.ItemsSource devient la valeur de CollectionViewSource.Source, et LongListSelector.IsGroupingEnabled devient CollectionViewSource.IsSourceGrouped. CollectionViewSource a un nom (remarque : pas une clé, comme prévu) afin que nous puissions le lier.
Ensuite, remplacez ce phone:LongListSelector
balisage, ce qui nous donnera une sémantique préliminaire à utiliser.
<SemanticZoom>
<SemanticZoom.ZoomedInView>
<ListView
ItemsSource="{Binding Source={StaticResource AuthorHasACollectionOfBookSku}}"
ItemTemplate="{StaticResource BookTemplate}">
<ListView.GroupStyle>
<GroupStyle
HeaderTemplate="{StaticResource AuthorGroupHeaderTemplate}"
HidesIfEmpty="True"/>
</ListView.GroupStyle>
</ListView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<ListView
ItemsSource="{Binding CollectionGroups, Source={StaticResource AuthorHasACollectionOfBookSku}}"
ItemTemplate="{StaticResource ZoomedOutAuthorTemplate}"/>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
La notion LongListSelector des modes de liste plate et de liste de raccourcis est répondue dans la notion SemanticZoom d’un zoom avant et d’une vue avec zoom arrière, respectivement. La vue avec zoom avant est une propriété et vous définissez cette propriété sur une instance d’un ListView. Dans ce cas, la vue avec zoom arrière est également définie sur un Contrôle ListView, et les deux contrôles ListView sont liés à notre CollectionViewSource. La vue avec zoom arrière utilise le même modèle d’élément, le modèle d’en-tête de groupe et le paramètre HideEmptyGroups (maintenant nommé HidesIfEmpty) que la liste plate de LongListSelector. Et la vue avec zoom arrière utilise un modèle d’élément très similaire à celui qui se trouve dans le style de liste de raccourcis de LongListSelector (AuthorNameJumpListStyle
). Notez également que la vue avec zoom arrière est liée à une propriété spéciale de CollectionViewSource nommée CollectionGroups, qui est une collection contenant les groupes plutôt que les éléments.
Nous n’avons plus besoin AuthorNameJumpListStyle
, au moins pas tout. Nous avons uniquement besoin du modèle de données pour les groupes (qui sont des auteurs dans cette application) dans la vue avec zoom arrière. Par conséquent, nous supprimons le AuthorNameJumpListStyle
style et le remplaceons par ce modèle de données.
<DataTemplate x:Key="ZoomedOutAuthorTemplate">
<Border Margin="9.6,0.8" Background="{Binding Converter={StaticResource JumpListItemBackgroundConverter}}">
<TextBlock Margin="9.6,0,9.6,4.8" Text="{Binding Group.Name}" Style="{StaticResource SubtitleTextBlockStyle}"
Foreground="{Binding Converter={StaticResource JumpListItemForegroundConverter}}" VerticalAlignment="Bottom"/>
</Border>
</DataTemplate>
Notez que, étant donné que le contexte de données de ce modèle de données est un groupe plutôt qu’un élément, nous liez à une propriété spéciale nommée Group.
Vous pouvez générer et exécuter l’application maintenant. Voici comment il ressemble à l’émulateur mobile.
Le modèle de vue et les vues avec zoom avant et arrière fonctionnent correctement, bien qu’un problème soit que nous devons faire un peu plus de style et de création de modèles. Par exemple, les styles et pinceaux corrects ne sont pas encore utilisés. Le texte est donc invisible sur les en-têtes de groupe que vous pouvez cliquer pour effectuer un zoom arrière. Si vous exécutez l’application sur un appareil de bureau, vous verrez un deuxième problème, c’est-à-dire que l’application n’adapte pas encore son interface utilisateur pour offrir la meilleure expérience et l’utilisation de l’espace sur les appareils plus volumineux où les fenêtres peuvent être potentiellement beaucoup plus grandes que l’écran d’un appareil mobile. Par conséquent, dans les sections suivantes (style initial et création de modèles, interface utilisateur adaptative et style final), nous allons résoudre ces problèmes.
Style initial et création de modèles
Pour espacer les en-têtes de groupe correctement, modifiez et définissez AuthorGroupHeaderTemplate
une marge sur "0,0,0,9.6"
la bordure.
Pour espacer correctement les éléments du livre, modifiez et définissez BookTemplate
la marge "9.6,0"
sur les deux TextBlock.
Pour disposer le nom de l’application et le titre de la page un peu mieux, à l’intérieurTitlePanel
, supprimez la marge supérieure sur le deuxième TextBlock en définissant la valeur sur "7.2,0,0,0"
. Et sur TitlePanel
lui-même, définissez la marge 0
sur (ou quelle valeur semble bonne pour vous)
Remplacez LayoutRoot
l’arrière-plan "{ThemeResource ApplicationPageBackgroundThemeBrush}"
par .
Interface utilisateur adaptative
Étant donné que nous avons commencé avec une application de téléphone, il n’est pas surprenant que la disposition de l’interface utilisateur de notre application transférée n’ait vraiment de sens pour les petits appareils et les fenêtres étroites à ce stade du processus. Mais nous aimerions vraiment que la disposition de l’interface utilisateur s’adapte et améliore l’utilisation de l’espace lorsque l’application s’exécute dans une large fenêtre (ce qui n’est possible que sur un appareil avec un grand écran) et pour qu’elle utilise uniquement l’interface utilisateur que nous avons actuellement lorsque la fenêtre de l’application est étroite (ce qui se produit sur un petit appareil, et peut également se produire sur un appareil volumineux).
Nous pouvons utiliser la fonctionnalité Visual State Manager adaptative pour y parvenir. Nous allons définir des propriétés sur des éléments visuels afin que, par défaut, l’interface utilisateur soit définie dans l’état étroit à l’aide des modèles que nous utilisons pour le moment. Ensuite, nous allons détecter quand la fenêtre de l’application est plus large que ou égale à une taille spécifique (mesurée en unités de pixels effectifs) et en réponse, nous allons modifier les propriétés des éléments visuels afin d’obtenir une disposition plus grande et plus large. Nous allons placer ces modifications de propriété dans un état visuel, et nous allons utiliser un déclencheur adaptatif pour surveiller et déterminer s’il faut appliquer cet état visuel, ou non, en fonction de la largeur de la fenêtre en pixels effectifs. Nous déclenchant sur la largeur de la fenêtre dans ce cas, mais il est également possible de se déclencher sur la hauteur de la fenêtre.
Une largeur de fenêtre minimale de 548 epx est appropriée pour ce cas d’usage, car c’est la taille du plus petit appareil sur lequel nous souhaitons afficher la disposition large. Les téléphones sont généralement plus petits que 548 epx, donc sur un petit appareil comme cela, nous resterions dans la disposition étroite par défaut. Sur un PC, la fenêtre s’ouvre par défaut suffisamment large pour déclencher le commutateur à l’état large, qui affiche 250 x 250 éléments de taille. À partir de là, vous pourrez faire glisser la fenêtre suffisamment étroite pour afficher au moins deux colonnes des éléments 250 x 250. Tout plus étroit que cela et le déclencheur sera désactivé, l’état visuel large sera supprimé et la disposition étroite par défaut sera en vigueur.
Avant d’aborder l’élément Visual State Manager adaptatif, nous devons d’abord concevoir l’état large et cela signifie ajouter de nouveaux éléments visuels et modèles à notre balisage. Ces étapes décrivent comment procéder. En utilisant des conventions d’affectation de noms pour les éléments visuels et les modèles, nous allons inclure le mot « large » dans le nom d’un élément ou d’un modèle correspondant à l’état large. Si un élément ou un modèle ne contient pas le mot « large », vous pouvez supposer qu’il s’agit de l’état étroit, qui est l’état par défaut et dont les valeurs de propriété sont définies en tant que valeurs locales sur les éléments visuels de la page. Seules les valeurs de propriété pour l’état large sont définies via un état visuel réel dans le balisage.
- Effectuez une copie du contrôle SemanticZoom dans le balisage et définissez-le
x:Name="narrowSeZo"
sur la copie. Sur l’original, définissezx:Name="wideSeZo"
et définissezVisibility="Collapsed"
également afin que le large ne soit pas visible par défaut. - Dans
wideSeZo
, remplacez les vues ListViewpar GridViewdans la vue avec zoom avant et la vue avec zoom arrière. - Effectuez une copie de ces trois ressources
AuthorGroupHeaderTemplate
ZoomedOutAuthorTemplate
, puisBookTemplate
ajoutez le motWide
aux clés des copies. En outre, mettez à jourwideSeZo
afin qu’elle référence les clés de ces nouvelles ressources. - Remplacez le contenu par
AuthorGroupHeaderTemplateWide
<TextBlock Style="{StaticResource SubheaderTextBlockStyle}" Text="{Binding Name}"/>
. - Remplacez le contenu du champ
ZoomedOutAuthorTemplateWide
avec :
<Grid HorizontalAlignment="Left" Width="250" Height="250" >
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"/>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
Style="{StaticResource SubtitleTextBlockStyle}"
Height="80" Margin="15,0" Text="{Binding Group.Name}"/>
</StackPanel>
</Grid>
- Remplacez le contenu du champ
BookTemplateWide
avec :
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"/>
<Image Source="{Binding CoverImage}" Stretch="UniformToFill"/>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}"
Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}"
TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"
Margin="12,0,24,0" Text="{Binding Title}"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{Binding Author.Name}"
Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis" Margin="12,0,12,12"/>
</StackPanel>
</Grid>
- Pour l’état large, les groupes de la vue avec zoom avant nécessitent un espace de respiration plus vertical autour d’eux. La création et le référencement d’un modèle de panneau d’éléments nous donnent les résultats souhaités. Voici l’apparence du balisage.
<ItemsPanelTemplate x:Key="ZoomedInItemsPanelTemplate">
<ItemsWrapGrid Orientation="Horizontal" GroupPadding="0,0,0,20"/>
</ItemsPanelTemplate>
...
<SemanticZoom x:Name="wideSeZo" ... >
<SemanticZoom.ZoomedInView>
<GridView
...
ItemsPanel="{StaticResource ZoomedInItemsPanelTemplate}">
...
- Enfin, ajoutez le balisage Visual State Manager approprié en tant que premier enfant de
LayoutRoot
.
<Grid x:Name="LayoutRoot" ... >
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="WideState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="548"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="wideSeZo.Visibility" Value="Visible"/>
<Setter Target="narrowSeZo.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
Style final
Tout ce qui reste sont quelques ajustements de style finals.
- Dans
AuthorGroupHeaderTemplate
, définissezForeground="White"
sur TextBlock afin qu’il semble correct lors de l’exécution sur la famille d’appareils mobiles. - Ajouter
FontWeight="SemiBold"
au TextBlock dans les deuxAuthorGroupHeaderTemplate
etZoomedOutAuthorTemplate
. - Dans
narrowSeZo
, les en-têtes de groupe et les auteurs de la vue avec zoom arrière sont alignés à gauche au lieu d’étirer. Nous allons donc travailler dessus. Nous allons créer un HeaderContainerStyle pour la vue zoomée avec HorizontalContentAlignment défini surStretch
. Et nous allons créer un ItemContainerStyle pour la vue avec zoom arrière contenant ce même Setter. Voici ce que ça ressemble.
<Style x:Key="AuthorGroupHeaderContainerStyle" TargetType="ListViewHeaderItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<Style x:Key="ZoomedOutAuthorItemContainerStyle" TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
...
<SemanticZoom x:Name="narrowSeZo" ... >
<SemanticZoom.ZoomedInView>
<ListView
...
<ListView.GroupStyle>
<GroupStyle
...
HeaderContainerStyle="{StaticResource AuthorGroupHeaderContainerStyle}"
...
<SemanticZoom.ZoomedOutView>
<ListView
...
ItemContainerStyle="{StaticResource ZoomedOutAuthorItemContainerStyle}"
...
Cette dernière séquence d’opérations de style laisse l’application ressembler à ceci.
Application Windows 10 transférée s’exécutant sur un appareil de bureau, vue avec zoom avant, deux tailles de fenêtre
Application Windows 10 transférée s’exécutant sur un appareil de bureau, vue avec zoom arrière, deux tailles de fenêtre
Application Windows 10 transférée s’exécutant sur un appareil mobile, vue avec zoom avant
Application Windows 10 portée s’exécutant sur un appareil mobile, vue avec zoom arrière
Rendre le modèle d’affichage plus flexible
Cette section contient un exemple d’installations qui nous ouvrent en raison d’avoir déplacé notre application pour utiliser UWP. Ici, nous expliquons les étapes facultatives que vous pouvez suivre pour rendre votre modèle d’affichage plus flexible lorsqu’il est accessible via une CollectionViewSource. Le modèle d’affichage (le fichier source est dans ViewModel\BookstoreViewModel.cs) que nous avons porté à partir de l’application Silverlight Windows Phone Bookstore2WPSL8 contient une classe nommée Author, qui dérive de List<T>, où T est BookSku. Cela signifie que la classe Author est un groupe de BookSku.
Lorsque nous liez CollectionViewSource.Source aux auteurs, la seule chose que nous transmettons est que chaque auteur dans Les auteurs est un groupe d’éléments. Nous l’avons laissé à CollectionViewSource pour déterminer que Author est, dans ce cas, un groupe de BookSku. Cela fonctionne : mais ce n’est pas flexible. Que se passe-t-il si nous voulons que l’auteur soit à la fois un groupe de BookSku et un groupe d’adresses où l’auteur a vécu ? L’auteur ne peut pas être les deux de ces groupes. Toutefois, Author peut avoir n’importe quel nombre de groupes. Et c’est la solution : utilisez le modèle has-a-group au lieu, ou en plus, du modèle is-a-group que nous utilisons actuellement. Voici comment procéder :
- Modifiez l’auteur afin qu’il ne dérive plus de la liste<T>.
- Ajouter ce champ à
- Ajouter cette propriété à
- Bien entendu, nous pouvons répéter les deux étapes ci-dessus pour ajouter autant de groupes à Author que nécessaire.
- Remplacez l’implémentation de la méthode AddBookSku par
this.BookSkus.Add(bookSku);
. - Maintenant que Author dispose d’au moins un groupe, nous devons communiquer avec collectionViewSource auquel de ces groupes il doit utiliser. Pour ce faire, ajoutez cette propriété à CollectionViewSource :
ItemsPath="BookSkus"
Ces modifications laissent cette application fonctionnellement inchangée, mais vous savez maintenant comment étendre Author et CollectionViewSource, si nécessaire. Nous allons apporter une dernière modification à Author afin que, si nous l’utilisons sans spécifier CollectionViewSource.ItemsPath, un groupe par défaut de notre choix sera utilisé :
public class Author : IEnumerable<BookSku>
{
...
public IEnumerator<BookSku> GetEnumerator()
{
return this.BookSkus.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.BookSkus.GetEnumerator();
}
}
Et maintenant, nous pouvons choisir de supprimer ItemsPath="BookSkus"
si nous aimons et que l’application se comportera toujours de la même façon.
Conclusion
Cette étude de cas implique une interface utilisateur plus ambitieuse que celle précédente. Toutes les fonctionnalités et concepts de Silverlight LongListSelector de Windows Phone, et bien plus encore, ont été disponibles pour une application UWP sous la forme de SemanticZoom, ListView, GridView et CollectionViewSource. Nous avons montré comment réutiliser, ou copier et modifier, à la fois le code impératif et le balisage dans une application UWP pour obtenir des fonctionnalités, une interface utilisateur et des interactions adaptées aux facteurs de forme d’appareil Windows les plus étroits et les plus larges et toutes les tailles entre elles.