127.0.0.1: Hogar dulce hogar
Mostrando entradas con la etiqueta silverlight. Mostrar todas las entradas
Mostrando entradas con la etiqueta silverlight. Mostrar todas las entradas

viernes, 24 de junio de 2011

Usando Reflection para tener una única ventana modal personalizable.

 

Tenemos el siguiente escenario: Una aplicación (en este caso en WPF) que tiene varias ventanas modales. Cada ventana contendrá un UserControl único, que a su vez se construirá mediante una serie de parámetros determinados dependientes de lo que requiera el propio control.

El primer enfoque, que lo podemos  llamar a fuerza bruta, sería crear una ventana por cada control de usuario que tenga. El segundo enfoque, algo más fino, sería crear el UserControl antes de la ventana y ajustarlo mediante una propiedad para que lo cargue en el contenedor que le digamos a la ventana. No es lo más elegante, pero estamos instanciando el UserControl en casos particulares y no es algo muy genérico.

El tercer enfoque, que es el que propongo aquí, sería usar Reflection para que la propia ventana sea quien instancie el UserControl y nosotros sólo nos ocupemos de crear la ventana con los parámetros adecuados. Así no tenemos que estar pendiente de la creación del control en sí, sino simplemente de crear la ventana, decirle qué queremos mostrar y mostrarla al usuario.

Para ello, en primer lugar creamos una ventana en WPF. Luego, en el constructor insertaremos los siguientes parámetros:

- Primero, el tipo del control que se instanciará.

- Y después, un listado de parámetros:

  1. 1:  public ModalPropertyWindow(Type childrenType, params object[] logicObject)
  2.  2:          {
  3.  3:              InitializeComponent();
  4.  4:          }

Luego, lo que tenemos que hacer es instanciar el control que queremos con los parámetros que se requieran. Usaremos Activator.CreateInstance, que creará esa instancia con una serie de parámetros. Internamente recorre esa clase y busca un constructor que tenga la disposición de parámetros que le indicamos. Si no lo encuentra, evidentemente producirá una excepción:

  1. this.stackPanel2.Children.Add(Activator.CreateInstance(childrenType, logicObject) as UIElement);

Nótese que directamente añadimos la instancia creada a un StackPanel.

Por último, sólo tenemos que llamar a la ventana e instanciarla con el control que queramos:

  1. ModalPropertyWindow window = new ModalPropertyWindow(typeof(ClientControl), this.currentCliente, this.loggedUser);
  2. window.ShowDialog();

Y así para todas las ventanas que queramos. En resumen: una única ventana modal, todos los controles que queramos y no nos tenemos que preocupar de instanciar cada uno de forma particular.

viernes, 8 de abril de 2011

[WPF/Silverlight] Binding de objetos relacionados en un ComboBox

 

Escenario: Tengo dos entidades relacionadas. La entidad A se relaciona con la entidad B a través de un identificador. Y lo que quiero es bindear eso, pero de modo que yo disponga en la interfaz de un combobox –o cualquier tipo de selección- que permita seleccionar todos los elementos. Entonces tenemos las siguientes tareas:

  1. Bindear los datos del campo con el identificador de la identidad.
  2. Pero yo no quiero ver un número. Yo quiero ver el nombre del objeto que relaciono. Por lo tanto, tendré un combobox que rellenar y bindear con respecto al identificador anteriormente citado.
  3. Y claro, para hacerlo bueno, bonito y barato, nada de hacerlo en el código. Todo debe ir en el XAML.

El primer enfoque puede ir dirigido a un binding normal. Es decir, obviamente tenemos que tener en cuenta el ID de la entidad A, puesto que ese será el valor elegido a mostrar. Y como quiero rellenar todo el combobox con el resto de entidades B, usaremos un Converter:

  1. <ComboBox ItemsSource="{Binding Converter={StaticResource ResourceKey=Converter}}" />

