Etiquetado: Windows 8

Binding a imágenes de carpetas especiales en Windows 8

Descarga el código de este artículo en Codeplex

Hoy para cambiar un poco haré un ejemplo en XAML/C#, pero seguimos con aplicaciones de la tienda Windows.
Os explicaré un truco que se puede realizar de dos formas: la primera, la forma automática, utilizando una clase del sistema que ya nos hace casi todo el trabajo y la segunda, una forma manual de realizarlo, que dejaremos para más adelante.

Las carpetas especiales

En las aplicaciones de la Tienda Windows podemos acceder directamente a algunas carpetas especiales del sistema siempre que declaremos en el manifiesto que queremos acceder y el usuario lo sepa:
pictureslibrary
Una vez hemos establecido el requisito en el manifiesto, ya podemos utilizar la clase Windows.Storage.KnownFolders para listar los elementos de la carpeta de imágenes:

var queryOptions = new Windows.Storage.Search.QueryOptions();
queryOptions.FolderDepth = FolderDepth.Deep;
var query = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions);

Nota: si intentáis esto en otra carpeta, como por ejemplo “Mis Documentos”, no funcionará, pues también hay que declarar que tipos de archivo vamos a abrir y sólo podremos abrir esos.

Listar todos los archivos dentro de la carpeta

Para obtener una lista de todos los archivos podemos llamar al método GetFilesAsync() y luego podremos trabajar sobre la misma. Si vamos a representar esa lista en el interfaz de usuario, WinRT nos ofrece una forma mucho mejor: podemos obtener una lista virtualizada, esto es, una lista con el mismo número de elementos que contiene la carpeta y las características de esos elementos se van obteniendo de forma asíncrona. De esta forma obtendremos los primeros resultados mucho más rápido y veremos en nuestra colección en pantalla el tamaño completo de la lista, aunque todavía no se hayan obtenido todos los resultados.

public class DataSource:INotifyPropertyChanged
{
    object _items;
    public object Items
    {
        get { return _items; }
        private set
        {
            _items = value;
            onPropertyChanged("Items");
        }
    }

    public DataSource()
    {
        try
        {
            var queryOptions = new Windows.Storage.Search.QueryOptions();
            queryOptions.FolderDepth = FolderDepth.Deep;
            var query = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions);
            var fileInformationFactory = new FileInformationFactory(query,
                Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
                150, Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale, true);
            Items = fileInformationFactory.GetVirtualizedFilesVector();
        }
        catch (Exception ex)
        {
            Items = new object[] {new{ Name = ex.Message} };
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void onPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Si observamos el código, veremos que la clase FileInformationFactory nos permite generar también una vista previa de cada uno de los archivos. El método no es asíncrono, esto quiere decir que nos devolverá muy rápidamente un resultado: la lista virtualizada llena de elementos vacíos, que luego se irán rellenando en segundo plano.

Ahora sólo nos hace falta crear un contenedor GridView para mostrar los archivos:

<Page
    x:Class="ImageBinding.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ImageBinding"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:localData="using:ImageBinding.DataModel">
    <Page.Resources>
        <localData:DataSource x:Key="mainDataSource"></localData:DataSource>
        <CollectionViewSource Source="{Binding Path=Items, Source={StaticResource mainDataSource}}"
                              x:Key="mainView"></CollectionViewSource>
        <DataTemplate x:Key="DataTemplate">
            <Grid Width="150">
                <TextBlock Text="{Binding Name}" TextTrimming="WordEllipsis" ></TextBlock>
            </Grid>
        </DataTemplate>
    </Page.Resources>
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}" 
        DataContext="{StaticResource mainDataSource}">
        <GridView
            Margin="0 90 0 0" ItemsSource="{Binding Source={StaticResource mainView }}" 
                  ItemTemplate="{StaticResource DataTemplate}" SelectionMode="None">
        </GridView>
    </Grid>
</Page>

Carga de imágenes

Una vez tenemos la lista de archivos vamos a mostrar las imágenes que os prometí al principio del artículo. La colección que nos proporciona FileInformationFactory.GetVirtualizedFilesVector() contiene elementos que cumplen con el interfaz IStorageItemInformation. Estos elementos tienen una propiedad Thumbnail a la que podremos enlazarnos para visualizar la imagen, pero no podremos hacerlo directamente pues el tipo StorageItemThumbnail en realidad es un tipo Stream y no una imagen, así que necesitaremos un Converter para poder utilizar la imagen dentro del interfaz de usuario:

public class StreamToBitmapConverter:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value != null)
        {
            var stream = (IRandomAccessStream)value;
            var img = new BitmapImage();
            img.SetSource(stream);
            return img;
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

A través del Converter ya podremos hacer el enlace correctamente:

<Page.Resources>
    <localData:DataSource x:Key="mainDataSource"></localData:DataSource>
    <CollectionViewSource Source="{Binding Path=Items, Source={StaticResource mainDataSource}}"
                          x:Key="mainView"></CollectionViewSource>
    <localConverters:StreamToBitmapConverter x:Key="bitmapConverter"/>
    <DataTemplate x:Key="DataTemplate">
        <Grid Width="150">
            <Grid.RowDefinitions>
                <RowDefinition Height="70"/>
                <RowDefinition Height="32"/>    
            </Grid.RowDefinitions>
            <Image Grid.RowSpan="2" Source="{Binding Thumbnail, 
                Converter={StaticResource bitmapConverter}}"></Image>
            <Border Grid.Row="1" Background="#80000000" Padding="5">
                <TextBlock Text="{Binding Name}" VerticalAlignment="Center" 
                           TextTrimming="WordEllipsis" />
            </Border>
        </Grid>
    </DataTemplate>
