En Windows 8, la gran mayoría de las aplicaciones se alimentan de contenido en internet y, por ello, deben hacer cargas de datos iniciales desde la red, como en aplicaciones de noticias, que podemos gestionar de diferentes formas más o menos elegantes.

  • Abrir la aplicación vacía de datos y, en la página principal realizar la carga de contenido desde internet.

    En este caso, se mostraría al usuario una página inicial "vacía" y se debería notificar al usuario que se están cargando los datos que, en muchos casos no deberían ser mostrados hasta la finalización de la carga de los mismos. Esto produciría un efecto muy feo de "Vacío-Lleno" además de que en las guías de diseño que ha realizado Microsoft, no se recomienda esta opción porque no da buena imagen mostrar la aplicación en su "esqueleto".

    image

     

  • Abrir la aplicación con datos almacenados de anteriores ejecuciones, es decir, datos antiguos "cacheados".

    Presenta un paradigma similar al anterior en el que, al mostrar la página inicial al usuario, habría que notificarle que se están cargando los datos pero, tendremos un problema mayor, dado que mientras se cargan los datos actuales el usuario podría interactuar con los datos antiguos, lo que podría provocar conflictos en la mayoría de los casos porque estará accediendo a las colecciones de datos mientras éstas se están actualizando. Una opción para salvar este problema sería bloquear la pantalla al usuario pero volvemos a entrar en conflicto con las recomendaciones que Microsoft indica en sus guías de diseño que, además, indican que no se debe bloquear la pantalla al usuario salvo necesidad imperativa.

    image

     

  • Extender la Splash Screen para realizar la carga de datos.

    Finalmente, la opción más elegante que además nos permitirá iniciar la aplicación datos y que éstos además sean los actuales, es extender la Splash Screen de nuestra aplicación mediante una "Extended Splash Screen" y que sea esta la que se encargue de cargar los datos a la vez que notifica al usuario el proceso. De esta forma, al terminar la carga de datos, la aplicación presentaría la página inicial de la aplicación al usuario con los datos actuales.

Extended-Splash-Screen

 

Una vez que tenemos claro que la mejor opción y la más elegante es el uso de una "Extended Splash Screen", tenemos que programarla correctamente, lo que no es algo trivial y que requiere editar más archivos de lo que a priori parecería.

Paso 1: Crear una nueva vista que he llamado ExtendedSplash y que servirá para realizar la carga de los datos. Esta vista sólo debe contener este "Grid" en el código XAML y no ser parte de una etiqueta "<Page>" ni "<commo:LayoutAwarePage>", es decir, al crearla, eliminamos todo el contenido XAML que contiene y ponemos este Grid que contiene, la imagen de SplashScreen que deberá ser puesta tal cual aparece en el ejemplo para que no haya diferencia con la vista anterior y además, he añadido otro Grid con un "ProgressRing" que indicará que hay un proceso en ejecución y un "TextBlock" con el texto "Cargando datos". Estos dos últimos elementos sí pueden ser modificados a nuestro gusto.

<Grid Background="#FFFFFFFF"
      x:Class="ExtendedSplashScreen.Views.ExtendedSplash"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      d:DesignWidth="1366" d:DesignHeight="768">
    
    <Image x:Name="extendedSplashImage" 
           Source="ms-appx:///Assets/SplashScreen.png" 
           Height="300" 
           Width="620" 
           VerticalAlignment="Center" 
           HorizontalAlignment="Center" />
    <Grid VerticalAlignment="Center" 
          HorizontalAlignment="Center" 
          Margin="0,300,0,0">
        <ProgressRing x:Name="ProgressRing" 
    		Foreground="#FF00529f" 
    		IsActive="True" 
    		Width="80" 
    		Height="80"
    		VerticalAlignment="Center"
    		HorizontalAlignment="Center"/>
        <TextBlock Text="Cargando datos..." 
                   FontSize="20" 
                   VerticalAlignment="Center" 
                   HorizontalAlignment="Center" 
                   Padding="0,20,0,0" 
                   Foreground="#FF00529F" 
                   Margin="0,40,0,-60"/>
    </Grid>
</Grid>

 

Paso 2: Una vez tengamos la vista creada, debemos indicarle a la aplicación que la navegación inicial la haga hacia nuestra Extended Splash Screen y no a la vista principal de la aplicación que teníamos anteriormente. Para ello debemos identificar el bloque condicional if (rootFrame.Content == null) y ponerlo como indico a continuación.

if (rootFrame.Content == null)
{
    bool loadState = (args.PreviousExecutionState == ApplicationExecutionState.Terminated);
    ExtendedSplash extendedSplash = new ExtendedSplash(args.SplashScreen, loadState);
    Window.Current.Content = extendedSplash;
}

 