De este modo ya podemos ver todos los valores de la entidad B. Y además, en el converter podemos declarar cómo queremos mostrar cada Item:

  1. public class MyIDConverter : IValueConverter
  2.     {
  3.         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  4.         {
  5.  
  6.             List<ComboBoxItem> list = new List<ComboBoxItem>();            
  7.             foreach (B b in B.All())
  8.             {
  9.                 ComboBoxItem comboItem = new ComboBoxItem();
  10.                 comboItem.Content = b.Name;
  11.                 comboItem.Tag = b;
  12.                 list.Add(comboItem);
  13.             }
  14.             return list;
  15.  
  16.         }
  17.  
  18.         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  19.         {
  20.             return null;
  21.         }
  22.     }

Ahora lo que falta es enlazar el ID de A para que el combobox tenga como seleccionado el item de B correspondiente. Dentro del primero enfoque, sería usar otro converter para ello, pero tenemos un problema: necesito obtener el combobox para indicarle SelectedItem. De acuerdo, podemos declarar un parámetro en el Converter y le pasaremos el propio combobox del siguiente modo:

  1. <ComboBox ItemsSource="{Binding Converter={StaticResource ResourceKey=Converter}, ConverterParameter={RelativeSource Mode=Self}}" />

Pero esto directamente no compila. ¿Por qué? Porque los parámetros que se pasan al Converter deben ser constantes y no objetos variables. Es decir, le puedo pasar binding de propiedades como objetos ya que las considera “constantes”, pero el propio objeto comboBox (que al bindearlo se convierte en un puntero this) va cambiando. Una solución a esto es añadir manualmente dicho combobox como un DependencyProperty propio de la clase. Y otra solución es escribir el código necesario para que esto funcione en el codebehind. Pero recuerdo que el objetivo de esto es usar únicamente el XAML

¿Solución? El segundo enfoque. Simplemente consiste en añadir, además del primer Converter para rellenar el comboBox, otro que se dedique a bindear el contenido del combobox con el identificador de la entidad A. Se implementa la interfaz IMultiValueConverter que permite bindear a la vez varios elementos sobre una misma propiedad. De este modo, bindearemos la propiedad identificador de B que aparece en A como relación junto con el propio ComboBox. Y lo bindearemos al SelectedIndex de ComboBox, para que cargue el objeto B apropiado y además, que al seleccionar otro, modifique el identificador que corresponda.

Veamos el código:

  1. <DataTemplate>
  2.                             <ComboBox>
  3.                                 <ComboBox.ItemsSource>
  4.                                     <Binding Converter="{StaticResource ResourceKey=MyIDConverter}">
  5.                                     </Binding>
  6.                                 </ComboBox.ItemsSource>
  7.                                 <ComboBox.SelectedIndex>
  8.                                     <MultiBinding Converter="{StaticResource ResourceKey=MyIDtoIDConverter}" UpdateSourceTrigger="PropertyChanged">
  9.                                         <MultiBinding.Bindings>
  10.                                             <Binding Path="BID" Mode="TwoWay"></Binding>
  11.                                             <Binding RelativeSource="{RelativeSource Mode=Self}" Path="."></Binding>
  12.                                         </MultiBinding.Bindings>
  13.                                     </MultiBinding>
  14.                                 </ComboBox.SelectedIndex>
  15.                             </ComboBox>
  16.                         </DataTemplate>

Y ahora la implementación del IMultiValueConverter

  1. public class MyIDtoIDConverter : IMultiValueConverter
  2. {
  3.     private ComboBox combo;
  4.     public MyIDtoIDConverter()
  5.     {
  6.         combo = null;
  7.     }
  8.  
  9.     public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  10.     {
  11.         this.combo = values[1] as ComboBox;
  12.         ComboBoxItem comboItem = null;
  13.         int index = 0;
  14.         foreach (ComboBoxItem item in combo.Items)
  15.         {
  16.             OperationType operationType = item.Tag as OperationType;
  17.             if (operationType.Id == values[0] as int?)
  18.             {
  19.                 comboItem = item;
  20.                 combo.SelectedIndex = index;
  21.             }
  22.             index++;
  23.         }
  24.  
  25.         return combo.SelectedIndex;
  26.     }
  27.  
  28.     public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
  29.     {
  30.         ComboBoxItem comboItem = this.combo.Items[(int)value] as ComboBoxItem;
  31.         B b = comboItem.Tag as B;
  32.         return new object[] { b.ID };
  33.     }
  34. }

 