</Page.Resources>

Y aquí tenemos el resultado:
picturesCollection
En el próximo artículo os contaré cómo hacer lo mismo pero programando nosotros la carga asíncrona de imágenes bajo demanda.

Descarga el código de este artículo en Codeplex

Anuncios

Filtros REST $filter con texto no ASCII desde JavaScript

Los servicios OData son muy cómodos pues se pueden consumir desde casi cualquier parte y tecnología. Consumir estos datos desde cualquier aplicación HTML/JavaScript, incluso desde las aplicaciones de Windows 8, es muy sencillo pues nos basta con utilizar las llamadas HTTP de toda la vida para interactuar con el servicio.

Desde las consultas REST podemos filtrar utilizando el parámetro $filter en la url, así podemos consultar el catálogo de películas de Netflix y filtrar para que sólo nos dé las series:
http://odata.netflix.com/v2/Catalog/Titles?$filter=Type eq ‘Series’

En la lengua de Shakespeare no tendremos ningún problema, pero si queremos filtrar con caracteres no ASCII tendremos comportamientos que no serán los que nos esperábamos. Por ejemplo, filtramos por el nombre José dentro del mismo servicio:
http://odata.netflix.com/v2/Catalog/Titles?$filter=substringof(‘José’,Name)
Y obtenemos resultados como este, donde el nombre José no aparece:
netflix_nonasciifilter
De hecho, dependiendo del servicio y desde dónde lo llamemos obtendremos resultados diferentes, pues la interpretación de los caracteres que recibe se puede hacer de varias formas.

La solución está en codificar el filtro dentro de la url, para codificar el texto la función adecuada en JavaScript es encodeURIComponent, pues la función escape produce también resultados no deseados. Podéis probar los ejemplos en jsfiddle, aunque al ser JSONP también produce efectos diferentes según como codifiquemos; podemos comparar el jsfiddle con el resultado de consultar directamente las urls:

En Windows 8 tendremos el mismo problema y lo resolveremos igual, utilizando la función encodeURIComponent:

var query = "José";
var encoded = encodeURIComponent(query);
WinJS.xhr({
    url: "http://odata.netflix.com/v2/Catalog/Titles?$filter=substringof('" +
        encoded + "',Name)&$format=json"
}).then(function (xhr) {
    var result = JSON.parse(xhr.responseText);
    var results = result.d.results;
    // a partir de aquí utilizamos los datos
    var body=document.querySelector("body");
    results.forEach(function (r) {
        var div=document.createElement("div");
        div.textContent=r.Name;
        body.appendChild(div);
    });
});

Antes de acabar, recordad que para enviar la consulta también deberíamos comprobar si el texto a buscar contiene el carácter de comilla simple (‘), pues deberemos sustituirlo por la misma comilla escrita dos veces seguidas (”).
Solucionado con un poco de regex:

