Etiquetado: Windows 8

Evento sobre la guerra de los drones

La guerra de los drones

Desde la comunidad técnica Netsaimada os proponemos un reto al que será difícil resistirse… desarrollar aplicaciones Windows 8 y Windows Phone para un AR.Drone 2.0. Tenemos el cuadricóptero para que podáis probar las apps. Para concursar sólo tenéis que registrar una idea en la web de la guerra de los drones antes de día 24 de noviembre y publicarla en la tienda antes del 16 de diciembre.

En la comunidad hicimos un evento el pasado miércoles 30 y aquí os dejo algunos de los vídeos usando el AR.Drone 2.0 dentro de la sala donde hicimos la presentación:

Anuncios

Optimizar el Canvas de Apps Windows en HTML5/JavaScript

En un artículo anterior sobre el patrón Memento en JavaScript introducía cómo podíamos ir guardando el estado de un elemento canvas para crear un sistema de deshacer/repetir en una app de dibujo. En dicha app había recibido algunas quejas sobre el rendimiento a partir de incluir esta funcionalidad, especialmente al ejecutar la aplicación en tabletas ARM con Windows RT.

Si observáis el código, obtenemos una copia de la imagen dibujada sobre el canvas con la siguiente llamada:

var img = this._ctx.getImageData(x, y, w, h);

En un principio pensé que el problema estaba en capturar toda la pantalla cada vez, pero intentar resolver eso complicaba mucho el desarrollo y tampoco se ganaba en rendimiento, sino todo lo contrario. Ya escribiré sobre ello en otro post.

En realidad el problema está en el rendimiento de los métodos getImageData y putImageData, que si buscáis un poco por internet os recomendarán utilizarlo lo menos posible, o sea, que ni se te ocurra hacerlo en un bucle. El caso es que aparte de algún artículo donde analizan el rendimiento de dicho método y de cómo algunos navegadores lo mejoran, para guardar la imagen de un canvas os dirán que tenéis que utilizar alguno de los siguientes métodos:

  • getImageData o getImageDataHD: copia todos los píxeles a un array, normalmente para poder modificar a nivel de píxel y luego volver a volcar al canvas con un putImageData.
  • toDataURL o toBlob: para guardar los datos, incluso con compresión png, para poder enviarlos a un servidor (aunque esto en Windows8 lo podemos hacer con métodos nativos).

El problema de estos métodos es que tienen un rendimiento bastante pobre, y si tenemos que copiar toda la pantalla cuando el usuario levanta el dedo de la puede que afecte al rendimiento de nuestras rutinas de dibujo, de hecho en mi App estaba afectando y mucho. Debemos recordar que muchos usuarios tendrán una versión RT con un chip ARM que no tiene el rendimiento de un desktop.

Canvas inception

Hay otra forma de guardar el estado de un canvas cuando no necesitamos actuar sobre píxeles individuales o guardar el contenido a un archvivo: pintando dentro de un canvas el contenido del canvas original.
Para guardar el canvas, basta crear uno nuevo de las mismas dimensiones y ejecutar el método drawImage que, además de pintar imágenes, si le damos otro canvas como parámetro pintará el contenido del canvas origen dentro del destino:

var newCanvas=document.createElement("canvas");
var newContext=newCanvas.getContext("2d");

var w=originalCanvas.width;
var h=originalCanvas.height;

newCanvas.width=w;
newCanvas.height=h;

newContext.drawImage(originalCanvas,0,0,w,h,0,0,w,h);

¿Tánto se gana?

Se puede ganar mucho rendimiento con este pequeño cambio. En algunos casos la mejora es tan espectacular que es difícil de creer, pero para que lo comprobéis vosotros mismos aquí os dejo este jsfiddle:
imagecopy

Luego, si queremos recuperar la imagen sólo tenemos que volver a pintar sobre el canvas original el canvas que hemos guardado. Aunque debemos ir con cuidado, no es conveniente ir creando infinitos canvas y esperar que el Garbage Collector vaya recogiendo lo que no necesitamos, aparte de que algunos navegadores no estarán demasiado contentos con eso. Es mucho mejor crear un pool de elementos canvas e ir reutilizándolos. Si queréis ver cómo ha quedado el nuevo patrón memento ahí tenéis el código, aunque por culpa del pool de canvas no está tan desacoplado como me gustaría:

