Windows Phone caso práctico de Silverlight a UWP: Bookstore2
Este caso práctico, que se basa en la información que se proporciona en Bookstore1, comienza con una aplicación Windows Phone Silverlight que muestra datos agrupados en un LongListSelector. En el modelo de vista, cada instancia de la clase Author representa el grupo de los libros escritos por ese autor y, en LongListSelector, podemos ver la lista de libros agrupados por autor o alejarnos para ver una lista de saltos de autores. La lista de accesos directos ofrece una navegación mucho más rápida que un desplazamiento por la lista de libros. Recorremos los pasos para migrar la aplicación a una aplicación de Windows 10 Plataforma universal de Windows (UWP).
Nota Al abrir Bookstore2Universal_10 en Visual Studio, si ve el mensaje "Se requiere la actualización de Visual Studio", siga los pasos para establecer La versión de la plataforma de destino en TargetPlatformVersion.
Descargas
Descargue la aplicación Bookstore2WPSL8 Windows Phone Silverlight.
Descargue la aplicación Bookstore2Universal_10 Windows 10.
La aplicación Windows Phone Silverlight
En la ilustración siguiente se muestra el aspecto de Bookstore2WPSL8(la aplicación que vamos a portar). Es un LongListSelector de desplazamiento vertical de libros agrupados por autor. Puede alejar la lista de accesos directos y, desde allí, puede volver a cualquier grupo. Hay dos partes principales de esta aplicación: el modelo de vista que proporciona el origen de datos agrupado y la interfaz de usuario que se enlaza a ese modelo de vista. Como veremos, ambas piezas se pueden migrar fácilmente desde Windows Phone tecnología silverlight a la Plataforma universal de Windows (UWP).
Migración a un proyecto de Windows 10
Es una tarea rápida crear un nuevo proyecto en Visual Studio, copiar archivos en él desde Bookstore2WPSL8 e incluir los archivos copiados en el nuevo proyecto. Empiece por crear un nuevo proyecto aplicación vacía (Windows Universal). Asígne un nombre Bookstore2Universal_10. Estos son los archivos que se van a copiar de Bookstore2WPSL8 a Bookstore2Universal_10.
- Copie la carpeta que contiene los archivos PNG de la imagen de portada del libro (la carpeta es \Assets\CoverImages). Después de copiar la carpeta, en Explorador de soluciones, asegúrese de que Mostrar todos los archivos está activado. Haga clic con el botón derecho en la carpeta que copió y haga clic en Incluir en el proyecto. Ese comando es lo que queremos decir por "incluir" archivos o carpetas en un proyecto. Cada vez que copie un archivo o carpeta, haga clic en Actualizar en Explorador de soluciones y, a continuación, incluya el archivo o la carpeta en el proyecto. No es necesario hacerlo para los archivos que va a reemplazar en el destino.
- Copie la carpeta que contiene el archivo de origen del modelo de vista (la carpeta es \ViewModel).
- Copie MainPage.xaml y reemplace el archivo en el destino.
Podemos mantener App.xaml y App.xaml.cs que Visual Studio generó para nosotros en el proyecto de Windows 10.
Edite el código fuente y los archivos de marcado que acaba de copiar y cambie las referencias al espacio de nombres Bookstore2WPSL8 a Bookstore2Universal_10. Una manera rápida de hacerlo es usar la característica Reemplazar en archivos . En el código imperativo del archivo de origen del modelo de vista, estos cambios de portabilidad son necesarios.
- Cambie
System.ComponentModel.DesignerProperties
a y, aDesignMode
continuación, use el comando Resolver en él. Elimine laIsInDesignTool
propiedad y use IntelliSense para agregar el nombre de propiedad correcto:DesignModeEnabled
. - Use el comando Resolver en
ImageSource
. - Use el comando Resolver en
BitmapImage
. - Elimine
using System.Windows.Media;
yusing System.Windows.Media.Imaging;
. - Cambie el valor devuelto por la propiedad Bookstore2Universal_10.BookstoreViewModel.AppName de "BOOKSTORE2WPSL8" a "BOOKSTORE2UNIVERSAL".
- Igual que hicimos para Bookstore1, actualiza la implementación de la propiedad BookSku.CoverImage (consulta Enlazar una imagen a un modelo de vista).
En MainPage.xaml, se necesitan estos cambios de portabilidad iniciales.
- Cambie
phone:PhoneApplicationPage
aPage
(incluidas las apariciones en la sintaxis del elemento de propiedad). - Elimine las
phone
declaraciones de prefijo de espacio de nombres yshell
. - Cambie "clr-namespace" a "using" en la declaración de prefijo del espacio de nombres restante.
- Elimine
SupportedOrientations="Portrait"
y , yOrientation="Portrait"
configure Vertical en el manifiesto del paquete de la aplicación en el nuevo proyecto. - Elimine
shell:SystemTray.IsVisible="True"
. - Los tipos de convertidores de elementos de lista de accesos directos (que están presentes en el marcado como recursos) se han movido al espacio de nombres Windows.UI.Xaml.Controls.Primitives. Por lo tanto, agregue la declaración de prefijo de espacio de nombres Windows_UI_Xaml_Controls_Primitives y asígnela a Windows.UI.Xaml.Controls.Primitives. En los recursos del convertidor de elementos de lista de accesos directos, cambie el prefijo de
phone:
aWindows_UI_Xaml_Controls_Primitives:
. - Igual que hicimos con Bookstore1, reemplace todas las referencias al
PhoneTextExtraLargeStyle
estilo TextBlock por una referencia aSubtitleTextBlockStyle
, reemplace porSubtitleTextBlockStyle
PhoneTextSubtleStyle
, reemplace porCaptionTextBlockStyle
PhoneTextNormalStyle
y reemplace por .PhoneTextTitle1Style
HeaderTextBlockStyle
- Hay una excepción en
BookTemplate
. El estilo del segundo TextBlock debe hacer referencia aCaptionTextBlockStyle
. - Quite el atributo FontFamily del textBlock dentro
AuthorGroupHeaderTemplate
y establezca el fondo del borde como referenciaSystemControlBackgroundAccentBrush
en lugar dePhoneAccentBrush
. - Debido a los cambios relacionados con los píxeles de vista, pase por el marcado y multiplique cualquier dimensión de tamaño fijo (márgenes, ancho, alto, etcetera) en 0,8.
Reemplazar LongListSelector
Al reemplazar LongListSelector por un control SemanticZoom, se realizarán varios pasos, por lo que vamos a iniciarlo. Un LongListSelector se enlaza directamente al origen de datos agrupado, pero un SemanticZoom contiene controles ListView o GridView, que se enlazan indirectamente a los datos a través de un adaptador CollectionViewSource. CollectionViewSource debe estar presente en el marcado como un recurso, por lo que vamos a empezar agregando eso al marcado en MainPage.xaml dentro <Page.Resources>
de .
<CollectionViewSource
x:Name="AuthorHasACollectionOfBookSku"
Source="{Binding Authors}"
IsSourceGrouped="true"/>
Tenga en cuenta que el enlace en LongListSelector.ItemsSource se convierte en el valor de CollectionViewSource.Source y LongListSelector.IsGroupingEnabled se convierte en CollectionViewSource.IsSourceGrouped. CollectionViewSource tiene un nombre (nota: no una clave, como podría esperar) para que podamos enlazar a él.
A continuación, reemplace por phone:LongListSelector
este marcado, que nos proporcionará una semántica preliminar con la que trabajar.
<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 noción LongListSelector de los modos de lista plana y de lista de accesos directos se responde en la noción SemanticZoom de un zoom y una vista alejada, respectivamente. La vista ampliada es una propiedad y se establece esa propiedad en una instancia de listView. En este caso, la vista alejada también se establece en listView y ambos controles ListView están enlazados a nuestro CollectionViewSource. La vista ampliada usa la misma plantilla de elemento, plantilla de encabezado de grupo y configuración HideEmptyGroups (ahora denominada HidesIfEmpty) que la lista plana de LongListSelector. Y la vista alejada usa una plantilla de elemento muy similar a la que hay dentro del estilo de lista de accesos directos de LongListSelector (AuthorNameJumpListStyle
). Además, tenga en cuenta que la vista alejada se enlaza a una propiedad especial de CollectionViewSource denominada CollectionGroups, que es una colección que contiene los grupos en lugar de los elementos.
Ya no necesitamos AuthorNameJumpListStyle
, al menos no todos. Solo necesitamos la plantilla de datos para los grupos (que son autores de esta aplicación) en la vista alejada. Por lo tanto, eliminamos el AuthorNameJumpListStyle
estilo y lo reemplazamos por esta plantilla de datos.
<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>
Tenga en cuenta que, dado que el contexto de datos de esta plantilla de datos es un grupo en lugar de un elemento, enlazamos a una propiedad especial denominada Group.
Ahora puede compilar y ejecutar la aplicación. Así es como se ve en el emulador móvil.
El modelo de vista y las vistas alejadas y alejadas funcionan correctamente, aunque un problema es que necesitamos hacer un poco más de estilo y trabajo de plantillas. Por ejemplo, los estilos y pinceles correctos aún no se usan, por lo que el texto es invisible en los encabezados de grupo que puede hacer clic para alejar. Si ejecuta la aplicación en un dispositivo de escritorio, verá un segundo problema, que es que la aplicación aún no adapta su interfaz de usuario para ofrecer la mejor experiencia y el uso de espacio en dispositivos más grandes donde las ventanas pueden ser potencialmente mucho más grandes que la pantalla de un dispositivo móvil. Por lo tanto, en las secciones siguientes (estilo inicial y plantillas, interfaz de usuario adaptable y estilo final), solucionaremos esos problemas.
Creación de plantillas y estilos iniciales
Para espaciar bien los encabezados de grupo, edite AuthorGroupHeaderTemplate
y establezca un margen de "0,0,0,9.6"
en el borde.
Para espaciar bien los elementos del libro, edite BookTemplate
y establezca el margen "9.6,0"
en en ambos TextBlocks.
Para diseñar el nombre de la aplicación y el título de la página un poco mejor, dentro TitlePanel
de , quite el margen superior en el segundo TextBlock estableciendo el valor "7.2,0,0,0"
en . Y en TitlePanel
sí mismo, establezca el margen en 0
(o cualquier valor que sea bueno para usted)
Cambie LayoutRoot
el valor de Background (Fondo) a "{ThemeResource ApplicationPageBackgroundThemeBrush}"
.
Interfaz de usuario adaptable
Dado que empezamos con una aplicación de teléfono, no es sorpresa que el diseño de la interfaz de usuario de la aplicación portado realmente solo tenga sentido para dispositivos pequeños y ventanas estrechas en esta fase del proceso. Pero, realmente nos gustaría que el diseño de la interfaz de usuario se adapte a sí mismo y hacer un mejor uso del espacio cuando la aplicación se ejecuta en una ventana ancha (que solo es posible en un dispositivo con una pantalla grande) y para que solo use la interfaz de usuario que tenemos actualmente cuando la ventana de la aplicación es estrecha (lo que sucede en un dispositivo pequeño, y también pueden ocurrir en un dispositivo grande).
Podemos usar la característica Visual State Manager adaptable para lograrlo. Estableceremos propiedades en los elementos visuales para que, de forma predeterminada, la interfaz de usuario se establezca en el estado estrecho mediante las plantillas que estamos usando en este momento. A continuación, detectaremos cuándo la ventana de la aplicación es más amplia o igual a un tamaño específico (medido en unidades de píxeles efectivos) y, en respuesta, cambiaremos las propiedades de los elementos visuales para que obtengamos un diseño más grande y ancho. Colocaremos esos cambios de propiedad en un estado visual y usaremos un desencadenador adaptable para supervisar continuamente y determinar si se debe aplicar ese estado visual, o no, en función del ancho de la ventana en píxeles efectivos. Estamos desencadenando en el ancho de la ventana en este caso, pero también es posible desencadenar en el alto de la ventana.
Un ancho de ventana mínimo de 548 epx es adecuado para este caso de uso porque es el tamaño del dispositivo más pequeño en el que queremos mostrar el diseño ancho. Los teléfonos suelen ser más pequeños que 548 epx, por lo que en un dispositivo pequeño como ese, permaneceríamos en el diseño estrecho predeterminado. En un equipo, la ventana se iniciará de forma predeterminada lo suficientemente ancha como para desencadenar el conmutador al estado ancho, que mostrará elementos de tamaño 250x250. Desde allí, podrá arrastrar la ventana lo suficientemente estrecha como para mostrar un mínimo de dos columnas de los elementos de 250x250. Cualquier más estrecho de lo que y el desencadenador desactivarán, se quitará el estado visual ancho y el diseño estrecho predeterminado estará en vigor.
Antes de abordar la parte de Visual State Manager adaptable, primero necesitamos diseñar el estado ancho y eso significa agregar algunos nuevos elementos visuales y plantillas a nuestro marcado. Estos pasos describen cómo hacerlo. Mediante las convenciones de nomenclatura para los elementos visuales y las plantillas, incluiremos la palabra "wide" en el nombre de cualquier elemento o plantilla que sea para el estado ancho. Si un elemento o plantilla no contiene la palabra "wide", puede suponer que es para el estado estrecho, que es el estado predeterminado y cuyos valores de propiedad se establecen como valores locales en los elementos visuales de la página. Solo los valores de propiedad para el estado ancho se establecen a través de un estado visual real en el marcado.
- Realice una copia del control SemanticZoom en el marcado y establezca
x:Name="narrowSeZo"
en la copia. En el original, establezcax:Name="wideSeZo"
y también establezcaVisibility="Collapsed"
para que el ancho no sea visible de forma predeterminada. - En
wideSeZo
, cambie ListView s a GridViews en la vista ampliada y en la vista alejada. - Realice una copia de estos tres recursos
AuthorGroupHeaderTemplate
,ZoomedOutAuthorTemplate
yBookTemplate
anexe la palabraWide
a las claves de las copias. Además, actualicewideSeZo
para que haga referencia a las claves de estos nuevos recursos. - Reemplace el contenido de
AuthorGroupHeaderTemplateWide
por<TextBlock Style="{StaticResource SubheaderTextBlockStyle}" Text="{Binding Name}"/>
. - Reemplace el contenido de
ZoomedOutAuthorTemplateWide
por:
<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>
- Reemplace el contenido de
BookTemplateWide
por:
<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>
- Para el estado ancho, los grupos en la vista ampliada necesitarán más espacio de respiración vertical alrededor de ellos. Crear y hacer referencia a una plantilla de panel de elementos nos proporcionará los resultados que queremos. Este es el aspecto del marcado.
<ItemsPanelTemplate x:Key="ZoomedInItemsPanelTemplate">
<ItemsWrapGrid Orientation="Horizontal" GroupPadding="0,0,0,20"/>
</ItemsPanelTemplate>
...
<SemanticZoom x:Name="wideSeZo" ... >
<SemanticZoom.ZoomedInView>
<GridView
...
ItemsPanel="{StaticResource ZoomedInItemsPanelTemplate}">
...
- Por último, agregue el marcado adecuado de Visual State Manager como primer elemento secundario 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>
...
Estilo final
Todo lo que queda son algunos ajustes finales de estilo.
- En
AuthorGroupHeaderTemplate
, establezcaForeground="White"
en TextBlock para que se vea correcto al ejecutarse en la familia de dispositivos móviles. - Agregue
FontWeight="SemiBold"
a TextBlock enAuthorGroupHeaderTemplate
yZoomedOutAuthorTemplate
. - En
narrowSeZo
, los encabezados de grupo y los autores de la vista alejada están alineados a la izquierda en lugar de estirados, por lo que vamos a trabajar en eso. Crearemos un HeaderContainerStyle para la vista ampliada con HorizontalContentAlignment establecidoStretch
en . Y crearemos un ItemContainerStyle para la vista alejada que contiene ese mismo establecedor. Esto es lo que parece.
<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}"
...
Esa última secuencia de operaciones de estilo deja que la aplicación tenga este aspecto.
La aplicación de Windows 10 portado que se ejecuta en un dispositivo de escritorio, vista ampliada, dos tamaños de ventana
La aplicación de Windows 10 migrada que se ejecuta en un dispositivo de escritorio, vista alejada, dos tamaños de ventana
La aplicación de Windows 10 migrada que se ejecuta en un dispositivo móvil, vista ampliada
La aplicación de Windows 10 migrada que se ejecuta en un dispositivo móvil, vista alejada
Hacer que el modelo de vista sea más flexible
Esta sección contiene un ejemplo de instalaciones que nos abren en virtud de haber movido nuestra aplicación para usar la UWP. Aquí se explican los pasos opcionales que puede seguir para que el modelo de vista sea más flexible cuando se accede a través de CollectionViewSource. El modelo de vista (el archivo de origen está en ViewModel\BookstoreViewModel.cs) que se ha migrado desde la aplicación Windows Phone Silverlight Bookstore2WPSL8 contiene una clase denominada Author, que deriva de List<T>, donde T es BookSku. Esto significa que la clase Author es un grupo de BookSku.
Cuando enlazamos CollectionViewSource.Source a Authors, lo único que estamos comunicando es que cada autor de autores es un grupo de algo. Lo dejaremos en CollectionViewSource para determinar que Author es, en este caso, un grupo de BookSku. Eso funciona: pero no es flexible. ¿Qué ocurre si queremos que Author sea un grupo de BookSku y un grupo de direcciones donde ha vivido el autor? El autor no puede ser ambos grupos. Sin embargo, Author puede tener cualquier número de grupos. Y esa es la solución: use el patrón has-a-group en lugar de, o además, el patrón is-a-group que estamos usando actualmente. A continuación, se indica cómo puede hacerlo.
- Cambie Author para que ya no derive de List<T>.
- Agregar este campo a
- Agregar esta propiedad a
- Y, por supuesto, podemos repetir los dos pasos anteriores para agregar tantos grupos a Author como necesitemos.
- Cambie la implementación del método AddBookSku a
this.BookSkus.Add(bookSku);
. - Ahora que Author tiene al menos un grupo, es necesario comunicarse con el CollectionViewSource que de esos grupos debe usar. Para ello, agregue esta propiedad a CollectionViewSource:
ItemsPath="BookSkus"
Estos cambios dejan esta aplicación funcionalmente sin cambios, pero ahora sabes cómo podrías ampliar Author y CollectionViewSource, si necesitas. Vamos a realizar un último cambio en Author para que, si lo usamos sin especificar CollectionViewSource.ItemsPath, se usará un grupo predeterminado de nuestra elección:
public class Author : IEnumerable<BookSku>
{
...
public IEnumerator<BookSku> GetEnumerator()
{
return this.BookSkus.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.BookSkus.GetEnumerator();
}
}
Y ahora podemos optar por quitar ItemsPath="BookSkus"
si nos gusta y la aplicación se comportará de la misma manera.
Conclusión
Este caso práctico implicaba una interfaz de usuario más ambiciosa que la anterior. Se encontraron todas las instalaciones y conceptos del Windows Phone LongListSelector de Silverlight, y mucho más, disponibles para una aplicación para UWP en forma de SemanticZoom, ListView, GridView y CollectionViewSource. Hemos mostrado cómo volver a usar, o copiar y editar, tanto el código imperativo como el marcado en una aplicación para UWP para lograr funcionalidad, interfaz de usuario e interacciones adaptadas para adaptarse a los factores de forma de dispositivo Windows más estrechos y anchos y todos los tamaños entre sí.