DNM+ Online
dotnetmania 2.0
Cómo definir nuestros propios paneles personalizados en WPF
A través de dotNetManía, los autores se proponen desvelarnos algunos secretos de WPF. Con este artículo nos ilustran cómo se puede ir más allá y personalizar recursos visuales para conseguir efectos no incluidos de base en WPF. En este caso nos desarrollan un novedoso panel que distribuye sus elementos de manera circular.

Lo primero que se aprecia en la interacción con la interfaz de una aplicación es la apariencia de la misma. Un aspecto relevante de la apariencia es cómo se agrupan y distribuyen visualmente los elementos que participan en la interfaz de usuario. Esta distribución es lo que se conoce como layout. Windows Presentation Foundation (WPF) brinda un conjunto flexible de tipos de objetos denominados paneles, mediante los que ofrece distintas formas de layout. Los distintos tipos de paneles, cada uno con una forma diferente para hacer el layout, pueden ser combinados en busca de formas de visualización más complejas y atractivas, acordes a las necesidades de la interfaz visual de una aplicación. Aunque con ello pueden cubrirse la mayor parte de los requerimientos, puede que no resulte suficiente si usted quiere aportar a su interfaz sus propios efectos.
En este artículo se describe cómo crear tipos de paneles personalizados utilizando las capacidades de WPF.

Los paneles
La función de los paneles es distribuir y mostrar visualmente los elementos que se ubiquen dentro del mismo. Un panel es un objeto de un tipo derivado de la clase Panel, cuya jerarquía se muestra en la figura 1. Un panel es a su vez un objeto contenedor que tiene un criterio para distribuir visualmente, en el área que tiene disponible, los elementos de interfaz (controles, figuras, imágenes, etc.) contenidos dentro del panel. La forma de poner los elementos en un panel es en general simple. Cuando un programa .NET usa un panel, usted sólo debe garantizar que al objeto panel se le añadan elementos mediante el método AddChild. Cuando describa el panel declarativamente desde el lenguaje de marcas XAML, usted sólo tendrá que escribir el XAML de cada elemento dentro de las etiquetas del panel. El resto será sintonizar la funcionalidad del panel asignándole valores a sus propiedades. En este artículo suponemos que usted tiene un conocimiento básico de los paneles de WPF1. El código XAML del fuente 1 nos muestra un panel DockPanel. Este panel contiene a su vez un panel Grid que muestra los números de dotNetManía disponibles y que ha sido ubicado en la parte inferior del DockPanel, gracias a que se ha utilizado la propiedad adjunta DockPanel.Dock="Bottom", que indica en qué zona de panel contenedor ubicarlo. El otro elemento que se ha ubicado en este DockPanel es la imagen <Image Source="DotNetMania/Titulo.gif" Height="100"/>, que de este modo ocupa el espacio que queda disponible después de desplegar el Grid. El resultado de este panel se muestra en la figura 2. Implementando un panel personalizado La apariencia de la interfaz que se muestra en la figura 2, aunque un poco más atractiva que la que actualmente tiene la revista en su sitio Web, es bastante parecida a las que usted puede encontrar por ahí. Para ser sinceros, debiéramos decir que está un poco "pasmada". Lo que se visualiza está estático y limitado al espacio disponible.
Si quisiéramos darle a esta interfaz un sello más personal y atractivo, tendríamos que hacer un trabajo más elaborado. A continuación vamos a ver los recursos que nos brinda WPF para desarrollar un panel más personalizado, como el que se muestra en la figura 3. La gracia consiste no solo en lograr desplegar la interfaz de esa manera, sino en que el trabajo desarrollado pueda ser "atrapado" en la definición de un nuevo tipo de panel que podamos reutilizar en otras aplicaciones. El sistema de layout en WPF no está limitado a los paneles predefinidos y sus combinaciones. Afortunadamente, el sistema de layout en WPF es extensible. Un panel personalizado (custom panel) no es más que una clase que debe heredar de Panel y redefinir los métodos MeasureOverride y ArrangeOverride. Estos dos métodos son los protagonistas de las dos fases por las que transita un panel en el proceso de aplicar la distribución espacial de su contenido. Medir (Measure) es el proceso en el que otro panel, o la ventana o página principal, le pregunta al panel cuánto espacio necesita o desea tener; el panel entonces a su vez propaga esta misma consulta a cada uno de sus hijos (elementos contenidos dentro del panel, que como ya se ha dicho pueden ser a su vez paneles) tratando de conocer cuánto espacio necesita cada uno y así calcular el espacio total que necesita como contenedor.
En la segunda fase (Arrange) es en la que, conociendo realmente el espacio disponible y el espacio demandado, el panel distribuye el contenido, tratando de aprovechar el espacio de la mejor forma posible.
Todo tipo que hereda de Panel hereda la propiedad Children para almacenar su contenido. Esta propiedad es de tipo UIElementCollection, y esta colección en sí misma maneja las llamadas a AddVisualChild, AddLogicalChild, RemoveVisualChild y RemoveLogicalChild para añadir o eliminar hijos a la colección. UIELementCollection puede llevar a cabo estas acciones, pues conoce del elemento padre (que tiene que ser pasado como parámetro en el constructor del panel). Realmente se pasan  un VisualParent y un LogicalParent, que usualmente son el mismo. Cuando usted define en XAML un panel dentro de otro, el panel contenedor es el que se denomina padre. En este caso, es WPF quien, cuando va a ejecutar este código XAML, pasa el elemento padre como parámetro al constructor del panel.