(function mementoNS(global) {
    global.CanvasState = global.CanvasState || {};
})(this);

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

//this is a buffer pool to avoid possible memory problems
(function bufferPoolInit(CanvasState) {
    CanvasState.bufferPool = {
        _buffer:[],
        addLevels: function (levels) {
            var buffer = CanvasState.bufferPool._buffer;
            for (var i = 0; i < levels; i++) {
                buffer.push([document.createElement("canvas"), false]);
            }
        },
        getBuffer: function () {
            var buffer = CanvasState.bufferPool._buffer;
            for (var i = 0; i < buffer.length; i++) {
                if (!buffer[i][1]) {
                    buffer[i][1] = true;
                    return { id: i, buffer: buffer[i][0] };
                }
            }
        },
        releaseBuffer: function (id) {
            CanvasState.bufferPool._buffer[id][1] = false;
        }
    }

})(this.CanvasState);

(function originatorInit(CanvasState) {
    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 buffer = CanvasState.bufferPool.getBuffer();
        var bufferContext = buffer.buffer.getContext("2d");
        buffer.buffer.width = w;
        buffer.buffer.height = h;
        bufferContext.drawImage(this._canvas, x, y, w, h, 0, 0, w, h);
        var memento = new CanvasState.memento({ image: buffer, x: x, y: y, w: w, h: h });
        return memento;
    };
    CanvasState.originator.prototype.restoreFromMemento = function (memento) {
        var state = memento.state;
        this._ctx.clearRect(state.x,state.y,state.w,state.h);
        this._ctx.drawImage(state.image.buffer, state.x, state.y);
    };
})(this.CanvasState);

(function caretakerInit(CanvasState) {
    CanvasState.caretaker = function (maxLevels) {
        CanvasState.bufferPool.addLevels(maxLevels+1);
        this._undoStates = [];
        this._redoStates = [];
        this._maxLevels = maxLevels;
    };
    CanvasState.caretaker.prototype.addMemento = function (memento) {
        this._undoStates.push(memento);
        this._redoStates.forEach(function (s) {
            CanvasState.bufferPool.releaseBuffer(s._state.image.id);
        });
        this._redoStates = [];
        if (this._undoStates.length > this._maxLevels) {
            CanvasState.bufferPool.releaseBuffer(this._undoStates[0]._state.image.id);
            this._undoStates.splice(0, 1);
        }
    };
    CanvasState.caretaker.prototype.getUndoMemento = function () {
        if (this._undoStates.length > 1) {
            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._redoStates.length > 0) {
            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._undoStates.length>1; },
        set: undefined 
    });
    Object.defineProperty(CanvasState.caretaker.prototype, "canRedo", {
        get: function () { return this._redoStates.length > 0; },
        set: undefined
    });
})(this.CanvasState);

Desarrollar desde una Surface RT

Surface RT on Boeing 733-800 (Virgin Australia)

En Windows RT, aparte de las aplicaciones de la suite Office, sólo tenemos aplicaciones de la tienda Windows, ninguna de nuestras aplicaciones favoritas de desarrollo funciona en el escritorio de Windows RT.  No tenemos Visual Studio, que por otra parte consumiría más recursos de los que disponemos en una Surface RT.

A pesar de esto, no todo está perdido, existen herramientas que podremos utilizar para desarrollar desde la tableta.

Windows RT en el mundo real

Hace unas semanas me inscribí a un curso online de Python para aprender otro lenguaje más y de paso entender mejor un libro sobre Machine Learning que compré hace poco. Coincidió que tuvimos que pasar unos días en el hospital para ver cómo nacía una nueva versión de mi chica y yo, pero eso es otra historia.

El caso es que como íbamos a pasar unas cuantas noches en vela, me llevé la Surface RT para entretenerme entre cambio de paquetes. Llevé la RT y no el portátil por varios motivos, entre ellos el peso, volumen y duración de batería… pero tenía un problema, no podía instalar el entorno de Python.

¿Qué podemos hacer si sentimos la llamada del código y sólo tenemos una tableta?

Podemos buscar por la web, pues hay bastantes páginas donde podemos desarrollar directamente desde la página para diferentes lenguajes, como por ejemplo jsfiddle para JavaScript, o Compile Online para casi todos, pero esa opción no me acaba de satisfacer en un “hotel” sin wifi y tirando de 3G.