El Converter almacena el comboBox para que después al hacer el ConvertBack, tenga la referencia del estado de los items. De este modo, tenemos las entidades bindeadas con las propiedades de los elementos de WPF y además manteniendo sus relaciones.

domingo, 13 de marzo de 2011

[WP7] An update to Visual Studio is required to open Silverlight for Windows Phone

 

Este error me ha surgido al abrir mis antiguos proyectos para Windows Phone 7, incluso después de instalar todas las últimas actualizaciones de las Windows Phone Tools y reiniciar contínuamente. ¿La solución? Pues la encontré en el foro de desarrolladores.

Lo único que hay que hacer es volver a enlazar la dll de Design.Platform de nuevo a Visual Studio, para ello:

  1. Abrir la consola de comandos en modo administrador
  2. Ir hasta c:\ProgramFiles\Microsoft SDKs\Windows\v7.0A\bin\NETFX4.0. Si estás con Windows de 64 bits, entonces tienes que ir hasta ProgramFiles(x86).
  3. Ejecutar el siguiente comando: - gacutil /i "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.Windows.Design.Platform.dll" /f. Del mismo modo que antes, si estamos con 64 bits debemos especificar la carpeta ProgramFiles(x86)
  4. Verificar que al ejecutar el comando, pone: "Assembly successfully added to the cache”.

Aquí dejo una captura de pantalla:

image

miércoles, 5 de enero de 2011

[WP7] My Borrowed List

¿Has prestado tus apuntes a alguien y no recuerdas a quién? ¿Ni cuando? ¿Quién tiene tu libro favorito?¿Cuándo le dejaste a tu primo tu último videojuego? Pues para gestionar esto tengo el placer de presentar otra aplicación mía que recientemente ha aparecido en el Windows Phone Marketplace: My Borrowed List.

 

List

A través de esta sencilla aplicación puedes gestionar qué has prestado, a quién se lo has prestado, cuándo se lo has prestado y el valor del objeto que prestas.

Como siempre, estaré encantado de recibir feedback de la aplicación. Si la quieres probar, puedes descargarla a través de aquí. Por cierto, la aplicación es gratis!

viernes, 31 de diciembre de 2010

[WP7] My Calculator

Recientemente he subido una nueva aplicación en el Marketplace de WP7. Es una calculadora científica, pensada para ingenieros informáticos. Como tal siempre he deseado tener en el móvil una calculadora en la que pudiera convertir (entre otras cosas) números de diferente base sin necesidad de usar la calculadora de Windows o el papel en su defecto. Con My Calculator puedes trabajar con el sistema decimal, radián, binario, hexadecimal y octal. Seguiré trabajando para añadirle features en el futuro. Como siempre, cualquier sugerencia será bien recibida, de igual manera si se encuentra algún bug intentaré ser lo más rápido posible en solventarlo.

calculator

La aplicación la puedes descargar aquí, por 0,99€

lunes, 27 de diciembre de 2010

[Silverlight] Convirtiendo tipos para DataTemplates

Una de las armas más poderosas de WPF y Silverlight son los DataTemplates, que te permiten especificar un template y después mediante el DataBinding puedes desplegar todos los datos de forma automática sin necesidad de rellenarlos a “pelo” insertando fila por fila.

