Usando un ContentControl como hijo de un Control SemanticZoom en XAML+C# de Windows 8
El Zoom Semantico de Windows 8 permite que los usuarios puedan hacer zoom sobre el contenido de una aplicación. Esto no significa que el Zoom esté centrado en pixeles como en normal, sino que el zoom está centrado en contenido. Veamos un ejemplo de esto:
SemanticZoom
El zoom semántico permite tener dos vistas de un mismo contenido. Una vista de zoom cercano y otra vista de zoom lejano, ambas se puede intercambiar a través de interacciones de la plataforma como teclado, ratón y táctil. El código XAML correspondiente a este control sería este:
<SemanticZoom> <SemanticZoom.ZoomedInView> <GridView /> </SemanticZoom.ZoomedInView> <SemanticZoom.ZoomedOutView> <GridView /> </SemanticZoom.ZoomedOutView> </SemanticZoom>
Este control tiene dos propiedades, ZoomedInView y ZommedOutView que son de tipo ISemanticZoomInformation. Esta interfaz es que permite la comunicación con el sistema de Zoom semántico para así poder cambiar la vista en cualquier momento que el usuario lo desee. Los dos únicos controles que implementan esa interfaz son el GridView y el ListView.
Aquí se puede ver un ejemplo de aplicación con dos vistas en zoom semántico.
Pero, ¿qué pasa si quiero usar otro control que no sea un GridView como hijo del Zoom Semántico?
SemanticZoomHost
Este control permite tener cualquier árbol de objetos en xaml y que a su vez pueda ser hijo de cualquiera de las dos propiedades del SematicZoom. Esta clase implementa la interfaz ISemanticZoomInformation, pero permite que la información de zoom se transadle a uno de sus hijos.
De esta manera se puede no tener un GridView únicamente sino que se puede tener cualquier tipo control y dentro de ese árbol un control GridView.
Ejemplo
<SemanticZoom> <SemanticZoom.ZoomedInView> <controls:SemanticZoomHost> <StackPanel> <TextBlock Text="Titulo"></TextBlock> <GridView controls:SemanticZoomHost.SemanticZoomHost="true"></GridView> </StackPanel> </controls:SemanticZoomHost> </SemanticZoom.ZoomedInView> <SemanticZoom.ZoomedOutView> <GridView /> </SemanticZoom.ZoomedOutView> </SemanticZoom>
¿Cómo está implementado SemanticZoomHost?
La idea que hay detrás es muy sencilla, una clase que herede de ContentControl, para que así pueda tener hijos en el árbol visual, y que implemente la interfaz ISemanticZoomInformation. La clase en sí no hace nada con la implementación de la interfaz, es decir, que simplemente traslada las llamadas al control que realmente implementa ISemanticZoomInformation. Es por eso que el control SemanticZoomHost tiene una propiedad de tipo ISematicZoomInformation que será el control que manejará el zoom semántico.
public ISemanticZoomInformation ISemanticZoomInformationHost { get { return (ISemanticZoomInformation)GetValue(ISemanticZoomInformationHostProperty); } set { SetValue(ISemanticZoomInformationHostProperty, value); } } public static readonly DependencyProperty ISemanticZoomInformationHostProperty = DependencyProperty.Register( "ISemanticZoomInformationHost", typeof(ISemanticZoomInformation), typeof(SemanticZoomHost), new PropertyMetadata(null));
Attached Property
Esa propiedad tiene que ser establecida de manera automática, es decir, que de alguna manera el sistema tiene que buscar que control del árbol visual es el encargado de establecer esa propiedad. Es en este punto donde las Attached property entran en acción para que el propio control SemanticZoomHost defina una attached property llamada SemanticZoomHost de tipo booleana, que será utilizada para marcar que control en el árbol visual es el encargado de ser el receptor de la información del zoom semántico.
public static bool GetSemanticZoomHost(DependencyObject obj) { return (bool)obj.GetValue(SemanticZoomHostProperty); } public static void SetSemanticZoomHost(DependencyObject obj, bool value) { obj.SetValue(SemanticZoomHostProperty, value); } public static readonly DependencyProperty SemanticZoomHostProperty = DependencyProperty.RegisterAttached( "SemanticZoomHost", typeof(bool), typeof(SemanticZoomHost), new PropertyMetadata(false));
Buscando ese control en el árbol visual
La última tarea que tiene la clase es buscar los controles en el árbol visual que tengan esa propiedad a true y establecer esa propiedad. Como el control es del tipo ContentControl, tiene el método OnApplyTemplate, pero que en este caso no se puede usar porque el control en sí se ha cargado pero no todos los hijos, así que hay que usar el evento Loaded para saber cuándo se ha terminado de cargar todo el árbol visual de todos los hijos.
void OnSemanticZoomHostLoaded(object sender, RoutedEventArgs e) { List<DependencyObject> all = ControlTreeHelper.GetAllControlsByType<DependencyObject>(this); foreach (var item in all) { if (SemanticZoomHost.GetSemanticZoomHost(item)) { ISemanticZoomInformation semanticZoom = (ISemanticZoomInformation)item; semanticZoom.IsActiveView = isActiveView; semanticZoom.IsZoomedInView = isZoomedView; semanticZoom.SemanticZoomOwner = owner; this.ISemanticZoomInformationHost = semanticZoom; break; } } }
El resto de la implementación son los métodos de la interfaz ISemanticZoomInformation.
Implementación
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace SematicZoomDemo.Controls { public class SemanticZoomHost : ContentControl, ISemanticZoomInformation { public SemanticZoomHost() { Loaded += OnSemanticZoomHostLoaded; } void OnSemanticZoomHostLoaded(object sender, RoutedEventArgs e) { List<DependencyObject> all = ControlTreeHelper.GetAllControlsByType<DependencyObject>(this); foreach (var item in all) { if (SemanticZoomHost.GetSemanticZoomHost(item)) { ISemanticZoomInformation semanticZoom = (ISemanticZoomInformation)item; semanticZoom.IsActiveView = isActiveView; semanticZoom.IsZoomedInView = isZoomedView; semanticZoom.SemanticZoomOwner = owner; this.ISemanticZoomInformationHost = semanticZoom; break; } } } public void CompleteViewChange() { ISemanticZoomInformationHost.CompleteViewChange(); } public void CompleteViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination) { ISemanticZoomInformationHost.CompleteViewChangeFrom(source, destination); } public void CompleteViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination) { ISemanticZoomInformationHost.CompleteViewChangeTo(source, destination); } public void InitializeViewChange() { ISemanticZoomInformationHost.InitializeViewChange(); } public void MakeVisible(SemanticZoomLocation item) { ISemanticZoomInformationHost.MakeVisible(item); } public void StartViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination) { ISemanticZoomInformationHost.StartViewChangeFrom(source, destination); } public void StartViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination) { ISemanticZoomInformationHost.StartViewChangeTo(source, destination); } public bool IsActiveView { get { return ISemanticZoomInformationHost.IsActiveView; } set { if (ISemanticZoomInformationHost == null) { isActiveView = value; } else { ISemanticZoomInformationHost.IsActiveView = value; } } } public bool IsZoomedInView { get { return ISemanticZoomInformationHost.IsZoomedInView; } set { if (ISemanticZoomInformationHost == null) { isZoomedView = value; } else { ISemanticZoomInformationHost.IsZoomedInView = value; } } } public SemanticZoom SemanticZoomOwner { get { return ISemanticZoomInformationHost.SemanticZoomOwner; } set { if (ISemanticZoomInformationHost == null) { owner = value; } else { ISemanticZoomInformationHost.SemanticZoomOwner = value; } } } private SemanticZoom owner; private bool isZoomedView; private bool isActiveView; public ISemanticZoomInformation ISemanticZoomInformationHost { get { return (ISemanticZoomInformation)GetValue(ISemanticZoomInformationHostProperty); } set { SetValue(ISemanticZoomInformationHostProperty, value); } } public static readonly DependencyProperty ISemanticZoomInformationHostProperty = DependencyProperty.Register( "ISemanticZoomInformationHost", typeof(ISemanticZoomInformation), typeof(SemanticZoomHost), new PropertyMetadata(null)); public static bool GetSemanticZoomHost(DependencyObject obj) { return (bool)obj.GetValue(SemanticZoomHostProperty); } public static void SetSemanticZoomHost(DependencyObject obj, bool value) { obj.SetValue(SemanticZoomHostProperty, value); } public static readonly DependencyProperty SemanticZoomHostProperty = DependencyProperty.RegisterAttached( "SemanticZoomHost", typeof(bool), typeof(SemanticZoomHost), new PropertyMetadata(false)); } }
Conclusión
En SematicZoom es un control muy útil a la hora de hacer zoom semántico en una aplicación de Windows 8, pero desde mi punto de vista es una limitación que solamente se pueda establecer un ListView o un GridView. En muchas ocasiones los layouts que se puede obtener son muy complejos.
ControlTreeHelper
Como podéis ver en el código se utiliza una clase llamada ControlTreeHelper. Esta clase fue diseñada para usarse en WPF (Windows Presentation Foundation), ¡qué tiempos aquellos!.
La clase contiene métodos que nos permite recorrer el árbol visual de manera cómoda, paso a comentar algunos de esos métodos.
-
List<T> GetAllControlsByType<T>(DependencyObject root): Obtiene todos los controles hijos filtrados por el tipo T y los devuelve en una lista de T.
-
T FindUniqueParentControl<T>(DependencyObject leaf): Busca un padre directo en el arbol visual de tipo T.
-
T FindNameInVisualTree<T>(DependencyObject root, string name): Busca un elemento en el arbol visual de tipo T con el nombre pasado por parametro.
El código de ejemplo se puede descargar de aquí.
Luis Guerrero.
Optimizaciones de JavaScript utilizadas en el proyecto de Prometheus
Introducción
Para los que no lo sepáis, próximamente se estrena la película Prometheus en Estados Unidos y desde Plain Concepts hemos desarrollado el training center del sitio web. Se puede acceder desde este enlace: http://www.projectprometheus.com/trainingcenter/. El proyecto ha estado financiado por Microsoft, más concretamente por el equipo de Internet Explorer, así que como página web que es, se ha desarrollado utilizando las últimas tecnologías web: HTML5 + CSS3.
Training center
El centro de entrenamiento es un sitio web donde los candidatos al proyecto Prometheus, de la empresa Weyland, puede probar su valía. El entrenamiento cuenta con 5 pruebas (juegos) que el recluta tiene que completar en un tiempo determinado. Una vez superadas las cinco pruebas el recluta puede formar parte de Weyland Industries. Los cinco juegos han sido desarrollados por gente de Plain Concepts:
-
Jesus David Garcia
-
Fernando
-
Luis Guerrero
Cada uno de los cuales ha desarrollado uno de los juegos del centro de entrenamiento. En mi caso he desarrollado el cubo de rubick en 2 y 3 dimensiones.
Aquí se puede ver una captura del juego en Internet Explorer llamado Prefrontal Cortex.
HTML5 / Javascript
Todos los juegos han sido desarrollados en HTML5 utilizando JavaScript para la parte de programación, en mi caso he utilizado Canvas para dibujar los cubos. Eso significa que todos los juegos funcionan perfectamente en todos los navegadores modernos, incluyendo Google Chrome, Firefox, Safari, Opera y Internet Explorer 9 y 10.
Para el desarrollo de los juegos se creo un motor en JavaScript que nos permitiera dibujar en un Canvas la geometría de los modelos de los cubos. Este motor no utiliza WebGL para renderizar los cubos, porque Internet Explorer no tiene soporte (además de en el resto es experimental), por lo que se opto por hacer un motor grafico completo desde cero. Es decir, todo el pipeline de grafica se tiene que hacer en JavaScript, esto significa, entre otras cosas, que tenemos que emular por software como funciona una tarjeta gráfica y eso normalmente es más lento que el propio hardware. Así que el desafío de implementar un pipeline gráfico por software es mayor ya que tiene que tener un rendimiento aceptable.
JavaScript 101
JavaScript es un lenguaje en el que existen varios tipos de datos básicos con los que podemos trabajar.
-
Object
-
Array
-
Number
-
String
-
Boolean
Los objetos son la forma más común a la hora de trabajar en JavaScript y se pueden utilizar de muchas maneras.
La forma más sencilla de crear un objeto es:
1: var myObject = {}
A partir de ahí se pueden ir agregando propiedades al objeto sin ningún tipo de restricción. No son propiedades como las que se puede estar acostumbrado en C#, sino que el tipo object se comporta como una especie de diccionario de pares nombre valor.
Se pueden crear propiedades de la siguiente manera a un objeto previamente definido.
1: myObject.name = ‘Luis’;
2: myObject.number = 42;
Así el objeto pasará a tener dos propiedades, una llamada ‘name’ con el valor ‘Luis’ y otra llamada ‘number’ con el valor ‘42’.
También se puede acceder a esas propiedades como si de un diccionario se tratase. Las dos formas son igual de válidas y correctas.
1: var name = myObject[‘name’];
Después de la ejecución de esta línea de código lo que se establece en la variable name es el valor de ‘Luis’, previamente establecido.
Con esta nueva forma de acceder a las propiedades no solo se pueden leer valores almacenados en un objeto sino que también se pueden guardar.
myObject[‘currentDate’] = new Date();
Vector3
Como ejemplo de objeto se va a definir Vector3; un vector de 3 dimensiones.
var vector3 = {x: 1, y: 1, z: 1}
¿Cuál es el problema con este Vector3?
El rendimiento. Como se ha dicho antes todos los objetos en JavaScript se comportan como un diccionario de pares nombre / valor, así que en cada una de las operaciones en las que se tenga que leer o escribir el valor de x, y o z, el runtime de JavaScript tiene que comprobar que la propiedad existe o no y luego leerla o almacenarla. Todo esto lleva tiempo. Es como si se programase todo el acceso a propiedades y campos en .NET utilizando únicamente la API de Reflexion (System.Reflection).
En el caso del motor de 3D en JavaScript Vector2, Vector3, Vector4, Color y Matrix son tipos que se están usando constantemente para dibujar la geometría de los cubos, así que esos tipos fueron los primero en ser optimizados.
La solución por la que se opto fue eliminar la definición de los tipos, es decir, que por ejemplo Vector2, Vector3, Vector4 y Color pasaron a ser un array de 2, 3, 4 y 4 posiciones respectivamente. Así que por convención lo que se hizo fue que la posición dentro del array representaba una coordenada de las dimensiones del vector.
-
X: array[0]
-
Y: array[1]
-
Z: array[2]
-
W: arrat[3]
En el caso de Matrix que se tenía M11, M12…M21,M22..M31..M44 pasaron a ser también las posiciones de un array.
Veamos como se ha cambiado la multiplicación de matrices, uno de los cuellos de botella, en cuanto a rendimiento se refiere.
Antes
1: function Multiply(matrix1, matrix2) {
2: var matrix = new Matrix();
3: matrix.M11 = (((matrix1.M11 * matrix2.M11) + (matrix1.M12 * matrix2.M21)) + (matrix1.M13 * matrix2.M31)) + (matrix1.M14 * matrix2.M41);
4: matrix.M12 = (((matrix1.M11 * matrix2.M12) + (matrix1.M12 * matrix2.M22)) + (matrix1.M13 * matrix2.M32)) + (matrix1.M14 * matrix2.M42);
5: matrix.M13 = (((matrix1.M11 * matrix2.M13) + (matrix1.M12 * matrix2.M23)) + (matrix1.M13 * matrix2.M33)) + (matrix1.M14 * matrix2.M43);
6: matrix.M14 = (((matrix1.M11 * matrix2.M14) + (matrix1.M12 * matrix2.M24)) + (matrix1.M13 * matrix2.M34)) + (matrix1.M14 * matrix2.M44);
7: matrix.M21 = (((matrix1.M21 * matrix2.M11) + (matrix1.M22 * matrix2.M21)) + (matrix1.M23 * matrix2.M31)) + (matrix1.M24 * matrix2.M41);
8: matrix.M22 = (((matrix1.M21 * matrix2.M12) + (matrix1.M22 * matrix2.M22)) + (matrix1.M23 * matrix2.M32)) + (matrix1.M24 * matrix2.M42);
9: matrix.M23 = (((matrix1.M21 * matrix2.M13) + (matrix1.M22 * matrix2.M23)) + (matrix1.M23 * matrix2.M33)) + (matrix1.M24 * matrix2.M43);
10: matrix.M24 = (((matrix1.M21 * matrix2.M14) + (matrix1.M22 * matrix2.M24)) + (matrix1.M23 * matrix2.M34)) + (matrix1.M24 * matrix2.M44);
11: matrix.M31 = (((matrix1.M31 * matrix2.M11) + (matrix1.M32 * matrix2.M21)) + (matrix1.M33 * matrix2.M31)) + (matrix1.M34 * matrix2.M41);
12: matrix.M32 = (((matrix1.M31 * matrix2.M12) + (matrix1.M32 * matrix2.M22)) + (matrix1.M33 * matrix2.M32)) + (matrix1.M34 * matrix2.M42);
13: matrix.M33 = (((matrix1.M31 * matrix2.M13) + (matrix1.M32 * matrix2.M23)) + (matrix1.M33 * matrix2.M33)) + (matrix1.M34 * matrix2.M43);
14: matrix.M34 = (((matrix1.M31 * matrix2.M14) + (matrix1.M32 * matrix2.M24)) + (matrix1.M33 * matrix2.M34)) + (matrix1.M34 * matrix2.M44);
15: matrix.M41 = (((matrix1.M41 * matrix2.M11) + (matrix1.M42 * matrix2.M21)) + (matrix1.M43 * matrix2.M31)) + (matrix1.M44 * matrix2.M41);
16: matrix.M42 = (((matrix1.M41 * matrix2.M12) + (matrix1.M42 * matrix2.M22)) + (matrix1.M43 * matrix2.M32)) + (matrix1.M44 * matrix2.M42);
17: matrix.M43 = (((matrix1.M41 * matrix2.M13) + (matrix1.M42 * matrix2.M23)) + (matrix1.M43 * matrix2.M33)) + (matrix1.M44 * matrix2.M43);
18: matrix.M44 = (((matrix1.M41 * matrix2.M14) + (matrix1.M42 * matrix2.M24)) + (matrix1.M43 * matrix2.M34)) + (matrix1.M44 * matrix2.M44);
19: return matrix;
20: }
La multiplicación simplemente accedía a cada uno de los índices de la matriz, los multiplicaba y luego los asignada de vuelta a la matriz de resultado. Como hemos dicho antes, esto implica leer una gran cantidad de propiedades durante el dibujado de un frame de la escena.
Ahora
1: function Multiply(matrix1, matrix2) {
2: var matrix = new Matrix();
3: var position = matrix.position;
4: var position1 = matrix1.position;
5: var position2 = matrix2.position;
6: position[0] = (((position1[0] * position2[0]) + (position1[1] * position2[4])) + (position1[2] * position2[8])) + (position1[3] * position2[12]);
7: position[1] = (((position1[0] * position2[1]) + (position1[1] * position2[5])) + (position1[2] * position2[9])) + (position1[3] * position2[13]);
8: position[2] = (((position1[0] * position2[2]) + (position1[1] * position2[6])) + (position1[2] * position2[10])) + (position1[3] * position2[14]);
9: position[3] = (((position1[0] * position2[3]) + (position1[1] * position2[7])) + (position1[2] * position2[11])) + (position1[3] * position2[15]);
10: position[4] = (((position1[4] * position2[0]) + (position1[5] * position2[4])) + (position1[6] * position2[8])) + (position1[7] * position2[12]);
11: position[5] = (((position1[4] * position2[1]) + (position1[5] * position2[5])) + (position1[6] * position2[9])) + (position1[7] * position2[13]);
12: position[6] = (((position1[4] * position2[2]) + (position1[5] * position2[6])) + (position1[6] * position2[10])) + (position1[7] * position2[14]);
13: position[7] = (((position1[4] * position2[3]) + (position1[5] * position2[7])) + (position1[6] * position2[11])) + (position1[7] * position2[15]);
14: position[8] = (((position1[8] * position2[0]) + (position1[9] * position2[4])) + (position1[10] * position2[8])) + (position1[11] * position2[12]);
15: position[9] = (((position1[8] * position2[1]) + (position1[9] * position2[5])) + (position1[10] * position2[9])) + (position1[11] * position2[13]);
16: position[10] = (((position1[8] * position2[2]) + (position1[9] * position2[6])) + (position1[10] * position2[10])) + (position1[11] * position2[14]);
17: position[11] = (((position1[8] * position2[3]) + (position1[9] * position2[7])) + (position1[10] * position2[11])) + (position1[11] * position2[15]);
18: position[12] = (((position1[12] * position2[0]) + (position1[13] * position2[4])) + (position1[14] * position2[8])) + (position1[15] * position2[12]);
19: position[13] = (((position1[12] * position2[1]) + (position1[13] * position2[5])) + (position1[14] * position2[9])) + (position1[15] * position2[13]);
20: position[14] = (((position1[12] * position2[2]) + (position1[13] * position2[6])) + (position1[14] * position2[10])) + (position1[15] * position2[14]);
21: position[15] = (((position1[12] * position2[3]) + (position1[13] * position2[7])) + (position1[14] * position2[11])) + (position1[15] * position2[15]);
22: return matrix;
23: }
Lo primero que se observa es que el código pasa a ser más críptico que el anterior, es decir, que ahora únicamente se tiene son los diferentes índices de position dentro de tres arrays, que representan las tres matrices con las que se esta trabajando en este momento.
Así que se ha pasado de,
matrix.M11 = (((matrix1.M11 * matrix2.M11) + (matrix1.M12 * matrix2.M21)) +
(matrix1.M13 * matrix2.M31)) + (matrix1.M14 * matrix2.M41);
a esto:
position[0] = (((position1[0] * position2[0]) + (position1[1] * position2[4])) +
(position1[2] * position2[8])) + (position1[3] * position2[12]);
Ya que ahora todas las posiciones de la matriz están almacenadas en un array de 16 posiciones lo que se tiene que hacer si se quiere acceder al valor M11 es acceder a la posición 0 de array, en el caso del valor M31 a la posición 8 de array y así sucesivamente.
Otras optimizaciones
Tamaño de los arrays
Si se tiene un array que tiene una propiedad length por la que se quiere iterar para realizar una acción por cada uno de los elementos del array, es recomendable no poner directamente el valor de myArray.length para comprobar si se ha llegado al final de array, sino guardar el tamaño del array en una variable y usar esta variable.
1: var myArray = new Array();
2:
3: for (var i = 0; i < myArray.length; i++) {
4: myArray[i] = i;
5: }
6:
7: var length = myArray.length;
8: for (var i = 0; i < length; i++) {
9: myArray[i] = i;
10: }
Cachear variables
Si durante la ejecución de un método se tiene variables que vamos a usar y estas variables son propiedades de un objeto, es mejor definirlas como variables en el ámbito del método que no referenciarlas desde el objeto original.
1: var myObject =
2: {
3: name: 'luis',
4: company: {
5: name: 'PlainConcepts',
6: location: 'address'
7: }
8: };
9:
10: var companyAddress = myObject.company.location;
11: var company = myObject.company;
12: companyAddress = company.location;
Espero que estas notas sobre optimización de JavaScript os sean útiles.
Luis Guerrero.
Material de la charla de Computación paralela en Windows de la CodeMotion
Como viene siendo habitual aquí tenéis el material de la charla sobre computación paralela del pasado sábado día 24 de marzo.
El código de ejemplo lo podéis descargar de aquí: http://bit.ly/TPLCodeMotion
Y ya sabéis nada de dejar los try/catch vacíos.
¡Espero que disfrutéis de todos los cores del mundo!
Saludos. Luis.
San Valentín se Baila
Este domingo en la plaza de Callao de Madrid habrá un evento de El Corte Inglés, Microsoft y Xbox 360 para celebrar el día de los enamorados. En este evento Plain Concepts presentará dos aplicaciones para Windows Phone 7 y Surface 2 para que las parejas de enamorados puedan hacerse fotografías con el teléfono móvil (un Nokia Lumia) y después componer una tarjeta de felicitación en un Surface 2.
Os invitamos a todos a que os paséis este domingo por la mañana por Callao con vuestras parejas, para celebrar San Valentín y disfrutar de estas aplicaciones.
Windows Phone 7
Las imágenes se suben a Azure y luego desde la aplicación de Surface 2 se pueden componer.
Surface 2
12 Horas de Visual Studio – Exprime las vistas en ASP.NET MVC
Este es el material que voy a utilizar sobre mi charla sobre “Exprime las vista en ASP.NET MVC”
El código de ejemplo os lo podéis descargar de aquí, http://bit.ly/12HorasMVCCode
12 Horas de Visual Studio – Calidad de Software y patrones de diseño en Windows Phone 7.5
Hoy es el evento de 12 Horas de Visual Studio de Microsoft y Globbtv, podeis ver el evento en directo aquí http://www.globbtv.com/vstudio12horas/
Este es el material que voy a utilizar sobre mi charla sobre “Calidad de Software y patrones de diseño de Windows Phone 7.5”
El código de ejemplo os lo podéis descargar de aquí, http://bit.ly/12HorasVSWindowsPhone
Hack-a-thon, repaso a las novedades de networking de Windows Phone 7
Este fin de semana en Málaga se ha celebrado el Hack-a-thon un evento de Microsoft para incentivar el desarrollo de aplicaciones de Windows Phone 7 en entornos universitarios. Es todo un fin de semana de programación de apps y entre medias los desarrolladores pueden elegir las charlas que quieres escuchar. Así que es un agenda dinámica que se decide en base a las valoraciones.
Yo es la segunda vez que participo en un evento de este tipo y en mi caso la charla que me ha tocado es la de Networking. Así que estando ahora mismo en el evento aprovecho para hacer un repaso de las novedades de Windows Phone 7.1 (Mango) en el apartado de comunicaciones.