Otra opción es montar una máquina virtual con el entorno y subir el VHD a Windows Azure para usarla por escritorio remoto. Es algo habitual, conozco mucha gente que utiliza este método desde su tableta y les funciona bien, pero sigo con el problema de la wifi.

Si tienes una tableta con Windows 8 RT estás de suerte. Buscando en la tienda encontré algunas aplicaciones interesantes y gratuitas, que harán nuestra vida de coders algo más llevadera cuando no tengamos un PC a mano.

Algunas aplicaciones de la Tienda Windows

Python 3 for Metro

Python 3 for Metro

Un interprete de Python 3 gratuito. Es bastante básico, pero como ahora mismo estoy aprendiendo me basta. Tiene una pantalla interactiva para poder ver los resultados de lo que estás haciendo y puede ejecutar ficheros externos. Le faltan bastantes cosas como poder cargar módulos y algo de estabilidad, pero me he llevado una grata sorpresa al encontrarlo y funciona en tabletas ARM.

Python Metro

Code Writer

Code Writer

Un editor de código estupendo con syntax highlighting para un montón de lenguajes:

Code Writer Main Window

El editor de código es muy profesional. Está muy bien resuelto para editar código desde una tableta, puedes tener abiertos múltiples ficheros en diferentes lenguajes,  colorea las palabras clave, etiquetas, cadenas, etc.

Code Writer Code Window

Con estas aplicaciones y gracias a la integración con Skydrive he podido seguir el curso y realizar los ejercicios sin problema desde una tableta.

Está claro que no es un entorno ideal, nos faltarán muchas cosas a las que nos hemos acostumbrado como intellisense y control de código, pero eh! que es una tableta! Y estoy seguro que poco a poco irán surgiendo más apps interesantes como estas.

Warning : DEP0810 : This app references Microsoft.WinJS.1.0 …

Si os aparece este warning en el Visual Studio 2012 cuando desarrolláis aplicaciones JavaScript para la Tienda Windows, significa que la versión de WinJS que tenéis en el VS no coincide con la que está instalada en el equipo. Lo más probable es que os falte una actualización de las extensiones de JavaScript.

El pasado día 8 actualizaron la versión de WinJS a 1.0.9200.20789. La podéis encontrar aquí: http://www.microsoft.com/en-us/download/details.aspx?id=30687

Reducir el tamaño de las aplicaciones JavaScript de la Tienda Windows

Compressed Car
Cuando utilizo librerías externas en mis aplicaciones de la tienda Windows suelo tirar de NuGet, es la forma más cómoda de encontrar e instalar las librerías más comunes, pero tiene dos pequeñas pegas que conviene saber antes de publicar las aplicaciones:

UTF-8

Muchas de las librerías vienen con un código de página diferente de UTF-8 + BOM. Resulta que el Kit de pruebas de Windows 8 detecta que si la página de código no es esta última baja el rendimiento y nos dará error de certificación. Este kit es el mismo que se pasa como proceso automatizado en la tienda, así que si no pasa en nuestro equipo tampoco pasará la certificación en la tienda. Resolverlo es muy sencillo, basta con volver a guardar el archivo .js como UTF8 + BOM. Para ello abrimos el archivo y en el menú de archivo seleccionamos Advanced Save Options…:
Advanced Save Options...

En las opciones avanzadas podremos cambiar el Encoding a UTF-8 with signature:
Unicode UTF-8 with signature

Archivos innecesarios

En el caso de librerías de JavaScript, los paquetes de NuGet suelen tener más archivos que la propia librería. Pueden incluir, por ejemplo, la versión completa para que podamos depurar, la versión minimizada para desplegar, algún Readme y otros archivos para que funcione mejor el intellisense de JavaScript. En el siguiente ejemplo tenemos la última versión de la librería jquery, en producción sólo necesitaremos el archivo jquery-2.0.3.min.js, el resto podemos eliminarlos del paquete, pero nos vendrá bien mantenerlos en la solución para poder depurar y tener intellisense:

Unused jquery files

Para evitar que esos archivos se incluyan en el paquete, sólo tenemos que cambiar la propiedad Package Action del archivo a None:
Package Action: none

Una vez hecho esto con todos los archivos que hemos importado con NuGet ya tendremos la solución limpia y mejor preparada para publicar en el Windows Store.

Juegos sociales online con SignalR y Windows Azure (3 de 3)