Pero en algunas ocasiones se necesitará contemplar elementos que deben aparecer o no en función de algunos valores de lógica. Por ejemplo, en mi lógica dispongo de una clase Item que tiene una propiedad booleana llamada Borrowed. Cuando esta propiedad está a true, debe aparecer un botón, y cuando no lo está, no debe aparecer nada. En Silverlight la propiedad Visibility permite especificar la visibilidad de un elemento cualquiera, pero esta propiedad se ajusta a través de un enumerado que indica si es visible ( Visible ) o no (Collapsed). Como medida sucia podría incluir dentro de mi lógica una propiedad que tuviera ese valor, pero entonces estaría causando una dependencia entre la lógica y la interfaz de la aplicación y como todos sabemos, eso nunca es bueno. Para solventar este tipo de cosas usaremos un Converter junto con el Binding. Vayamos por partes.

Usaremos el converter para transformar el valor de una propiedad en otro. En nuestro caso, transformaremos el valor booleano de Borrowed por el valor Visible de la interfaz. Para ello crearemos una clase que implemente la interfaz IValueConverter. Esta interfaz consta de dos métodos, Convert y ConvertBack. En nuestro caso vamos a implementar únicamente el Convert, ya que el ConverBack se usará en sentido inverso (de interfaz a lógica).

   1: public class BorrowedToVisibilityConverter : System.Windows.Data.IValueConverter
   2:     {
   3:         #region IValueConverter Members
   4:  
   5:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   6:         {
   7:             Visibility isVisible = Visibility.Collapsed;
   8:             if ((value == null))
   9:                 return isVisible;
  10:             if (((bool)value))
  11:             {
  12:                 isVisible = Visibility.Visible;
  13:             }
  14:             return isVisible;
  15:         }
  16:  
  17:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  18:         {
  19:             throw new NotImplementedException();
  20:         }
  21:  
  22:         #endregion


Dentro de Convert interesa el parámetro value (lo que vamos a usar para convertir). El resto de parámetros no interesa en este caso, pero no es necesario explicarlos. Como se puede apreciar el código es sencillo, si el valor es positivo se devuelve Visibility.Visible y en cualquier otro caso se devuelve Visibiliy.Collapsed. Ahora teniendo esto ya implementado, tenemos que asociarlo a nuestro binding y por ello, a partir de este momento nos dedicamos ya con el fichero XAML correspondiente.

Ante de asociarlo, tenemos que mapear este conversor como un recurso. Para ello simplemente le asignamos una clave de referencia y especificamos el nombre del conversor:

   1: <UserControl.Resources>
   2:          <converter:BorrowedToVisibilityConverter x:Key="BorrowedToVisibility" />
   3: </UserControl.Resources>

Y por último lo asociamos a nuestro binding. En este caso irá en un botón en el su propiedad Visibility dependerá del valor de la propiedad Borrowed del binding asociado al template. Es aquí cuando ya debemos especificar el conversor de forma explícita:

   1: <Button Content="Ok!" Height="72" Width="160"  Visibility="{Binding Borrowed, Converter={StaticResource BorrowedToVisibility} }" HorizontalAlignment="Right" Click="Button_Click" Tag="{Binding Id}" Background="White" BorderBrush="Black" Foreground="Black" />

Y más en detalle:

   1: Visibility="{Binding Borrowed, Converter={StaticResource BorrowedToVisibility} }" 

Asociamos la propiedad del objeto y le indicamos que tenemos un conversor, incluido como recurso estático y su clave de referencia.

martes, 2 de noviembre de 2010

[.NET] Isolated Storage

Tanto en aplicaciones SilverLight como en XNA 4.0 para Windows Phone 7, no se puede usar el espacio de nombres System.File.IO. La pregunta es simple: ¿cómo podemos acceder entonces a un fichero a través de estos sistemas? Pues a través de Isolated Storage.

El motivo es bien sencillo. Una aplicación SilverLight se ejecuta a través del cliente, por lo que por motivos de seguridad sería mejor impedirle cualquier tipo de acceso al sistema externo. En el caso de WP7 es bien distinto, ya que por la política de Microsoft se impide el acceso a la jerarquía de archivos del sistema. Las ventajas de usar un sistema como este son obvias por los siguientes aspectos:

  • Control del código descargado como complementos y la seguridad de que no escribirá en nuestro disco. Sólo se escribirá en el ámbito de la aplicación correspondiente.
  • Almacenamiento de componentes compartidos: Aquellos componentes que se comparten entre aplicaciones se pueden usar en Isolated Storage para garantizar el control de acceso a los datos.
  • Servidor de almacenamiento: Como el acceso a los recursos de Isolated se distribuye en función de los usuarios (más adelante lo explico) el servidor puede distinguir fácilmente entre los datos de los diferentes usuarios.
  • Almacenamiento móvil: Podemos almacenar información móvil del usuario fácilmente.

Evidentemente hay algunos casos donde NO se recomienda usar Isolated Storage:

  • No se debe usar para almacenar claves no encriptadas, valores secretos o contraseñas, ya que el sistema no está protegido contra código mal intencionado u otros usuarios del sistema.
  • Evidentemente,no lo uses para almacenar código!
  • Y tampoco lo uses para almacenar la configuración de la aplicación! Para ello ya dispones de los ConfigurationSettings.

En resumen, ¿qué es Isolated Storage? Pues es un sistema permite trabajar con un sistema virtual de ficheros. Sobre esta encapsulación, ya podemos trabajar con un flujo de datos como si estuviéramos en frente a un sistema real de ficheros. Veamos cómo se puede usar:

En primer lugar debemos incluir el espacio de nombres adecuado para emplear Isolated Storage:

   1: using System.IO;
   2: using System.IO.IsolatedStorage;

Luego vamos a instanciar una referencia al directorio de la aplicación que estamos usando:

   1: IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

Podemos establecer referencias en función no sólo del ámbito de la aplicación, sino también a partir del usuario, del ensamblado. En este caso deseamos obtenerlo del ámbito de la aplicación, puesto que todo lo que hagamos irá en referencia exclusiva a ella.

Con la referencia podemos manejar Isolated Storage como un sistema de ficheros al uso. Supongamos que queremos comprobar si existe un directorio llamado “SavedGames” y si no existe, lo creamos:

   1: if ( !isf.DirectoryExists("SavedGames") )
   2: {
   3:     isf.CreateDirectory("SavedGames");
   4: }

Con esto se puede crear todo el árbol de directorios. El puntero a la referencia del sistema de ficheros incluye métodos para comprobar ficheros, directorios (como el ejemplo anterior), crear ficheros, crear directorios, borrar y abrir ficheros. Ahora, una vez que seguro tenemos nuestro directorio creado, vamos a almacenar la partida. Para ello primero creamos el fichero para almacenarlo en Isolated Storage y creamos un flujo de datos, como si de un “fichero físico” se tratase:

   1: IsolatedStorageFileStream isfs = new IsolatedStorageFileStream("game.txt",FileMode.Create, isf);
   2: using (StreamWriter writer = new StreamWriter(isfs))
   3: {
   4: ...
   5: }
   6: isfs.Close();

Esto se puede extrapolar a cualquier tipo de fichero que queramos. Además, las operaciones de cargar datos o tratamiento de ficheros son exactamente iguales que en el caso normal.

martes, 21 de septiembre de 2010

[SilverLight] Integrando UserControls en Layouts (II), pero automático.

Volviendo a la integración, la opción del artículo anterior tiene un problema  de escalabilidad. Si tuviésemos 500 pantallas, ¿implica que deberíamos tener un manejador y una clase principal con 500 atributos privados que iremos instanciando? Entonces, si aplicamos la idea anterior y la resolvemos de un modo un poco más elegante la solución será que cada botón u acción de cambio de estado sea el responsable de internamente notificar a la aplicación cuál es ese estado para que instancie correctamente el objeto adecuado. Para ello, vamos a usar la propiedad DataContext que tiene la clase Button como sigue:

   1: <Button x:Name="buttonArticle" Content="Articles" Click="buttonArticle_Click" BorderBrush="#FF7100FF" Cursor="Hand" ClickMode="Press">
   2:                 <Button.DataContext>
   3:                     <local:Article/>
   4:                 </Button.DataContext>
   5:             </Button>
   6:             <Button x:Name="buttonAbout" Content="About" Click="buttonAbout_Click" BorderBrush="#FF7100FF" Cursor="Hand" ClickMode="Press">
   7:                 <Button.DataContext>
   8:                     <local:About/>
   9:                 </Button.DataContext>
  10:             </Button>

Estamos rellenando la propiedad DataContext con el nombre del UserControl que vamos a instanciar. Cuando hagamos click en cada botón, usaremos el Assembly para instanciarlo. Con esto nos ahorramos tener que declarar y gestionar los atributos o variables necesarias para instanciar manualmente cada una de las clases. Por ejemplo, cuando haga click en el botón de About ocurriá lo siguiente:

 

   1: private void buttonAbout_Click(object sender, System.Windows.RoutedEventArgs e)
   2: {
   3:     this.renderOnCanvas((Button)sender).DataContext);
   4: }

