Edición especial de Windows 10 de 2015
Volumen 30, número 11
Diseño de IU: Aplicaciones adaptables para Windows 10
Por Clint Rutkas | Windows 2015
Con la Plataforma universal de Windows (UWP) en Windows 10, ahora las aplicaciones se pueden ejecutar en varias familias de dispositivos y se pueden escalar en distintos tamaños de pantalla y ventana, con la ayuda de los controles de plataforma. ¿Cómo admiten estas familias de dispositivos la interacción de los usuarios con las aplicaciones, y cómo responden las aplicaciones y se adaptan al dispositivo en que se ejecutan? Exploraremos esto y las herramientas y recursos que proporciona Microsoft en la plataforma, para que no tenga que escribir y mantener un código complejo para las aplicaciones que se ejecutan en distintos tipos de dispositivo.
Para empezar, exploraremos las técnicas dinámicas que se pueden usar para optimizar la IU para distintas familias de dispositivos. A continuación, describiremos detalladamente cómo puede la aplicación adaptarse a las funcionalidades específicas del dispositivo.
Antes de empezar con los controles, API y código, dediquemos un momento a explorar las familias de dispositivos de las que hablamos. En palabras simples: Una familia de dispositivos es un grupo de dispositivos con un factor de forma específico, desde dispositivos de IoT, smartphones, tablets y PC de escritorio a consolas de juego de Xbox, dispositivos Surface Hub con pantallas de gran tamaño e incluso wearables. Las aplicaciones funcionarán en todas estas familias de dispositivos, pero es importante considerar las familias de dispositivos en que se usarán las aplicaciones a la hora de diseñarlas.
Aunque hay muchas familias de dispositivos, la UWP se ha diseñado para que el 85 por ciento de las API sean totalmente accesibles para cualquier aplicación, independientemente de dónde se ejecute. Además, al observar las 1.000 aplicaciones principales, el 96,2 por cien de todas las API usadas se incluyen en el conjunto de API universales de Windows básico. La mayor parte de la funcionalidad está presente y disponible como parte de la UWP, con API especializadas en cada dispositivo disponible para personalizar la aplicación aún más.
Bienvenido de nuevo, Windows
Uno de los mayores cambios en la forma de usar las aplicaciones en Windows es algo con lo que ya está familiarizado: la ejecución de aplicaciones en una ventana. Windows 8 y Windows 8.1 permiten a las aplicaciones ejecutarse en pantalla completa o en paralelo con un máximo de cuatro aplicaciones simultáneamente. En cambio, Windows 10 permite al usuario organizar y cambiar de tamaño y posición las aplicaciones como quiera. El nuevo enfoque de Windows 10 ofrece al usuario una mayor flexibilidad de la IU, pero puede requerir cierto trabajo por su parte para optimizarla. Las mejoras de XAML en Windows 10 incluyen varias formas de implementar técnicas dinámicas en la aplicación para que se vea bien, independientemente del tamaño de la pantalla o ventana. A continuación, exploraremos tres de estos enfoques.
VisualStateManager En Windows 10, la clase VisualStateManager se ha ampliado con dos mecanismos para implementar un diseño dinámico en las aplicaciones basadas en XAML. Las nuevas API VisualState.StateTriggers y VisualState.Setters permiten definir estados visuales correspondientes a determinadas condiciones. Los estados visuales pueden cambiar en función de la altura y la anchura de la ventana. Para ello, es necesario usar AdaptiveTrigger (incorporado) como StateTrigger de VisualState y configurar las propiedades MinWindowHeight y MinWindowWidth. También puede ampliar Windows.UI.Xaml.StateTriggerBase para crear sus propios desencadenadores, por ejemplo, en función de la familia de dispositivos o el tipo de entrada. Observe el código de la Ilustración 1.
Ilustración 1 Creación de desencadenadores de estado personalizados
<Page>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<!-- VisualState to be triggered when window
width is >=720 effective pixels. -->
<AdaptiveTrigger MinWindowWidth="720" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="myPanel.Orientation"
Value="Horizontal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel x:Name="myPanel" Orientation="Vertical">
<TextBlock Text="This is a block of text. It is text block 1. "
Style="{ThemeResource BodyTextBlockStyle}"/>
<TextBlock Text="This is a block of text. It is text block 2. "
Style="{ThemeResource BodyTextBlockStyle}"/>
<TextBlock Text="This is a block of text. It is text block 3. "
Style="{ThemeResource BodyTextBlockStyle}"/>
</StackPanel>
</Grid>
</Page>
En el ejemplo de la Ilustración 1, la página muestra tres elementos TextBlock apilados unos encima de otros en su estado predeterminado. VisualStateManager tiene AdaptiveTrigger definido con un valor de MinWindowWidth de 720, lo que provoca que la orientación de StackPanel cambie a Horizontal cuando la ventana tenga una anchura mínima de 720 píxeles efectivos. Esto le permite usar la superficie de pantalla horizontal adicional cuando los usuarios cambian el tamaño de ventana o pasan del modo vertical al horizontal desde un teléfono o tableta. Recuerde que, si define tanto la propiedad de altura como la de anchura, el desencadenador solo se activará si la aplicación cumple ambas condiciones a la vez. Puede explorar los ejemplos de desencadenadores de estado de GitHub (wndw.ms/XUneob) para ver más escenarios que usen desencadenadores, como varios desencadenadores personalizados.
RelativePanel En el ejemplo de la Ilustración 1, se usa StateTrigger para cambiar la propiedad Orientation de StackPanel. Los numerosos elementos de contenedor de XAML, combinados con StateTriggers, permiten manipular la IU de muchas formas, pero no ofrecen ninguna manera de crear fácilmente una IU compleja y dinámica donde se puedan ordenar los elementos relacionados entre sí. Aquí entra en juego el nuevo elemento RelativePanel. Tal y como se muestra en la Ilustración 2, puede usar RelativePanel para ordenar los elementos expresando las relaciones espaciales entre ellos. Eso significa que puede usar fácilmente RelativePanel con AdaptiveTriggers para crear una IU dinámica donde pueda desplazar los elementos en función del espacio disponible en pantalla.
Ilustración 2 Expresión de relaciones espaciales con RelativePanel
<RelativePanel BorderBrush="Gray" BorderThickness="10">
<Rectangle x:Name="RedRect" Fill="Red" MinHeight="100" MinWidth="100"/>
<Rectangle x:Name="BlueRect" Fill="Blue" MinHeight="100" MinWidth="100"
RelativePanel.RightOf="RedRect" />
<!-- Width is not set on the green and yellow rectangles.
It's determined by the RelativePanel properties. -->
<Rectangle x:Name="GreenRect" Fill="Green"
MinHeight="100" Margin="0,5,0,0"
RelativePanel.Below="RedRect"
RelativePanel.AlignLeftWith="RedRect"
RelativePanel.AlignRightWith="BlueRect"/>
<Rectangle Fill="Yellow" MinHeight="100"
RelativePanel.Below="GreenRect"
RelativePanel.AlignLeftWith="BlueRect"
RelativePanel.AlignRightWithPanel="True"/>
</RelativePanel>
Como recordatorio, la sintaxis que usa con las propiedades adjuntas implica paréntesis adicionales, como se muestra aquí:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="720" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="GreenRect.(RelativePanel.RightOf)"
Value="BlueRect" />
</VisualState.Setters>
</VisualState>
Puedes consultar otros escenarios con RelativePanel en el ejemplo de técnicas dinámicas de GitHub (wndw.ms/cbdL0q).
SplitView El tamaño de ventana de la aplicación tiene mayor impacto que el contenido que se muestra en páginas de aplicación; puede requerir que los elementos de navegación respondan a los cambios en el tamaño de la propia ventana. El nuevo control SplitView incorporado en Windows 10 se suele usar para crear una experiencia de navegación de nivel superior que se puede ajustar para tener un comportamiento distinto en función del tamaño de la ventana de la aplicación. Recuerde que, aunque se trata de uno de los casos de uso más comunes de SplitView, no se limita estrictamente a este uso. SplitView se divide en dos áreas distintas: panel y contenido.
Se pueden usar varias propiedades de control para manipular la presentación. En primer lugar, DisplayMode especifica cómo se representa en panel en relación con el área de contenido con los cuatro modos disponibles: Overlay, Inline, CompactOverlay y CompactInline. En la Ilustración 3 se incluyen ejemplos de los modos Inline, Overlay y CompactInline representados en una aplicación.
Ilustración 3 Elementos de navegación de DisplayMode
La propiedad PanePlacement muestra el panel en el lado izquierdo (predeterminado) o derecho del área de contenido. La propiedad OpenPaneLength especifica la anchura del panel cuando se amplía por completo (el valor predeterminado es de 320 píxeles efectivos).
Tenga en cuenta que el control SplitView no incluye ningún elemento de IU integrado para que los usuarios cambien el estado del panel, como el menú “hamburguesa”, común en aplicaciones móviles. Si quiere exponer este comportamiento, debe definir este elemento de IU en la aplicación y proporcionar un código para alternar la propiedad IsPaneOpen de SplitView.
¿Quiere explorar todo el conjunto de características que ofrece SplitView? Asegúrese de consultar el ejemplo de menú de navegación XAML de GitHub (wndw.ms/qAUVr9).
Recuperación del botón Atrás
Si solía desarrollar aplicaciones para versiones anteriores de Windows Phone, puede que esté acostumbrado a que cada dispositivo cuente con un botón de hardware o software para volver atrás, que permite a los usuarios navegar hacia atrás en la aplicación. Sin embargo, para Windows 8 y 8.1, tenía que crear su propia IU para la navegación hacia atrás. Para facilitar las cosas al orientarse a varias familias de dispositivos en la aplicación de Windows 10, existe una forma de garantizar un mecanismo de navegación hacia atrás coherente para todos los usuarios. Esto puede ayudar a liberar espacio de la IU en las aplicaciones en adelante.
Para habilitar un botón atrás de sistema para la aplicación, incluso para familias de dispositivos que no cuenta con un botón de hardware o software (por ejemplo, un portátil o PC de escritorio), use la propiedad AppViewBackButtonVisibility de la clase SystemNavigationManager. Use SystemNavigationManager para la vista actual y defina la visibilidad del botón atrás como se muestra en el código siguiente:
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Visible;
La clase SystemNavigationManager también expone un evento BackRequested, que se activa cuando el usuario invoca el botón, gesto o comando de voz que proporciona el sistema para la navegación hacia atrás. Esto significa que puede controlar este único evento para navegar hacia atrás de forma coherente en la aplicación en cualquier familia de dispositivos.
Beneficios de Continuum
Por último, pero no menos importante, nos gustaría mencionar uno de nuestros favoritos personales: Continuum en Windows 10. Con Continuum, Windows 10 ajusta la experiencia a lo que quiera hacer y a cómo lo quiera hacer. Si la aplicación se ejecuta en un PC 2 en 1 de Windows, por ejemplo, implementar Continuum en la aplicación permite a los usuarios usar la función táctil o un mouse y teclado para optimizar la productividad. Con la propiedad UserInteractionMode de la clase UIViewSettings, la aplicación puede determinar si el usuario está interactuando con la vista a través del tacto o un mouse y teclado con una sola línea de código:
UIViewSettings.GetForCurrentView().UserInteractionMode;
// Returns UserInteractionMode.Mouse or UserInteractionMode.Touch
Después de detectar el modo de interacción, puede optimizar la IU de la aplicación con acciones como aumentar o reducir márgenes, mostrar u ocultar características complejas, etc. Consulte el artículo de TechNet “Windows 10 Apps: Leverage Continuum Feature to Change UI for Mouse/Keyboard Users Using Custom StateTrigger” (Aplicaciones de Windows 10: Uso de la característica Continuum para cambiar la IU para usuarios de mouse/teclado con un valor personalizado de StateTrigger), por Lee McPherson (wndw.ms/y3gB0J) que muestra cómo combinar los nuevos elementos StateTriggers y UserInteractionMode para crear su propio StateTrigger de Continuum personalizado.
Aplicaciones adaptables
Las aplicaciones que pueden responder a cambios en el tamaño y la orientación de la pantalla son útiles, pero, para conseguir una funcionalidad convincente multiplataforma, UWP proporciona a los desarrollados dos tipos adicionales de comportamiento adaptable:
- Las aplicaciones adaptables a versiones responden a distintas versiones de UWP al detectar las API y recursos disponibles. Por ejemplo, puede que quiera que la aplicación use ciertas API nuevas que solo estén disponibles en dispositivos que ejecutan las versiones más recientes de UWP y, al mismo tiempo, sea compatible para los usuarios que aún no han hecho la actualización.
- Las aplicaciones adaptables a las plataformas responden a las funcionalidades únicas disponibles en distintas familias de dispositivos. Así pues, se puede crear una aplicación para ejecutarla en todas las familias de dispositivos, aunque es posible que quiera usar API específicas para dispositivos móviles para ejecutarla en un dispositivo móvil como un smartphone.
Como ya se ha mencionado, con Windows 10, la gran mayoría de API de UWP son totalmente accesibles para cualquier aplicación, independientemente del dispositivo en que se ejecute. Las API especializadas asociadas a cada familia de dispositivos permiten a los desarrolladores personalizar más sus aplicaciones.
La idea fundamental detrás de las aplicaciones adaptables es que la aplicación comprueba la funcionalidad (o característica) que necesita y solo la usa cuando está disponible. En el pasado, una aplicación comprobaría el SO y, a continuación, invocaría la API asociada a esta versión. Con Windows 10, la aplicación puede comprobar en el tiempo de ejecución si una clase, método, propiedad, evento o contrato de API es compatible con el SO actual. Si lo es, la aplicación invoca la API correspondiente. La clase ApiInformation, situada en el espacio de nombres Windows.Foundation.Metadata, contiene varios métodos estáticos (por ejemplo, IsApiContractPresent, IsEventPresent e IsMethodPresent), que se usan para consultar API. Aquí se muestra un ejemplo:
using Windows.Foundation.Metadata;
if(ApiInformation.IsTypePresent("Windows.Media.Playlists.Playlist"))
{
await myAwesomePlaylist.SaveAsAsync( ... );
}
Este código hace dos cosas: Hace una comprobación en tiempo de ejecución sobre la presencia de la clase Playlist y, a continuación, invoca el método SaveAsAsync en una instancia de la clase. Observe la facilidad de comprobar la presencia de un tipo en el SO actual, con la API IsTypePresent. En el pasado, esta comprobación podría haber requerido LoadLibrary, GetProcAddress, QueryInterface, Reflection o el uso de una palabra clave “dinámica”, entre otras cosas, en función del lenguaje y el marco de trabajo. Observe también la referencia fuertemente tipada al invocar el método. Si se usa Reflection o “dynamic”, se pierde el diagnóstico de tiempo de compilación estático que, por ejemplo, le informaría si escribe el nombre del método incorrectamente.
Detección con contratos de API
Básicamente, el contrato de API es un conjunto de API. Un contrato de API hipotético podría representar un conjunto de API con dos clases, cinco interfaces, una estructura, dos enumeraciones, etc. Agrupamos los tipos relacionados lógicamente un contrato de API. De muchas formas, un contrato de API representa una característica: un conjunto de API relacionadas que, en conjunto, ofrecen una funcionalidad particular. Todas las API de Windows Runtime desde Windows 10 en adelante pertenecen a algún contrato de API. La documentación disponible en msdn.com/dn706135 describe la variedad de contratos de API disponibles. Observará que la mayoría representan un conjunto de API de funcionalidad relacionada.
El uso de contratos de API también le proporciona, como desarrollador, ciertas garantías adicionales. La más importante es que, cuando una plataforma implementa cualquier API en un contrato de API, debe implementar todas las API del contrato. En otras palabras, un contrato de API es una unidad atómica y probar la compatibilidad del contrato de API equivale a probar todas las API del conjunto. La aplicación puede invocar cualquier API del contrato API detectado sin tener que comprobar todas las API individualmente.
El contrato de API más grande y más usado es Windows.Foundation.UniversalApiContract. Contiene casi todas las API de la Plataforma universal de Windows. Si quiere comprobar si el SO actual admite UniversalApiContract, escriba el código siguiente:
if (ApiInformation.IsApiContractPresent(
"Windows.Foundation.UniversalApiContract"), 1, 0)
{
// All APIs in the UniversalApiContract version 1.0 are available for use
}
Ahora mismo, la única versión vigente de UniversalApiContract es la versión 1.0, de modo que esta comprobación es un poco absurda. Pero una actualización futura de Windows 10 podría incorporar más API y dar lugar a la versión 2.0 de UniversalApiContract, con las nuevas API universales. En el futuro, una aplicación que quiera ejecutarse en todos los dispositivos, pero también quiera usar las API de la nueva versión 2.0 podría usar el código siguiente:
if (ApiInformation.IsApiContractPresent(
"Windows.Foundation.UniversalApiContract"), 2, 0)
{
// This device supports all APIs in UniversalApiContract version 2.0
}
Si la aplicación solo necesitara invocar un único método de la versión 2.0, podría comprobar el método directamente a través de IsMethodPresent. En tal caso, puede usar el enfoque que le parezca más sencillo.
Además de UniversalApiContract, hay otros contratos de API. La mayoría representan una característica o conjunto de API que no están presentes universalmente en todas las plataformas de Windows 10, sino en una o más familias de dispositivos específicas. Como ya hemos mencionado, ya no hace falta comprobar un tipo de dispositivo concreto y, a continuación, inferir la compatibilidad para una API. Basta con consultar el conjunto de API que quiere usar la aplicación.
Ahora puedo modificar el ejemplo original para que busque la presencia de Windows.Media.Playlists.PlaylistsContract, en lugar de comprobar solo la presencia de la clase Playlist:
if(ApiInformation.IsApiContractPresent(
"Windows.Media.Playlists.PlaylistsContract"), 1, 0)
{
// Now I can use all Playlist APIs
}
Cuando la aplicación necesite invocar una API que no esté presente en todas las familias de dispositivos, debe agregar una referencia al SDK de extensión que defina la API. En Visual Studio 2015, vaya al cuadro de diálogo Agregar referencia y abra la pestaña Extensiones. Allí encontrará las tres extensiones más importantes: extensión para dispositivos móviles, extensión para equipos de escritorio y extensión para IoT.
Lo único que debe hacer la aplicación es comprobar la presencia del contrato de API deseado e invocar la API correspondiente condicionalmente. No tiene que preocuparse por el tipo de dispositivo. Ahora, la pregunta es: Necesito invocar la API Playlist, pero no es una API disponible universalmente. La documentación (bit.ly/1QkYqky) me indica en qué contrato de API se encuentra la clase. Pero, ¿qué SDK de extensión la definen?
La clase Playlist solo está disponible (actualmente) en dispositivos de escritorio, no en dispositivos móviles, Xbox ni otras familias de dispositivos. Por lo tanto, debe agregar una referencia al SDK de la extensión de dispositivos de escritorio antes de que se compile el código anterior.
Lucian Wischik, miembro del equipo de Visual Studio y autor ocasional de MSDN Magazine, ha creado una herramienta que puede ayudar. Analiza el código de la aplicación al invocar una API específica de plataforma y comprueba que se hizo la comprobación de adaptabilidad correspondiente. Si no se hizo ninguna comprobación, el analizador notifica una advertencia y proporciona una “corrección rápida” útil para insertar la comprobación correcta en el código al presionar Ctrl+punto o hacer clic en la bombilla. (Para obtener más información, consulte bit.ly/1JdXTeV). El analizador también se puede instalar a través de NuGet (bit.ly/1KU9ozj).
Para acabar, observemos ejemplos más completos de código adaptable para Windows 10. En primer lugar, se incluye un código que no es adaptable:
// This code will crash if called from IoT or Mobile
async private Task CreatePlaylist()
{
StorageFolder storageFolder = KnownFolders.MusicLibrary;
StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
Windows.Media.Playlists.Playlist myAwesomePlaylist =
new Windows.Media.Playlists.Playlist();
myAwesomePlaylist.Files.Add(pureRockFile);
// Code will crash here as this is a Desktop-only call
await myAwesomePlaylist.SaveAsAsync(KnownFolders.MusicLibrary,
"My Awesome Playlist", NameCollisionOption.ReplaceExisting);
}
Veamos el mismo código después de agregar una línea que comprueba que la API opcional es compatible en el dispositivo de destino antes de invocar el método. De este modo, se impiden los bloqueos en tiempo de ejecución. Probablemente querrá desarrollar más este ejemplo y no mostrar la IU que invoca el método CreatePlaylist si la aplicación detecta que la funcionalidad de lista de reproducción no está disponible en el dispositivo:
async private Task CreatePlaylist()
{
StorageFolder storageFolder = KnownFolders.MusicLibrary;
StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
Windows.Media.Playlists.Playlist myAwesomePlaylist =
new Windows.Media.Playlists.Playlist();
myAwesomePlaylist.Files.Add(pureRockFile);
// Now I'm a safe call! Cache this value if this will be queried a lot
if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
"Windows.Media.Playlists.Playlist"))
{
await myAwesomePlaylist.SaveAsAsync(
KnownFolders.MusicLibrary, "My Awesome Playlist",
NameCollisionOption.ReplaceExisting);
}
}
Finalmente, se incluye un ejemplo de código que intenta acceder al botón de cámara dedicado disponible en muchos dispositivos móviles:
// Note: Cache the value instead of querying it more than once
bool isHardwareButtonsAPIPresent =
Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
"Windows.Phone.UI.Input.HardwareButtons");
if (isHardwareButtonsAPIPresent)
{
Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
HardwareButtons_CameraPressed;
}
Tenga en cuenta el paso de detección. Si hiciera referencia directamente al objeto HardwareButtons para el evento CameraPressed en un PC de escritorio, sin comprobar si está presente HardwareButtons, la aplicación se bloquearía.
Se están haciendo muchas cosas en relación con las IU dinámicas y las aplicaciones adaptables de Windows 10. ¿Quiere más información? Consulte la fantástica charla sobre contratos de API que dio Brent Rector en la conferencia Build 2015 (wndw.ms/IgNy0I). Además, asegúrese de ver el vídeo informativo de Microsoft Virtual Academy sobre código adaptable (bit.ly/1OhZWGs) dedicado a este tema, que incluye más detalles.
Clint Rutkases administrador de productos sénior en Windows y está centrado en la plataforma para desarrolladores. Ha trabajado en Halo en 343 Industries y en Channel 9 en Microsoft y ha creado algunos proyectos sorprendentes con tecnología de Windows, como una pista de baile controlada por ordenador, un Ford Mustang personalizado, un robot que dispara camisetas, etc.
Rajen Kishnatrabaja actualmente como administrador de marketing de producto sénior en el equipo de marketing para desarrolladores para la plataforma Windows de Microsoft en Redmond, Wash. Anteriormente, trabajo como consultor y experto técnico para Microsoft en los Países Bajos.
Gracias a los siguientes expertos técnicos de Microsoft por revisar este artículo: Sam Jarawan, Harini Kannan y Brent Rector