Paso 3: En este momento, nuestra aplicación ya será capaz de navegar a la Extended Splash Screen pero ésta de momento no hace nada, por lo que es hora de añadir el código necesario para la carga de los datos y que posteriormente continúe con la ejecución de la aplicación de forma normal en la que queremos que sea la vista inicial de nuestra aplicación. Para ello, el archivo de Code Behind debería quedar similar al ejemplo que sigue.

    partial class ExtendedSplash
    {
        internal Rect splashImageRect; // Almacena las coordenadas de la imagen.
        internal bool dismissed = false; // Control del estado de finalización.
        internal Frame rootFrame;
        private SplashScreen splash; // Variable to hold the splash screen object.

        public ExtendedSplash(SplashScreen splashscreen, bool loadState)
        {
            InitializeComponent();

            // Escucha los eventos de redimensionamiento de la vista para reposicionar la Extended Splash Screen
            // Importante para asegurarnos que la vista responde bien ante los diferentes estados (Snapped, Portrait, Fill)
            Window.Current.SizeChanged += new WindowSizeChangedEventHandler(ExtendedSplash_OnResize);

            splash = splashscreen;

            if (splash != null)
            {
                // Registra un manejador de eventos que se ejecutará cuando la SplashScreen haya sido finalizada
                splash.Dismissed += new TypedEventHandler<SplashScreen, Object>(DismissedEventHandler);

                // Recupera las coordenadas de la imagen
                splashImageRect = splash.ImageLocation;
            }

            // Se crea un frame que actúa como context de naveigación 
            rootFrame = Window.Current.Content as Frame;

            // Recupera la session guardada si es necesario
            RestoreStateAsync(loadState);

        }

        async void RestoreStateAsync(bool loadState)
        {
            if (loadState)
                await SuspensionManager.RestoreAsync();
        }

        void ExtendedSplash_OnResize(Object sender, WindowSizeChangedEventArgs e)
        {
            // Actualiza las coordenadas de la imagen Splash Screen de forma segura.
	     // Este evento será lanzado para responder a los cambios de estado Snap, Rotación, etc.
            if (splash != null)
            {
                // Actualiza la imagen de Splash Screen.
                splashImageRect = splash.ImageLocation;
            }
        }

        // Incluye el código a ejecutar cuando el Sistema haya transicionado desde la Splash Screen hasta la Extended.
        async void DismissedEventHandler(SplashScreen sender, object e)
        {
            // Se inicia el proceso de carga de datos
            await LoadData();

            // Navegar desde la Extended Splash Screen hasta la página principal de la aplicación
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                rootFrame.Navigate(typeof(GroupedItemsPage));

                // Se establece el estado de la Extended Splash Screen en la página principal de la aplicación
                ((GroupedItemsPage)rootFrame.Content).SetExtendedSplashInfo(dismissed);
	
                // Se coloca el frame en la ventana actual
                Window.Current.Content = rootFrame;
            });
        }

        /// <summary>
        /// En esta función se cargan los datos de la aplicación
        /// </summary>
        /// <returns></returns>
        private async Task LoadData()
        {
            App.SampleDataGroup = SampleDataSource.GetGroups(AllGroups);
            dismissed = true;
        }
    }


Para que no extrañéis y por comodidad para el ejemplo, he establecido una propiedad estática en App.xaml.cs llamada App.SampleDataGroup donde almaceno los datos que he cargado para que puedan ser usados posteriormente en la vista principal de la aplicación. Si siguiéramos una estructura MVVM estricta esto se debería resolver con buenas artes.

 

Paso 4: Ya nos encontramos en el último paso del proceso de carga de datos que, por fin es mostrárselos al usuario y, para esto, tenemos que cambiar unas poquitas cosas en la vista principal de la aplicación. Lo primero es declarar una variable "Dismissed" y un método público para establecer su valor. Si os habéis fijado con detenimiento, os daréis cuenta que este método se usa en la Extended Splash Screen para notificar su finalización de carga. En principio no es necesario hacer nada con esta variable, pero podría sernos de utilidad en algunos casos.

        internal bool Dismissed;

        public void SetExtendedSplashInfo(bool dismissStat)
        {
            Dismissed = dismissStat;
        }

 

En Segundo lugar, los datos ya no se van a cargar en la vista principal, sino que cuando la aplicación llegue aquí, los datos ya deberían estar cargados donde hayamos indicado y, en este caso, yo lo he almacenado por comodidad en App.SampleDataGroup, por lo que el evento LoadState de esta vista quedaría así.

 

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            this.DefaultViewModel["Groups"] = App.SampleDataGroup;
        }

 

 

Conclusión: Tal y como nos recomienda Microsoft, las aplicaciones tienen que ofrecer los datos lo más rápidamente posible o, al menos, notificar que se están obteniendo dado que nuestras aplicaciones deben ser "Fast & Fluid". Con las Extended Splash Screens podemos paliar el efecto de carga de datos permitiendo que el usuario sea consciente del proceso que se está ejecutando. Además, es importante tener en cuenta que si nuestras aplicaciones superan los 5 segundos pausadas en la SplashScreen por defecto, porque estemos haciendo carga de datos, Microsoft no nos va a certificar la aplicación por falta de rendimiento, por lo que en esos caso, el uso de una Extended Splash Screen se hace absolutamente necesario.

 

Podéis descargaros una aplicación que hace uso de la plantilla "Aplicación de cuadrícula (Grid App)" con el código de ejemplo para verlo en ejecución.

download