Y el método renderOnCanvas será el encargado de obtener la clase adecuada cuyo nombre hemos asignado dentro del DataContext e instanciarla:

   1: private void renderOnCanvas(string controlName)
   2: {
   3:     Type type = this.GetType();
   4:     Assembly assembly = type.Assembly;
   5:     UserControl newPage = (UserControl)assembly.CreateInstance(type.Namespace + "." + controlName);
   6:     canvas.Children.Clear();
   7:     canvas.Children.Add(newPage);
   8: }

De este modo evitamos el añadir manualmente los atributos o variables de cada instancia y podemos automatizarlo, además de traspasar la responsabilidad de cada sección al botón correspondiente.

[SilverLight] Integrando UserControls en Layouts (I)

En el momento de hacer una aplicación se pueden tomar varias decisiones para organizar las diferentes pantallas y opciones que tengan. Una de ellas, la más básica es tener una serie de N canvas en los cuales al pulsar en la opción correspondiente, nosotros haremos que se muestre el adecuado. Sin embargo para mí esto presenta dos grandes inconvenientes:

  1. El diseño de la interfaz se va haciendo más complejo a medida que incrementamos la cantidad de canvas a gestionar.
  2. Al instanciar la pantalla principal, estamos instanciando todas las opciones/pantallas disponibles (independientemente de que sean usadas o no).