Peticiones HTTP
Dentro de apartado de peticiones HTTP de toda la vida, WP7 tiene dos clases para realizar este trabajo: WebClient y HttpWebRequest (+Response). Las dos API se distinguen una de la otra por la simplicidad y opciones que ofrecen.
Hay que recordad que todas las comunicaciones en WP7 son asíncronas.
WebClient
WebClient es la API más sencilla para hacer peticiones HTTP, simplemente hay que crear una instancia de esta clase, suscribirse al evento deseado, por ejemplo, DownloadStringCompleted y llamar al método DownloadStringAsync(Uri) para descargarse el contenido de una URI como un string.
public partial class MainPage : PhoneApplicationPage { WebClient client; // Constructor public MainPage() { InitializeComponent(); client = new WebClient(); client.DownloadStringCompleted += new DownloadStringCompletedEventHandler( client_DownloadStringCompleted); } void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { if (e.Error == null) { XElement twitterElements = XElement.Parse(e.Result); var postList = from tweet in twitterElements.Descendants("status") select new TwitterPost { UserImage = tweet.Element("user").Element("profile_image_url").Value, PostText = tweet.Element("text").Value, DatePosted = tweet.Element("created_at").Value }; tweetsListBox.ItemsSource = postList; } } private void loadButton_Click(object sender, RoutedEventArgs e) { string url = "http://twitter.com/statuses/user_timeline/" + nameTextBox.Text + ".xml"; client.DownloadStringAsync(new Uri(url)); } }
En este ejemplo podemos ver como en los argumentos DownloadStringCompletedEventArgs podemos obtener una propiedad llamada Result que contiene el string con el contenido de la petición.
En este tipo de peticiones no podemos añadir cookies ni configurar ningún otro tipo de propiedad para la petición.
Las opciones de personalización son:
-
Añadir cabeceras en la petición y leer las cabeceras de la respuesta.
-
Configurar credenciales para autenticación de usuarios.
-
Permitir la lectura buffereada del contenido de la lectura y de la escritura.
-
Codificación usada para lectura y escritura.
HttpWebRequest
HttpWebRequest es la clase de bajo nivel que permite hacer peticiones HTTP configurando todas las opciones que queramos, es mucho más flexible, pero más complejo de consumir. Estas API utiliza el APM (Asychonous Programming Model) de .NET lo que significa que utiliza para las notificaciones asíncronas IAsyncResult.
Estas son las características:
-
Acceso a todas las cabeceras.
-
Podemos agregar cookies en las peticiones y leer las cookies de respuesta.
-
Podemos especificar el método de la petición (GET o POST)
-
Podemos escribir en el cuerpo de la petición.
Así tenemos un ejemplo completo de peticiones usando HttpWebRequest:
public class ComplexRestRequest : BaseRequestProcessor { public override void ProcessRequest(Uri uri, string body) { content = body; request = HttpWebRequest.Create(uri); request.Method = "POST"; request.Headers["Authorization"] = AuthorizationService.AuthorizationToken.Token; request.Headers["IsComplex"] = "true"; request.BeginGetRequestStream(new AsyncCallback(OnBeginGetRequestStream), null); } private void OnBeginGetRequestStream(IAsyncResult result) { Stream stream = request.EndGetRequestStream(result); byte[] buff = System.Text.Encoding.UTF8.GetBytes(content); stream.Write(buff, 0, buff.Length); buff = null; request.BeginGetResponse(OnBeginGetResponse, null); } private void OnBeginGetResponse(IAsyncResult result) { try { response = request.EndGetResponse(result); string authorizationHeader = response.Headers["Authorization"]; if (!string.IsNullOrEmpty(authorizationHeader)) { AuthorizationService.UpdateAuthorizationToken(authorizationHeader); } string content = null; if (response.ContentLength > 0L) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, true)) { content = reader.ReadToEnd(); } } } FireEndRequestCompleted(new HttpResult(content, false, null)); } catch (Exception ex) { Trace.WriteLine(ex.ToString()); FireEndRequestCompleted(new HttpResult(null, true, ex)); } } private WebRequest request; private WebResponse response; private string content; }
Socket
La siguiente gran funcionalidad de comunicaciones, muy esperada, en Windows Phone 7 son los sockets. Los sockets permiten una comunicación más directa en un canal de comunicación orientado a conexión (TCP) o no orientado a conexión (UDP y multicasting).
Windows Phone 7 soporta direcciones IPv4 pero no soporta IPv6. Toda la API es asíncrona.
Background file transfers
Si tenemos que descargar o subir ficheros al isolated storage de nuestra aplicación, pero queremos que esa descarga se haga cuando el usuario no esté usando la conexión a internet de su dispositivo móvil, podemos usar Background file transfers.
Esta API permite programar la descarga de un fichero al almacenamiento aislado de Windows Phone 7 incluso si nuestra aplicación no está ejecutándose. Soporta HTTP y HTTPS pero no FTP. Alguna de las cabeceras HTTP están reservadas, principalmente las de control de cache.
Estos son los valores de cuota:
-
Tamaño máximo de subida: 5MB
-
Tamaño máximo de descarga sobre 2G/3G: 20 MB
-
Tamaño máximo de descarga sobre WiFi: 100MB
API de información de comunicaciones
Todas las aplicaciones que utilicen recursos online deberán de ser tolerantes a faltas de conectividad por parte del usuario en su dispositivo. Si el usuario está modo avión, no tiene cobertura ni Wifi, la aplicación no debería de fallar y cerrarse, sino que debería de ofrecer la posibilidad de reconectarse de nuevo.
Para eso necesitamos saber cuál es el estado de las comunicaciones del dispositivo.
-
Consulta del operador móvil
-
DeviceNetworkInformation.CellularMobileOperator
-
-
Consulta si hay red disponible
-
DeviceNetworkInformation.IsNetworkAvailable
-
-
Consulta si hay red celular 2G/3G
-
DeviceNetworkInformation.IsCellularDataEnabled
-
-
Consulta si el romaing está habilitado
-
DeviceNetworkInformation.IsCellularDataRoamingEnabled
-
-
Consulta si el WiFi está habilitado
-
DeviceNetworkInformation.IsWiFiEnabled
-
Eligiendo la mejor serialización para aplicaciones móviles
Cuando desarrollamos aplicaciones móviles tenemos que tener en cuenta el tamaño de los datos que enviamos al cliente. Por eso tenemos que elegir la serialización que permite utilizar el menor tamaño para enviar los datos. Aquí tenemos una comparativa de los diferentes formatos para los mismos datos envíados.
| Wire Serialization Format | Size in Bytes |
| ASMX SOAP – DataSet (XML) | 39670 |
| ODATA XML | 73786 |
| ODATA JSON | 34030 |
| REST + JSON | 15540 |
| REST + JSON GZip | 8680 |
Luis Guerrero.
Como tener más de un emulador de Windows Phone 7 en la misma máquina
Si en alguna ocasión habéis tenido la necesidad de tener más de un emulador ejecutándose en la misma máquina, para por ejemplo, depurar vuestro código de Socket para Mango, ahora podéis hacerlo.
Esto son los pasos.
Ve a la carpeta C:\ProgramData\Microsoft\Phone Tools\CoreCon\10.0\addons
En esa carpeta hay un fichero llamado ImageConfig.en-us.xsl copiadlo por otro fichero llamado por ejemplo ImageConfig2.en-us.xsl.
Una vez hecho esto abridlo con un bloc de notas elevador (como administrador)
En el nodo PLATFORM hay una propiedad llamada ID, teneis que generar una nueva GUID. Podéis usar la herramienta que viene en Visual Studio para generar GUID’s. Una vez generada la nueva GUID, cambiar el atributo del nodo xml.
Podéis cambiar el nombre del emulador cambiando el atributo name del mismo nodo.
Buscad una propiedad llamada VMID en el valor tenéis que poner la misma GUID.
Ahora cerrad el Visual Studio y abridlo de nuevo, ahora teneis dos valores en el combo de Device de Windows Phone 7.

Aquí tenéis una captura de dos emuladores ejecutando ByeByeBrain.
Espero que sea de utilidad.
Saludos. Luis.
Material de la charla de HTML5 en el CIIN
El lunes 3 de septiembre estuve con la gente del CIIN dando una charla sobre desarrollo de aplicaciones en HTML5 y cuáles son las novedades en este sentido.
Aquí os dejo la presentación, las demos y algunos enlaces que comenté durante la charla.
Agradecer a Alejandro Hidalgo (MVP de Internet Explorer 9) por la presentación en HTML5.
Enlaces de interés:
- Sproutcore: Es un framework para desarrollo de aplicaciones en HTML5 utilizando MCV, tiene características muy interesantes como, bindings, handlebars, moustache, observable, datasources, un modelo relacional en cliente KVO, ect.
- Como desarrollar autorización en una API WEB en REST
Decir que en PlainConcepts utilizamos Sproutcore para nuestros desarrollos y además formamos parte del soporte oficial de SproutCore para Microsoft y Strobe.
Saludos. Luis.