query=query.replace(/'/g, "''")

Micrófono en HP TouchSmart con Windows 8

En la oficina tenemos unos cuantos HP TouchSmart 300, que a pesar de no estar pensados para Windows 8 y tener sólo dos puntos de contacto, dan bastante juego a la hora de probar las aplicaciones en modo táctil, tanto de Windows 8 como de Windows Phone.

De algunos dispositivos no es fácil encontrar los drivers y hay que descargarlos de aquí y de allá, el que me ha costado más ha sido el driver del micrófono, así que aquí va una pequeña ayudita para los que os hayáis encontrado con el mismo problema:

  1. Hay que descargar el driver actualizado de SoundMAX, pues el Windows Update no lo reconoce directamente. Para ello:
  2. El driver viene dentro de un archivo .cab, así que lo tendremos que descomprimir para poder instalarlo en Windows 8. Con el mismo explorador de Windows podemos abrirlo, o con alguna aplicación para descomprimir como WinRAR.
  3. Una vez descomprimido, buscamos el adihdaud.info, pulsamos con botón derecho->instalar

    instalar soundmax

  4. Para comprobar que tenemos instalado el “Microphone Array”, pulsamos Windows+X y abrimos el administrador de dispositivos:

    microfono instalado

Y listos, ya podemos utilizar nuestras aplicaciones de audio favoritas (y también Skype y cualquier otra app en condiciones).

La funcionalidad deshacer y repetir en JavaScript para Windows 8

Undo button
Los participantes de un hackathon tienen que poner todo su empeño y mucho más para conseguir en pocos días crear una aplicación brillante; eso les obliga a buscar soluciones para problemas que no se habían planteado nunca antes. Durante los hackathones, megathones y demás formaciones que he ido dando sobre Windows 8 suelen surgir dudas más profundas que en un curso estándar sobre algún lenguaje o tecnología concretos.

Una de las cuestiones más recurrentes suele ser la funcionalidad deshacer en una aplicación, ya sea una calculadora o un programa de dibujo. En este artículo voy a intentar solucionaros esa duda con un ejemplo de código.

Descarga el código fuente de esta aplicación desde codeplex

Patrones de diseño

Como no me gusta reinventar la rueda vamos a hacer uso de uno de los dos patrones de diseño del GoF que se suelen utilizar en estos casos, a saber:

  • Memento: nos permite almacenar el estado de un objeto, respetando la encapsulación del mismo, para poder recuperarlo después.
  • Command: encapsula una petición como un objeto, con el método y los parámetros que va a utilizar sobre el receptor de la petición. Esto nos permite crear una cola de solicitudes y permitir el retroceso de operaciones (siempre que la operación lo permita).

Dependiendo del caso utilizaremos un método u otro. Por ejemplo, en el caso de un programa de dibujo sobre lienzo el Memento nos puede resultar más fácil de implementar, pues cada vez que se dibuje un elemento podemos guardar una foto del mismo, o una porción para optimizar memoria. En el caso de un procesador de textos el patrón Command seguramente será mucho más óptimo, pero cada comando tendrá que implementar su propia función deshacer.

Para hacer un ejercicio sencillo vamos a basarnos en el patrón memento para desarrollar un ejemplo.

El patrón Memento en JavaScript

En el patrón Memento utilizamos tres objetos:

  • Memento: es el almacén del estado interno del objeto, guarda la información necesaria para recuperar el estado del objeto a un momento concreto
  • Originator: crea el Memento que contiene la instantánea de su estado interno y sabe restaurar ese estado usando el Memento
  • Caretaker: gestiona la lista de Mementos, pero no utiliza nunca la información del Memento directamente.

Para demostrar el uso del patrón utilizaremos una aplicación de pintado en un canvas ya utilizada en este blog para tratar los eventos táctiles.

Empezaremos por definir un espacio de nombres para evitarnos problemas en JavaScript y definiremos el objeto básico Memento:

(function mementoDefinition(global) {
    global.CanvasState = global.CanvasState || {};

    ///Memento
    CanvasState.memento = function (state) {
        this._state = state;
    };
    Object.defineProperty(CanvasState.memento.prototype, "state", {
        get: function () { return this._state; }
    });
    ///...

Podréis observar que es extremadamente sencillo: un constructor con el estado y una propiedad para leer el estado. No es modificable ni tiene ninguna funcionalidad, sólo almacena información.

El siguiente paso es crear el objeto Caretaker, el que se encarga de almacenar la lista de objetos Memento; necesitaremos un Array para almacenar los estados de deshacer y otro para los estados de rehacer.

En esta implementación ponemos un parámetro de límite, para evitar llenar toda la memoria con las operaciones de undo. Para gestionar los estados tenemos las funciones addMemento, que nos permite ir añadiendo estados a la lista y las funciones getUndoMemento y getRedoMemento para recuperar los estados, dependiendo de si queremos deshacer una operación o volver a hacerla desde el historial.

///Caretaker constructor
CanvasState.caretaker = function (maxLevels) {
    this._undoStates = [];
    this._redoStates = [];
    this._maxLevels = maxLevels;
};

CanvasState.caretaker.prototype.addMemento = function (memento) {
    this._undoStates.push(memento);
    this._redoStates = [];
    if (this._undoStates.length > this._maxLevels) {
        this._undoStates.splice(0, 1);
    }
};

CanvasState.caretaker.prototype._canUndo = function () {
    return this._undoStates.length > 1;
}
CanvasState.caretaker.prototype._canRedo = function () {
    return this._redoStates.length > 0;
}

CanvasState.caretaker.prototype.getUndoMemento = function () {
    if (this._canUndo()) {
        var state = this._undoStates.pop();
        this._redoStates.push(state);
        return this._undoStates[this._undoStates.length - 1];
    }
    else
        throw "Undo not allowed, states array empty";
};
CanvasState.caretaker.prototype.getRedoMemento = function () {
    if (this._canRedo()) {
        var state = this._redoStates.pop();
        this._undoStates.push(state);
        return state;
    }
    else
        throw "Redo not allowed, states array empty";
}

Object.defineProperty(CanvasState.caretaker.prototype, "canUndo", {
    get: function () { return this._canUndo(); }
});
Object.defineProperty(CanvasState.caretaker.prototype, "canRedo", {
    get: function () { return this._canRedo(); }
});

Por ahora, con estos dos objetos no hemos realizado ninguna operación con el objeto canvas, sólo tenemos el objeto que almacena el estado y el que se encarga de mantenerlo en memoria.

El tercer y último objeto es el Originator, donde ocurren todas las operaciones. Este objeto sabe cómo crear un Memento y cómo recuperar el estado a partir del mismo. En el ejemplo vamos a utilizar el objeto canvas para recuperar una imagen y la guardaremos dentro de un Memento que podremos utilizar más adelante para restaurar el estado.

    ///Originator
    CanvasState.originator = function (canvas) {
        this._canvas = canvas;
        this._ctx = canvas.getContext("2d");
    };
    CanvasState.originator.prototype.saveToMemento = function (x, y, w, h) {
        if (x === undefined)
            x = 0;
        if (y === undefined)
            y = 0;
        if (w === undefined)
            w = this._canvas.width;
        if (h === undefined)
            h = this._canvas.height;

        var img = this._ctx.getImageData(x, y, w, h);
        return new CanvasState.memento({ image: img, x: x, y: y, w: w, h: h });
    };
    CanvasState.originator.prototype.restoreFromMemento = function (memento) {
        this._ctx.putImageData(memento.state.image, memento.state.x, memento.state.y);
    };

})(this);

Y así cerramos la función auto-ejecutable que define todos nuestros objetos. Todo el código anterior lo podemos meter en un archivo llamado memento.js y así lo podremos reutilizar fácilmente.

Como podréis observar, en todo el código el único objeto que sabe algo del canvas es el objeto originator.

Aplicación del patrón a un caso real

Ahora que tenemos todos los objetos preparados, vamos a utilizarlos en la aplicación que hicimos en un post anterior sobre los eventos táctiles. Para demostrar los eventos pintábamos sobre un canvas y ahora vamos a ir guardando el estado para poder recuperarlo más tarde.

Para empezar, en default.js añadimos unas variables que vamos a utilizar para la nueva funcionalidad, justo debajo de las declaraciones del canvas y el ctx:

var originator, caretaker;

Seguimos en default.js, tras los eventos táctiles dentro de app.onactivated, crearemos los objetos originator y un caretaker de 20 niveles de deshacer:

// creamos el originator sobre nuestro canvas
// y un caretaker de 20 niveles
originator = new CanvasState.originator(canvas);
caretaker = new CanvasState.caretaker(20);

//lo primero que haremos será añadir el canvas vacío como memento.
caretaker.addMemento(originator.saveToMemento());

La función startDrawing quedará exactamente igual, donde vamos a guardar el estado es al levantar el dedo de la pantalla, en la función stopDrawing, que quedará así:

function stopDrawing(e) {
    doDraw = false;
    caretaker.addMemento(originator.saveToMemento(0, 0, canvas.width, canvas.height));
}

Ahora guardamos los cambios cada vez que levantamos el dedo de la pantalla. Nos falta poder recuperarlos. Para ello vamos a añadir una barra de botones en la página default.html justo debajo del canvas:

<div id="appBar" data-win-control="WinJS.UI.AppBar" data-win-options="{sticky:true}">
    <button data-win-control="WinJS.UI.AppBarCommand" 
        data-win-options="{id:'cmdUndo',label:'Undo',icon:'undo',section:'global'}">
    </button>
    <button data-win-control="WinJS.UI.AppBarCommand" 
        data-win-options="{id:'cmdRedo',label:'Redo',icon:'redo',section:'global'}">
    </button>
</div>

La marcamos como sticky para que no desaparezca mientras pintamos y nos resulte más cómoda de utilizar. Añadimos dos manejadores de evento en default.js, después de las líneas que crean el originator y el caretaker:

document.getElementById('cmdUndo').winControl.addEventListener("click", function () {
   if (caretaker.canUndo)
      originator.restoreFromMemento(caretaker.getUndoMemento());
});

document.getElementById('cmdRedo').winControl.addEventListener("click", function () {
   if (caretaker.canRedo)
      originator.restoreFromMemento(caretaker.getRedoMemento());
});

Vuestro turno

Está claro que a este código le queda mucho margen de mejora, así que hoy os voy a poner deberes:

  • Intentad mejorar el dibujado cuando hay múltiples puntos táctiles simultáneos
  • Para mejorar el rendimiento y la gestión de memoria de la app, deberíamos almacenar sólo la zona que se ha modificado del canvas en lugar de guardarlo todo. ¿Os atrevéis?

Descarga el código fuente de esta aplicación desde codeplex

Protección infantil en Windows 8 dentro de un dominio

El término BYOD (Bring Your Own Device) está cada vez más de moda. Estos últimos años se ha generalizado debido a la gran cantidad de dispositivos que entran en nuestros hogares y que acabamos llevando al trabajo, aunque el termino también se refiere a los trabajadores que prefieren comprarse ellos mismos el portátil en lugar de utilizar uno proporcionado por la empresa, probablemente más antiguo y menos potente.

Para variar un poco, yo he hecho lo contrario y tengo un dispositivo del trabajo en casa. Lo que se llama ahora BYOD inverso, aunque existe hace muchos más años que el BYOD.

Windows 8 con múltiples usuarios

Una de las ventajas que tiene Windows 8 sobre otros sistemas que funcionan sobre tabletas es su capacidad de mantener múltiples cuentas de usuario. Esta funcionalidad nos permite por una parte mejorar la seguridad y por otra evitar tener en nuestra cuenta todas las aplicaciones de Hello Kitty™, Pocoyo™, Disney™ y similares para que jueguen nuestros niños; una pantalla táctil en casa es demasiado atractiva como para poder acapararla el 100% del tiempo 🙂

Windows 8 Multi-usuario

Protección Infantil integrada en Windows 8

Tener cuentas separadas para nuestros hijos tiene otras ventajas añadidas: podremos filtrar qué webs pueden abrir, qué aplicaciones pueden utilizar, qué apps pueden descargar del Windows Store, tanto de forma manual mediante listas blancas y negras como de forma automatizada con filtros por edades. En Windows 8 ya está integrada la protección infantil como parte del sistema operativo, mientras que en versiones anteriores la podemos descargar de forma gratuita con el paquete Windows Live Essentials.
Para utilizar la protección infantil en Windows 8 basta marcar la casilla de seguridad cuando estemos añadiendo una cuenta de usuario, indicando que el nuevo usuario necesita el filtro de protección infantil:

Agregar un usuario infantil en Windows 8

No voy a extenderme demasiado sobre el tema, pues tenéis toda la información y el paso a paso en el apartado sobre seguridad familiar de la web de Windows.

Protección infantil en dominios de Windows

Si utilizáis equipos que están dentro de un dominio no os aparecerá la casilla para marcar a un usuario como menor. Esto también nos ocurrirá si somos un poco geeks y tenemos nuestro propio dominio Home Server en casa.

Cuando un equipo está dentro de un dominio de Windows existe una política que deshabilita por defecto el filtro familiar. Tiene cierto sentido, pues no suele ser necesario tener filtros para niños en una empresa y evita problemas innecesarios a los administradores de sistemas.
Si queremos añadir usuarios infantiles en un equipo de un dominio de Windows tendremos que habilitar la política que nos permite añadir los filtros familiares de nuevo, siguiendo estos pasos desde una cuenta del dominio con privilegios de administrador local de la máquina:

  1. Abrimos el editor de directivas de grupo local:

    editar directiva de grupo

  2. Habilitamos la política de Protección Infantil en Dominio. La encontraremos en Configuración del equipo > Plantillas Administrativas > Componentes de Windows > Protección Infantil:

    ProteccionInfantilDominio

  3. Forzamos el refresco de las políticas de grupo desde la línea de comando; hace falta que el equipo se comunique con el controlador de dominio para activar la directiva. Abrimos un cmd como administrador y ejecutamos la siguiente línea:
    gpupdate /force
  4. Por último, debemos reiniciar el equipo para que los cambios sean efectivos. Tras el reinicio ya podemos añadir un usuario y marcar la casilla de activación de la protección infantil.

Configurar los permisos de la cuenta infantil

Una vez creada la cuenta infantil, debemos recordar que hay que configurar las restricciones que queremos en la cuenta.

La configuración por defecto viene sin filtros ni límites ni restricciones.

protección infantil usuarios

En la configuración del usuario podremos editar todos los límites y restricciones de todo lo que puede hacer el menor en el equipo. configuración de usuario infantil

Como primer paso os recomiendo restringir los sitios web mediante el filtro automático para menores, más adelante podemos ir añadiendo algunos sitios a la listas blancas y negras para refinar el acceso: restricciones web

Y esto es todo por hoy 😉
 

Salir de la vista acoplada por código en Windows 8

La vista acoplada (o ajustada, o Snapped View) es una de las formas que tiene Windows 8 para que nuestras aplicaciones estén presentes el mayor tiempo posible. Es una vista obligatoria y tenemos que diseñarla para que nuestra aplicación muestre algo interesante en ese modo. Al tener unas dimensiones reducidas no podremos tener la funcionalidad completa de la aplicación y en algunas ocasiones necesitaremos volver a pasar a pantalla completa.

Podemos indicar al usuario la necesidad de volver a pantalla completa/rellena dentro de la misma vista:

Snapped View

Y además podemos llamar a un método que intentará desajustar la pantalla de nuestra aplicación y ajustar la que haya en primer plano:

Windows.UI.ViewManagement.ApplicationView.tryUnsnap();

Podéis consultar el API del método TryUnsnap en MSDN.

¿Dónde está el menú de la Tienda Windows en Visual Studio 2012?

Cuando nos pasamos a Visual Studio 2012 después de utilizar la versión Express para desarrollar aplicaciones de Windows 8, no encontraremos el menú de la tienda Windows en el mismo lugar. Está un escondido dentro del menú de proyecto, pero seguimos teniendo toda la funcionalidad:
Store Menu

¿Cómo encontrar más aplicaciones en la tienda Windows 8?

Ayer estaba ayudando al alumnado del instituto de FP Sant Josep Obrer con las aplicaciones de Windows 8 y Windows Phone 8 que han estado haciendo estos días. Ha sido un trabajo intenso, pues me han tenido que aguantar tres tardes enteras donde les he explicado las características básicas de Windows 8 y su hermano pequeño Windows Phone 8.
El caso es que nos encontramos con el siguiente problema: en algunos equipos no se veían las aplicaciones que ya les habían aprobado en la tienda Windows.
Uno de los alumnos encontró la solución: configurar la tienda de aplicaciones para que vea las aplicaciones de todos los países y en todos los idiomas:

¿Cómo encontrar más aplicaciones en la tienda Windows 8?
Esto me lleva a una recomendación general para cuando publiquemos apps:

Si queremos que nuestra aplicación llegue a mucha gente debemos traducir la aplicación y publicarla en todos los mercados.

Motor de animaciones para aplicaciones HTML (Web + Windows Store)

En artículos anteriores he descrito cómo realizar animaciones de forma óptima utilizando JavaScript y la nueva función requestAnimationFrame. Los ejemplos que daba eran bastante sencillos, pero cuando hacemos una aplicación más grande, como por ejemplo un juego, debemos mover muchos elementos distintos, cada uno con su propia lógica de movimiento.
Para facilitarnos el trabajo y evitar repetir cada vez lo mismo podemos crear un motor de animaciones sencillo y reutilizable.

TL;DR

Este artículo tiene cantidades ingentes de código JavaScript, pero para qué leerlo si puedes ejecutarlo y jugar con él en Fiddle o ver cómo usarlo en una app Windows 8.
El código de ejemplo contiene los siguientes objetos:

  • Animation: un objeto que contiene una lista de elementos a animar y se encarga de gestionar el bucle de animación.
  • Planet y Moon: dos objetos de ejemplo que demuestran cómo se calcula la animación de cada elemento.

Loop Engine

Ahora que ya habéis jugado y destrozado el código os explico qué está haciendo. Para empezar, como quería un código lo más estándar posible, pero que me sea fácil de usar dentro de una aplicación Windows Store, definiré un espacio de nombres sin depender de WinJS:

(function initializeLoop(global) {
    "use strict";

    //espacio de nombres Loop
    global.Loop = global.Loop || {};
})(this);

Una vez tenemos definido nuestro espacio de nombres Loop vamos con el objeto principal del motor.

El objeto Animation

En un alarde de originalidad he definido un objeto llamado Animation. Dicho objeto gestiona el famoso ciclo update + draw dentro de una llamada requestAnimationFrame y además contiene una matriz con los elementos a animar. Así, para que los objetos se animen nos bastará con incorporarlos a la matriz y el objeto se encargará de llamar a las funciones de cálculo y pintado de cada uno.

(function initializeAnimationObject(Loop) {
    "use strict";

    //objeto Animation
    Loop.Animation = function(canvas) {
        this.canvas = canvas;
        this.context2d = canvas.getContext("2d");

        this.animationElements = [];
        this.size = [canvas.width, canvas.height];
        this.delta = 0;

        this._oldTime = undefined;
        this._lastAnimationFrame = undefined;
        this._enabled = false;
        this._paused = false;
    };

El objeto incorpora las funciones start, stop, pause y resume para manejar la animación.

    //empieza la animación
    Loop.Animation.prototype.start = function() {
        if (!this._enabled) {
            this._enabled = true;
            this._loop();
        }
    };

    //para la animación y vuelve a poner el tiempo a 0
    Loop.Animation.prototype.stop = function() {
        this._enabled = false;
        this._paused = false;

        if (this._lastAnimationFrame) {
            cancelAnimationFrame(this._lastAnimationFrame);
        }
        this.animationElements.forEach(function(e) {
            if (e.stop) {
                e.stop();
            }
        });

        this._oldTime = undefined;
        this.update(0);
        this.draw(0);
        this._oldTime = undefined;
    };

    //pausa la animación
    Loop.Animation.prototype.pause = function() {
        if (this._enabled) {
            this._paused = true;
            this.animationElements.forEach(function(e) {
                if (e.pause) {
                    e.pause();
                }
            });
        }
    };

    //continua la animación a partir de la pausa
    Loop.Animation.prototype.resume = function() {
        this._paused = false;
        this.animationElements.forEach(function(e) {
            if (e.resume) {
                e.resume();
            }
        });
    }

La lista de elementos debe contener objetos que pueden tener definidas (o no) las siguientes funciones:

  • update: la función donde se realizan todos los cálculos de posición del elemento
  • draw: en esta función debemos procurar tenerlo todo listo para que sólo tengamos que dibujar
  • pause: sólo la implementamos si tenemos que realizar algo especial al pausar la animación
  • resume: la implementamos en el caso de que debamos reaccionar de alguna manera a la reanudación de la animación.

De esta manera se comunica a los objetos qué deben hacer en cada momento, si es que quieren saberlo.

Desde la función start se llama a una función privada llamada _loop. Esta crea el bucle de llamadas requestAnimationFrame. Como estas llamadas funcionan como un callback, no se ejecutan dentro del contexto del objeto, pero nosotros queremos poder llamar a las funciones this.update y this.draw. El truco utilizado aquí es crear una closure que nos almacene el objeto this dentro de la variable that.

    //Definición de las funciones de Animation
    Loop.Animation.prototype._loop = function() {
        //ciclo de animación básico
        var that = this;
        var insideLoop = function(loopTime) {
            if (that._enabled) {
                that._lastAnimationFrame = requestAnimationFrame(insideLoop);
                that.update(loopTime);
                that.draw(loopTime);
            }
        };
        requestAnimationFrame(insideLoop);
    };

Desde el bucle se llama a las funciones update y draw, que a su vez llaman a las funciones homónimas de los elementos de la lista.

    //update: donde se realizan los cálculos, se llama antes del draw.
    Loop.Animation.prototype.update = function (animationTime) {
        if (animationTime === undefined) {
            animationTime = Date.now();
        }

        if (this._oldTime === undefined) {
            this._oldTime = animationTime;
        }
        this.delta = (animationTime - this._oldTime);
        this._oldTime = animationTime;
        if (!this._paused) {
            this.animationElements.forEach(function (e) {
                if (e.update) {
                    e.update(animationTime);
                }
            });
        }
    };

    //draw: una vez realizados los cálculos se pueden dibujar los elementos en el canvas
    //borra el canvas antes de empezar a pintar el nuevo.
    Loop.Animation.prototype.draw = function (animationTime) {
        var context2d = this.context2d;
        context2d.clearRect(0, 0, this.canvas.width, this.canvas.height);

        this.animationElements.forEach(function (e) {
            if (e.draw) {
                e.draw(animationTime);
            }
        });
        if (this.showFPS) {
            context2d.strokeStyle = '#4f4';
            context2d.strokeText("FPS: " + (Math.round(1000 / this.delta)).toString(), 10, 10);
        }
    };
})(this.Loop);

¿Cómo utilizamos este motor?

Para utilizar el motor necesitaremos por una parte una página HTML con un canvas sobre el que animar:

<canvas style="position:absolute;background-color:black" id="animationCanvas"></canvas>
<div style="position:absolute; top:10px; width:100%">
    <div style="width:100px;margin-left:auto;margin-right:auto;">
        <button class="roundButton" id="playPauseButton" >║</button>
        <button class="roundButton" id="stopButton">■</button>
    </div>
</div>

Y en el script de la página esperaremos a que cargue para llamar a la función start de nuestra animación:

this.onresize = resizeCanvas;
var animation;

function start() {
    resizeCanvas();
    animation = new Loop.Animation(animationCanvas);
    createPlanets(animation);
    animation.start();
}

function resizeCanvas() {
    animationCanvas.height = window.innerHeight;
    animationCanvas.width = window.innerWidth;
}

document.addEventListener("visibilitychange", function() {
    animation.pause();
    if (animation.isPaused) {
        playPauseButton.innerHTML = "►";
    }
}, false);

this.onload = function () {
    playPauseButton.onclick = function () {
        if (!animation.isEnabled) {
            animation.start();
            playPauseButton.innerHTML = "&#x2551";
        }
        else if (animation.isPaused) {
            animation.resume();
            playPauseButton.innerHTML = "║";
        }
        else {
            animation.pause();
            playPauseButton.innerHTML = "►";
        }
    };

    stopButton.onclick = function () {
        animation.stop();
        playPauseButton.innerHTML = "►";
    }

    start();
};

Animando una lista de planetas

Una vez tenemos el motor, nos queda añadir los elementos a animar. Vamos a crear una animación de planetas muy sencilla. Para ello necesitaremos definir los objetos Planet con sus métodos update y draw:

(function initializePlanetsNS(global) {
    "use strict";

    //espacio de nombres Loop
    global.Planets = global.Planets || {};
})(this);

(function createPlanetsObjects(Planets) {
    var x = 0,
        y = 1;
    var pi2 = Math.PI * 2;

    Planets.Planet = function(animation) {
        if (animation) {
            this.animation = animation;

            var canvas = this.animation.canvas;
            this.speed = 0;
            this.rotationCenter = [canvas.width / 2, canvas.height / 2];
            this._rotation = 0;
            this.rotationRadius = 0;
            this.position = [this.rotationCenter[x] + this.rotationRadius, this.rotationCenter[y]];
            this.radius = 50;
            this.foreColor = "#FF0";
        }
    }

    Planets.Planet.prototype.update = function(time) {
        var delta = this.animation.delta;
        this._rotation += delta * this.speed / 1000;
        if (Math.abs(this._rotation) > pi2) this._rotation -= pi2 * (this._rotation / this._rotation);

        this.position[x] = this.rotationCenter[x] + this.rotationRadius * Math.cos(this._rotation);
        this.position[y] = this.rotationCenter[y] + this.rotationRadius * Math.sin(this._rotation);
    };
    Planets.Planet.prototype.draw = function(time) {
        var context2d = this.animation.context2d;
        context2d.fillStyle = this.foreColor;
        context2d.beginPath();
        context2d.arc(this.position[x], this.position[y], this.radius, 0, pi2);
        context2d.fill();
    };

    Planets.Planet.prototype.stop = function() {
        this._rotation = 0;
    };

Para darle más vida a la animación vamos a crear también satélites a los planetas. En este caso la función de draw no hace falta definirla, reaprovechamos la del objeto Planet:


    Planets.Moon = function(animation, planet) {
        Planets.Planet.apply(this, arguments);
        this.planet = planet;
    }
    Planets.Moon.prototype = new Planets.Planet();

    //store the update from the Planet object to avoid repeating code
    Planets.Moon.prototype.updateBase = Planets.Moon.prototype.update;

    //change update function to take planet position as rotation center
    Planets.Moon.prototype.update = function(time) {
        this.rotationCenter = this.planet.position;
        this.updateBase(time);
    };
})(this.Planets);

Y en el script principal creamos los planetas con sus lunas:

function createPlanets(animation) {
    var sun = new Planets.Planet(animation);
    sun.radius = 40;
    animation.animationElements.push(sun);
    for (var i = 0; i < 1+rnd(8,2); i++) {
        var planet = new Planets.Planet(animation);
        planet.rotationCenter = [animationCanvas.width / 2, animationCanvas.height / 2];
        planet.radius = 10 + rnd(20);
        planet.speed = rnd(90, 1) * Math.PI * 2 / 360;
        planet.rotationRadius = sun.radius * 2 + i * 150 + rnd(100);
        planet.foreColor = "rgb(" + rnd(255) + "," + rnd(255) + "," + rnd(255) + ")";
        animation.animationElements.push(planet);

        for (var m = 0; m < rnd(5); m++) {
            var moon = new Planets.Moon(animation, planet);
            moon.radius = 5 + rnd(planet.radius / 3);
            moon.speed = rnd(180, 1) * Math.PI * 2 / 360 * (Math.random() > 0.5 ? -1 : 1);
            moon.rotationRadius = planet.radius + moon.radius + 10 + rnd(40);
            moon.foreColor = "rgb(" +
                (50 + rnd(205)) + "," +
                (50 + rnd(205)) + "," +
                (50 + rnd(205)) + ")";
            animation.animationElements.push(moon);
        }
    }
}

function rnd(max, min) {
    if (!min)
        min = 0;
    return Math.round(min+( Math.random() * (max == undefined ? 1 : (max-min))));
}

Windows 8

El código anterior funcionará directamente en una aplicación Windows Store, pero para mejorar un poco su funcionamiento podemos aplicar algunos trucos como la reacción a los eventos de suspensión para pausar la animación.

Ya estamos preparados para probarlo en un navegador moderno o en una app Windows 8.

Nota: este ejercicio es sólo un ejemplo, no es un motor optimizado ni preparado para funcionar bien en todos los navegadores. Aún así funciona en la mayoría de navegadores, contiene código fallback para los navegadores que no dispongan todavía de la versión definitiva de requestAnimationFrame e incluso funciona en algunos navegadores móviles.

Conclusiones

Hemos visto que gracias a las nuevas características de HTML5 y JavaScript podemos crear animaciones suaves y definir un modelo de objetos que nos permita crear objetos que controlan su propio movimiento.
El ejemplo funciona tanto en navegadores como dentro de una aplicación Windows Store de Windows 8.

Algunos ejemplos de HTML y JavaScript para aplicaciones Windows Store

doubts...
He renovado la página de inicio de los ejemplos que escribo para resolver las dudas que me vais pidiendo sobre Windows 8. Como algunos sabréis estoy utilizando codeplex para que estén disponibles para todo el mundo. Los ejemplos que hay hasta ahora son los siguientes:

  • Listas heterogéneas: un ejemplo de cómo representar listas con elementos de diferentes tamaños. Explicado en el artículo del mismo nombre en desarrolloweb.com.
  • Integración Google Maps: cómo utilizar las vistas seguras de HTML para poder incluir código dinámico externo a nuestras aplicaciones. También en desarrolloweb.com.
  • PaintJS: una aplicación táctil para pintar sobre un elemento <canvas>, el resultado acabó siendo una aplicación Windows Store: Finger Paint. Encontraréis una introducción en mi entrada del blog sobreeventos táctiles.
  • Ejemplo Timer: cómo crear un contador que no se pare cuando nuestra aplicación no está en marcha, también en el Windows Store: Big Stopwatch. La explicación en el artículo sobre la Cuenta Atrás.
  • Planets (en progreso): ejemplo de uso de un motor de animaciones propio (Loop).

Estoy preparando unos cuantos ejemplos más y pronto actualizaré los que hayan quedado anticuados. Os mantendré informados.