Un panel circular Una de las limitaciones del panel Grid de la figura 2 es que si la cantidad de elementos que queremos distribuir es grande, entonces los elementos se tendrían que ver muy pequeños para que cupieran todos. Como ejemplo de panel personalizado, vamos a definir un panel que distribuirá los elementos circularmente, como se muestra en la figura 3. El fuente 2 muestra parte del código de la implementación del panel CircularPanel.
La propiedad Front nos indica el índice del elemento (según el orden que tiene como hijo del panel) que se muestra al frente. Si el valor no está en el rango 0 a Children.Count - 1, entonces el panel lo convierte en un índice de elemento de manera circular. Es decir, que para el panel un valor de Front igual a n * Children.Count + k es lo mismo que k. Tome nota de que el valor es double. Si Front tiene un valor entero, entonces el elemento correspondiente se mostrará centrado al frente, como se muestra en la figura 3 (donde Front tiene valor 0).  Pero si damos a Front un valor fraccionario, por ejemplo 3.5, entonces el panel ubicaría al frente a los elementos 3 y 4. La propiedad AltitudeAngle, que toma un valor entre -90 y 90, nos indica el ángulo con el que se muestran los elementos que quedarían en la semicircunferencia posterior con relación a los elementos que quedan en la semicircunferencia al frente. Para el resultado que se muestra en la figura 3 el valor es 15. Un valor -15 produciría la imagen de la figura 4a, un valor 90 nos mostraría una circunferencia completa (figura 4c) y un valor 0 nos mostraría los elementos al fondo al mismo nivel que los que están al frente, y por tanto solo se podrían ver a través de  los espacios entre ellos (figura 4b). La propiedad Perspective nos indica la perspectiva de tamaño con que se muestran los elementos de la parte posterior con relación a los elementos al frente. Esta propiedad toma valores entre 0 y 1. Un valor 0 quiere decir que no hay perspectiva (y por tanto las imágenes al fondo se verán del mismo tamaño que las del frente), mientras que 1 quiere decir "máxima perspectiva". En la figura 4c se ha utilizado una perspectiva 0; por eso las imágenes se ven del mismo tamaño. Para el resto de las figuras se ha utilizado perspectiva 1. La propiedad Gap indica la separación entre elementos, relativa al ancho de los mismos (en todas las figuras se usado un valor de 0.5). Las propiedades FrontOpacity y BackOpacity nos indican la opacidad con que se mostrarán los elementos según su posición el círculo. El panel les da un gradiente que va desde FrontOpacity (para los que están más al frente) a BackOpacity (para los que están mas al fondo); en todas las imágenes de ejemplo, a estas propiedades se les ha asignado 1 y 0, respectivamente. El panel circular redefine los métodos MeasureOverride y ArrangeOverride. El método MeasureOverride corresponde a la fase de medir  cuánto espacio el panel necesita, y para ello realiza la misma consulta a cada uno de sus hijos. Cuando se le pide a un elemento que se mida, el resultado de la medición se guarda en la propiedad DesiredSize. En este caso, lo que hemos hecho en la implementación de MeasureOverride en el fuente 2 es obtener la menor área en la que podría caber cualquiera de los elementos, ya que este panel circular debe garantizar que al menos el elemento Front se pueda visualizar.
El método ArrangeOverride es el que implementa cómo distribuir los elementos en forma circular, según lo indicado por las propiedades explicadas anteriormente. No disponemos de espacio para explicar esta implementación aquí, pero el lector se puede descargar el código fuente de la misma. Solo queremos destacar el concepto de ZIndex ("Z-orden"), que es una propiedad adjunta que le pone un panel a todos sus elementos contenidos. El valor de esta propiedad determina cuál es el orden en que WPF dibujará los elementos contenidos en el panel, siendo el de valor 0 el que dibujará primero. A igual valor de ZIndex, WPF los dibujará según el orden en que estén definidos como hijos del panel. Lo que hace el método GetPositions que hemos implementado es determinar el orden en que se deberán dibujar los elementos del panel según la configuración de éste. Note que después hacemos Panel.SetZIndex(Children[i], infos[i].ZIndex); y con ello le estamos indicando a cada elemento del panel cuál es su ZIndex. Las siguientes instrucciones aplican a cada elemento las transformaciones deseadas, para que finalmente el método Arrange lo ubique adecuadamente. El fuente 3 nos muestra cómo usar este tipo de panel para visualizar los números de dotNetManía disponibles de la manera que se presenta en la figura 3. Note que cada elemento del panel es a su vez un StackPanel constituido por una imagen y una casilla de verificación. Animando el panel circular Bueno, pero un panel en el que no se puedan ver por igual todos los elementos puede que no tenga mucha gracia, pensará usted. Animando la propiedad Front, podemos hacer rotar en el panel los elementos, para que sea posible verlos todos y seleccionar los que se desee. El fuente 4 nos muestra dos animaciones a asociar al panel: una para mostrar los elementos en el panel circulando hacia la derecha y otra para mostrarlos circulando hacia la izquierda. Note cómo la animación está asociada a la propiedad