Eight!
En los artículos anteriores de esta serie hemos visto cómo utilizar SignalR para proporcionar un canal de comunicaciones desde el servidor hacia el cliente. Además, modificamos el código para funcionar en Azure para mejorar la escalabilidad de nuestro servicio. Llegamos al final de la serie con un reto que me he puesto a mi mismo, utilizar lo que hemos hecho desde una aplicación de Windows 8 y que no se nos complique el artículo.

SignalR tiene librerías cliente tanto para .Net como JavaScript. Como os prometí un artículo sencillo, utilizaremos JavaScript, porque así aprovecharemos el código que ya tenemos de forma casi directa dentro de nuestra aplicación.

App de tres en raya

Como sabéis, Windows 8 te permite utilizar librerías estándar como jQuery, lo más probable es que lo que hicimos en la versión web nos funcione con muy pocos cambios. Así que… ¡Manos a la obra!
Empezamos creando una aplicación de la tienda Windows en HTML5/JavaScript:

3er.30.w8project

Para conectarnos a nuestro Hub necesitaremos las librerías cliente de SignalR. De nuevo Nuget nos ayudará a instalarlas:

3er.30.w8jsclient

HTML

El código HTML será casi el mismo que el que hicimos en nuestro default.html, sólo cambiaremos una cosa, pues no podemos utilizar la función prompt para pedir el nombre de usuario; mientras no tengamos un login, ponemos una caja de texto en pantalla. Recordad que estoy enfocando los ejemplos al uso básico de SignalR, conectarnos al sistema de autentificación ya lo haremos en otro momento:

<body>
    <label for="userName">Nombre del jugador: </label><input type="text" id="userName" />
    <div id="partida">        
...

Cliente del Hub

SignalR nos permite definir manualmente las llamadas al Hub directamente, pero también genera automáticamente un proxy con el código para nosotros en http://127.0.0.1/signalr/hubs. Si abrimos esta dirección con el Internet Explorer intentará descargarse el archivo JavaScript autogenerado:
descargarhub

Nos guardarmos el archivo en la carpeta js de nuestro proyecto para poder referenciarlo desde la página default.html. Cuando abramos el script veremos que en la línea donde se define el signalR.hub se utiliza un path relativo. Nos bastará cambiar el path por uno absoluto que apunte a nuestro servidor de Azure. Como por ahora seguimos usando el emulador, apuntamos a la dirección de bucle local 127.0.0.1:

signalR.hub = $.hubConnection("http://127.0.0.1/signalr", { useDefaultPath: false });

El script tresenraya.js también lo copiamos a la carpeta js y modificamos la llamada por la lectura en el campo username que hemos creado antes:

//prompt("Escribe tu nombre");
$username.change(null, function (e) {
    nombre = e.target.value;
});

Definimos la variable $username junto a las otras:

$username = $("#userName");

Y ya casi lo tenemos, sólo nos queda enlazar a los scripts y cambiar el tema a light para que se vea parecido a nuestra página. La etiqueta head de la página default.html de nuestra app queda como esta:

<head>
    <meta charset="utf-8" />
    <title>TresEnRapp</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0/css/ui-light.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

    <!-- TresEnRapp references -->
    <link href="/css/default.css" rel="stylesheet" />

    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <script src="Scripts/jquery.signalR-1.1.2.min.js"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="/js/hub.js"></script>
    <script src="js/tresenraya.js"></script>
    <script src="/js/default.js"></script>

</head>

Y aquí tenéis la aplicación en Windows 8 jugando contra otra en IE10:

3er.24.sidebysidew8

Forzar el uso de WebSockets

Nuestra aplicación Windows 8 ha funcionado bien, pero si miramos la salida de la consola de JavaScript veremos los siguientes mensajes:
3er.25.websockets
Tenemos dos mensajes de error. El primero proviene de una comprobación que hace jQuery para ajustarse a las capacidades de cada navegador. El segundo debe preocuparnos un poco más, nuestra aplicación no está utilizando WebSockets y podría hacerlo.
El mensaje nos lo dice claro, no hemos habilitado las llamadas cross-domain en SignalR, aunque ha sido lo suficientemente listo como para encontrar otra manera de funcionar sin WebSockets. Antes no necesitábamos habilitar las llamadas cross-domain pues el código se ejecutaba en la página proveniente del sitio, pero ahora hemos creado el front-end dentro de una app de la tienda Windows, así que la llamada es cross-domain. Para habilitarlo sólo tenemos que configurar SignalR antes de arrancarlo en el Global.asax.cs:

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableCrossDomain = true;
// Register the default hubs route: ~/signalr
RouteTable.Routes.MapHubs(hubConfiguration);

