Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Net
(Avanzado)
WPF
MARÍA TERESA CRESPI NOIR
PROGRAMACIÓN EN .NET
(AVANZADO) - MANUAL
WPF
2
Programación en .Net (Avanzado) - Manual WPF
Crespi Noir, María Teresa
1a Edición
ISBN Nº 978-987-25214-2-4
Multigraphic
Avenida Belgrano 520 – Capital Federal
10 de agosto de 2010
ISBN 978-987-25214-2-4
3
Windows
Presentation
Foundation
(WPF)
4
NOTA: El material incluido en el presente puede contener datos y ejemplos extraídos de sitios
web públicos sin restricción de acceso. Los mismos son utilizados solamente a fines didácticos
y pueden encontrarse en el final de la publicación agrupados bajo el título Links Relacionados.
5
MÓDULO 1 : INTRODUCCIÓN A WPF ..................................................................................... 12
1- Introducción........................................................................................................................... 13
Definición ................................................................................................................................ 13
Programar con WPF ............................................................................................................... 13
Código de lenguaje marcado y código subyacente ................................................................ 13
XAML ...................................................................................................................................... 13
Código Subyacente ................................................................................................................. 14
Aplicaciones WPF ................................................................................................................... 15
Seguridad ................................................................................................................................ 15
6
2- Controles de Diseño ............................................................................................................. 33
Sistema de Diseño de Interfase de Usuario ........................................................................... 33
Border ..................................................................................................................................... 34
Canvas .................................................................................................................................... 34
DockPanel ............................................................................................................................... 35
Grid.......................................................................................................................................... 36
StackPanel .............................................................................................................................. 37
GroupBox ................................................................................................................................ 37
Expander ................................................................................................................................. 38
InkCanvas ............................................................................................................................... 38
Menu ....................................................................................................................................... 39
Frame ...................................................................................................................................... 39
WrapPanel .............................................................................................................................. 40
1- Enlazar Datos......................................................................................................................... 59
Enlace a Datos ........................................................................................................................ 59
Clase Binding ......................................................................................................................... 59
Modos de Enlazar – Propiedad Mode..................................................................................... 59
Momento del Enlace ............................................................................................................... 60
Propiedad Asociadas al Enlace de Datos ............................................................................... 61
Enlace entre Controles ........................................................................................................... 62
XmlDataProvider ..................................................................................................................... 63
ObjectDataProvider ................................................................................................................. 64
7
MethodParameters.................................................................................................................. 65
DataTemplate.......................................................................................................................... 67
DataContext ............................................................................................................................ 68
Controles ListView - Gridview ................................................................................................. 69
1- Documentos........................................................................................................................... 94
Definición ................................................................................................................................ 94
Tipos de documentos .............................................................................................................. 94
4- Multimedia............................................................................................................................ 127
Media API .............................................................................................................................. 127
Modos Media Playback ......................................................................................................... 127
SoundPlayer .......................................................................................................................... 128
10
Audiencia
Este curso está orientado a aquellos profesionales o estudiantes que quieran adquirir los
conocimientos y habilidades necesarias para desarrollarse como proveedor de software
independiente. Así mismo desarrolladores .NET (C#) que quieran incursionar en las nuevas
tecnologías de .NET.
Pre-requisitos
Duración
El curso tiene una duración de 28 horas reloj con una carga horaria no superior a 12 horas
semanales.
Al Finalizar el curso
11
Módulo 1
Introducción a WPF
12
1- Introducción
Definición
Windows Presentation Foundation (WPF) es un sistema de presentación para crear
aplicaciones cliente de Windows que proporcionen una experiencia impactante para el usuario
desde el punto de vista visual. Con WPF, puede crear una amplia gama de aplicaciones
independientes y hospedadas en explorador.
El núcleo de WPF es un motor de representación independiente de la resolución y basado en
vectores construido para aprovechar al máximo el hardware de gráficos moderno. WPF amplía
el núcleo con un completo conjunto de características de programación de aplicaciones, entre
las que se incluyen Lenguaje de marcado de aplicaciones extensible (XAML), controles, enlace
de datos, diseño, gráficos 2D y 3D, animación, estilos, plantillas, documentos, multimedia, texto
y tipografía. WPF se incluye en Microsoft .NET Framework, lo que permite crear aplicaciones
que incorporen otros elementos de la biblioteca de clases de .NET Framework.
XAML
XAML es un lenguaje de marcado basado en XML que se utiliza para implementar la apariencia
de una aplicación mediante declaración. Se suele utilizar para crear ventanas, cuadros de
13
diálogo, páginas y controles de usuario, así como para rellenarlos con controles, formas y
gráficos.
Ejemplo:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="Ventana con un Botón"
Width="250" Height="100">
<Button Name="button">Presionar</Button>
</Window>
El código XAML define una ventana y un botón mediante los elementos Window y Button,
respectivamente. Cada elemento se configura con atributos o propiedades, como el atributo
Title del elemento Window, para especificar el texto de la barra de título de la ventana. En
tiempo de ejecución, WPF convierte los elementos y los atributos definidos en el marcado en
instancias de clases de WPF.
Puesto que XAML se basa en XML, la interfaz de usuario que se crea con este lenguaje se
ensambla en una jerarquía de elementos anidados que se denomina árbol de elementos. El
árbol de elementos proporciona una manera lógica e intuitiva de crear y administrar las
interfases de usuario.
Código Subyacente
El comportamiento principal de una aplicación es implementar la funcionalidad que responde a
las interacciones con el usuario, lo que incluye controlar los eventos (por ejemplo, hacer clic en
un menú, una barra de herramientas o un botón) y llamar, en respuesta, a la lógica de negocios
y de acceso a los datos. En WPF, este comportamiento se suele implementar en código
asociado al marcado. Este tipo de código se denomina subyacente.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="Ventana con un Botón"
Width="250" Height="100">
Código subyacente:
using System.Windows;
namespace EjemploWFP
{
public partial class miEjemplo : Window
{
public void button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Probando Windows Presentation Foundation!");
}
}
}
14
Aplicaciones WPF
Microsoft .NET Framework, System.Windows, así como el código de lenguaje marcado y
subyacente, constituyen la base de la experiencia de programación de aplicaciones en WPF.
Además, WPF cuenta con características completas para crear experiencias de usuario con
contenido enriquecido. Para empaquetar este contenido y distribuirlo a los usuarios en forma
de aplicaciones, WPF proporciona tipos y servicios denominados colectivamente el modelo de
aplicación. El modelo de aplicación admite la programación de aplicaciones independientes y
hospedadas en explorador.
Aplicaciones independientes: Para las aplicaciones independientes, puede utilizar la
clase Window para crear las ventanas y cuadros de diálogo a los que se tiene acceso
desde las barras de menús y las barras de herramientas.
Aplicaciones hospedadas en explorador: También denominadas Aplicaciones del
explorador XAML (XBAPs – XAML Browsers Applications, generan archivos con
extensión xbap), puede crear páginas (Page) y funciones de página (PageFunction<T>)
entre las que se puede navegar mediante hipervínculos (clases Hyperlink). Estas
aplicaciones pueden hospedarse en Internet Explorer a partir de la versión 6.
Seguridad
Dado que las XBAPs se hospedan en un explorador, la seguridad es importante. En particular,
las XBAPs utilizan un recinto de seguridad de confianza parcial para aplicar restricciones
menores o iguales a las que se imponen a las aplicaciones basadas en HTML. Aún así, la
mayoría de las características de WPF se pueden ejecutar con seguridad desde las XBAPs.
2- Conceptos Básicos
Controles
En WPF, un control es un término general que se aplica a una categoría de clases de WPF
hospedadas en una ventana o una página, tienen una interfaz de usuario (UI) e implementa un
comportamiento determinado.
Los controles casi siempre detectan las acciones del usuario y responden a ellas. El sistema de
entrada de WPF utiliza eventos directos y enrutados para admitir entradas de texto, la
administración del enfoque y la posición del mouse.
A continuación se muestra la lista de controles de WPF integrados.
Botones: Button y RepeatButton.
Cuadros de diálogo: OpenFileDialog, PrintDialog y SaveFileDialog.
Entradas manuscritas digitales: InkCanvas y InkPresenter.
Documentos: DocumentViewer, FlowDocumentPageViewer, FlowDocumentReader,
FlowDocumentScrollViewer y StickyNoteControl.
Entrada: TextBox, RichTextBox y PasswordBox.
Diseño: Border, BulletDecorator, Canvas, DockPanel, Expander, Grid, GridView,
GridSplitter, GroupBox, Panel, ResizeGrip, Separator, ScrollBar, ScrollViewer,
StackPanel, Thumb, Viewbox, VirtualizingStackPanel, Window y WrapPanel.
Multimedia: Image, MediaElement y SoundPlayerAction.
Menús: ContextMenu, Menu y ToolBar.
Navegación: Frame, Hyperlink, Page, NavigationWindow y TabControl.
Selección: CheckBox, ComboBox, ListBox, TreeView y RadioButton, Slider.
Información para el usuario: AccessText, Label, Popup, ProgressBar, StatusBar,
TextBlock y ToolTip.
Diseño
15
Al crear una interfaz de usuario, se organizan los controles según su ubicación y tamaño para
crear un diseño. Un requisito fundamental de cualquier diseño es adaptarse a los cambios de
tamaño de la ventana y de configuración de pantalla. En lugar de obligarle a escribir código que
adapte el diseño en estas circunstancias, WPF le proporciona un sistema de diseño extensible
de primera clase.
La base del sistema de diseño es la situación relativa, que aumenta la capacidad de adaptación
a los cambios en la configuración de las ventanas y de la pantalla. Además, el sistema de
diseño administra la negociación entre los controles para determinar el diseño. La negociación
es un proceso de dos pasos: en primer lugar, el control indica a su elemento primario qué
ubicación y tamaño necesita; en segundo lugar, el elemento primario indica al control de qué
espacio dispone.
El sistema de diseño se expone a los controles secundarios mediante las clases base de WPF.
Para los diseños comunes, como son las cuadrículas, el apilamiento y el acoplamiento, WPF
incluye varios controles de diseño:
Canvas: los controles secundarios proporcionan su propio diseño.
DockPanel: los controles secundarios se alinean con los bordes del panel.
Grid: los controles secundarios se sitúan por filas y columnas.
StackPanel: los controles secundarios se apilan vertical u horizontalmente.
VirtualizingStackPanel: los controles secundarios se organizan en una vista "virtual" de
una sola línea en sentido horizontal o vertical.
WrapPanel: los controles secundarios se sitúan por orden de izquierda a derecha y se
ajustan a la línea siguiente cuando hay más controles de los que caben en la línea
actual.
Enlace de Datos
La mayoría de las aplicaciones se crean para proporcionar recursos a los usuarios que les
permitan ver y editar los datos. Para aplicaciones WPF, el trabajo de almacenar los datos y
tener acceso a ellos se proporciona mediante tecnologías existentes, como Microsoft SQL
Server y ADO.NET. Para simplificar la programación de aplicaciones, WPF proporciona un
motor de enlace de datos que realiza estos pasos automáticamente. La unidad que constituye
el núcleo del motor de enlace de datos es la clase Binding, encargada de enlazar un control (el
destino del enlace) a un objeto de datos (el origen del enlace).
El motor de enlace de datos de WPF proporciona compatibilidad adicional que incluye
validación, ordenación, filtrado y agrupación. Además, el enlace de datos admite el uso de
plantillas de datos, a fin de crear una interfaz de usuario personalizada para los datos
enlazados cuando la interfaz de usuario mostrada por los controles estándar de WPF no es
adecuada.
Gráficos
WPF presenta un conjunto extenso, escalable y flexible de características de gráficos que
aportan las ventajas siguientes:
Gráficos independientes de la resolución e independientes del dispositivo. La unidad de
medida básica del sistema de gráficos de WPF es el píxel independiente del
dispositivo, que es 1/96 de pulgada, independientemente de la resolución de pantalla
real, y que proporciona la base para la representación independiente de la resolución y
del dispositivo. Cada píxel independiente del dispositivo se escala automáticamente
para coincidir con el valor de puntos por pulgada (ppp) del sistema en que se
representa.
Precisión mejorada. El sistema de coordenadas de WPF se mide con números de
punto flotante de precisión doble, en lugar de precisión simple. Las transformaciones y
los valores de opacidad también se expresan como doble precisión. WPF admite
16
también una amplia gama de colores (scRGB) y proporciona compatibilidad integrada
para administrar las entradas desde espacios de color diferentes.
Compatibilidad con gráficos avanzados y animación. WPF simplifica la programación
de gráficos administrando automáticamente las escenas de animación.
Aceleración de hardware. El sistema de gráficos de WPF saca partido del hardware de
gráficos para minimizar el uso de CPU.
Formas 2D
WPF proporciona una biblioteca de formas 2D comunes dibujadas mediante vectores. Una
función interesante de las formas es que no sirven únicamente para su presentación; las
formas implementan muchas de las características que cabe esperar de los controles, incluida
la entrada de datos desde el teclado y el Mouse.
Geometrías 2D
Las formas 2D proporcionadas por WPF abarcan el conjunto estándar de formas básicas. Sin
embargo, puede que sea preciso crear formas personalizadas para facilitar el diseño de una
interfaz de usuario personalizada. Para este fin, WPF proporciona las geometrías. Estas
permiten dibujar directamente, utilizar como un pincel o utilizar para recortar otras formas y
controles.
Los objetos Path se pueden utilizar para dibujar formas cerradas o abiertas, varias formas o
incluso formas curvas.
Los objetos Geometry se pueden utilizar para el recorte, la comprobación de visitas y la
representación de datos de gráficos 2D.
Efectos 2D
Un subconjunto de las funciones 2D de WPF son los efectos visuales, tales como degradados,
mapas de bits, dibujos, pintar con vídeos, rotación, ajuste de escala y sesgo. Todas ellas se
aplican mediante pinceles.
Representaciones 3D
WPF también incluye funciones de representación 3D que se integran con los gráficos 2D para
permitir la creación de interfases de usuarios más importantes.
Animaciones
La compatibilidad de WPF con la animación permite hacer que los controles crezcan, tiemblen,
giren o se desvanezcan, crear transiciones de página interesante, y mucho más. Puede animar
la mayoría de las clases de WPF, incluso las personalizadas.
Multimedia
Una manera de mostrar un contenido enriquecido es utilizar medios audiovisuales (multimedia).
WPF proporciona compatibilidad especial con imágenes, vídeo y audio.
Imágenes: Las imágenes están presentes en la mayoría de las aplicaciones y WPF
proporciona varias maneras de utilizarlas.
Video y Audio: El control MediaElement es capaz de reproducir vídeo y audio y
presenta la flexibilidad suficiente para constituir la base de un reproductor multimedia
personalizado. El marcado XAML siguiente implementa un reproductor multimedia.
17
Texto y Tipografía
Para facilitar una representación de texto de gran calidad, WPF ofrece las características
siguientes:
Compatibilidad con fuentes OpenType.
Mejoras de ClearType.
Alto rendimiento que saca partido de la aceleración de hardware.
Integración de texto con multimedia, gráficos y animación.
Compatibilidad con fuentes internacionales y mecanismos de reserva.
Documentos
WPF permite trabajar de forma nativa con tres tipos de documentos: documentos dinámicos,
documentos fijos y documentos de XML Paper Specification (XPS). WPF proporciona también
servicios para crear, ver, administrar, anotar, empaquetar e imprimir documentos.
Documentos dinámicos: Los documentos dinámicos se han diseñado para optimizar su
presentación y legibilidad ajustando dinámicamente su contenido y modificando su flujo
cuando se producen cambios en el tamaño de la ventana y la configuración de pantalla.
Documentos Fijos: Los documentos fijos están destinados a aplicaciones que requieren
una presentación con representación fiel (lo que se ve es lo que se obtiene, o
WYSIWYG, en sus siglas en inglés) precisa, especialmente por lo que respecta a su
impresión. Los usos típicos para los documentos fijos incluyen la creación de
publicaciones, el procesamiento de textos y el diseño de formularios, donde es vital que
se respete el diseño de página original. Los documentos fijos conservan la
organización precisa de su contenido de una manera independiente del dispositivo. El
diseño permanece inalterado en todos los casos, aunque la calidad del documento
varía según las funciones de cada dispositivo.
Documentos XPS: Los documentos de XML Paper Specification (XPS) se basan en
documentos fijos de WPF. Los documentos de XPS se describen con un esquema
basado en XML que básicamente es una representación en forma de página de
documentos electrónicos. XPS es un formato de documento abierto habilitado para
varios exploradores y diseñado para facilitar la creación, el uso compartido, la
impresión y el almacenamiento de documentos paginados.
Anotaciones
Las anotaciones son notas o comentarios que se agregan a los documentos para marcar la
información o resaltar elementos de interés, a fin de consultarlos más adelante. Aunque escribir
notas en documentos impresos es fácil, la posibilidad de "escribir" notas en los documentos
electrónicos con frecuencia es limitada o no está disponible en absoluto. Sin embargo, en WPF
se proporciona un sistema de anotaciones que admite la inserción de notas rápidas y el
resaltado.
Empaquetado
Las APISystem.IO.Packaging de WPF permiten que las aplicaciones organicen los datos, el
contenido y los recursos en un único documento ZIP portátil, sencillo de distribuir y de fácil
acceso. Es posible incluir firmas digitales para autenticar los elementos contenidos en un
paquete y comprobar que el elemento firmado no se haya manipulado ni modificado. También
puede cifrar los paquetes mediante la administración de derechos para restringir el acceso a la
información protegida.
Impresión
18
Microsoft .NET Framework incluye un subsistema de impresión al que WPF aporta, además,
compatibilidad con el control de sistemas de impresión mejorados. Las mejoras de impresión
incluyen las siguientes:
Instalación en tiempo real de servidores de impresión y colas remotos.
Detección dinámica de funciones de impresora.
Configuración dinámica de opciones de impresión.
Modificación del enrutamiento y las prioridades de los trabajos de impresión.
Los documentos XPS presenta, además, una mejora fundamental del rendimiento. La ruta de
acceso de Interfaz de dispositivo gráfico de Microsoft Windows (GDI) de impresión suele
requerir dos conversiones:
La primera conversión del documento a un formato de procesador de impresión, como
Metarchivo mejorado (EMF).
Una segunda conversión al lenguaje de descripción de páginas de la impresora, como
PCL o PostScript.
Sin embargo, los documentos XPS evitan estas conversiones porque un componente del
formato de archivo XPS es un lenguaje de procesador de impresión y un lenguaje de
descripción de páginas. Esta compatibilidad ayuda a reducir el tamaño del archivo de cola y las
cargas de las impresoras integradas en red.
Personalización
Para simplificar la organización de los controles en una interfase de usuario y asegurarse de
que la organización se conserve aunque se modifiquen el tamaño de la ventana y la
configuración de pantalla, se utiliza el sistema de diseño de WPF. Dado que la mayoría de las
aplicaciones permiten a los usuarios interactuar con los datos, los enlaces de datos se utilizan
para reducir el trabajo de integración de la interfaz de usuario con esos datos. A fin de mejorar
la apariencia visual de la aplicación, se utiliza toda la gama de gráficos, animación y multimedia
que WPF proporciona. Por último, si la aplicación funciona a través de texto y documentos,
puede utilizar las funciones de tipografía, documentos, anotación, empaquetado e impresión de
WPF.
Sin embargo, con frecuencia estos elementos fundamentales no bastan para crear y
administrar una experiencia del usuario realmente diferenciada y visualmente impactante.
Puede que los controles de WPF no se integren con la apariencia deseada de la aplicación. Es
posible que los datos no se muestren del modo más eficaz. La apariencia y el funcionamiento
predeterminados de los temas de Windows pueden no ser adecuados para proporcionar la
experiencia global del usuario con respecto a la aplicación. En muchos aspectos, una
tecnología de presentación requiere la extensibilidad visual tanto como cualquier otro tipo de
extensibilidad.
Por esta razón, WPF proporciona gran variedad de mecanismos para la creación de
experiencias de usuario únicas, incluido un modelo de contenido enriquecido para los
controles, estilos, desencadenadores, plantillas de controles y datos, estilos, recursos de la
interfaz de usuario, temas y máscaras.
Modelo de Contenido: El propósito principal de la mayoría de los controles de WPF es
mostrar contenido. En WPF, el tipo y número de elementos que pueden constituir el
contenido de un control se denomina el modelo de contenido del control. Algunos
controles pueden contener un solo elemento y tipo de contenido; por ejemplo, el
contenido de un control TextBox es un valor de tipo String que está asignado a la
propiedad Text. Otros controles, sin embargo, pueden contener varios elementos con
tipos diferentes de contenido; el contenido de un control Button, especificado por la
propiedad Content, puede contener gran variedad de elementos, entre los que se
incluyen controles de diseño, texto, imágenes y formas.
Desencadenadores (Triggers): Aunque el propósito principal del marcado XAML es
implementar la apariencia de una aplicación, también puede utilizar XAML para
implementar algunos aspectos del comportamiento de una aplicación. Un ejemplo de
19
ello es el uso de desencadenadores para cambiar la apariencia de una aplicación de
acuerdo con las interacciones con el usuario.
Plantillas de Control: Las UIs predeterminadas para los controles de WPF suelen
construirse a partir de otros controles y formas. A veces, la apariencia predeterminada
de un control puede ser incongruente con la apariencia global de una aplicación. En
este caso, puede utilizar una plantilla de control ControlTemplate para cambiar la
apariencia de la interfase de usuario del control sin modificar su contenido ni su
comportamiento.
Plantilla de Datos: Mientras que una plantilla de control permite especificar la
apariencia de un control, una plantilla de datos permite especificar la apariencia del
contenido del control. Las plantillas de datos se utilizan con frecuencia para mejorar la
manera de mostrar los datos enlazados.
Estilos: Los estilos permiten a los programadores y diseñadores normalizar un aspecto
determinado de sus productos. WPF proporciona un modelo de estilo sólido, que es la
base del elemento Style.
Recursos: Los controles de una aplicación deben tener la misma apariencia, que puede
incluir todo tipo de recursos, desde fuentes y colores de fondo hasta plantillas de
control, pasando por las plantillas de datos y los estilos. Puede utilizar la compatibilidad
de WPF con los recursos de la interfase de usuario (UI) para encapsular estos recursos
en una ubicación única y poder reutilizarlos.
Temas y Máscaras: Desde una perspectiva visual, un tema define la apariencia global
de Windows y de las aplicaciones que se ejecutan dentro del mismo. La apariencia
definida por un tema establece la apariencia predeterminada de una aplicación WPF.
Sin embargo, WPF no se integra directamente con los temas de Windows. Como la
apariencia de WPF se define mediante plantillas, WPF incluye una plantilla para cada
uno de los temas conocidos de Windows, como Aero (Windows Vista), Clásico
(Microsoft Windows 2000), etc. Estos temas están empaquetados en diccionarios de
recursos que se resuelven cuando no se encuentran los recursos correspondientes en
una aplicación. Por otro lado, la experiencia del usuario para algunas aplicaciones no
procede necesariamente de los temas estándar. Tales UIs tienden a proporcionar
temas personalizados, específicos de cada aplicación. Se denominan máscaras, y las
aplicaciones con máscaras suelen proporcionar enlaces que permiten a los usuarios
personalizar diversos aspectos de la máscara.
Controles Personalizados: Aunque WPF proporciona una amplísima compatibilidad con
funciones de personalización, puede encontrar situaciones en que los controles
existentes de WPF no satisfagan las necesidades de la aplicación o de los usuarios.
WPF permite crear controles.
3- Eventos y Comandos
20
La declaración de XAML para incorporar un evento es parecida a una asignación de propiedad
en XAML, pero el resultado es un enlace de evento normal en el objeto que especificó el
controlador de eventos. Si cambia la ventana al editor de código verá lo siguiente:
Esto es igual a cualquier otra conexión de evento .NET, se tiene un delegado explícitamente
declarado y enlazado a un evento de un objeto, y el delegado apunta a un método de control.
El único indicio de que se están usando eventos enrutados es el tipo del argumento de evento
para el evento Click, que es RoutedEventArgs. Entonces, ¿qué tienen de especial los eventos
enrutados?
Un evento enrutado es un tipo de evento que puede invocar controladores o varios agentes de
escucha en un árbol de elementos, en lugar de simplemente en el objeto que lo desencadenó.
Para comprender mejor esta definición, necesitará antes comprender el modelo de
composición elemental de WPF.
<Window>
<Grid>
<Button/>
</Grid>
</Window>
Cada uno de estos elementos representa una instancia en tiempo de ejecución de un tipo .NET
correspondiente y la jerarquía declarada de elementos forma lo que se denomina un árbol
lógico. Además, muchos controles de WPF son a su vez contenedores, lo que significa que
pueden tener elementos secundarios. Por ejemplo, un Button puede tener un elemento
secundario complejo como contenido. El árbol lógico se podría expandir como se muestra aquí:
<Window>
<Grid>
<Button>
<StackPanel>
<Image/>
<TextBlock/>
</StackPanel>
</Button>
</Grid>
</Window>
Como puede imaginar, el árbol podría contener varias ramas y el árbol lógico puede crecer
considerablemente en complejidad. Lo más importante a tener en cuenta sobre los elementos
WPF del árbol lógico es que lo que se ve no es realmente lo que se obtiene en tiempo de
ejecución. Cada uno de esos elementos suele ampliarse hasta formar un árbol más complejo
de elementos visuales en tiempo de ejecución.
Cuando hago clic en mi botón, puede que en realidad no esté haciendo clic en el elemento
Button; puedo estar haciendo clic en un elemento secundario del árbol visual, posiblemente
incluso uno que no se muestre en mi árbol lógico (como ButtonChrome). Por ejemplo, digamos
que hago clic sobre la imagen que contiene mi botón. En realidad, el clic se manifiesta
21
inicialmente como un evento de MouseLeftButtonDown incluido dentro del elemento Image.
Pero de alguna forma esto hay que trasladarlo a un evento Click al nivel del Button. Es aquí
donde entra en juego el enrutamiento en eventos.
Enrutamiento de eventos
Es importante entender un poco los árboles lógicos y visuales porque los eventos enrutados se
enrutan principalmente según el árbol visual. Los eventos enrutados admiten una
RoutingStrategy que puede ser Bubble, Tunnel o Direct.
Bubble es la más común y significa que un evento se propagará hacia la parte superior
del árbol visual a partir del elemento origen hasta que se controle o alcance el
elemento raíz. Esto le permite controlar un evento de un objeto aún más arriba de la
jerarquía de elementos desde el elemento origen. Por ejemplo, se podría adjuntar un
controlador Button.Click en elemento Grid que lo contiene en vez de directamente en el
propio botón.
Tunnel: Los eventos Tunnel van en dirección contraria, se inician en el elemento raíz y
van bajando por el árbol de elementos hasta que se controlan o alcanzan el elemento
origen para el evento. Esto permite que los elementos precedentes intercepten el
evento y lo controlen antes de que alcance el elemento origen. Los eventos Tunnel
usan como prefijo de sus nombres Preview como convención (por ejemplo,
PreviewMouseDown).
Direct: Los eventos directos se comportan como eventos normales de .NET
Framework. El único controlador posible para el evento es un delegado que se enlaza
al evento.
22
llega a Button. El botón controla ese evento, establece la marca Handled en verdadero y
acciona su propio evento Click.
Las implicaciones son bastante eficaces. Por ejemplo, si elijo reemplazar la apariencia
predeterminada del botón aplicando una plantilla de control que contenga un elemento Ellipse,
no tengo que hacer nada para garantizar que los clics hechos fuera de Ellipse no accionen el
evento Click. Los clics que quedan justo fuera del borde de Ellipse seguirán estando dentro de
los límites rectangulares de mi botón, pero Ellipse tiene su propia detección de aciertos para
MouseLeftButtonDown, cosa que no ocurre para las partes vacías del botón que están fuera de
Ellipse.
Así que sólo los clics que se hacen dentro de Ellipse accionan el evento
MouseLeftButtonDown. Sigue estando controlado por la clase Button a la que se adjunta la
plantilla, así que se obtendrá el comportamiento previsible incluso del botón personalizado.
Esto también es un concepto muy importante que recordar al escribir sus propios controles
compuestos personalizados, porque lo más probable es que necesite hacer cosas parecidas a
las que hace Button para administrar eventos de elementos secundarios que se coloquen
dentro de su control.
Eventos adjuntos
Los eventos adjuntos son eventos enrutados que admiten un enlace XAML sobre elementos
distintos del tipo en el que se declara el evento. Por ejemplo, si desea que el elemento Grid
esté atento al paso de un evento Button.Click, sólo tendría que enlazarlo de esta forma:
<Grid Button.Click="myButton_Click">
<Button Name="myButton" >Presionar</Button>
</Grid>
Los eventos adjuntos sólo dan un poco más de flexibilidad en los sitios donde conecta sus
controladores de eventos. Pero si los elementos están contenidos en la misma clase (como en
este ejemplo), puede no resultar aparente qué diferencia hay porque, en ambos casos, el
método de control sigue siendo sólo un método de la clase Window.
23
<Button Command="ApplicationCommands.Save">Guardar</Button>
Para concretar y ver rápidamente las ventajas de los comandos enrutados, veamos un ejemplo
sencillo. En la siguiente figura se puede ver una IU con dos TextBoxs y un botón de barra de
herramientas para realizar una acción Cortar sobre el texto de los cuadros de texto.
Para poder conectar esto usando eventos, necesitaría definir un controlador Click para el botón
de la barra de herramientas y ese código necesitaría hacer referencia a los dos cuadros de
texto. Tendría que determinar qué cuadro de texto tiene el foco y llamar a operaciones
apropiadas del portapapeles según la selección del texto en el control. También tendría que
preocuparse de habilitar y deshabilitar el botón de la barra de herramientas según donde esté
el foco y si se seleccionó algo en el cuadro de texto. Esto generaría un código complejo y
desordenado.
Con comandos, lo único que hay que hacer es establecer la propiedad Command del botón de
la barra de herramientas en el comando ApplicationCommands.Cut que está definido en WPF
y ya está listo.
<StackPanel>
<ToolBar DockPanel.Dock="Top" Height="25">
<Button Command="ApplicationCommands.Cut">
<Image Source="cut.ico"/>
</Button>
</ToolBar>
<TextBox Name="txt1" Width="100" Height ="23"></TextBox>
<TextBox Name="txt2" Width="100" Height ="23"></TextBox>
</StackPanel>
Ahora podría ejecutar la aplicación y comprobar que el botón de la barra de herramientas está
inicialmente deshabilitado. Después de seleccionar texto en uno de los cuadros de texto, el
botón de la barra de herramientas pasa a estar habilitado y, si se hace clic en él, el texto se
corta y pasa al portapapeles. Y funcionaría para cualquier TextBox de cualquier lugar de la IU.
Lo que sucede aquí es que la implementación de la clase TextBox tiene un enlace de
comandos integrado para el comando Cut y encapsula automáticamente el control del
portapapeles para ese comando (además de Copy y Paste). Pero, ¿cómo se invoca solamente
el comando en el cuadro de texto enfocado, y cómo llega el mensaje al cuadro de texto para
decirle que controle el comando? Aquí es donde entra en juego el enrutamiento de comandos.
24
Enrutamiento de comandos
La diferencia entre comandos enrutados y eventos enrutados es la forma en que el comando
se dirige desde el invocador del comando al controlador del comando. Concretamente, los
eventos enrutados se usan internamente para dirigir mensajes entre los invocadores y los
controladores de comandos (mediante el enlace de comandos que se engancha en el árbol
visual).
Aquí podría haber una relación de varios a varios, pero sólo un controlador de comando estará
realmente activo en cada momento. El controlador de comandos activo lo determina una
combinación de las posiciones del invocador y del administrador de comandos en el árbol
visual, y de la posición del foco en la interfase de usuario. Los eventos enrutados se usan para
llamar al controlador de comandos activo para preguntarle si se debe habilitar el comando, así
como para invocar al controlador de método Execute del controlador de comandos.
Generalmente, un invocador de comandos busca un enlace de comandos entre su propia
ubicación en el árbol visual y la raíz del árbol visual. Si encuentra uno, el controlador de
comandos enlazado determinará si el comando está habilitado y se llamará cuando se invoque
el comando. Si el comando está conectado a un control de una barra de herramientas o de un
menú, entonces se ejecuta lógica adicional que también examina la ruta de acceso del árbol
visual desde la raíz al elemento del foco buscando un enlace de comando.
Definición de comandos
Existen cinco clases de comandos integradas en WPF:
ApplicationCommands: Close, Cut, Copy, Paste, Save, Print
NavigationCommands: BrowseForward, BrowseBack, Zoom, Search
EditingCommands: AlignXXX, MoveXXX, SelectXXX
MediaCommands: Play, Pause, NextTrack, IncreaseVolume, Record, Stop
ComponentCommands: MoveXXX, SelectXXX, ScrollXXX, ExtendSelectionXXX
XXX indica diversas operaciones como MoveNext y MovePrevious. Los comandos de cada
clase están definidos como propiedades para que pueda conectarlos fácilmente.
<Button Command="Save">Save</Button>
25
Módulo 2
Creando Aplicaciones
WPF
26
1- Crear una aplicación WFP
Aplicación WPF
Dentro de Visual Studio:
En el menú Archivo, haga clic en Nuevo proyecto. Aparecerá el cuadro de diálogo de
Nuevo proyecto.
Seleccione el lenguaje y elija Aplicación WPF.
Cambie el nombre de la aplicación.
Haga clic en Aceptar.
Se creará una nueva carpeta para el proyecto con el nombre del proyecto y, a continuación,
mostrará el nuevo formulario WPF titulado Window1 en la vista Diseñador. Dentro de esta vista
se puede trabajar el formulario de forma similar al de las aplicaciones Windows. Arrastre
controles y modifique sus propiedades usando la ventana de propiedades. Escriba los
atajadores de eventos en el archivo .xaml.cs o .xaml.vb según el lenguaje seleccionado.
La ventana de WPF que se ve en la vista Diseñador es una representación visual de la ventana
que se abrirá al iniciar la aplicación. En la vista Diseñador, puede arrastrar diversos controles
desde el Cuadro de herramientas hasta la ventana WPF. Después de haber colocado un
control a la ventana de WPF, Visual C# crea automáticamente código que hará que el control
se sitúe apropiadamente cuando se ejecute el programa.
27
Clase Window
Permite crear, configurar, mostrar y administrar la duración de las ventanas y los cuadros de
diálogo. El punto de interacción entre el usuario y una aplicación independiente es una
ventana. Una ventana de WPF se compone de dos áreas distintas:
Un área no cliente, que hospeda los elementos gráficos de las ventanas, incluidos un
icono, título, menú del sistema, botón para minimizar, botón para maximizar, botón para
restablecer, botón para cerrar y un borde.
Un área cliente, que hospeda el contenido específico de la aplicación.
<Window x:Class="AplWPF.Principal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Principal" Height="500" Width="500" WindowStyle="ThreeDBorderWindow"
ResizeMode="CanResize" ShowInTaskbar="True" WindowState="Maximized">
<Application x:Class="WpfCSharp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Principal.xaml">
28
<Application.Resources>
</Application.Resources>
</Application>
Button Button
CheckBox CheckBox
ColorDialog
29
ComboBox ComboBox
ContextMenuStrip ContextMenu
ErrorProvider
FolderBrowserDialog
FontDialog
Form Window
GroupBox GroupBox
Label Label
ListBox ListBox
MenuStrip Menu
MonthCalendar
NotifyIcon
OpenFileDialog OpenFileDialog
PageSetupDialog
Panel Canvas
30
PictureBox Image
PrintDialog PrintDialog
PrintDocument
PrintPreviewControl DocumentViewer
PrintPreviewDialog
ProgressBar ProgressBar
RadioButton RadioButton
RichTextBox RichTextBox
SaveFileDialog SaveFileDialog
SoundPlayer MediaPlayer
SplitContainer GridSplitter
StatusStrip StatusBar
TabControl TabControl
TableLayoutPanel Grid
TextBox TextBox
Timer DispatcherTimer
ToolStrip ToolBar
ToolTip ToolTip
TrackBar Slider
TreeView TreeView
Controladores de Eventos
Para crear un controlador de eventos se puede hacer doble clic sobre el control. De esta
manera se crea el atajador del evento predeterminado de un control.
También puede agregarse como atributo escribiendo sobre el editor XAML. Al escribir el
nombre del evento o seleccionarlo de la lista de ayuda aparecerá una opción <Nuevo
31
Controlador de Eventos> y el nombre de procedimientos posibles de ser seleccionados
teniendo en cuenta la firma del atajador del evento seleccionado. El nombre predeterminado
del atajador del evento será NombreObjeto_Evento. El controlador de eventos se escribirá en
la clase asociada al formulario.
Todas las librerías del Framework de .Net esta disponible para ser usada en este tipo de
aplicaciones.
Recursos
WPF provee recursos como una forma simple de reusar objetos y valores definidos. Se pueden
crear recursos en XAML o por código. Todos los elementos de nivel FrameWork tienen la
propiedad Resources, aunque generalmente se los usa en elementos de tipo raíz como
Window.
Los recursos deben contener una identificación única. Esta se declara usando x:Key.
Generalmente la clave se la declara como carácter (string), aunque puede ser declarada
usando otros tipos de datos.
<Page.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<Style TargetType="Border" x:Key="PageBackground">
<Setter Property="Background" Value="Blue"/>
</Style>
<Style TargetType="TextBlock" x:Key="TitleText">
<Setter Property="Background" Value="Blue"/>
<Setter Property="DockPanel.Dock" Value="Top"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="#4E87D4"/>
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="Margin" Value="0,40,10,10"/>
</Style>
<Style TargetType="TextBlock" x:Key="Label">
<Setter Property="DockPanel.Dock" Value="Right"/>
<Setter Property="FontSize" Value="8"/>
<Setter Property="Foreground" Value="{StaticResource MyBrush}"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0,3,10,0"/>
</Style>
</Page.Resources>
2- Controles de Diseño
Border
Dibuja un borde, un fondo o ambos alrededor de otro elemento.
<Border Background="LightBlue"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="50"
Padding="5" Height="36" VerticalAlignment="Top" Margin="100,44,116,0">
<CheckBox Name="Prueba" IsChecked="True" >Prueba</CheckBox>
</Border>
Canvas
Define un área en la que pueden colocarse explícitamente los elementos secundarios utilizando
las coordenadas relativas al área del control Canvas. Canvas es el único elemento del panel
que no tiene ninguna característica de diseño inherente. Tiene las propiedades
predeterminadas Height y Width de cero. Siempre se da a los elementos secundarios de
Canvas el tamaño máximo que requieren. Como resultado, la alineación vertical y horizontal no
tiene ningún efecto en un control Canvas. Canvas es un control de diseño de nivel superior que
puede utilizarse para la colocación absoluta del contenido secundario.
En el siguiente ejemplo se muestra un control Canvas con dos TextBlock contenidos. El uso de
las propiedades Canvas.Top y Canvas.Left permite posicionar los controles contenidos.
34
<Canvas Background="LightSteelBlue">
<TextBlock FontSize="14" Canvas.Top="10" Canvas.Left="5">Hola
Mundo!</TextBlock>
<TextBlock FontSize="22" Canvas.Top="25"
Canvas.Left="35">Hola</TextBlock>
</Canvas>
DockPanel
Define un área en la que se pueden organizar horizontalmente o verticalmente los elementos
secundarios, uno respecto al otro. La posición de los elementos secundarios de un control
DockPanel en la pantalla está determinada por la propiedad Dock de los elementos
secundarios respectivos y el orden relativo de estos elementos secundarios en DockPanel. Por
tanto, un conjunto de elementos secundarios con los mismos valores de la propiedad Dock se
puede organizar de forma diferente en la pantalla dependiendo del orden de estos elementos
secundarios dentro del DockPanel. La clasificación de los elementos secundarios afecta a la
posición porque DockPanel recorre en iteración sus elementos secundarios en orden, y
establece la posición de cada elemento en función del espacio restante.
El método SetDock cambia la posición de un elemento respecto a otros elementos del mismo
contenedor. Las propiedades de alineación, como HorizontalAlignment, cambian la posición de
un elemento respecto a su elemento principal.
Si establece la propiedad LastChildFill en true, que es el valor predeterminado, el último
elemento secundario de DockPanel rellena siempre el espacio restante, sin tener en cuenta
ningún valor que se haya establecido en este elemento. Para acoplar un elemento secundario
en otra dirección, debe establecer la propiedad LastChildFill en false y especificar además una
dirección de acoplamiento explícita para este último elemento secundario.
<DockPanel LastChildFill="True">
<Border Height="25" Background="SkyBlue" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Top">
<TextBlock Foreground="Black">Dock = "Top"</TextBlock>
</Border>
<Border Height="25" Background="LemonChiffon" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Bottom">
<TextBlock Foreground="Black">Dock = "Bottom"</TextBlock>
</Border>
<Border Width="200" Background="PaleGreen" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Left">
<TextBlock Foreground="Black">Dock = "Left"</TextBlock>
</Border>
<Border Background="White" BorderBrush="Black" BorderThickness="1">
35
<TextBlock Foreground="Black">This content will "Fill" the remaining
space</TextBlock>
</Border>
</DockPanel>
Grid
Define un área de cuadrícula flexible que está compuesta de columnas y filas. Los elementos
secundarios de Grid se dibujan en el orden en que aparecen en el código. En consecuencia, se
puede lograr un orden dispuesto en capas (también conocido como orden Z) cuando los
elementos comparten las mismas coordenadas.
Grid y Table comparten alguna funcionalidad común, pero se puede aplicar cada uno de ellos
en escenarios adecuados para hacer un mejor uso de sus características integradas. Grid
agrega elementos basándose en un índice de fila y columna; Table no lo hace. El elemento
Grid permite la disposición en capas del contenido si puede haber más de un elemento dentro
de una sola celda. Table no admite la disposición en capas. Los elementos secundarios de un
elemento Grid se pueden colocar de forma absoluta con respecto a la esquina superior
izquierda de los límites de su celda. Table no admite esta característica. Grid también
proporciona un comportamiento de cambio de tamaño más flexible que Table.
Grid utiliza el objeto GridLength para definir las características de ajuste de tamaño de
RowDefinition o ColumnDefinition.
En el siguiente ejemplo se muestra el uso del control Grid. La definición inicial de filas y
columnas y el uso de las propiedades Grid.Row y Grid.Column para asociar los controles
contenidos a la celda correspondiente.
StackPanel
Organiza los elementos secundarios en una única línea que se puede orientar horizontal o
verticalmente. La propiedad Orientation permite organizar el contenido horizontal o
verticalmente.
GroupBox
Representa un control que crea un contenedor que tiene un borde y un encabezado para el
contenido de interfaz de usuario. Su propiedad de contenido es Content y su propiedad de
encabezado es Header.
En el siguiente ejemplo se muestra el uso del control GroupBox. La propiedad Header permite
ponerle el título. El uso de un StackPanel, otro de los controles contenedores, permite
organizar el cuerpo del GroupBox.
En el siguiente ejemplo se muestra el uso del control Expander. Las propiedades de este
control permiten controlar la expansión y contracción del cuerpo del mismo.
InkCanvas
InkCanvas es un elemento que se puede utilizar para recibir y mostrar entradas de entrada
manuscrita. Normalmente esta operación se lleva a cabo mediante el uso de un lápiz o un
mouse que interactúan con un digitalizador para generar trazos de entrada manuscrita. Los
trazos creados se representan como objetos Stroke y se pueden manipular mediante
programación o a través de los datos proporcionados por el usuario. InkCanvas permite al
usuario modificar o eliminar un objeto Stroke existente.
38
Menu
Representa un control de menú de Windows que le permite organizar jerárquicamente los
elementos asociados a comandos y controladores de eventos.
Presenta una lista de elementos que especifican comandos u opciones para una aplicación.
Normalmente, al hacer clic en un elemento de un menú, se abre un submenú o una aplicación
ejecuta un comando. MenuItem es el tipo más común de elemento de Menu. MenuItem puede
contener elementos secundarios.
En el siguiente ejemplo se muestra el uso del control Menu y la forma de crear los MenuItems.
Frame
Es un control de contenido que proporciona la capacidad para navegar y mostrar el contenido.
Se puede hospedar dentro de otro contenido, como con otros controles y elementos. El
39
contenido puede ser cualquier tipo de objeto de .NET Framework y archivos HTML. En general,
sin embargo, las páginas son la manera preferida de empaquetar contenido para la
navegación. A un contenido se puede navegar estableciendo la propiedad Source con el URI
para el contenido deseado. Además, a un contenido se puede navegar utilizando una de las
sobrecargas del método Navigate.
WrapPanel
Posiciona los elementos hijos en forma secuencial de izquierda a derecha, cortando el
contenido de manera que quede sobre la línea siguiente al llegar al borde del contenedor. El
orden se mantiene de arriba hacia abajo o de derecha a izquierda dependiente del valor de la
propiedad Orientation.
40
3- Otros Controles
Label - AccessText
Representa la etiqueta de texto de un control. Esta clase proporciona compatibilidad funcional y
visual con las teclas de acceso. Se utiliza con frecuencia para permitir el acceso rápido desde
el teclado a controles como TextBox. Para asociar un control Label a otro control, establezca la
propiedad Target en el control que debe obtener el foco cuando el usuario presione la tecla de
acceso. Para establecer la tecla de acceso, agregue un guión bajo antes del carácter que debe
ser la tecla de acceso. Se accede con Alt + la letra seleccionada.
TextBox
Representa un control que se puede utilizar para mostrar o editar texto sin formato.
TextBlock
Proporciona un control ligero que permite mostrar pequeños fragmentos de contenido
dinámico. TextBlock se ha diseñado para que sea ligero y se ha preparado específicamente
para integrar pequeños fragmentos de contenido dinámico en una interfaz de usuario (UI).
TextBlock proporciona un rendimiento óptimo en la presentación de líneas únicas y un
rendimiento apropiado en la presentación de hasta unas pocas líneas de contenido.
Button
Su propiedad de contenido es Content. Su evento principal es Click.
En el siguiente ejemplo se muestra un botón que contiene una imagen y un texto formado por
controles contenidos.
41
<Button Name="btn5" Canvas.Left="64" Canvas.Top="17" Width="129" Height="33"
Click="btnAceptar_Click" >
<StackPanel Orientation="Horizontal" Width="Auto">
<Image Source="ms.jpg" Width="20" Height="20"></Image>
<TextBlock Text="Aceptar" TextAlignment="Right" VerticalAlignment="Center"
Width="69"></TextBlock>
</StackPanel>
</Button>
ListBox - ComboBox
El ListBox permite mostrar una lista de ítems desplegada. Las propiedades de contenido son
las colecciones Items e ItemsSource. La propiedad SelectionMode permite establecer cuantos
ítems pueden ser seleccionados. La opciones son Single (1 solo ítem), Multiple (varios ítems) y
Extended (se pueden seleccionar varios ítems consecutivos usando la tecla Shift o no
consecutivos usando la tecla Ctrl).
El ComboBox permite ocultar y desplegar la lista de ítems. Las propiedades IsEditable e
IsReadOnly especifican cómo se comporta ComboBox cuando el usuario escribe una cadena
para seleccionar un elemento o copia y pega un valor en el cuadro de texto.
Estos controles permiten también el uso de listas con contengan CheckBox, imágenes, etc.
como controles contenidos para cada ítem.
CheckBox
Representa un control que un usuario puede activar y desactivar. Pueden tener tres estados:
activado, desactivado e indeterminado.
RadioButton
Representa un botón que el usuario puede seleccionar, pero no borrar. La propiedad
IsChecked de RadioButton se puede establecer haciendo clic en él, pero sólo se puede borrar
mediante programación. Normalmente se lo utiliza como elemento de un grupo de controles
RadioButton. La selección está determinada por el estado de su propiedad IsChecked. Su
evento principal es Checked.
43
Otros Controles
ToolBar: Identifica una barra de herramientas, como el control que contiene un
conjunto de botones de comando en una ventana de la aplicación.
Slider: Identifica un control deslizante.
TreeView: Muestra datos jerárquicos, como una tabla de contenido, en una estructura
de árbol.
StatusBar: Identifica un control de barra de estado.
ProgressBar: Identifica un control de barra de progreso, que indica visualmente el
progreso de una operación prolongada.
PasswordBox: Representa un control diseñado para escribir y administrar las
contraseñas.
<StackPanel>
<ToolBar Height="26" Name="toolBar1" Width="200" >
<Button>
<Image Source="setup.ico"/>
</Button>
<Button>Nuevo</Button>
<Button>Abrir</Button>
</ToolBar>
<Slider Height="21" Name="slider1" Width="100" />
<TreeView Height="150">
<TreeViewItem Header="Archivo">
<TreeViewItem Header="Nuevo" />
<TreeViewItem Header="Abrir" />
<TreeViewItem Header="Cerrar" />
</TreeViewItem>
<TreeViewItem Header="Editar">
<TreeViewItem Header="Cortar" />
<TreeViewItem Header="Copiar" />
<TreeViewItem Header="Pegar" />
</TreeViewItem>
</TreeView>
<StatusBar Height="23" Name="statusBar1" Width="120" >
<TextBlock Height="15" Text="Procesando...." />
<ProgressBar Height="15" Name="progressBar1" Width="100" Value="20" />
</StatusBar>
<PasswordBox Name="pwdBox" Width="100" MaxLength="64" PasswordChar="*" />
</StackPanel>
44
4- Información General de Controles
Propiedades Generales de los Controles
BitmapEffect: Obtiene o establece efectos visuales.
<Button.BitmapEffect>
<BevelBitmapEffect />
</Button.BitmapEffect>
<Button.BitmapEffect>
<DropShadowBitmapEffect />
</Button.BitmapEffect>
Focusable: Obtiene o establece un valor que indica si el foco se puede establecer en este
elemento. Para exigir que un elemento del panel reciba el foco, establezca la propiedad
Focusable en true.
HorizontalAlignment: Obtiene o establece las características de alineación horizontal que se
aplican a este elemento cuando se crea dentro de un elemento primario, como un panel o
control de elementos.
IsEnabled: Obtiene o establece un valor que indica si este elemento está habilitado en la
interfase de usuario.
Margin: Permite establecer los márgenes de un control con respecto a su contenedor. Se
escribe en el siguiente formato (izquierda, arriba, derecha y abajo). Ejemplo:
Margin="50,10,50,10"
MaxHeight: Obtiene o establece la restricción de alto máximo del elemento.
MaxWidth: Obtiene o establece la restricción de ancho máximo del elemento.
MinHeigth: Obtiene o establece la restricción de alto mínimo del elemento.
MinWidth: Obtiene o establece la restricción de ancho mínimo del elemento.
Opacity: Permite generar objetos transparentes o semi transparentes. Los valores aceptados
van del 0 al 1, siendo 0 totalmente transparente y 1 totalmente opaco.
Padding: Obtiene o establece el relleno interior del control. El relleno se establece en el orden
siguiente: izquierda, arriba, derecha y abajo. El valor predeterminado es un grosor de 0 en los
cuatro lados.
VerticalAlignment: Obtiene o establece las características de alineación vertical que se aplican
a este elemento cuando se crea dentro de un elemento primario, como un panel o control de
elementos.
Visibility: Obtiene o establece la visibilidad de la interfaz de usuario de este elemento.
46
Módulo 3
Personalizando la
Apariencia de
Aplicaciones WPF
47
1- Personalizar Apariencia
Composición
Un requisito habitual para personalizar la apariencia de un control es crear controles
compuestos, o sea que incluyan más de un control. La mayoría de los controles WPF son
contenedores de otros controles. Puede usar la composición para incrustar los elementos
XAML dentro de otros. En varios de los ejemplos mostrados en el capítulo anterior usamos esta
técnica para la creación de los controles.
Estilos
Los estilos y plantillas permiten el mantenimiento y el uso compartido de la apariencia tanto
dentro de una aplicación como entre las diversas aplicaciones. Hacen referencia a un conjunto
de características que permiten a los diseñadores de aplicaciones, documentos o interfases de
usuario crear aplicaciones visualmente atractivas y estandarizar la apariencia del producto.
Otra característica del modelo de estilos de WPF es la separación de la presentación y la
lógica. Esto significa que los diseñadores pueden trabajar en la apariencia de una aplicación
utilizando solamente XAML, al mismo tiempo que los desarrolladores trabajan en la lógica de
programación utilizando C# o Visual Basic.
La clase Style permite generar los estilos en aplicaciones WPF. Puede establecer una clase
Style en cualquier elemento que derive de FrameworkElement o FrameworkContentElement.
Un estilo se suele declarar normalmente como un recurso de la sección de Resources. Puesto
que los estilos son recursos, siguen las mismas reglas de ámbito que se aplican a todos los
recursos; por tanto, cuando se declara un estilo, afecta a la parte donde se puede aplicar. Si,
por ejemplo, se declara el estilo en el elemento raíz del archivo XAML de definición de la
aplicación, el estilo se puede usar en cualquier parte de la aplicación. Si se crea una aplicación
de navegación y se declara el estilo en uno de sus archivos XAML, el estilo sólo se puede usar
en ese archivo XAML.
La declaración de estilo está formada por un objeto Style que contiene una colección de uno o
más objetos Setter. Cada objeto Setter consta de una propiedad Property y una propiedad
Value. La propiedad es el nombre de la propiedad del elemento al que se aplica el estilo. Una
vez declarado el estilo como recurso, se puede hacer referencia a él como en el caso de
cualquier otro recurso. Si hubiera muchos estilos con la misma propiedad Property es establece
el valor del declarado en último lugar.
Si se establece la propiedad TargetType en un control sin asignar al estilo un atributo x:Key, el
estilo se aplicará a todos los elementos de ese tipo. Esto significa que si se establece
explícitamente el atributo x:Key en un valor, el objeto Style no se aplicará automáticamente a
todos los elementos de ese tipo. Si el estilo está en la sección de recursos y no se establece la
propiedad TargetType del estilo, se deberá proporcionar un atributo x:Key.
En el ejemplo siguiente se define un estilo que se aplicará a todas las instancias del elemento
Button. Como se ve en el ejemplo los botones toman automáticamente el estilo sin necesidad
de asociarlo por código.
<Window.Resources>
48
<Style TargetType="{x:Type Button}">
<Setter Property="FontFamily" Value="Times New Roman" />
<Setter Property="FontSize" Value="30" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="#FFCA5132" />
</Style>
</Window.Resources>
<Button Height="54" Name="button1" Width="134">Button</Button>
<Window.Resources>
<Style x:Key="Style1">
<Setter Property="Control.Background" Value="Yellow"/>
</Style>
</Window.Resources>
<StackPanel>
<Label Content="Label Amarillo" Style="{StaticResource Style1}"
Width="100"/>
<Button Name="button1" Style="{StaticResource Style1}" Height="20"
Width="50">Button</Button>
</StackPanel>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Color="#DDDDDD" Offset="0" />
<GradientStop Color="#88FF88" Offset=".6" />
<GradientStop Color="#EEEEEE" Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
49
<Button Height="50" Width="50">
<StackPanel>
<TextBlock>Play</TextBlock>
<Polygon Points="0,0 0,26 17,13"
Fill="Black" />
</StackPanel>
</Button>
Nota: Muchos controles de WPF constan de una combinación de otros controles de WPF, por
lo que la creación de un estilo que se aplique a todos los controles de un tipo puede tener un
gran impacto. Por ejemplo, si se crea un estilo cuyo destino son los controles TextBlock de un
control Canvas, el estilo se aplica a todos los controles TextBlock del lienzo, aunque TextBlock
forme parte de otro control, como ListBox.
Plantillas
Los estilos están limitados a la configuración de las propiedades predeterminadas en
elementos XAML. Para una libertad completa de una apariencia del control, necesita usar
plantillas. Para ello, debe crear un estilo y especificar la propiedad de plantilla. El valor de la
propiedad de plantilla pasa a ser un elemento ControlTemplate que especifica cómo redactar
el control.
La mayoría de controles tienen una apariencia y un comportamiento. Considere un botón: la
apariencia es el área elevada que se puede presionar y el comportamiento es el evento Click
que se provoca en respuesta a un clic. En ocasiones, puede haber un control que proporcione
el comportamiento necesario, pero no la apariencia necesaria. Hasta ahora, hemos mostrado
que puede utilizar establecedores de estilo para establecer valores de propiedad que afecten a
la apariencia del control. Sin embargo, para cambiar la estructura de un control o establecer
valores de propiedad en los componentes de un control, debe utilizar un objeto
ControlTemplate.
En WPF, el objeto ControlTemplate de un control define su apariencia. Puede cambiar la
estructura y la apariencia de un control definiendo un nuevo objeto ControlTemplate para el
control. En muchos casos, esto ofrece suficiente flexibilidad como para no tener que escribir
controles personalizados.
50
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" x:Key="PlayButton" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}" Stroke="DarkGray"
VerticalAlignment="Top"
HorizontalAlignment="Left" Fill="LightGray" />
<Polygon Points="18,12 18,38 35,25" Fill="Black" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Height="50" Width="100">Botón Normal</Button>
<Button Height="50" Width="50" Style="{StaticResource PlayButton}" />
</StackPanel>
Otro ejemplo de cómo aplicar plantillas. Tenga en cuenta que la plantilla se aplica siempre a
través de un estilo. Este ejemplo usa una plantilla que a través de un control Border redondea
la forma de los botones.
<Window.Resources>
<ControlTemplate x:Key="PlantBotonAzul" TargetType="{x:Type Button}">
<Border BorderBrush="Navy" BorderThickness="1" CornerRadius="5"
Background="CornflowerBlue">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="1" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</ControlTemplate>
<Style x:Key="EstiloBotonAzul" TargetType="{x:Type Button}">
<Setter Property="Template" Value="{StaticResource PlantBotonAzul}" />
</Style>
</Window.Resources>
<Button Height="40" Name="button1" Width="120" Content="Aceptar"
Style="{StaticResource EstiloBotonAzul}" />
51
Desencadenadores (Triggers)
La clase Trigger representa un desencadenador que aplica valores de propiedad o realiza
acciones si se cumplen determinadas condiciones. WPF define propiedades que se
corresponden con acciones del usuario final, como la propiedad IsMouseOver cuyo valor se
establece en true cuando el usuario desplaza el puntero sobre un objeto, o bien, la
correspondiente propiedad IsMouseOver de un objeto. La representación de las acciones del
usuario final en los valores de propiedad junto con el elemento Trigger permite a los estilos de
WPF cambiar en el marcado los valores de las propiedades según esas acciones del usuario
final.
Las propiedades cambiadas por los desencadenadores se restablecen automáticamente en su
valor anterior si ya no se cumple la condición de activación. Los desencadenadores se
optimizan para los estados transitorios de los que se espera que cambien y vuelvan a su
estado original, como IsPressed en el control Button e IsSelected en el control ListBoxItem. La
propiedad Property de interés debe ser una propiedad de dependencia.
Observe que debe especificar las propiedades Property y Value en un objeto Trigger para que
el desencadenador tenga sentido. La propiedad Setters de un objeto Trigger sólo puede estar
formada por objetos Setter.
En el siguiente ejemplo se muestra el uso de desencadenadores de manera que cada vez que
se hace clic sobre un botón este cambia el tamaño de la letra de su contenido a 14 y cada vez
que se pasa el Mouse por encima del mismo el color del fondo se vuelve rojo.
<Window.Resources>
<Style x:Key="TrCambioColor" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter Property = "FontSize" Value="14"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property = "Background" Value="red"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel >
<Button Height="23" Name="button1" Width="75" Style="{StaticResource
TrCambioColor}">Button</Button>
</StackPanel>
52
cierto control de la interfaz de usuario (UI). A diferencia de Trigger, EventTrigger no tiene
ningún concepto de finalización de estado, por lo que no se deshará la acción una vez que la
condición que provocó el evento deje de ser verdadera.
Tenga en cuenta que al usar EventTrigger, debe elegir eventos que no interfieran con el
comportamiento inherente del control. Los controles como Button o TextBox realizan acciones
concretas en los eventos de usuario como clics del mouse y eventos de teclado. Por ejemplo, si
está creando un botón e intenta establecer el evento MouseDown como la propiedad
RoutedEvent de EventTrigger, EventTrigger nunca se aplica porque el botón administra primero
el evento. En su lugar, puede usar el evento PreviewMouseDown o un evento diferente.
En el siguiente ejemplo, los objetos EventTrigger especifican que, cuando el puntero del mouse
entre en el elemento Button, la propiedad Height se establezca en 90 durante un período de 0.2
segundos. Cuando el mouse sale del elemento, la propiedad se establece de nuevo en el valor
original durante un período de 1 segundo. Note que se escriben ambos eventos de manera que
la acción se detenga al cambiar el estado.
<Window.Resources>
<Style x:Key="TrCambioColor" TargetType="{x:Type Button}">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.2"
Storyboard.TargetProperty="Height" To="90" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
Storyboard.TargetProperty="Height" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel >
<Button Height="23" Name="button1" Width="75" Style="{StaticResource
TrCambioColor}">Button</Button>
</StackPanel>
53
Además de Trigger y EventTrigger, hay otros tipos de desencadenadores. MultiTrigger permite
establecer valores de propiedad en función de varias condiciones. DataTrigger y
MultiDataTrigger se utilizan cuando la propiedad de la condición está enlazada a datos.
Controles
Es imposible suministrar todos los controles que se podrían necesitar. Por este motivo existe la
creación de controles. En Windows Presentation Foundation, la composición, el estilo y las
plantillas habilitan la personalización de los controles existentes hasta un nivel sin precedentes
en tecnologías anteriores, pero la creación de controles permite armar controles una única vez
y re usarlos en distintos formularios o proyectos.
El primer paso que debe dar antes de escribir su propio control es decidir qué método va a usar
para crearlo. Existen dos maneras principales de crear controles en Windows Presentation
Foundation: los controles de usuario y los controles personalizados. Ambos enfoques ofrecen
ventajas.
Controles de usuario: Es un fragmento de una ventana o página. Permite incluir varios
controles y su funcionalidad como una forma de encapsulamiento visual. Se los llama
también controles compuestos.
Controles personalizados: Generalmente heredan de un solo control y nos permiten
sumar funcionalidad o apariencia al mismo. Su propósito es mejorar los controles
existentes.
Controles de Usuario
Lo primero que debe hacer al crear un control de usuario es agregar un elemento nuevo a su
proyecto. Si hace clic al proyecto con el botón secundario y luego a Agregar, no elija la opción
de Control de Usuario del menú contextual. Lamentablemente, esta función intentará crear un
control de usuario nuevo de Windows Forms. En su lugar, escoja la opción Agregar nuevo
elemento. En el diálogo Agregar nuevo elemento, escoja el elemento Control de Usuario
(WPF).
54
Al crear el control de usuario nuevo se crea un archivo XAML y un archivo de código de
seguridad. El archivo XAML es semejante al archivo principal, creado con proyectos nuevos de
Windows Presentation Foundation; la diferencia es que el elemento raíz del archivo XAML
nuevo es un elemento UserControl. Dentro del elemento UserControl debe crear el contenido
que configurará su control.
En el siguiente ejemplo se crea un UserControl para armar un botón redondo con el símbolo de
play.
<UserControl x:Class="WpfCSharp.UserControlWPF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="55" Width="55">
<Grid>
<Ellipse Width="50" Height="50" Stroke="DarkGray"
VerticalAlignment="Top" HorizontalAlignment="Left"
Name="BotonNegro" Fill="LightGray" />
<Polygon Name="IconoPlay" Points="18,12 18,38 35,25" Fill="Black" />
</Grid>
</UserControl>
A continuación se muestra el código para usar el control en una ventana. Antes de poder usar
un control de usuario en una ventana debe referenciar la ventana al espacio de nombres y
darle un alias a dicha referencia. Eso se hace agregando la línea
xmlns:UC="clr-namespace:WpfCSharp"
xmlns:cust=”clr-namespace:CustomWPF;assembly=CustomWPF”
55
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UC="clr-namespace:WpfCSharp"
Title="Window2" Height="300" Width="500" >
<StackPanel>
<TextBlock>Prueba de usercontrol</TextBlock>
<UC:UserControlWPF HorizontalAlignment="left" />
</StackPanel>
</Window>
Y en la ventana que usa a este control se asigna el valor de la propiedad como cualquiera de
las otras propiedades del control.
<StackPanel>
<TextBlock>Prueba de usercontrol</TextBlock>
<UC:UserControlWPF HorizontalAlignment="left" ColorIcono="Red" />
</StackPanel>
56
También podríamos crear un Control de Usuario WPF y cambiar las referencias a la clase
UserControl por el control con el cual se desea trabajar, de manera de personalizar solamente
un control.
<Button x:Class="Pruebas.controlPrueba"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="23" Width="100">
</Button>
Controles Personalizados
Si deseamos agregar a la aplicación un control personalizado lo hacemos desde Agregar
Elementos, Control Personalizado.
Una vez creado veremos la diferencia con respecto a los controles de usuario. Estos controles
no tienen código XAML, por lo tanto necesitamos crear los controles que vayamos a utilizar
mediante código subyacente lo cual genera un desarrollo mucho más complejo.
57
Módulo 4
Enlace a Datos
58
1- Enlazar Datos
Enlace a Datos
El enlazado de datos es el proceso que permite establecer una conexión entre la interfase de
usuario y la lógica de negocios. Si el enlace es correcto y los datos proveen notificaciones
correctamente, al cambiar los datos los elementos enlazados a ellos reflejan el cambio
automáticamente y a su vez también debería funcionar en sentido inverso, o sea si
modificamos el valor sobre los elementos, el dato asociado también se modifica.
WPF proporciona una forma eficaz de enlace de datos. Con WPF, puede realizar la
manipulación de datos mediante código Microsoft® .NET Framework, XAML o una combinación
de ambos. Puede realizar el enlace a controles, propiedades públicas, XML u objetos,
convirtiendo las operaciones de enlace de datos en tareas rápidas, flexibles y realmente fáciles.
Para usar el enlace de datos de WPF, debe disponer siempre de un destino y un origen.
El destino del enlace puede ser cualquier elemento o propiedad accesible que se
derive de DependencyProperty, un ejemplo es la propiedad Text del control TextBox.
El origen del enlace puede ser cualquier propiedad Public, incluidas propiedades de
otros controles, objetos de common language runtime (CLR), elementos XAML,
DataSets de ADO.NET, fragmentos XML, etc.
Clase Binding
Define un enlace que conecta las propiedades de destinos de enlace y de orígenes de datos.
Cada enlace debe especificar el elemento de destino, la propiedad de destino y el origen de
datos. Las propiedades de uso más común de esta clase son:
Source: Referencia a la fuente de datos.
ElementName: El nombre del control al cual enlazamos una propiedad, se usa como
alternativa de Source cuando el enlace es entre controles.
Path: Indica la propiedad del elemento al cual enlazaremos el dato
Converter: Instancia de una clase que implementa la interfase IValueConverter, que
intercepta los movimientos de datos entre el origen y el destino de los datos o
viceversa y permite la conversión de datos, el formateo de los mismo, etc. Son muy
convenientes y se utilizan con frecuencia
Al crear un enlace se usará la propiedad ElementName cuando el enlace se genera contra otro
elemento WPF. Se usará la propiedad Source cuando el objeto a enlazar no es un elemento
WPF, por ejemplo cuando es un recurso. No es necesario especificar ninguno de esas dos
propiedades cuando el enlace se hace mediando el uso del objeto DataContext.
DataContext="{Binding ElementName=Customer,Path=Items,Mode=OneWay}"
<StackPanel>
<StackPanel.Resources>
<TextBlock x:Key=" miFuente "></TextBlock>
</StackPanel.Resources>
60
<Label>Ingrese el nombre:</Label>
<TextBox>
<TextBox.Text>
<Binding Source="{StaticResource miFuente }" Path="Name"
UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label>El nombre ingresado es:</Label>
<TextBlock Text="{Binding Source={StaticResource miFuente }, Path=Name}"/>
</StackPanel>
En el siguiente ejemplo se agregó un botón que en su evento Click que invoca a dicho método.
De esta manera para actualizar el dato deberá presionar el botón que crea una instancia de la
clase BindingExpression e invoca al método UpdateSource.
<StackPanel>
<StackPanel.Resources>
<TextBlock x:Key="miFuente"></TextBlock>
</StackPanel.Resources>
<Label>Ingrese el Nombre:</Label>
<TextBox Name="txtNombre">
<TextBox.Text>
<Binding Source="{StaticResource miFuente}" Path="Name"
UpdateSourceTrigger="Explicit" />
</TextBox.Text>
</TextBox>
<Button Height="23" Width="70" Content="Actualizar" Click="Button_Click"></Button>
<Label>El nombre ingresado:</Label>
<TextBlock Text="{Binding Source={StaticResource miFuente}, Path=Name}"/>
</StackPanel>
En la ventana de código:
61
DisplayMemberPath: Indica el nombre de la propiedad del objeto de origen que será mostrada
en el elemento enlazado.
ItemsSource: Representa la colección que contiene los datos. Se usa cuando se enlaza a
colecciones.
IsSynchronizedWithCurrentItem: Indica si la selección debe mantener la propiedad
SelectedItem sincronizada con el elemento actual en la propiedad Items. Puede establecer el
valor de la propiedad IsSynchronizedWithCurrentItem en true para asegurarse de que el
elemento seleccionado siempre se corresponde con la propiedad CurrentItem de
ItemCollection. Se usa mucho en relaciones Cabecera-Detalle. Por ejemplo, suponga que
existen dos controles ListBox con su propiedad ItemsSource establecida en el mismo origen.
Establezca la propiedad IsSynchronizedWithCurrentItem en true en ambos cuadros de lista
para asegurarse de que el elemento seleccionado en cada ListBox sea el mismo.
<StackPanel>
<ListBox x:Name="lbProvincias" Width="248" Height="80">
<ListBoxItem Content="Buenos Aires"/>
<ListBoxItem Content="Córdoba"/>
<ListBoxItem Content="Mendoza"/>
<ListBoxItem Content="Salta"/>
<ListBoxItem Content="Neuquen"/>
</ListBox>
<TextBlock Width="248" Height="24" Text="Provincia elegida:" />
<TextBlock Width="248" Height="24">
<TextBlock.Text>
<Binding ElementName="lbProvincias"
Path="SelectedItem.Content"/>
</TextBlock.Text>
</TextBlock>
</StackPanel>
La propiedad Text del TextBlock declara un enlace al elemento seleccionado del ListBox con la
etiqueta <Binding>. El atributo ElementName de la etiqueta Binding indica el nombre del control
al cual está enlazada la propiedad Text de TextBlock. El atributo Path indica la propiedad del
elemento (en este caso ListBox) a la cual realizaremos el enlace. El resultado de este código
es que cuando se selecciona una provincia del ListBox, el nombre de esa provincia se muestra
en TextBlock.
62
El código anterior también puede escribirse de manera abreviada:
XmlDataProvider
El objeto XmlDataProvider puede usarse como origen de datos. Este objeto puede enlazar a un
documento o fragmento XML que se encuentre incrustado en la etiqueta o en un archivo al que
se hace referencia en una ubicación externa.
Se debe dar a XmlDataProvider un valor x:Key de manera que se pueda hacer referencia a él
a través de los destinos de enlace de datos. Tenga en cuenta el atributo XPath. Este atributo
define el nivel del contenido XML que se usará como el origen de datos. Esto es muy útil
cuando se enlaza a una estructura XML grande que puede incluirse en un archivo o base de
datos y los datos a los que desea realizar el enlace no constituyen el elemento raíz. El
contenido XML incrustado debe colocarse en una etiqueta <x:XData>.
En este ejemplo los datos XML están incrustados dentro del XMLDataProvider
<StackPanel>
<StackPanel.Resources>
<XmlDataProvider x:Key="Colores" XPath="/colores">
<x:XData>
<colores>
<color name="Azul"/>
<color name="Verde"/>
<color name="Amarillo"/>
<color name="Blanco"/>
<color name="Negro"/>
</colores>
</x:XData>
</XmlDataProvider>
</StackPanel.Resources>
<ListBox x:Name="lbColor" Width="248" Height="56"
ItemsSource="{Binding Source={StaticResource Colores},
XPath=color/@name}">
</ListBox>
</StackPanel>
Se puede crear un archivo XML y luego referenciar dicho archivo usando XMLDataProvider. El
ejemplo siguiente enlaza a un archivo llamado Colores.xml.
<StackPanel.Resources>
<XmlDataProvider x:Key="Colores" Source="Colores.xml" XPath="/colores"/>
</StackPanel.Resources>
Archivo Colores.xml
<?xml version="1.0" encoding="utf-8" ?>
<colores >
<color name="Azul"/>
<color name="Verde"/>
<color name="Amarillo"/>
63
<color name="Blanco"/>
<color name="Negro"/>
</colores>
ObjectDataProvider
Si se desea realizar un enlace a un objeto o a una lista de objetos, se puede utilizar el objeto
ObjectDataProvider como un recurso. La propiedad ObjectType del ObjectDataProvider
designa el objeto que proporcionará el origen de enlace de datos mientras que la propiedad
MethodName indica el método que será invocado para obtener los datos.
Existe otra serie de propiedades disponibles en ObjectDataProvider. La propiedad
ConstructionParameters le permite pasar los parámetros al constructor de la clase que se
invoca. Puede especificar también los parámetros mediante la propiedad MethodParameters y
usar la propiedad ObjectInstance para especificar una instancia existente de un objeto como
el origen.
Si desea que los datos se recuperen de manera asincrónica, puede configurar la propiedad
IsAsynchronous de ObjectDataProvider en true. A continuación el usuario podrá interactuar
con la pantalla mientras espera que los datos rellenen el control de destino que está enlazado
con el origen de ObjectDataProvider.
Al agregar un ObjectDataProvider, es necesario calificar el espacio de nombres de la clase de
origen de datos. En este caso, tengo que agregar un atributo xmlns a la etiqueta <Window>
para que se califique el acceso directo y éste indique el espacio de nombres apropiado.
En el siguiente ejemplo se crea una clase llamada Países la cual contiene un método llamado
TraerPaises, el cual devuelve todos los países de una tabla de la base de datos
AdventureWorks de SQL Server.
64
Una vez generada la clase podemos generar la referencia al método usando el control
ObjectDataProvider y asociarla a un control enlazable. Para poder llamar a la clase Paises
deberá generar la referencia con la línea xmlns:svc="clr-namespace:WpfCSharp al igual que
en el uso de controles.
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp">
<StackPanel>
<StackPanel.Resources>
<ObjectDataProvider x:Key="misPaises" ObjectType="{x:Type
svc:Paises}" MethodName="TraerPaises"/>
</StackPanel.Resources>
<ListBox x:Name="lbPersons" Height="250" Width="200"
ItemsSource="{Binding Source={StaticResource misPaises}}"
DisplayMemberPath="Name" />
</StackPanel>
</Window>
MethodParameters
Permite armar la lista de parámetros para pasarlos en la llamada al método definido en la
propiedad MethodName.
El ejemplo siguiente muestra una clase Provincias que contiene un método TraerProvincias
que recibe un parámetro de tipo carácter que permite devolver solo las provincias asociadas al
código del país recibido.
65
{
using (SqlDataAdapter da = new SqlDataAdapter("Select *
From Person.stateProvince Where
CountryRegionCode=@Cod", cn))
{
da.SelectCommand.Parameters.Add(new
SqlParameter("@Cod", SqlDbType.Char, 3));
da.SelectCommand.Parameters["@Cod"].Value = Cod;
da.Fill(dt);
}
}
return dt;
}
}
}
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp"
xmlns:system="clr-namespace:System;assembly=mscorlib" >
<StackPanel>
<StackPanel.Resources>
<ObjectDataProvider x:Key="ProvxPais" ObjectType="{x:Type svc:Provincias}"
MethodName="TraerProvincias">
<ObjectDataProvider.MethodParameters>
<system:String>US</system:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</StackPanel.Resources>
<TextBox Name="txtPais" Width="200">
<TextBox.Text>
<Binding Source="{StaticResource ProvxPais}"
Path="MethodParameters[0]"
BindsDirectlyToSource="true"
UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<ListBox x:Name="lbPersons" Height="250" Width="200"
ItemsSource="{Binding Source={StaticResource ProvxPais}}"
DisplayMemberPath="Name" />
</StackPanel>
</Window>
66
DataTemplate
Se usa para generar una visualización de los datos personalizada. DataTemplate es el objeto
que permite armar la plantilla de datos.
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp">
<StackPanel>
<StackPanel.Resources>
<ObjectDataProvider x:Key="misPaises" ObjectType="{x:Type svc:Paises}"
MethodName="TraerPaises"/>
<DataTemplate x:Key="LayoutPais" >
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Path=CountryRegionCode}" />
<TextBlock Text=" - " />
<TextBlock Text="{Binding Path=Name}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<ListBox x:Name="lbPersons" Height="250" Width="200"
ItemsSource="{Binding Source={StaticResource misPaises}}"
ItemTemplate="{DynamicResource LayoutPais}" />
</StackPanel>
</Window>
67
DataContext
Todos los controles que derivan de FrameworkElement tienen la propiedad DataContext. Esta
propiedad permite relacionarse con los datos. La propiedad DataContext es heredada por los
elementos hijos del elemento al cual está asociada, por lo cual se recomienda asociarla a los
elementos contenedores. Es muy útil para construir formularios en donde varias propiedades
se asocian a datos de la misma fuente de datos.
En el siguiente ejemplo enlaza el origen de datos al objeto Grid, de esta manera que todos los
controles contenidos especifican solo el nombre del campo ya que el enlace principal está
asociado a su contenedor.
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="500">
<Window.Resources>
<ObjectDataProvider x:Key="misPaises" ObjectType="{x:Type
svc:Paises}" MethodName="TraerPaises"/>
</Window.Resources>
<Grid DataContext="{StaticResource misPaises}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Height="21" Name="txbCodigo"
Text="Código:" />
<TextBlock Grid.Column="0" Grid.Row="1" Height="21" Name="txbNombre"
Text="Nombre:" />
<TextBlock Grid.Column="0" Grid.Row="2" Height="21" Name="txbFecha"
Text="Fecha:" />
68
<TextBox Grid.Column="1" Grid.Row="1" Height="23" Name="txtNombre"
Text="{Binding Name}"/>
<TextBox Grid.Column="1" Grid.Row="2" Height="23" Name="txtFecha" Text="{Binding
ModifiedDate}"/>
</Grid>
</Window>
<Window.Resources>
<ObjectDataProvider x:Key="misPaises" ObjectType="{x:Type svc:Paises}"
MethodName="TraerPaises"/>
</Window.Resources>
<ListView Height="150" Width="280" ItemsSource="{Binding Source={StaticResource
misPaises}}" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="50" Header="Código"
DisplayMemberBinding="{Binding
Path=CountryRegionCode}"/>
<GridViewColumn Width="200" Header="Nombre"
DisplayMemberBinding="{Binding Path=Name}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
69
2- Notificaciones de Cambio de Propiedad
Interfase INotifyPropertyChanged
Permite notificar a un cliente que el valor de una propiedad cambió. Se usa generalmente en
clientes enlazados para notificarlos que una propiedad fue modificada. Para controlar estas
notificaciones entre clientes y fuentes de datos enlazados se podría hacer de dos maneras:
Implementar una clase con la interfase INotifyPropertyChanged
Generar eventos de cambios para cada propiedad
La primera opción es la recomendada.
Supongamos el siguiente ejemplo. Tenemos una clase Persona con dos propiedades Nombre
y Edad.
int _Edad;
public int Edad
{
get { return _Edad; }
set {_Edad=value;}
}
public Persona( ) {}
public Persona(string pNombre, int pEdad)
{
Nombre = pNombre ;
Edad = pEdad;
}
}
Y la siguiente interfase de usuario que muestra los datos de una instancia de la clase Persona
y permite modificar la edad cada vez que se presiona el botón de Cumpleaños:
<Window x:Class="Notificaciones.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<Grid VerticalAlignment="Top" HorizontalAlignment="Left" Width="250" Height="100">
<Grid.ColumnDefinitions >
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition />
<RowDefinition />
<RowDefinition />
70
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" >Nombre:</TextBlock>
<TextBox Grid.Row="0" Grid.Column="1" Height="23" Name="txtNombre" />
<TextBlock Grid.Row="1" Grid.Column="0">Edad:</TextBlock>
<TextBox Name="txtEdad" Grid.Row="1" Grid.Column="1" Height="23"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Name="btnCumple"
Click="btnCumple_Click">Cumpleaños</Button>
</Grid>
</Window>
Este ejemplo funciona perfectamente, pero como no tiene sincronizados los valores del
oPersona con los TextBoxs estos tienen que actualizarse manualmente cada vez que se
genera un cambio en alguno de los dos valores.
Una forma para que la UI lleve registro de los cambios, mejor que la del ejemplo anterior, es
que el objeto avise cuando una propiedad cambia, por ejemplo, lanzando un evento. La forma
correcta de hacer esto, es que el objeto implemente INotifyPropertyChanged.
using System.ComponentModel.
public class Persona: INotifyPropertyChanged
{
......
#region Miembros de INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
De esta manera deberíamos entonces agregar a la clase Persona las siguientes líneas. Estas
líneas agregan un evento a la clase llamado PropertyChanged y el mismo es disparado a
71
través del método Notificar, que es invocado por los Set de las propiedades, reflejando el
cambio.
int _Edad;
public int Edad
{
get { return _Edad; }
set {
_Edad=value;
Notificar ("Edad");
}
}
public Persona( ) {}
public Persona(string pNombre, int pEdad)
{
Nombre = pNombre ;
Edad = pEdad;
}
72
public partial class Window1 : Window
{
Persona oPersona;
public Window1()
{
InitializeComponent();
}
Sin importar entonces en donde se de el cambio (objeto ,UI), ambos elementos se mantienen
sincronizados.
Claramente, la cantidad de código a escribir puede llegar a ser importante si la cantidad de
objetos, propiedades de los objetos y eventos aumenta. Además, este tipo de tareas parecen
73
bastante repetitivas. Por eso es que WPF las abstrae en funcionalidades del framework,
dándole el nombre de Data Binding.
3- Convertir Datos
ValueConverters
Si se desea enlazar dos propiedades de diferente tipo se deberá usar un ValueConverter. Los
ValueConventer convierten los valores del tipo de dato de la fuente al tipo de dato del destino o
viceversa. Para generar clases convertidora personalizadas es necesario usar la interfase
IValueConverter
Interfase IValueConverter
Esta interfase permite generar clases que apliquen código de conversión de datos que pueden
usarse en enlaces de datos. Para ellos cree una nueva clase e implemente la interfase
IValueConverter. Esta interfase contiene dos métodos Convert y ConvertBack. Esta clase
podrá convertir los datos de un tipo de dato a otro o modificar el aspecto de la presentación.
Ambos métodos están asociados a culturas. Si el uso de culturas no es necesario en la
aplicación con la cual se está trabajando este parámetro puede ignorarse.
using System.Windows.Data;
using System.Globalization;
using System.Windows;
Luego crearemos un recurso que nos permita relacionar esta ventana con la clase. Al momento
del enlace usando el atributo Converter podemos generar la llamada a nuestro convertidor.
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="200" Width="300"
xmlns:svc="clr-namespace:WpfCSharp" >
<StackPanel>
<StackPanel.Resources>
<svc:ConversionBooleanToVisibility x:Key="boolToVisible" />
</StackPanel.Resources>
En el siguiente ejemplo se crea una clase para controlar la validación y formateo de datos de
tipo fecha. Al escribir una fecha sobre el TextBox, cuando reconoce que lo escrito tiene un
formato tipo fecha la devuelve en formato dd/mm/aaaa en el TextBlock
[ValueConversion(typeof(DateTime), typeof(String))]
public class ConvierteFecha : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo
culture)
{
DateTime fecha;
if (value != null && DateTime.TryParse(value.ToString(), out fecha))
{
return fecha.ToString("dd/MM/yyyy");
75
}
else
return "";
}
<Window.Resources >
<svc:ConvierteFecha x:Key="ConvFecha" />
</Window.Resources>
<StackPanel>
<TextBox Name="txtFecha" />
<TextBlock Name="tbFecha" Text="{Binding ElementName=txtFecha,
Path=Text, Converter={StaticResource ConvFecha}}" />
<Button Height="20" Width="100" Content="Aceptar"></Button>
</StackPanel>
4- Validar Datos
ValidationRules
La clase Binding cuenta con una propiedad llamada ValidationRules, que puede almacenar
varias clases derivadas de ValidationRule. Todas esas reglas pueden contener cierta lógica
que intenta comprobar si el valor enlazado es válido. Se pueden crear reglas personalizadas
creando clases que deriven de ValidationRule o usar la clase ExceptionValidationRule que
invalida el dato si existen excepciones durante el enlace.
76
Clase Binding.ValidationRule
Permite crear reglas personalizadas de manera de poder validar el ingreso de datos enlazados.
En el siguiente ejemplo se creará una clase que valida el rango de edad aceptado en una
aplicación. Valida que solo se ingresen números y que el rango esté entre dos valores que
pueden establecerse a través de propiedades.
Se crea también una clase llamada GenerarPersonas que devuelve un objeto Persona
(definido en el punto 2 de este mismo capítulo). La llamada a este método generará la fuente
de datos para el enlace.
77
public class GenerarPersonas
{
public Persona TraerPersona()
{
Persona oPersona = new Persona();
oPersona.Nombre = "Juan";
oPersona.Edad = 1;
return oPersona;
}
}
Enlazamos entonces la propiedad Edad del objeto Persona a un TextBox llamado tbEdad.
También le asociamos la regla ReglaEdad y le damos valor a las propiedades MaxEdad y
MinEdad de manera que establecer el rango.
Creamos una plantilla y su estilo asociado para poder marcar los TextBox con error mostrando
un punto rojo a la derecha del mismo y el mensaje de error en un ToolTip.
<Window.Resources>
<ObjectDataProvider x:Key="Datos" ObjectType="{x:Type
svc:GenerarPersonas}" MethodName="TraerPersona"></ObjectDataProvider>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right" Margin="2,0" Width="10" Height="10">
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="#11FF1111" Offset="0" />
<GradientStop Color="#FFFF0000" Offset="1" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
78
<Binding Path="(Validation.Errors)[0].ErrorContent"
RelativeSource="{x:Static RelativeSource.Self}" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Name="tbEdad" DataContext="{StaticResource Datos}"
Style="{StaticResource EstiloError}" Width="100">
<TextBox.Text>
<Binding Path="Edad" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<svc:ReglaEdad MinEdad="1" MaxEdad="80" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Height="20" Width="100" Content="Aceptar"></Button>
</StackPanel>
IDataErrorInfo
Con la llegada de Microsoft .NET Framework 3.5, la compatibilidad de WPF para la validación
de entradas ha mejorado significativamente. ValidationRule es de utilidad para las validaciones
sencillas, pero las aplicaciones del mundo real se enfrentan a la complejidad de los datos y las
reglas de negocio. La codificación de reglas de negocio en objetos ValidationRule no sólo
vincula dicho código a la plataforma WPF, sino que además no permite que haya lógica de
negocio ahí donde debe existir: en los objetos de negocios.
Muchas aplicaciones tienen una capa de negocio, en la que la complejidad del procesamiento
de reglas de negocio se incluye en un conjunto de objetos de negocios. Al compilar en
Microsoft .NET Framework 3.5, puede usar la interfase IDataErrorInfo para que WPF pregunte
a los objetos de negocios si están en un estado válido o no. De esta forma, se elimina la
necesidad de agregar a los objetos una lógica de negocio independiente desde la capa de
negocio, y permite crear objetos de negocios independientes de la plataforma de la interfase de
usuario. Dado que la interfase IDataErrorInfo existe desde hace años, se facilita en gran
medida el uso repetido de objetos de negocios de una aplicación heredada de Windows Forms
o ASP.NET.
IDataErrorInfo implementa 2 propiedades:
Error: Devuelve el mensaje de error indicando lo que no es correcto en el objeto
79
Item: Devuelve el mensaje de error para la propiedad
Para el ejemplo tomaremos la misma clase Persona del ejemplo anterior y en ella
implementaremos la interfase IDataErrorInfo.
using System.ComponentModel;
public class Persona : IDataErrorInfo
{
private string _Nombre;
public string Nombre
{
get { return _Nombre; }
set { _Nombre = value; }
}
int _Edad;
public int Edad
{
get { return _Edad; }
set { _Edad = value; }
}
public Persona() { }
public Persona(string pNombre, int pEdad)
{
Nombre = pNombre;
Edad = pEdad;
}
80
}
return result;
}
}
}
Teniendo en cuenta las mismas fuentes de datos, plantilla y estilo del ejemplo anterior
modificamos el código para que valide usando las propiedades de esta interfase.
<StackPanel>
<TextBox Name="txtNombre" DataContext="{StaticResource Datos}"
Style="{StaticResource EstiloError}" Width="100">
<TextBox.Text>
<Binding Path="Nombre" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
<DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Name="txtEdad" DataContext="{StaticResource Datos}"
Style="{StaticResource EstiloError}" Width="100">
<TextBox.Text>
<Binding Path="Edad" UpdateSourceTrigger="PropertyChanged"
ValidatesOnExceptions="true" ValidatesOnDataErrors="true" />
</TextBox.Text>
</TextBox>
<Button Height="20" Width="100" Content="Aceptar"></Button>
</StackPanel>
ExceptionValidationRule
Representa la regla que verifica las excepciones que son disparadas durante las
modificaciones de las propiedades enlazadas.
DataErrorValidationRule
Representa la regla que verifica los errores disparados por la implementación de
IDataErrorInfo.
81
Módulo 5
Enlace a Colecciones
82
1- Enlace a Colecciones
Enlace a Colecciones
Un objeto de origen del enlace se puede tratar como un objeto único cuyas propiedades
contienen los datos, o como una recolección de datos de objetos polimórficos que suelen estar
agrupados (como el resultado de una consulta a una base de datos). Por ejemplo, es habitual
utilizar ItemsControl como ListBox, ListView o TreeView para mostrar una recolección de datos.
La propiedad que se utiliza es ItemsSource. Puede considerar la propiedad ItemsSource como
el contenido del ItemsControl. El enlace será OneWay porque la propiedad ItemsSource admite
el enlace OneWay de forma predeterminada.
using System.ComponentModel;
using System.Collections.ObjectModel;
Creamos un recurso a la clase que contiene los datos de la colección y generamos el enlace en
un ListBox
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp"
Title="Enlazar a una Colección" SizeToContent="WidthAndHeight">
<Window.Resources>
<svc:Empleados x:Key="Empleados"/>
</Window.Resources>
<StackPanel>
<TextBlock FontFamily="Verdana" FontSize="11"
Margin="5,15,0,10" FontWeight="Bold">Empleados de la Empresa</TextBlock>
<ListBox Width="200" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource Empleados}}"/>
</StackPanel>
</Window>
85
2- Vistas de Colecciones
Vistas de colecciones
Una vez que ItemsControl esté enlazado a una recolección de datos, quizás desee ordenar,
filtrar o agrupar los datos. Para ello, se utilizan vistas de colección, que son clases que
implementan la interfase ICollectionView.
Una vista de colección es un nivel situado encima de la colección de origen del enlace, que le
permite navegar y mostrar la colección de origen en función de las consultas de ordenamiento,
filtrado y agrupación, sin tener que cambiar la propia colección de origen subyacente. Una vista
de colección también contiene un puntero al elemento actual de la colección. Si la colección de
origen implementa la interfase INotifyCollectionChanged, los cambios provocados por el evento
CollectionChanged se propagarán a las vistas.
Dado que las vistas no cambian las colecciones de origen subyacente, cada colección de
origen puede tener varias vistas asociadas. El uso de vistas le permite mostrar los mismos
datos de formas diferentes. Por ejemplo, en el lado izquierdo de la página es posible que desee
mostrar las tareas ordenadas por prioridad y, en el lado derecho, agrupadas por área.
Ordenar
Las vistas pueden aplicar un criterio de orden a una colección. Cuando este criterio existe en la
colección subyacente, los datos pueden o no tener un orden relevante. La vista de la colección
le permite aplicar un orden o cambiar el orden predeterminado, en función de los criterios de
comparación especificados. Con las vistas, se puede aplicar ordenamientos controlados por el
usuario, sin tener que realizar ningún cambio en la colección subyacente ni tener tampoco que
volver a consultar el contenido de la colección. El ordenamiento se realiza usando la propiedad
CollectionViewSource.SortDescriptions.
Para mejorar el rendimiento, las vistas de colección para objetos DataTable o DataView de
ADO.NET delegan el ordenamiento y el filtrado a DataView. Esto hace que todas las vistas de
colección del origen de datos compartan el ordenamiento y el filtrado. Para habilitar el
ordenamiento y el filtrado independientes de cada vista de colección, inicialice cada vista de
este tipo con su propio objeto DataView.
El siguiente ejemplo permite ordenar los datos de la colección Empleados del ejemplo anterior
por el campo Apellido y mostrarlos en un ListBox
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
86
Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="Enlazar a una Colección" SizeToContent="WidthAndHeight">
<Window.Resources>
<svc:Empleados x:Key="Empleados"/>
<CollectionViewSource Source="{StaticResource Empleados}"
x:Key="EmplOrdenados">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Apellido"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<StackPanel>
<TextBlock FontFamily="Verdana" FontSize="11"
Margin="5,15,0,10" FontWeight="Bold">Empleados de la Empresa</TextBlock>
<ListBox Width="200" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource EmplOrdenados}}"/>
</StackPanel>
</Window>
Agrupar
Todas las vistas de colección admiten la funcionalidad de agrupación, que permite al usuario
dividir la colección en la vista de colección en grupos lógicos. Los grupos pueden ser explícitos,
donde el usuario proporciona una lista de grupos, o implícitos, donde los grupos se generan
dinámicamente en función de los datos. La agrupación se realiza usando la propiedad
CollectionViewSource.GroupDescriptions.
Teniendo en cuenta el ejemplo anterior agregamos una nueva propiedad en la clase llamada
Ciudad de manera de organizar a los empleados en su lugar de trabajo. El ejemplo permite
mostrar a los empleados organizados por ciudad
88
}
}
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="Enlazar a una Colección" SizeToContent="WidthAndHeight">
<Window.Resources>
<svc:Empleados x:Key="Empleados"/>
<CollectionViewSource Source="{StaticResource Empleados}"
x:Key="EmplOrdenados">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Apellido"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Ciudad"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<StackPanel>
<TextBlock FontFamily="Verdana" FontSize="11"
Margin="5,15,0,10" FontWeight="Bold">Empleados de la Empresa</TextBlock>
<ListBox Width="200" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource EmplOrdenados}}"
DisplayMemberPath="Apellido" Name="lb">
<ListBox.GroupStyle>
<x:Static Member="GroupStyle.Default"/>
</ListBox.GroupStyle>
</ListBox>
</StackPanel>
</Window>
Filtrar
Las vistas pueden aplicar también un filtro a una colección. Esto significa que aunque un
elemento pueda existir en la colección, esta vista en concreto está destinada a mostrar
únicamente determinado subconjunto de la colección completa. Los datos se filtran en función
a condiciones usando la propiedad Filter.
89
El siguiente ejemplo aplica un filtro de manera de no mostrar todos los datos que están sobre el
ListBox en base al valor del CheckBox. Si el CheckBox esta tildado solo se muestran los
empleados cuyo apellido empieza con P, si está destildado muestra todos los empleados. La
propiedad Filter del objeto CollectionViewSource se asocia al método que controla la condición
del filtro.
<Window x:Class="WpfCSharp.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="500"
xmlns:svc="clr-namespace:WpfCSharp"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="Enlazar a una Colección" SizeToContent="WidthAndHeight">
<Window.Resources>
<svc:Empleados x:Key="dsEmpleados" />
<CollectionViewSource Source="{StaticResource dsEmpleados}"
x:Key="EmplFiltrados" Filter="filtroP" />
</Window.Resources>
<StackPanel>
<TextBlock FontFamily="Verdana" FontSize="11"
Margin="5,15,0,10" FontWeight="Bold">Empleados de la Empresa</TextBlock>
<CheckBox Name="chkFiltro" IsChecked="True" Checked="chkFiltro_Checked"
Unchecked="chkFiltro_Unchecked" >Empleados con P</CheckBox>
<ListBox Width="200" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource EmplFiltrados}}" />
</StackPanel>
</Window>
90
if (EmplFilter != null)
EmplFilter.Filter -= new FilterEventHandler(filtroP);
}
}
Navegación de Colecciones
Cuando se enlaza datos a controles con propiedades que soportan un solo elemento de la
colección es necesario poder navegar la misma. Este escenario es común cuando tengo una
ventana que muestra datos por registro en Labels o TextBoxs. Dado que solo es posible ver un
solo registro es necesario poder navegar la colección de manera de ver todos sus elementos.
Para navegar por controles se usa la interfase ICollectionView que contiene métodos que
permiten no solo la navegación, sino también el ordenado, filtrado, etc.
En este ejemplo creamos un DataContext con la colección Empleados del ejemplo anterior y
agregamos botones para poder navegar la colección.
92
Módulo 6
Administrar
Documentos
93
1- Documentos
Definición
Windows Presentation Foundation (WPF) proporciona una amplia gama de documentos que
permiten la creación de contenido de alta fidelidad diseñado para facilitar su acceso y lectura
con respecto a las generaciones anteriores de Windows. Además de las mejoras en las
funciones y en la calidad, WPF proporciona servicios integrados para la presentación,
empaquetado y seguridad de los documentos.
Tipos de documentos
WPF divide los documentos en dos categorías generales basándose en su uso previsto
Documentos Dinámicos
Documentos Fijos
Los documentos fijos están diseñados para las aplicaciones que requieren una presentación "lo
que ve es lo que imprime" (WYSIWYG) precisa, independiente del hardware de pantalla o de
impresión utilizado. Los usos típicos para los documentos fijos incluyen la creación de
publicaciones, el procesamiento de textos y el diseño de formularios, donde es vital que se
respete el diseño de página original. Un documento fijo mantiene la colocación posicional
precisa de los elementos de contenido con independencia del dispositivo de pantalla o de
impresión utilizado. Por ejemplo, una página de un documento fijo presentada en una pantalla
de 96 ppp aparecerá exactamente igual cuando se imprima en una impresora láser de 600 ppp
o en una máquina tipográfica fotográfica de 4800 ppp. El diseño de la página permanece
inalterado en todos los casos, aunque la calidad del documento se maximiza de acuerdo con
las funciones de cada dispositivo.
En comparación, los documentos dinámicos están diseñados para optimizar su presentación y
legibilidad y son óptimos para su uso cuando la facilidad de lectura constituye el principal
escenario de consumo del documento. En lugar de establecerse en un diseño predefinido, este
tipo de documentos ajusta y recoloca dinámicamente su contenido basándose en las variables
de tiempo de ejecución, tales como el tamaño de la ventana, la resolución del dispositivo y las
preferencias opcionales del usuario. Una página web constituye un ejemplo sencillo de un
documento dinámico donde se da formato al contenido de la página dinámicamente para
ajustarlo a la ventana activa. Los documentos dinámicos optimizan la experiencia de
visualización y lectura del usuario, basándose en el entorno de tiempo de ejecución. Por
ejemplo, el mismo documento dinámico cambiará su formato dinámicamente para aportar una
legibilidad óptima en una pantalla de 19 pulgadas de alta resolución o en la pequeña pantalla
de un PDA de 2 x 3 pulgadas. Además, los documentos dinámicos tienen varias características
integradas que incluyen la búsqueda, modos de presentación que optimizan la legibilidad y la
capacidad de cambiar el tamaño y aspecto de las fuentes.
FlowDocumentScrollViewer
<FlowDocumentScrollViewer IsToolBarVisible="true">
<FlowDocument>
<Paragraph>
Este es una prueba de FlowDocument dentro de un
FlowDocumentScrollViewer
</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
FlowDocumentPageViewer
<FlowDocumentPageViewer>
<FlowDocument>
<Paragraph>
Este es una prueba de FlowDocument dentro de un FlowDocumentPageViewer
</Paragraph>
</FlowDocument>
</FlowDocumentPageViewer>
95
FlowDocumentReader
<FlowDocumentReader >
<FlowDocument>
<Paragraph>
Este es una prueba de FlowDocument dentro de un FlowDocumentReader
</Paragraph>
</FlowDocument>
</FlowDocumentReader>
El documento quedará:
Elementos en Bloque
Como hemos visto, los elementos en bloque acomodan el texto dentro del documento, entre
estos elementos tenemos:
Paragraph: Este es el más básico de los elementos en bloque y definen un bloque de
texto dentro de un párrafo. Puede haber varios párrafos en un documento.
96
List: El elemento List permite definir una lista de elementos. Cada elemento debe estar
dentro de un elemento ListItem, y los ListItem deben contener otro elemento como por
ejemplo un Paragraph.
Table: El elemento Table es muy parecido al Table de HTML, para crear una tabla, se
crea un elemento Table, que tendrá como hijo un elemento TableRowGroup, en el
TableRowGroup habrá una colección de elementos TableRow, y los elementos
TableRow tendrán a su vez una colección de elementos TableCell el cuál tendrá el
contenido de una celda, como por ejemplo un elemento Paragraph.
Section: El elemento Section es útil para agrupar otros elementos bloque, por ejemplo
podemos tener una sección de muchos párrafos con un estilo y otra sección con otros
estilos en el mismo documento.
BlockUIContainer: Permite incorporar otros elementos UIElement, como un Button, un
ListBox y otros.
List
<FlowDocumentScrollViewer>
<FlowDocument>
<List>
<ListItem>
<Paragraph>Buenos Aires</Paragraph>
</ListItem>
<ListItem>
<Paragraph>Mendoza</Paragraph>
</ListItem>
<ListItem>
<Paragraph>Córdoba</Paragraph>
</ListItem>
<ListItem>
<Paragraph>San Juan</Paragraph>
</ListItem>
<ListItem>
<Paragraph>San Luis</Paragraph>
</ListItem>
</List>
</FlowDocument>
</FlowDocumentScrollViewer>
La propiedad MarketStyle permite configurar las viñetas. Los valores posibles son: Box,
Circle, Decimal, Disc, LowerLatin, LowerRoman, None, UpperLatin, UpperRoman.
97
Tabla
<FlowDocumentScrollViewer>
<FlowDocument>
<Table BorderBrush="Black">
<TableRowGroup>
<TableRow>
<TableCell ColumnSpan="4" TextAlignment="Center">
<Paragraph FontSize="16pt" FontWeight="Bold">Ejemplo
tabla</Paragraph>
</TableCell>
</TableRow>
<TableRow FontWeight="Bold" Foreground="Red" >
<TableCell>
<Paragraph>Visitas</Paragraph>
</TableCell>
<TableCell>
<Paragraph>IP</Paragraph>
</TableCell>
</TableRow>
<TableRow Background="White">
<TableCell>
<Paragraph>100</Paragraph>
</TableCell>
<TableCell>
<Paragraph>127.0.0.1</Paragraph>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Paragraph>100</Paragraph>
</TableCell>
<TableCell>
<Paragraph>127.0.0.1</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</FlowDocument>
</FlowDocumentScrollViewer>
98
Section
<FlowDocumentScrollViewer>
<FlowDocument>
<Section FontStyle="Italic">
<Paragraph>Escribimos este párrafo en letra italic</Paragraph>
</Section>
<Section Foreground="red">
<Paragraph>Este párrafo esta escrito en rojo</Paragraph>
</Section>
</FlowDocument>
</FlowDocumentScrollViewer>
BlockUIContainer
<FlowDocument>
<Section FontStyle="Italic">
<Paragraph>Párrafo en letra italic para probar FlowDocuments.</Paragraph>
</Section>
<BlockUIContainer>
<Button Width="500" Height="23" HorizontalAlignment="Center">clic para más
información</Button>
</BlockUIContainer>
</FlowDocument>
99
Elementos en Línea (Contenido dinámico)
Los elementos dinámicos envuelven texto dentro de bloques, son usados para dar formato a
los textos, como crear hipervínculos, aplicar propiedades como negrita a una parte del texto, y
otras.
Run: El elemento run, contiene texto normal, cuando no se crea ningún elemento
dinámico, el elemento Run es aplicado implícitamente al texto
Bold, Italic y Underline: Estos elementos aplicados a un texto dan el formato de negrita,
cursiva y subrayado.
Hyperlink: Agrega un hipervínculo al texto
LineBreak: Provoca un salto de línea, este elemento no soporta contenido directo.
Floater: Permite crear una sección del documento que es paralela con el flujo principal
de otro contenido, es útil para las imágenes.
InlineUIContainer: Permite insertar elementos UIElement, dentro del elemento
dinámico, es parecido al BlockUIContainer.
<FlowDocument>
<Paragraph>Un ejemplo del los elementos
<Bold>Bold</Bold> ,
<Italic>Italic</Italic> y
<Underline>Underline.</Underline>
<LineBreak/>
Aca sigue el párrafo
<LineBreak/>
</Paragraph>
<Paragraph>Probando Hyperlink
<Hyperlink>http://www.microsoft.com</Hyperlink>
<InlineUIContainer>
<Button Width="100" Height="23">Presionar</Button>
</InlineUIContainer>
</Paragraph>
</FlowDocument>
<FlowDocument>
<Paragraph>
Este es un párrafo normal.
100
<Floater FontFamily="TimesNewRoman" FontSize="12" Width="100"
HorizontalAlignment="Left">
<Paragraph>Este párrafo esta escrito con Floater</Paragraph>
</Floater>
<LineBreak/>
Y Esta parte del párrafo comparte el renglón con el párrafo armado con Floater.
</Paragraph>
</FlowDocument>
3- Documentos Fijos
Definición
Hospeda un documento portable, de formato fijo y alta fidelidad con acceso de lectura para la
selección de texto de usuario, navegación mediante teclado y búsqueda. Para crearlos se usa
el elemento FixedDocument que enlaza lógicamente una secuencia ordenada de páginas en
un documento único, de varias páginas y diseño fijo.
PageContent es el único elemento secundario permitido del elemento FixedDocument. Cada
elemento PageContent hace referencia al origen del contenido para una página única. Los
elementos PageContent deben estar en orden de marcado secuencial, coincidiendo con el
orden de página del documento.
FixedDocument está diseñado para aplicaciones "lo que ve es lo que imprime" (WYSIWYG)
donde la aplicación define y controla el diseño del documento a fin de representar con la
máxima exactitud la pantalla o el dispositivo de impresión.
PageContent
Proporciona información sobre los elementos FixedPage dentro de FixedDocument sin exigir a
la aplicación que cargue páginas individuales. Los elementos PageContent son los únicos
elementos secundarios permitidos de FixedDocument. El orden de los elementos PageContent
dentro de FixedDocument define el orden de las páginas.
La propiedad de dependencia Source especifica el identificador de recursos uniforme (URI) del
objeto FixedPage correspondiente.
FixedPage
Proporciona el contenido de una página de formato fijo de alta fidelidad. FixedPage se utiliza
normalmente para proporcionar el contenido de una página dentro de FixedDocument.
101
FixedPage define automáticamente saltos de página al inicio y al final del contenido. Permite
controlar el tamaño de la página usando las propiedades Width y Height. Tiene también la
propiedad ContentBox que le permite establecer el área de la página durante la impresión.
DocumentViewer
Representa un control de visualización de documentos que puede hospedar contenido
FixedDocument paginado como XpsDocument.
En el ejemplo siguiente generamos una clase Persona con dos propiedades Nombre y Apellido.
Luego una segunda clase que tiene un método que devuelve un objeto List<> que devuelve
varias instancias de la clase Persona. Por último generamos una grilla para mostrar los datos y
ponemos la grilla dentro de un documento fijo.
<Window.Resources>
<ObjectDataProvider x:Key="dsPersonas" ObjectType="{x:Type svc:Personas}"
102
MethodName="TraerPersonas"/>
</Window.Resources>
<FixedDocument>
<PageContent>
<FixedPage>
<TextBlock Text="Listado de Personas" HorizontalAlignment="Center"
FontSize="14" FontWeight="Bold"></TextBlock>
<ListView Height="150" Width="280" ItemsSource="{Binding
Source={StaticResource dsPersonas}}" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="50" Header="Nombre"
DisplayMemberBinding="{Binding Path=Nombre}"/>
<GridViewColumn Width="200" Header="Apellido"
DisplayMemberBinding="{Binding Path=Apellido}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</FixedPage>
</PageContent>
</FixedDocument>
// lo muestra en la ventana
DocumentViewer dv = new DocumentViewer();
dv.Document = fd;
((IAddChild)this).AddChild(dv);
}
Empaquetado
Las API de System.IO.Packaging proporcionan un medio eficaz de organizar los datos de la
aplicación, el contenido de los documentos y los recursos relacionados en un contenedor único
de fácil acceso, portátil y sencillo de distribuir. Un archivo ZIP es un ejemplo de un tipo de
Package capaz de contener varios objetos en una sola unidad. La API de empaquetado
proporciona una implementación de ZipPackage predeterminada diseñada mediante una
norma basada en la especificación Open Packaging Conventions (OPC, o Convenciones de
empaquetado abierto) con arquitectura de archivo XML y ZIP. Las API de empaquetado de
WPF facilitan la creación de paquetes, así como el almacenamiento y acceso de objetos en su
interior. Un objeto almacenado en un Package se denomina PackagePart (elemento). Los
paquetes también pueden incluir certificados digitales firmados que se pueden utilizar para
identificar al originador de un elemento y comprobar que no se haya modificado el contenido de
un paquete. Los paquetes también incluyen una característica PackageRelationship que
permite agregar información adicional a un paquete o asociarla con elementos concretos sin
que ello modifique el contenido de los elementos existentes. Los servicios de empaquetado
también admiten Microsoft Windows Rights Management (RM).
La arquitectura de paquetes de WPF constituye los cimientos de varias tecnologías
fundamentales:
Documentos XPS que cumplen XML Paper Specification (XPS).
Documentos XML de formato abierto (.docx) de Microsoft Office "12".
Formatos de almacenamiento personalizados para su propio diseño de aplicaciones.
Basándose en las API de empaquetado, XpsDocument está diseñado específicamente para
almacenar documentos de contenido fijo de WPF. XpsDocument es un documento autónomo
que se puede abrir en un visor, mostrar en un control DocumentViewer, enrutar a una cola de
impresión o imprimir directamente en una impresora compatible con XPS.
104
Empaquetar componentes
Las API de empaquetado de WPF permiten organizar los datos y documentos de la aplicación
en una sola unidad portátil. Un archivo ZIP es uno de los tipos más comunes de paquetes y
constituye el tipo de empaquetado predeterminado proporcionado con WPF. Package es una
clase abstracta desde la que se implementa ZipPackage mediante una arquitectura de archivos
XML de norma abierta y ZIP. De manera predeterminada, el método Open utiliza ZipPackage
para crear y utilizar los archivos ZIP. Un paquete puede contener tres tipos básicos de
elementos:
PackagePart: Contenido de aplicaciones, datos, documentos y archivos de recursos.
En un archivo ZIP, los elementos del paquete corresponden a los archivos individuales
almacenados dentro del archivo ZIP. Gracias a las API de empaquetado de WPF, las
aplicaciones pueden escribir, almacenar y leer varios objetos PackagePart utilizando un
solo contenedor de archivos ZIP.
PackageDigitalSignatures: Certificado X.509 para la identificación, autenticación y
validación. La firma digital no evita que se modifique un elemento, pero se produce un
error en una comprobación de la validación en la firma digital si el elemento se ha
modificado de alguna forma. La aplicación puede emprender la acción adecuada; por
ejemplo, bloquear la apertura del elemento o notificar al usuario que se ha modificado
el elemento y ya no es seguro.
PackageRelationships: Información agregada relacionada con el paquete o con un
elemento concreto del mismo. Las relaciones de los paquetes proporcionan un medio
reconocible de agregar y asociar información adicional a los elementos individuales o al
paquete completo. Las relaciones de los paquetes se utilizan para dos funciones
primarias, definir las relaciones de dependencia entre un elemento y otro y definir las
relaciones de información que agregan notas u otros datos relacionados con el
elemento.
Un documento XML Paper Specification (XPS) es un paquete que contiene uno o más
documentos fijos junto con todos los recursos y la información necesarios para su
representación. XPS también es el formato de archivo nativo de cola de impresión de Windows
Vista. XpsDocument se almacena en un conjunto de datos ZIP estándar y puede incluir una
combinación de componentes XML y binarios, tales como archivos de imagen y de fuentes. Las
relaciones PackageRelationships se utilizan para definir las dependencias entre el contenido y
los recursos que se necesitan para representar totalmente el documento. El diseño de
XpsDocument proporciona una solución de documento único de alta fidelidad que admite varios
usos:
Lectura, escritura y almacenamiento de contenido y recursos de documentos fijos en
un único archivo portátil y fácil de distribuir.
Presentación de documentos con la aplicación Visor de XPS.
Generación de documentos en el formato de salida de cola de impresión de Windows
Vista.
Enrutamiento directo de documentos a las impresoras compatibles con XPS.
XpsDocumentWriter
Provee métodos para escribir en un documento XPS o en la cola de impresión. Esta clase no
tiene constructor, para crearla se usa el método CreateXpsDocumentWriter de la clase
XpsDocument o de la clase PrintQueue.
using System.Windows.Xps;
105
using System.Printing;
Impresión de Documentos
La clase PrintDialog provee otra forma de imprimir documentos usando el método
PrintDocument. Este método envía un DocumentPaginator a una cola de impresión. La clase
DocumentPaginator provee una clase abstracta que admite la creación de elementos de varias
páginas de un documento único, o sea que soporta paginado.
Un objeto DocumentPaginator puede ser obtenido desde un documento WPF, tanto fijo o
dinámico.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<FlowDocumentReader Grid.Row="0">
<FlowDocument Name="DocPrueba" >
<Paragraph>Escribiendo un párrafo en un documento dinámico
para probar impresión</Paragraph>
</FlowDocument>
</FlowDocumentReader>
<Button Grid.Row="1" Width="100" Height="23"
Click="Imprimir_Click">Imprimir</Button>
</Grid>
106
Módulo 7
Gráficos, Animaciones y
Multimedia
107
1- Gráficos 2D
Dibujos y formas
WPF proporciona objetos Drawing y Shape para representar el contenido de los dibujos
gráficos. Sin embargo, los objetos Drawing son estructuras más sencillas que los objetos
Shape y proporcionan mejores características de rendimiento.
Shape
Un objeto Shape permite dibujar una forma gráfica en la pantalla. Se pueden utilizar dentro de
los paneles y de la mayoría de los controles. Son fáciles de usar y proporcionan numerosas
características útiles, tales como la administración del diseño y el control de eventos. WPF
proporciona varios objetos de forma listos para usar. Todos los objetos de formas heredan de
la clase Shape.
Los objetos de formas disponibles incluyen
Ellipse: Borde elíptico.
Line: Dibuja una línea recta entre dos puntos.
Path: Dibuja una serie de líneas y curvas conectadas.
Polygon: Dibuja un polígono, que es una serie de líneas conectadas que crean una
forma cerrada.
Polyline: Dibuja una serie de líneas rectas conectadas.
Rectangle: Obtiene un área rectangular que define los puntos inicial y final del
degradado.
<Canvas>
<Ellipse Canvas.Left="10" Canvas.Top="0" Height="50" Name="ellipse1" Stroke="Black"
Width="100" Fill="Gray" />
<Rectangle Canvas.Left="80" Canvas.Top="20" Height="70" Name="rectangle1"
Stroke="Blue" Width="150" Fill="Beige" />
<Line X1="20" Y1="20" X2="150" Y2="80" Stroke="Red" StrokeThickness="4" ></Line>
<Polygon Canvas.Left="200" Canvas.Top="10"
Points="50,30 60,80 40,55 30,60"
Stroke="Purple" StrokeThickness="5"></Polygon>
</Canvas>
Drawing
Por otro lado, los objetos Drawing proporcionan una implementación más ligera para
representar formas, imágenes y texto. Hay cuatro tipos de objetos Drawing:
GeometryDrawing se utiliza para representar contenido de geometría. Los objetos de
geometría se pueden utilizar para definir la región de un control, por ejemplo, o definir
la región de recorte que se aplicará a una imagen. Los objetos de geometría pueden
ser regiones simples, tales como rectángulos y círculos, o bien regiones compuestas
creadas a partir de dos o más objetos de geometría. Las regiones de geometría más
complejas se pueden crear combinando objetos derivados de PathSegment, como
ArcSegment, BezierSegment y QuadraticBezierSegment.
ImageDrawing dibuja una imagen. proporciona menos características que Image para
representar imágenes, sin embargo, ofrece ventajas de rendimiento que lo hace ideal
para describir los fondos y las imágenes prediseñadas.
GlyphRunDrawing dibuja texto.
DrawingGroup dibuja otros dibujos. Utilice un grupo de dibujo para combinar otros
dibujos en un único dibujo compuesto.
Propiedad Clip
Obtiene o establece una región que limita la región de dibujo del gráfico.
Brush
Define los objetos usados para pintar objetos gráficos. Las clases que se derivan de Brush
describen cómo se pinta el área. Un objeto Brush "pinta" o "rellena" un área con sus resultados.
Los distintos pinceles tienen tipos de resultados diferentes. Algunos pinceles pintan un área
con un color sólido, otros con un degradado, una trama, una imagen o un dibujo. La lista
siguiente describe los distintos tipos de pinceles de WPF:
SolidColorBrush: pinta un área con un color (Color) sólido.
LinearGradientBrush: pinta un área con un degradado lineal.
RadialGradientBrush: pinta un área con un degradado radial.
ImageBrush: pinta un área con una imagen (representada por un objeto ImageSource).
110
DrawingBrush: pinta un área con un objeto Drawing. El dibujo puede incluir vectores y
objetos de mapa de bits.
VisualBrush: pinta un área con un objeto Visual. VisualBrush permite duplicar el
contenido de una parte de la aplicación en otra área; es muy útil para crear efectos de
reflexión y ampliar partes de la pantalla.
<StackPanel>
<Rectangle Width="50" Height="20" Fill="#FF0000FF" />
<Rectangle Width="50" Height="20">
<Rectangle.Fill>
<SolidColorBrush>
<SolidColorBrush.Color>
<!-- Usa RGB. Cada valor tiene un rango de 0-255.
R red, G green, B blue. A controla transparencia -->
<Color A="255" R="255" G="0" B="255" />
</SolidColorBrush.Color>
</SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="200" Height="50">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Yellow" Offset="0.0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Width="200" Height="100">
<Rectangle.Fill>
<RadialGradientBrush
GradientOrigin="0.5,0.5"
Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5">
<RadialGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
</StackPanel>
111
2- Imágenes
Imágenes
La creación de imágenes de WPF proporciona una mejora significativa con respecto a las
funciones de creación de imágenes de las versiones anteriores de Windows. Las funciones de
creación de imágenes, tales como la presentación de mapas de bits o el uso de imágenes en
controles comunes, se administraban principalmente por las interfases de programación de
aplicaciones (API) de la interfaz de dispositivo gráfico (GDI) o GDI+ de Microsoft Windows.
Estas API proporcionaban la funcionalidad básica para la creación de imágenes, pero carecían
de características tales como compatibilidad con la extensibilidad de códec y con imágenes de
alta fidelidad. La API de creación de imágenes de WPF se ha rediseñado para superar las
limitaciones de GDI y GDI+ y proporcionar un nuevo conjunto de API para mostrar y utilizar
imágenes en las aplicaciones.
Al utilizar imágenes, tenga en cuenta las recomendaciones siguientes para obtener el mejor
rendimiento:
Si su aplicación le exige que muestre imágenes en miniatura, puede ser conveniente
crear una versión de dimensiones reducidas de la imagen. De manera predeterminada,
WPF carga la imagen y la decodifica a su tamaño completo. Si sólo desea una versión
en miniatura de la imagen, WPF decodifica innecesariamente la imagen a su tamaño
completo y, a continuación, la reduce a la escala en miniatura. Para evitar este
consumo de recursos innecesario, puede solicitar a WPF que decodifique la imagen a
un tamaño en miniatura o bien solicitar a WPF que cargue una imagen en miniatura.
Siempre decodifique la imagen al tamaño deseado y no al tamaño predeterminado. De
este modo, no sólo mejorará el espacio de trabajo de la aplicación, sino también la
velocidad de ejecución.
Si es posible, combine las imágenes en una imagen única, como una tira
cinematográfica creada de varias imágenes.
Se utiliza un códec para descodificar o codificar cada formato multimedia concreto. Las
imágenes WPF incluyen un códec para los formatos de imagen BMP, JPEG, PNG, TIFF,
Windows Media Photo, GIF y de icono. Cada uno de estos códecs permite a las aplicaciones
decodificar y, con la excepción de los iconos, codificar sus formatos de imagen respectivos. La
selección del códec es automática a menos que se especifique un descodificador concreto.
Control Image
Hay varias maneras de mostrar una imagen en una aplicación de Windows Presentation
Foundation (WPF). Las imágenes se pueden mostrar mediante un control Image, pintado en un
objeto visual con un objeto ImageBrush, o dibujado con un objeto ImageDrawing.
Image es un elemento de marco de trabajo y la manera principal de mostrar imágenes en
aplicaciones. En muchos casos se utiliza un objeto BitmapImage para hacer referencia a un
archivo de imagen. BitmapImage es un BitmapSource especializado que se optimiza para la
carga en Lenguaje de marcado de aplicaciones extensible (XAML) y constituye una manera
fácil de mostrar imágenes como la propiedad Source de un control Image. El uso de las
propiedades DecodePixelWidth o DecodePixelHeight ahorra memoria de la aplicación.
<StackPanel>
<Image Width="150" Source="mslogo.jpg"/>
<Image Width="130">
<Image.Source>
<BitmapImage DecodePixelWidth="130" UriSource="mslogo.jpg" />
</Image.Source>
</Image>
</StackPanel>
112
Girar, Convertir y Recortar Imágenes
WPF permite a los usuarios transformar imágenes utilizando las propiedades de BitmapImage
o mediante objetos BitmapSource adicionales, tales como CroppedBitmap o
FormatConvertedBitmap. Estas transformaciones de imagen pueden girar una imagen, ajustar
su escala, cambiar su formato de píxel o recortarla.
Los giros de imagen se realizan mediante la propiedad Rotation de BitmapImage. Los giros
sólo se pueden hacer en incrementos de 90 grados.
113
En este ejemplo recortamos la imagen.
Expandir imágenes
La propiedad Stretch controla cómo se expande una imagen para rellenar su contenedor. La
propiedad Stretch acepta los valores siguientes, definidos por la enumeración Stretch:
None: la imagen no se expande para rellenar el área de salida. Si la imagen es mayor
que el área de salida, la imagen se dibuja en el área de salida, y se recorta lo que
sobra.
Fill: se ajusta la escala de la imagen al área de salida. Dado que la escala se aplica de
manera independiente al alto y al ancho de la imagen, puede que no se conserve su
relación de aspecto original. Es decir, es posible que la imagen se distorsione para
rellenar totalmente el contenedor de salida.
Uniform: se cambia la escala de la imagen de modo que se ajuste completamente al
área de salida. Se conserva la relación de aspecto de la imagen.
UniformToFill: la escala de la imagen se ajusta hasta rellenar completamente el área de
salida y se conserva la relación de aspecto original de la imagen.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Grid.Column="0" Source="mslogo.jpg" Stretch="None" />
<Image Grid.Row="0" Grid.Column="1" Source="mslogo.jpg" Stretch="Fill" />
<Image Grid.Row="0" Grid.Column="2" Source="mslogo.jpg" Stretch="Uniform" />
<Image Grid.Row="0" Grid.Column="3" Source="mslogo.jpg" Stretch="UniformToFill" />
</Grid>
114
Pintar con imágenes
Las imágenes también se pueden mostrar en una aplicación pintándolas con un objeto Brush.
Los pinceles permiten pintar los objetos de una interfase de usuario con cualquier cosa desde
simples colores sólidos hasta conjuntos complejos de tramas e imágenes. Para pintar con
imágenes, utilice un ImageBrush. Un ImageBrush es un tipo de TileBrush que define su
contenido como una imagen de mapa de bits. Un ImageBrush muestra una imagen única, que
se especifica mediante su propiedad ImageSource. Puede controlar cómo se expande, alinea o
coloca en mosaico la imagen, a fin de evitar la distorsión y crear tramas y otros efectos.
3- Gráficos 3D
La implementación de 3D en WPF permite a los programadores dibujar, transformar y animar
gráficos 3D en el marcado y en el código de procedimientos, utilizando las mismas funciones
permitidas por la plataforma a los gráficos 2D. Los programadores pueden combinar gráficos
2D y 3D para crear controles enriquecidos, proporcionar ilustraciones complejas de datos o
mejorar la experiencia del usuario con la interfaz de una aplicación. La compatibilidad con 3D
de WPF no está diseñada para proporcionar una plataforma con todas las funciones para la
programación de juegos.
Viewport3D
El contenido de los gráficos 3D de WPF se encapsula en un elemento, Viewport3D, que puede
participar en la estructura de elementos bidimensionales. El sistema de gráficos trata
Viewport3D como un elemento visual bidimensional como tantos en WPF. Viewport3D actúa
como una ventana, un área de visualización, a una escena tridimensional. Expresado de modo
más preciso, es la superficie sobre la que se proyecta una escena 3D.
En una aplicación 2D convencional, utilice Viewport3D como cualquier otro elemento
contenedor, como Grid o Canvas. Aunque puede utilizar Viewport3D con otros objetos de
dibujo 2D en el mismo gráfico de escena, no puede combinar objetos 2D y 3D dentro de
Viewport3D.
115
Espacio de coordenadas 3D
El sistema de coordenadas de WPF para gráficos 2D localiza el origen en la parte superior
izquierda del área de representación (que suele ser la pantalla). En el sistema 2D, los valores
positivos del eje X se extienden hacia la derecha, y los valores positivos del eje Y extienden
hacia abajo. En el sistema de coordenadas 3D, sin embargo, el origen se encuentra en el
centro del área de representación, los valores positivos del eje X se extienden hacia la derecha,
los valores positivos del eje Y se extienden hacia arriba (no hacia abajo) y los valores positivos
del eje Z se extienden hacia el exterior partiendo del origen, es decir, hacia el espectador.
Cámaras y proyecciones
Los programadores que trabajan en 2D están acostumbrados a colocar los elementos de dibujo
primitivos en una pantalla bidimensional. Al crear una escena 3D, es importante recordar que,
en realidad, se está creando una representación 2D de los objetos 3D. Dado que una escena
3D tiene un aspecto diferente dependiendo del punto de vista del espectador, debe especificar
ese punto de vista. La clase Camera permite especificar este punto de vista para una escena
3D.
Otra manera de entender cómo se representa una escena 3D en una superficie 2D consiste en
describir dicha escena como una proyección sobre la superficie de visualización.
ProjectionCamera permite especificar proyecciones diferentes y sus propiedades para
cambiar la manera en que el espectador ve los modelos 3D. PerspectiveCamera proporciona
una perspectiva de punto de fuga. Puede especificar la posición de la cámara en el espacio de
coordenadas de la escena, la dirección y el campo de visión de la cámara, y un vector que
define la dirección "hacia arriba" en la escena.
Las propiedades NearPlaneDistance y FarPlaneDistance de ProjectionCamera limitan el
intervalo de proyección de la cámara. Dado que las cámaras se pueden ubicar en cualquier
parte de la escena, es posible situarlas dentro de un modelo o muy cerca de él, con lo que
resultaría difícil distinguir correctamente los objetos. NearPlaneDistance permite especificar
una distancia mínima a la cámara a partir de la cual no se dibujarán objetos. A la inversa,
FarPlaneDistance permite especificar una distancia máxima a la cámara, más allá de la que no
se dibujarán objetos, lo que garantice que no se incluyan en la escena aquellos objetos que
estén demasiado lejos para ser reconocibles.
OrthographicCamera especifica una proyección ortogonal de un modelo 3D sobre una
superficie visual 2D. Al igual que otras cámaras, especifica una posición, dirección de
visualización y dirección "hacia arriba". OrthographicCamera describe un cuadro de vista cuyos
lados son paralelos, en lugar de uno cuyos lados convergen en un punto de la cámara.
116
Elementos primitivos de modelo y malla
Model3D es la clase base abstracta que representa un objeto 3D genérico. Para generar una
escena 3D, necesita algunos objetos que ver; los objetos que componen el gráfico de escena
se derivan de la clase Model3D. En la actualidad, WPF permite geometrías de modelado con
GeometryModel3D.
Para crear un modelo, comience por crear un elemento primitivo, o malla. Un elemento 3D
primitivo es una colección de vértices que constituyen una entidad 3D única. La mayoría de los
sistemas 3D proporcionan elementos primitivos modelados a partir de la figura cerrada más
simple: un triángulo definido por tres vértices. Dado que los tres puntos de un triángulo son
coplanares, puede seguir agregando triángulos para modelar formas más complejas,
denominadas mallas.
El sistema 3D de WPF proporciona actualmente la clase MeshGeometry3D, que permite
especificar cualquier geometría; en este momento, no admite elementos 3D primitivos
predefinidos, tales como esferas o formas cúbicas. Empiece por crear MeshGeometry3D
especificando una lista de vértices de triángulos como su propiedad Positions. Cada vértice se
especifica como un Point3D. (En Lenguaje de marcado de aplicaciones extensible (XAML),
especifique esta propiedad como una lista de números agrupados en ternas, que representan
las coordenadas de cada vértice.) Según cuál sea la geometría, la malla puede estar
compuesta de muchos triángulos, algunos de los cuales compartirán las mismas esquinas
(vértices). WPF necesita información sobre qué triángulos comparten qué vértices para dibujar
la malla correctamente. Esta información se proporciona especificando una lista de índices de
triángulos con la propiedad TriangleIndices. Esta lista especifica el orden en el que los puntos
especificados en la lista Positions determinarán un triángulo.
Puede seguir definiendo el modelo especificando valores para las propiedades Normals y
TextureCoordinates. Para representar la superficie del modelo, el sistema de gráficos necesita
información sobre en qué dirección mira la superficie de cualquier triángulo dado. Utiliza esta
información para realizar los cálculos de iluminación del modelo: las superficies que miran
directamente hacia una fuente de luz parecen más luminosas que las que tienen un ángulo que
las oculta de la luz. Aunque WPF puede determinar los vectores normales predeterminados
utilizando las coordenadas de posición, también es posible especificar vectores normales
diferentes para crear un aspecto más aproximado de las superficies curvas.
117
Materiales del Modelo
Para que una malla parezca un objeto tridimensional, debe tener una textura aplicada que
cubra la superficie definida por sus vértices y triángulos, de manera que se pueda iluminar y
proyectar por la cámara. En 2D, se utiliza la clase Brush para aplicar colores, patrones,
degradados y otro contenido visual a las áreas de la pantalla. El aspecto de los objetos 3D, sin
embargo, depende del modelo de iluminación, no sólo del color o del patrón que se les aplica.
Los objetos reales reflejan la luz de manera distinta según la calidad de su superficie: las
superficies satinadas y brillantes no tienen el mismo aspecto que las superficies ásperas o
mates, y algunos objetos parecen absorber la luz, mientras que otros la emiten. Puede aplicar a
los objetos 3D los mismos pinceles que a los objetos 2D, pero no directamente.
Para definir las características de la superficie de un modelo, WPF utiliza la clase abstracta
Material. Las subclases concretas de Material determinan algunas de las características del
aspecto de la superficie del modelo y, además, cada una de ellas proporciona una propiedad
Brush a la que puede pasar SolidColorBrush, TileBrush o VisualBrush.
DiffuseMaterial especifica que el pincel se aplicará al modelo como si estuviera
iluminado con una luz difusa. Utilizar DiffuseMaterial es lo que más se parece al uso
directo de pinceles en los modelos 2D; las superficies del modelo no reflejan la luz
como si brillasen.
SpecularMaterial especifica que el pincel se aplicará al modelo como si la superficie del
modelo fuese dura o brillante, capaz de reflejar la iluminación. Puede establecer el
grado en que la textura sugerirá esta cualidad de reflexión, o "brillo", especificando un
valor para la propiedad SpecularPower.
EmissiveMaterial permite especificar que la textura se aplicará como si el modelo
estuviera emitiendo luz del mismo color que el pincel. Esto no convierte el modelo en
una luz; sin embargo, participará de manera diferente en el sombreado que si se aplica
textura con DiffuseMaterial o SpecularMaterial.
Para mejorar rendimiento, las caras ocultas de GeometryModel3D (aquéllas que están fuera de
la vista porque se encuentran en el lado del modelo opuesto a la cámara) se seleccionan de la
escena. Para especificar el Material que se aplicará a la cara oculta de un modelo, como un
plano, establezca la propiedad BackMaterial del modelo.
Para lograr algunas cualidades de la superficie, como el brillo o los efectos de reflejo, puede
ser conveniente aplicar sucesivamente varios pinceles diferentes a un modelo. Puede aplicar y
reutilizar varios materiales mediante la clase MaterialGroup. Los elementos secundarios de
MaterialGroup se aplican del primero al último en varias pasadas de representación.
Iluminar la escena
Las luces de los gráficos 3D hacen lo mismo que las luces en el mundo real: permiten ver las
superficies. Más concretamente, las luces determinan qué parte de una escena se incluye en la
proyección. Los objetos de luz en WPF crean gran variedad de efectos de luz y sombra y
siguen el modelo de comportamiento de diversas luces del mundo real. Debe incluir por lo
menos una luz en la escena, pues de lo contrario no habrá ningún modelo visible.
Las luces siguientes se derivan de la clase base Light:
AmbientLight: proporciona iluminación de ambiente que ilumina uniformemente todos
los objetos sin tener en cuenta su ubicación u orientación.
DirectionalLight: ilumina como una fuente de luz distante. Las luces direccionales
tienen Direction, que se especifica como Vector3D, pero ninguna ubicación concreta.
PointLight: ilumina como una fuente de luz cercana. Las luces puntuales tienen
posición y emiten la luz desde esa posición. Los objetos de la escena se iluminan
dependiendo de su posición y distancia con respecto a la luz. PointLightBase expone
una propiedad Range, que determina una distancia más allá de la cual la luz no
iluminará los modelos. PointLight también expone propiedades de atenuación que
118
determinan cómo disminuye la intensidad de la luz con la distancia. Puede especificar
interpolaciones constantes, lineales o cuadráticas para la atenuación de la luz.
SpotLight: hereda de PointLight. Los focos de luz iluminan como las luces puntuales, y
tienen posición y dirección. Proyectan la luz en un área cónica establecida por las
propiedades InnerConeAngle y OuterConeAngle, especificada en grados.
Las luces son objetos Model3D, por lo que puede transformar y animar las propiedades de la
luz, incluidas su posición, posición, color, dirección y alcance.
119
</MaterialGroup>
</GeometryModel3D.Material>
Transformar modelos
Al crear modelos, éstos tienen una ubicación determinada en la escena. Para mover esos
modelos por la escena, girarlos o cambiar su tamaño, no es práctico cambiar los vértices que
definen los propios modelos. En lugar de ello, al igual que en 2D, se aplican transformaciones a
los modelos.
Cada objeto de modelo tiene una propiedad Transform con la que puede mover, reorientar o
cambiar el tamaño del modelo. Al aplicar una transformación, en realidad lo que se hace es
desplazar todos los puntos del modelo según un vector o valor especificado por la
transformación. En otras palabras, se transforma el espacio de coordenadas en el que se ha
definido el modelo ("espacio del modelo"), pero no se cambian los valores que constituyen la
geometría del modelo en el sistema de coordenadas de la escena completa ("espacio
universal").
Animar modelos
La implementación de 3D en WPF utiliza el mismo sistema de control de tiempo y animación
que los gráficos 2D. En otras palabras, para animar una escena 3D, se animan las propiedades
de sus modelos. Es posible animar directamente las propiedades de los elementos primitivos,
pero suele ser más fácil de animar las transformaciones que cambian la posición o el aspecto
120
de los modelos. Dado que las transformaciones se pueden aplicar a los objetos Model3DGroup
así como a los modelos individuales, es posible aplicar un conjunto de animaciones a un
elemento secundario de Model3DGroup y otro conjunto de animaciones a un grupo de objetos
secundarios. También puede lograr gran variedad de efectos visuales animando las
propiedades de iluminación de la escena. Finalmente, si lo desea puede animar la propia
proyección, animando la posición de la cámara o el campo de visión.
Para animar un objeto en WPF, se crea una escala de tiempo, se define una animación (que,
en realidad, es un cambio de algún valor de propiedad a lo largo del tiempo) y se especifica la
propiedad a la que aplicar la animación. Dado que todos los objetos de una escena 3D son
elementos secundarios de Viewport3D, las propiedades de destino de cualquier animación que
desea aplicar a la escena son propiedades de propiedades de Viewport3D.
La clase Storyboard controla las animaciones con una escala de tiempo y proporciona
información de destino de objetos y propiedades para sus animaciones secundarias. Un objeto
Storyboard puede considerarse como un contenedor de otros objetos de animación (por
ejemplo, DoubleAnimation) y de otros objetos Storyboard. Es decir, se pueden anidar objetos
Storyboard unos dentro de otros y especificar valores de BeginTime para cada Storyboard por
separado. El uso de guiones gráficos anidados puede ayudarle a orquestar secuencias de
animación detalladas. Cada Storyboard secundario esperará hasta que comience su
Storyboard primario y, a continuación, iniciará la cuenta atrás antes de que comience a su vez.
La Clase DoubleAnimation anima el valor de una propiedad Double entre dos valores de
destino usando la interpolación lineal en una propiedad Duration especificada. Una animación
actualiza el valor de una propiedad durante un período de tiempo. Un efecto de animación
puede ser sutil, como mover un Shape unos cuantos píxeles a la izquierda o a la derecha, o
espectacular, como ampliar el tamaño original de un objeto 200 veces mientras gira y cambia
de color. Para crear una animación, se asocia una animación al valor de propiedad de un
objeto. La clase DoubleAnimation crea una transición entre dos valores de destino. Para
establecer sus valores de destino, use sus propiedades From, To y By. La animación progresa
desde el valor From hasta el valor especificado por la propiedad To. By permite sumar valores.
Transformaciones de Traslación
Las transformaciones 3D heredan de la clase base abstracta Transform3D; se incluyen las
clases de transformaciones afines TranslateTransform3D, ScaleTransform3D y
RotateTransform3D. El sistema 3D de Windows Presentation Foundation (WPF) también
proporciona una clase MatrixTransform3D que permite especificar las mismas transformaciones
en operaciones de matrices más precisas.
TranslateTransform3D translada objetos en un plazo tridimensional (x-y-z).
121
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D TriangleIndices="0,1,2 3,4,5 "
Normals="0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 "
TextureCoordinates="0,0 1,0 1,1 1,1 0,1 0,0 "
Positions="-0.5,-0.5,0.5 0.5,-0.5,0.5 0.5,0.5,0.5 0.5,0.5,0.5 -0.5,0.5,0.5
-0.5,-0.5,0.5 " />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<MaterialGroup>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="Cyan" Opacity="0.3"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup>
</GeometryModel3D.Material>
<!-- La propiedad OffsetX esta animada por el Storyboard de abajo. -->
<GeometryModel3D.Transform>
<TranslateTransform3D x:Name="myTranslateTransform3D"
OffsetX="0" OffsetY="0" OffsetZ="0" />
</GeometryModel3D.Transform>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
<!-- Dispara la transformación de traslación -->
<Viewport3D.Triggers>
<EventTrigger RoutedEvent="Viewport3D.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myTranslateTransform3D"
Storyboard.TargetProperty="OffsetX"
To="-0.8" AutoReverse="True" RepeatBehavior="Forever" />
<!-- Si se desea animar OffsetY y/o OffsetZ crear un código similar-->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Viewport3D.Triggers>
</Viewport3D>
</Canvas>
122
Transformaciones de Escala
ScaleTransform3D cambia la escala del modelo mediante un vector de escala especificado
respecto a un punto central. Especifique una escala uniforme, que escala el modelo mediante
el mismo valor en los ejes X, Y y Z, para cambiar proporcionalmente el tamaño del modelo. Por
ejemplo, si se establece el valor de las propiedades ScaleX, ScaleY y ScaleZ de la
transformación en mitades de 0,5 del tamaño del modelo; al establecer el valor de las mismas
propiedades en 2 se duplica su escala en los tres ejes.
123
Para escalar un modelo "en contexto", especifique el centro del mismo estableciendo el valor
de las propiedades CenterX, CenterY y CenterZ de ScaleTransform3D. De esta forma, se
asegura de que el sistema de gráficos escala el espacio del modelo y, a continuación, lo
traslada para centrarse en el objeto Point3Despecificado. A la inversa, si ha generado el
modelo en el origen y ha especificado un punto central diferente, espere ver el modelo
trasladado fuera del origen.
Para ver la transformación de escala prueba el código siguiente con y la parte de código en
negrita y observe la transformación.
124
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
</Canvas>
Transformaciones de Giro
Puede girar un modelo 3D de varias maneras diferentes. Una transformación de giro típica
especifica un eje y un ángulo de giro alrededor de ese eje. La clase RotateTransform3D
permite definir un objeto Rotation3D con su propiedad Rotation. Después especifique las
propiedades Axis y Angle en Rotation3D, en este caso un objeto AxisAngleRotation3D, para
definir la transformación.
<!-- La propiedad Rotation del objeto esta animada para causar la rotación del
mismo -->
<GeometryModel3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="myAngleRotation" Axis="0,3,0"
Angle="40" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
126
Storyboard.TargetName="myAngleRotation"
Storyboard.TargetProperty="Axis"
From="0,3,0" To="1,0,1" Duration="0:0:4" AutoReverse="True"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Viewport3D.Triggers>
</Viewport3D>
</Canvas>
4- Multimedia
Multimedia permite a Windows Presentation Foundation (WPF) integrar Audio y Video a las
aplicaciones.
Media API
Las clases MediaElement y MediaPlayer se usan para presentar Audio y Video. Estas clases
pueden ser controladas interactivamente o por reloj. Se pueden usar con el control Microsoft
Windows Media Player 10.
MediaElement es un elemento que puede ser utilizado como contenido de varios controles.
Puede usarse desde XAML o por código. MediaPlayer, en cambio, está diseñado para objetos
Drawing. Solo puede ser presentado usando VideoDrawing o directamente interactuando con
DrawingContext. No puede usarse en XAML.
<StackPanel Margin="20">
<MediaElement Source="media/numbers-aud.wmv" />
</StackPanel>
SoundPlayer
Controla la reproducción del sonido de un archivo .wav. Proporciona una interfaz simple para
cargar y reproducir un archivo .wav. Permite cargar un archivo .wav desde una ruta de acceso
de archivo, una dirección URL, un Stream que contiene un archivo .wav o un recurso
incrustado que contiene un archivo .wav.
Para reproducir un sonido mediante la clase SoundPlayer, configure un SoundPlayer con una
ruta de acceso al archivo .wav y llame a uno de los métodos de reproducción. Para identificar el
archivo .wav que desee reproducir, utilice uno de los constructores o establezca el valor de la
propiedad SoundLocation o Stream. El archivo se puede cargar antes de reproducirlo utilizando
uno de los métodos de carga, o bien, se puede diferir la carga hasta que se llame a uno de los
métodos de reproducción. El objeto SoundPlayer configurado para cargar un archivo .wav
desde una Stream o una dirección URL debe cargar el archivo en la memoria antes de que
comience la reproducción.
128
Módulo 8
Configuración y
Distribución
129
1- Configuración
Configurar Aplicaciones
La configuración de la aplicación le permite almacenar y recuperar dinámicamente la
configuración de propiedades y cualquier otra información de la aplicación, así como mantener
las preferencias de usuario y de la aplicación personalizada en el equipo cliente. Con
frecuencia son datos necesarios, como una cadena de conexión, para ejecutar la aplicación
que no desea incluir directamente en el código. Quizá desee almacenar dos cadenas de
conexión a bases de datos diferentes y recuperar una de ellas en función de la ubicación del
equipo en tiempo de ejecución. O bien, puede almacenar las preferencias de color de un
usuario y, a continuación, recuperarlas la siguiente vez que ejecute la aplicación.
Cada configuración debe tener un nombre único; el nombre puede ser cualquier combinación
de letras, números o un carácter de subrayado que no empiece por un número y sin espacios.
Se puede cambiar el nombre mediante la propiedad Name.
La configuración de la aplicación se puede almacenar como cualquier tipo de datos que sea
XML serializable o que tenga un TypeConverter que implemente ToString/FromString. Los tipos
más comunes son String, Integer y Boolean, pero también puede almacenar valores como
Color, Object, o bien como una cadena de conexión.
La configuración de la aplicación también contiene un valor. El valor se establece mediante la
propiedad Value y debe coincidir con el tipo de datos de la configuración.
Además, la configuración de la aplicación se puede enlazar directamente a una propiedad de
un formulario o de un control en tiempo de diseño.
Hay dos tipos de configuración de la aplicación, en función del ámbito:
La configuración de ámbito de aplicación se puede utilizar para obtener información
como una dirección URL para un servicio Web o una cadena de conexión a bases de
datos. Estos valores se asocian a la aplicación; por tanto, los usuarios no pueden
cambiarlos en tiempo de ejecución.
La configuración de ámbito de usuario se puede utilizar para obtener información como
recordar la última posición de un formulario o una preferencia de fuente. Los usuarios
pueden modificar estos valores en tiempo de ejecución.
Puede cambiar el tipo de una configuración con la propiedad Scope.
App.config
Este archivo XML permite configurar las aplicaciones WPF. Si la aplicación no contiene este
archivo puede agregarse con la opción Agregar Nuevo Elemento – Archivo de Configuración de
Aplicaciones.
En tiempo de diseño, existen dos maneras de crear la configuración de la aplicación: mediante
la página Configuración del Diseñador de proyectos o desde la ventana Propiedades de un
formulario o un control, lo que le permite enlazar una configuración directamente a una
propiedad.
Cuando crea una configuración con ámbito de aplicación (por ejemplo, una cadena de
conexión a bases de datos o una referencia a los recursos del servidor), Visual Studio la
guarda en un archivo app.config con la etiqueta <applicationSettings> (las cadenas de
conexión se guardan en la etiqueta <connectionStrings>). Cuando crea una configuración con
ámbito de usuario (por ejemplo, fuente predeterminada, página principal o tamaño de la
ventana), Visual Studio la guarda en un archivo app.config con la etiqueta <userSettings>.
131
Valor: El valor a devolver al leer la configuración
string sc = Properties.Settings.Default.AdvWorks;
XCopy
La implementación de XCopy se refiere al uso del programa de línea de comandos XCopy para
copiar los archivos de una ubicación a otra. La implementación de XCopy es adecuada en las
siguientes circunstancias:
Una aplicación es autónoma; no necesita actualizar el cliente para ejecutarse.
Los archivos la aplicación se deben mover de una ubicación a otra; por ejemplo, de la
ubicación de compilación (disco local, recurso compartido de archivos UNC, etc.) a la
ubicación de publicación (sitio web, recurso compartido de archivos UNC, etc.).
Una aplicación no requiere la integración en el shell (acceso directo del menú de Inicio,
icono de escritorio, etc.).
Aunque XCopy es adecuado para escenarios de implementación simples, presenta limitaciones
cuando se requieren funciones de implementación más complejas. En particular, al utilizar
Xcopy se provoca una sobrecarga al crear, ejecutar y mantener los scripts necesarios para
administrar la implementación de una manera robusta. Además, XCopy no admite el control de
versiones, la desinstalación ni la reversión.
Publicación ClickOnce
Dentro de las propiedades del proyecto WPF tenemos una solapa Publicar. Podemos usar
esta solapa para configurar la publicación. Podemos elegir la ubicación predetermina para la
publicación, el modo de instalación, los archivos que forman parte de la aplicación, cuales
serán lo requisititos previos a la instalación (verificación de componentes que deberán estar
previamente instalados), si la instalación soportará actualizaciones automáticas, versionado,
etc.
133
Como Publicar
Publicar en Web
3. En la página ¿Dónde desea publicar la aplicación?, escriba una dirección URL válida con el
formato http://www.microsoft.com/foldername y, a continuación, haga clic en Siguiente.
4. En la página ¿La aplicación estará disponible sin conexión?, haga clic en la opción
adecuada:
- Si desea permitir que se ejecute la aplicación cuando el usuario esté desconectado de
la red, haga clic en Sí, esta aplicación va a estar disponible con conexión o sin ella. Se
creará un acceso directo en el menú Inicio para la aplicación.
- Si desea ejecutar la aplicación directamente desde la ubicación de publicación, haga
clic en No, esta aplicación sólo está disponible en línea. No se creará un acceso directo
en el Menú Inicio.
Haga clic en Siguiente para continuar.
5. Haga clic en Finalizar para publicar la aplicación.
El estado de la publicación se muestra en el área de notificación de estado.
CD-ROM o DVD-ROM
3. En la página ¿Dónde desea publicar la aplicación?, escriba la ruta de acceso del archivo o
ubicación del FTP donde se publicará la aplicación, por ejemplo d:\deploy. A continuación,
haga clic en Siguiente.
4. En la página Instalación de la aplicación, haga clic en Desde un CD-ROM o un DVD-ROM y,
a continuación, haga clic en Siguiente.
5. Si distribuye la aplicación en CD-ROM, puede que desee proporcionar actualizaciones desde
un sitio Web. En la página ¿Dónde buscará la aplicación las actualizaciones?, elija una opción
de actualización:
- Si la aplicación comprueba las actualizaciones, haga clic en la opción La aplicación
buscará actualizaciones en la siguiente ubicación y escriba la ubicación donde se
enviarán las actualizaciones. Puede ser una ubicación de archivo, un sitio web o un
servidor FTP.
- Si la aplicación no comprueba las actualizaciones, haga clic en la opción La aplicación
no buscará actualizaciones.
Haga clic en Siguiente para continuar.
6. Haga clic en Finalizar para publicar la aplicación.
El estado de la publicación se muestra en el área de notificación de estado.
136
Links Relacionados
Introducción a WPF
http://msdn.microsoft.com/es-ar/library/aa970268.aspx
Enlace a Datos
http://msdn.microsoft.com/es-ar/magazine/cc163299.aspx
http://channel9.msdn.com/posts/eliseta/WPF-para-desarrolladores/
http://www.c-
sharpcorner.com/UploadFile/mahesh/GridViewWpf11082009182813PM/GridViewWpf.aspx
http://www.elguille.info/colabora/2007/juanpablogc_wpf_gridview.htm
Enlace a Colecciones
http://msdn.microsoft.com/es-ar/library/ms752347.aspx#binding_to_collections
Documentos
http://msdn.microsoft.com/es-es/library/ms748388(VS.90).aspx
http://msdn.microsoft.com/es-es/library/ms771612.aspx
Graficos y multimedia
http://msdn.microsoft.com/es-es/library/ms742562.aspx
http://msdn.microsoft.com/es-es/library/ms748873.aspx
http://msdn.microsoft.com/es-es/library/ms747437(v=VS.90).aspx
http://msdn.microsoft.com/es-es/library/ms753347(VS.90).aspx
http://msdn.microsoft.com/en-us/library/ms748248.aspx
ClickOnce
http://msdn.microsoft.com/es-es/library/142dbbz4(v=VS.90).aspx
137