Por lo tanto, me surge la duda de cómo se podría evitar esto y me encontrado dos opciones. La primera de ellas consiste en una extensión algo más limpia del método básico, con la diferencia de que cada una de las pantallas tendrá su propia clase y lo único que tendremos que hacer será instanciarlas cuando sean requeridas e integrarlas en nuestro canvas principal. Como por ejemplo, lo siguiente:

Disponemos de varios UserControls, entre los cuales destacan estos ( sírvase únicamente como ejemplo ):

   1: public partial class About : UserControl
   2: {}
   1: public partial class Articles : UserControl
   2: {}

En primer lugar declaro cada uno de los userControls dentro de nuestra MainPage (o donde corresponda):

   1: private About aboutUX;
   2: private Articles articlesUX;

Supongamos que disponemos de un layout de tipo Grid donde tenemos dispuesto un layout Canvas que en un alarde de originalidad lo he denominado canvas; sobre él se irán cargando los respectivos UserControls:

   1: <Canvas x:Name="canvas" Margin="1,1,0,0" Grid.Row="4"/>

La idea es sencilla. Cuando se quiera cambiar a cada pantalla, podemos gestionarlo de modo que sólo instancie el user control si no está creado aún:

   1: private void changeToAbout()
   2: {
   3:     if ( this.aboutUX == null )
   4:     {
   5:         this.aboutUX = new About();
   6:     }
   7:     //borramos el contenido del canvas
   8:     this.canvas.Childrens.Clear();
   9:     //añadimos el nuevo user control
  10:     this.canvas.Childrens.Add(this.aboutUX);
  11: }

Así podemos controlar levemente la gestión de UserControls.