Al ejecutar de nuevo la aplicación ya habrá desaparecido el error y estaremos aprovechando todo el potencial que nos proporciona Windows 8.

¿Y ahora qué?

Tal como os prometí, hacer que nuestra app funcionara no ha sido complicado. Crear una aplicación válida para la tienda Windows y que además tenga éxito ya es otra historia.
Ahora es vuestro turno. Nos quedan, entre otras cosas, las siguientes tareas:

  • Autentificación: podemos utilizar los sistemas estándar de autentificación de usuarios y luego aplicar el atributo Authorize para permitir o denegar el acceso a métodos y servicios.
  • Control de conexión y desconexión de usuarios: SignalR nos avisa cuando ocurre una conexión/desconexión/reconexión de usuario, nosotros “sólo” tendremos que gestionar esos cambios de estado para saber si un usuario está online o no.
  • Windows 8: esta aplicación es sólo una prueba de concepto. Hacer que nuestra aplicación brille nos costará un poco más, para conseguirlo tenemos grandes consejos en el MSDN

Espero que os haya gustado. Podéis descargar el código completo del ejemplo en el siguiente enlace de Codeplex.

Importar estilos XAML en otro proyecto Windows Store

Si queremos compartir los estilos entre diferentes librerías en las aplicaciones de Windows Store debemos tener en cuenta que los estilos no estarán embebidos dentro de la librería tal como ocurre en las aplicaciones WPF, sino que se guarda el XAML dentro de una carpeta con el nombre de la librería. Esta diferencia implica que el enlace al XAML no será igual.

Así, si nuestra librería referenciada es MiLibreria.Estilos y tenemos el estilo en el raíz, deberemos escribir el ResourceDictionary de la siguiente manera:

<ResourceDictionary Source="ms-appx:///MiLibreria.Estilos/StandardStyles.xaml"/>

Es fácil de comprobar, pues al compilar nuestro proyecto veremos como dentro de nuestra carpeta de compilación se crea una carpeta para la librería referenciada que contiene los archivos XAML.

Formularios interactivos dentro de un ListView

En algunas aplicaciones de la Tienda Windows vemos una colección de elementos dentro de un ListView en las que uno de ellos contiene un formulario con el que podemos interactuar, por ejemplo en esta aplicación, que no sólo tiene uno sino dos:
booking

Vamos a ver cómo podemos crear un interfaz parecido en nuestras aplicaciones HTML5/JavaScript de la Tienda Windows.

Plantilla condicional

Podéis ver en el artículo sobre diseño de listas heterogéneas cómo escribir diferentes plantillas y cargarlas condicionalmente. En nuestro caso vamos a crear una plantilla adicional para el primer elemento de la lista e insertaremos un elemento select, sólo a efectos de demostrar la técnica, sirve con cualquier cosa que tengamos dentro del elemento.

Empezamos con la plantilla de Aplicación de Cuadrícula (Grid App), que ya tiene datos y un control ListView para poder realizar la demostración. Una vez creada, en la página groupedItems.html copiamos la plantilla itemTemplate justo debajo y la modificamos un poco, añadiendo la etiqueta select con tres opciones:

<div class="interactiveitemtemplate itemtemplate" data-win-control="WinJS.Binding.Template">
    <div class="item">
        <img class=" item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
        <div class="item-overlay">
            <!--<h4 class="item-title" data-win-bind="textContent: title"></h4>-->
            <select>
                <option>Opt 1</option>
                <option>Opt 2</option>
                <option>Opt 3</option>
            </select>
        </div>
    </div>
</div>

Tras añadir la plantilla adicional, podemos modificar la función ready del código de groupedItems.js para que se ejecute la nueva plantilla al crear el primer elemento del ListView (tal como comento en el artículo que he citado antes) y la plantilla general para el resto, sustituyendo el itemTemplate por una función que ejecuta una plantilla u otra según el índice:

var standardTemplate = element.querySelector(".itemtemplate").winControl;
var interactiveTemplate = element.querySelector(".interactiveitemtemplate").winControl;
listView.itemTemplate = function (itemPromise) {
    return itemPromise.then(function (item) {
        var template;
        if (item.index==0) {
            template = interactiveTemplate;
        }
        else {
            template = standardTemplate;
        }
        return template.render(item.data);
    });
};

El modo interactivo: win-interactive

Si ejecutamos el código anterior veremos el primer elemento con la plantilla que hemos aplicado pero no podremos interactuar con el selector. El comportamiento por defecto de los elementos de un ListView es que podamos pulsar sobre el elemento para realizar una navegación. Como nosotros queremos interactuar directamente con el contenido del elemento necesitamos desactivar ese comportamiento por defecto.

En este caso el truco es muy sencillo: basta con añadir la clase win-interactive al elemento que queremos activar, WinJS se encargará del resto de la magia:

<div class="interactiveitemtemplate itemtemplate" data-win-control="WinJS.Binding.Template">
    <div class="item win-interactive">
...

A partir de ahora todo el recuadro se volverá interactivo y dejará de responder como un elemento de ListView. Si queremos que el elemento contenedor siga actuando a la pulsación podemos poner la clase win-interactive sólo en el elemento select … o en el que queramos nosotros.

En otro post ya os contaré más trucos para crear efectos parecidos, pero sin un control ListView.

Cómo usar bien la fórmula calc en CSS3 y la importancia del espacio

Hoy un truco rápido que me ha hecho perder un buen rato en una app de Windows 8, suerte que se me ha ocurrido buscar en MSDN.

A veces vamos a necesitar que un elemento nos ocupe un porcentaje de la página/aplicación, pero dejando libre un cierto margen de pixels.

Hay muchas formas de hacerlo, pero en el caso que tenía hoy necesitaba asignar un margen de 20px y un tamaño del elemento al 100% para que cubriera todo el espacio restante del contenedor.
Las propiedades de estilo margin y padding no se llevan demasiado bien con asignaciones a height y width en formato de porcentaje, el 100% calculado seguirá siendo el total del espacio del contenedor sin el margen. Así que necesitamos realizar un cálculo entre el valor del porcentaje y la cantidad de pixels que estamos utilizando en los margenes. Para esto no hace falta código JavaScript, pues tenemos la función calc que permite hacer exactamente eso en CSS, pero tenemos que ir con mucho cuidado al escribirla, busca las diferencias:

Código erróneo:

.homepage section[role=main] {
    margin-top:20px;
    height:calc(100%-20px);
}

Código correcto:

.homepage section[role=main] {
    margin-top:20px;
    height:calc(100% - 20px);
}

La diferencia es sutil pero importante: entre el 100%, 20px y el símbolo de restar, en un caso no hay espacios y en el código que funciona sí. Es muy fácil olvidar los espacios y si no los ponemos no pasará nada, nada de nada, pues, tal como dicta la norma, un atributo css inválido es ignorado.

Vuelve el Megathon


Hoy ha empezado la formación online para el Megathon 2013 que tendrá lugar los días 12, 13 y 14 de abril en múltiples ciudades. El año pasado un servidor junto con Netsaimada y el MICTT y con la colaboración de edib, UIB, Microsoft DPE, SD Assessors, IBLabs, el ParcBit que nos dejaba el auditorio e IBRed que nos puso la WiMAX hicimos dos hackathones de desarrollo de aplicaciones de Windows 8. Hubo una participación enorme y un nivel muy bueno, más teniendo en cuenta que Windows 8 era una completa novedad y teníamos que explicarlo todo desde cero y en muy poco tiempo.
megathon

Si os gustó el Megathon del año pasado, este año será mucho mejor. Además de los mentores que estaremos durante el evento para resolveros vuestras dudas, DPE ha organizado toda una serie de sesiones de formación online programadas hasta el día antes del Megathon. Encontraréis sesiones de Windows Phone y de Windows 8 con los mejores tutores que hay en España, para que estéis completamente preparados y poder darlo todo para crear la mejor aplicación de Windows Phone o Windows 8 en un fin de semana.

Tendremos entre nosotros a expertos en XAML/C#, C++ e incluso alguno de HTML5/JavaScript 🙂 para las dudas que puedan salir y alguna demo de aparatos voladores controlados por un Windows 8. Si queréis ver todo esto y mucho más, nos vemos en el Megathon. Apuntaos ya antes de que se acaben las plazas!