Storyboard.TargetProperty= "(p:CircularPanel.Front)"

La animación va cambiando el valor de Front, en un caso incrementándolo (By="1") y en el otro caso decrementándolo (By="-1"), lo que provoca que el panel vaya trayendo al frente al elemento correspondiente. Como el valor va variando de uno en uno, esto produce el efecto de que todos los elementos circulan delante de nosotros. Experimente con el código que acompaña a este artículo para que pueda disfrutarlo.
Conclusiones Este ejemplo ha mostrado cómo se puede definir un nuevo tipo de panel personalizado para dotarlo de efectos que no están presentes en los paneles predeterminados que ofrece WPF.
Con esta nueva forma de distribuir los elementos usando el CircularPanel, podemos dotar a nuestra aplicación de un sello personal en su apariencia. Este panel además nos permite lograr un mejor aprovechamiento del espacio disponible en la distribución de los elementos, manteniendo al mismo tiempo la sensación de poder verlos todos. Algo que con la forma tradicional de un Grid solo podría lograrse reduciendo el tamaño de los elementos o dotando al panel de una barra de desplazamiento.
Por razones de espacio, no todos los detalles de la implementación de este panel se han podido explicar aquí. Invitamos al lector a descargar el código fuente completo del sitio Web de la revista, para que pueda experimentar e interactuar con él.
En cualquier caso, esto no termina aquí: en una próxima entrega veremos cómo definir un panel similar, pero dotado de efectos 3D, para que en lugar de que los elementos circulen frente a nosotros, podamos "meternos dentro" y tener la sensación de que miramos a nuestro alrededor.

blog comments powered by Disqus
autores
referencias