Está en la página 1de 137

Programación en .

Net
(Avanzado)
WPF
MARÍA TERESA CRESPI NOIR

PROGRAMACIÓN EN .NET
(AVANZADO) - MANUAL
WPF

Buffa Sistemas S.R.L


2010
Buenos Aires

2
Programación en .Net (Avanzado) - Manual WPF
Crespi Noir, María Teresa

1a Edición

Buffa Sistemas SRL


Alsina 655 4° Piso
Buenos Aires

ISBN Nº 978-987-25214-2-4

Multigraphic
Avenida Belgrano 520 – Capital Federal
10 de agosto de 2010

Crespi Noir, María Teresa


Programación en .Net (Avanzado) - Manual WPF
Buenos Aires : Buffa Sistemas S.R.L., 2010.
307 p. ; 21 x 29,7 cm.

ISBN 978-987-25214-2-4

(c), 2010 Buffa Sistemas SRL

Queda hecho el depósito que establece la Ley 11.723.

Libro de edición Argentina

No se permite la reproducción parcial o total, el almacenamiento, el alquiler, la


transmisión o la transformación de este libro, en cualquier forma o por cualquier
medio, sea electrónico o mecánico, mediante fotocopias, digitalización u otros
métodos, sin el permiso previo y escrito del editor.Su infracción está penada
por las leyes 11723 y 25446.-

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

2- Conceptos Básicos ............................................................................................................... 15


Controles ................................................................................................................................. 15
Diseño ..................................................................................................................................... 15
Enlace de Datos ...................................................................................................................... 16
Gráficos ................................................................................................................................... 16
Formas 2D .............................................................................................................................. 17
Geometrías 2D ........................................................................................................................ 17
Efectos 2D ............................................................................................................................... 17
Representaciones 3D.............................................................................................................. 17
Animaciones ............................................................................................................................ 17
Multimedia ............................................................................................................................... 17
Texto y Tipografía ................................................................................................................... 18
Documentos ............................................................................................................................ 18
Anotaciones ............................................................................................................................ 18
Empaquetado .......................................................................................................................... 18
Impresión ................................................................................................................................ 18
Personalización ....................................................................................................................... 19

3- Eventos y Comandos ............................................................................................................ 20


Información general sobre Eventos Enrutados ....................................................................... 20
Árboles de elementos WPF .................................................................................................... 21
Enrutamiento de eventos ........................................................................................................ 22
Eventos enrutados y composición .......................................................................................... 22
Eventos adjuntos..................................................................................................................... 23
Información general sobre Comandos enrutados ................................................................... 23
Enrutamiento de comandos .................................................................................................... 25
Definición de comandos .......................................................................................................... 25
Desafíos de los comandos enrutados..................................................................................... 25

MÓDULO 2: CREANDO APLICACIONES WPF ........................................................................ 26

1- Crear una aplicación WFP .................................................................................................... 27


Aplicación WPF ....................................................................................................................... 27
Clase Window ......................................................................................................................... 28
Configurar la ventana de inicio ............................................................................................... 28
Propiedades de la Clase Window ........................................................................................... 29
Controles WPF y su equivalente con Controles Windows Forms .......................................... 29
Controladores de Eventos ...................................................................................................... 31
Recursos ................................................................................................................................. 32
Recursos Estáticos y Dinámicos............................................................................................. 32

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

3- Otros Controles ..................................................................................................................... 41


Label - AccessText.................................................................................................................. 41
TextBox ................................................................................................................................... 41
TextBlock ................................................................................................................................ 41
Button ...................................................................................................................................... 41
ListBox - ComboBox ............................................................................................................... 42
CheckBox ................................................................................................................................ 43
RadioButton ............................................................................................................................ 43
Otros Controles ....................................................................................................................... 44

4- Información General de Controles ...................................................................................... 45


Propiedades Generales de los Controles ............................................................................... 45
Creación dinámica de controles .............................................................................................. 45

MÓDULO 3: PERSONALIZANDO LA APARIENCIA DE APLICACIONES WPF .................... 47

1- Personalizar Apariencia ....................................................................................................... 48


Composición ........................................................................................................................... 48
Estilos ...................................................................................................................................... 48
Plantillas .................................................................................................................................. 50
Desencadenadores (Triggers) ................................................................................................ 52

2- Crear Controles de Usuario ................................................................................................. 54


Controles ................................................................................................................................. 54
Controles de Usuario .............................................................................................................. 54
Personalizando Controles de Usuario .................................................................................... 56
Controles Personalizados ....................................................................................................... 57

MÓDULO 4: ENLACE A DATOS ............................................................................................... 58

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

2- Notificaciones de Cambio de Propiedad ............................................................................ 70


Interfase INotifyPropertyChanged ........................................................................................... 70

3- Convertir Datos ..................................................................................................................... 74


ValueConverters ..................................................................................................................... 74
Interfase IValueConverter ....................................................................................................... 74

4- Validar Datos ......................................................................................................................... 76


ValidationRules ....................................................................................................................... 76
Clase Binding.ValidationRule .................................................................................................. 77
IDataErrorInfo.......................................................................................................................... 79
ExceptionValidationRule ......................................................................................................... 81
DataErrorValidationRule ......................................................................................................... 81

MÓDULO 5: ENLACE A COLECCIONES ................................................................................. 82

1- Enlace a Colecciones ........................................................................................................... 83


Enlace a Colecciones.............................................................................................................. 83
Cómo implementar colecciones .............................................................................................. 83

2- Vistas de Colecciones .......................................................................................................... 86


Vistas de colecciones.............................................................................................................. 86
Cómo crear una vista .............................................................................................................. 86
Ordenar ................................................................................................................................... 86
Agrupar ................................................................................................................................... 87
Filtrar ....................................................................................................................................... 89
Punteros de elemento actual .................................................................................................. 91
Navegación de Colecciones ................................................................................................... 91

MÓDULO 6: ADMINISTRAR DOCUMENTOS ........................................................................... 93

1- Documentos........................................................................................................................... 94
Definición ................................................................................................................................ 94
Tipos de documentos .............................................................................................................. 94

2- Documentos Dinámicos (FlowDocuments) ........................................................................ 94


Definición ................................................................................................................................ 94
Contenedores para Documentos Dinámicos .......................................................................... 95
FlowDocumentScrollViewer .................................................................................................... 95
FlowDocumentPageViewer ..................................................................................................... 95
FlowDocumentReader ............................................................................................................ 96
Formateando Documentos Dinámicos.................................................................................... 96
Elementos en Bloque .............................................................................................................. 96
List ........................................................................................................................................... 97
Tabla ....................................................................................................................................... 98
Section .................................................................................................................................... 99
BlockUIContainer .................................................................................................................... 99
Elementos en Línea (Contenido dinámico) ........................................................................... 100
8
3- Documentos Fijos ............................................................................................................... 101
Definición .............................................................................................................................. 101
PageContent ......................................................................................................................... 101
FixedPage ............................................................................................................................. 101
DocumentViewer ................................................................................................................... 102

4- Empaquetado e Impresión de Documentos ..................................................................... 104


Empaquetado ........................................................................................................................ 104
Empaquetar componentes .................................................................................................... 105
Documentos XPS (XPSDocument)....................................................................................... 105
XpsDocumentWriter .............................................................................................................. 105
Impresión de Documentos .................................................................................................... 106

MÓDULO 7: GRÁFICOS, ANIMACIONES Y MULTIMEDIA ................................................... 107

1- Gráficos 2D .......................................................................................................................... 108


Dibujos y formas ................................................................................................................... 108
Shape .................................................................................................................................... 108
Propiedades de las formas ................................................................................................... 108
Drawing ................................................................................................................................. 109
Propiedad Clip ....................................................................................................................... 110
Brush ..................................................................................................................................... 110

2- Imágenes .............................................................................................................................. 112


Imágenes .............................................................................................................................. 112
Control Image........................................................................................................................ 112
Girar, Convertir y Recortar Imágenes ................................................................................... 113
Expandir imágenes ............................................................................................................... 114
Pintar con imágenes ............................................................................................................. 115

3- Gráficos 3D .......................................................................................................................... 115


Viewport3D ............................................................................................................................ 115
Espacio de coordenadas 3D ................................................................................................. 116
Cámaras y proyecciones ...................................................................................................... 116
Elementos primitivos de modelo y malla............................................................................... 117
Materiales del Modelo ........................................................................................................... 118
Iluminar la escena ................................................................................................................. 118
Transformar modelos ............................................................................................................ 120
Animar modelos .................................................................................................................... 120
Transformaciones de Traslación ........................................................................................... 121
Transformaciones de Escala ................................................................................................ 123
Transformaciones de Giro .................................................................................................... 125

4- Multimedia............................................................................................................................ 127
Media API .............................................................................................................................. 127
Modos Media Playback ......................................................................................................... 127
SoundPlayer .......................................................................................................................... 128

MÓDULO 8: CONFIGURACIÓN Y DISTRIBUCIÓN ................................................................ 129

1- Configuración ...................................................................................................................... 130


Configurar Aplicaciones ........................................................................................................ 130
App.config ............................................................................................................................. 130
9
Recuperando información de app.config .............................................................................. 132
Almacenar información en app.config en run-time .............................................................. 132

2- Distribución de Aplicaciones WPF .................................................................................... 132


XCopy ................................................................................................................................... 132
Microsoft Windows Installer .................................................................................................. 132
ClickOnce .............................................................................................................................. 133
Publicación ClickOnce .......................................................................................................... 133
Como Publicar ....................................................................................................................... 134
Comparación entre ClickOnce y Microsoft Windows Installer .............................................. 135

Links Relacionados ................................................................................................................. 137

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

Desarrolladores en Programación .NET (C #) o bien personas que hayan aprobado el curso


de MS Net Junior (160 hs)

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

Después de completar este curso los alumnos serán capaces de:


 Construir un interfase de usuario en una aplicación WPF
 Personalizar la apariencia de una aplicación WPF
 Crear controles de usuario en una aplicación WPF
 Enlazar controles a fuentes de datos
 Enlazar controles a colecciones
 Manejar documentos en aplicaciones WPF
 Trabajar con gráficos y multimedia en aplicaciones WPF
 Configurar e instalar aplicaciones WPF

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.

Programar con WPF


WPF constituye un subconjunto de tipos de .NET Framework, en su mayoría ubicados en el
espacio de nombres de System.Windows. Si ha creado previamente aplicaciones con .NET
Framework mediante tecnologías administradas como ASP.NET y formularios Windows Forms,
los conceptos fundamentales de la programación en WPF deberán resultarle familiares; creará
instancias de clases, definirá propiedades, llamará a métodos y controlará eventos con el
lenguaje de programación de .NET Framework que prefiera, como C# o Visual Basic.
Para admitir algunas de las funciones de WPF más eficaces y simplificar la experiencia de
programación, WPF incluye construcciones de programación adicionales que mejoran las
propiedades y los eventos

Código de lenguaje marcado y código subyacente


WPF proporciona mejoras de programación adicionales para el desarrollo de aplicaciones
cliente de Windows. Una mejora evidente es la capacidad para programar una aplicación
mediante código de lenguaje marcado y subyacente, una experiencia con la que resultará
familiar a los programadores de ASP.NET. En general, se utiliza el lenguaje marcado Lenguaje
de marcado de aplicaciones extensible (XAML) para implementar la apariencia de una
aplicación, y los lenguajes de programación administrados (c#, visual Basic, etc.) para
implementar su comportamiento. Esta separación entre la apariencia y el comportamiento
aporta las ventajas siguientes:
 Se reducen los costos de programación y mantenimiento, al no estar el marcado
específico de la apariencia estrechamente relacionado con el código específico del
comportamiento.
 La programación es más eficaz porque los diseñadores pueden implementar la
apariencia de una aplicación al mismo tiempo que los programadores implementan su
comportamiento.
 Se pueden utilizar varias herramientas de diseño para implementar y compartir el
lenguaje marcado XAML, a fin de responder a los requisitos de los colaboradores de
programación de aplicaciones. Microsoft Expression Blend proporciona una experiencia
apropiada para los diseñadores, mientras que Visual Studio 2008 está dirigido a los
programadores.
 La globalización y localización de las aplicaciones WPF se ha simplificado en gran
medida.

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.

En el ejemplo siguiente se muestran el código subyacente y el marcado actualizado del


ejemplo anterior.

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="Ventana con un Botón"
Width="250" Height="100">

<Button Name="button" Click=”button_Click”>Presionar</Button>


</Window>

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

Información general sobre Eventos Enrutados


En los primeros pasos con WPF, lo más probable es que use eventos enrutados aún sin saber
que los está usando. Por ejemplo, si agrega un botón a una ventana en el diseñador de Visual
Studio®, modifica el nombre a miBoton y después hacer doble clic sobre él, el evento Click se
conectará al marcado XAML y se agregará un controlador de eventos para este evento al
código que hay tras la clase Window. Esto es lo mismo que la conexión de eventos en
Windows Forms y ASP.NET. En concreto, en el marcado XAML para el botón, terminará por
tener un código parecido a este:

<Button Name=" miBoton " Click="miBoton_Click">Click Me</Button>

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:

private void myButton_Click(object sender, RoutedEventArgs e) { }

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.

Árboles de elementos WPF


Si empieza con una ventana nueva en un proyecto y arrastra un botón en la ventana del
diseñador, terminará con un árbol de elementos en el XAML parecido a éste (los atributos se
omitieron para mejorar la claridad):

<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.

Eventos enrutados y composición


Vayamos paso a paso para ver cómo el evento Button.Click se produce y regresa a casa, ya
que es importante. Como he mencionado anteriormente, un usuario iniciará un evento Click con
un evento MouseLeftButtonDown en algún elemento secundario del árbol visual del Button,
como en el caso de Image en el ejemplo anterior.
Cuando se produce el evento MouseLeftButtonDown dentro del elemento Image, se inicia un
evento PreviewMouseLeftButtonDown en la raíz y profundiza hacia Image. Si ningunos de los
controladores establecen la marca Handled en verdadero para el evento de vista previa, se
inicia la propagación del evento MouseLeftButtonDown desde el elemento Image hasta que

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.

Información general sobre Comandos enrutados


Los comandos enrutados de WPF ofrecen un mecanismo específico para enlazar controles de
IU como botones de barra de herramientas y elementos de menú a controladores, sin tener que
realizar muchos emparejamientos integrados ni código repetitivo en la aplicación. Los
comandos enrutados ofrecen tres cosas importantes además del control normal de eventos:
 Los elementos origen del comando enrutado (invocadores) pueden estar
desconectados de los destinos de comandos (controladores), no es necesario que
haya referencias directas entre ellos, como ocurriría si estuvieran vinculados por un
controlador de eventos.
 Los comandos enrutados habilitarán o deshabilitarán todos los controles de IU
asociados cuando el controlador indica que el comando está deshabilitado.
 Los comandos enrutados le permiten asociar métodos abreviados de teclado y otras
formas de movimientos de entrada (movimientos de lápiz óptico, por ejemplo) como
otra manera de invocar el comando.
Además, una variedad específica del comando enrutado (la clase RoutedUICommand) agrega
la capacidad de definir una sola propiedad Text para usarla como indicaciones de texto de
cualquier control que sea invocador para el comando. La propiedad Text también se puede
localizar más fácilmente que visitando todos los controles del invocador asociado.
Para declarar un comando en un invocador, sólo tiene que establecer una propiedad
Command sobre el control que iniciará el comando:

23
<Button Command="ApplicationCommands.Save">Guardar</Button>

La propiedad Command la admiten MenuItem, Button, RadioButton, Checkbox, Hyperlink y


otros controles.

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.

También puede usar estos comandos con una notación abreviada

<Button Command="Save">Save</Button>

Desafíos de los comandos enrutados


Los comandos enrutados funcionan bien en escenarios de interfases de usuario sencillas,
conectando barras de herramientas y elementos de menú, y controlando esas cosas que están
emparejadas intrínsecamente al foco del teclado (como las operaciones con el portapapeles).
Sin embargo, donde los comandos enrutados se muestran insuficientes, es cuando se empieza
a crear interfases de usuario complejas, donde la lógica del control de comandos está en el
código de soporte para las definiciones de vista y los invocadores de comando no siempre
están dentro de una barra de herramientas o de un menú.
El problema que surge al trabajar de este modo es que la habilitación y el control de lógica para
un comando puede no formar parte del árbol visual directamente; en su lugar puede estar
situada en un presentador o en un modelo de presentación. Además, el estado que determina
si el comando debe habilitarse puede no tener relación con la situación de los invocadores de
comandos y las vistas en el árbol visual. También puede encontrar casos donde haya más de
un controlador válido al mismo tiempo para un comando particular.
Para evitar los problemas potenciales de la ubicación del árbol visual con comandos enrutados,
necesitará simplificar las cosas. Normalmente necesitará asegurarse de que los controladores
de comando están en el mismo elemento o en niveles superiores del árbol visual del elemento
que invocará el comando.

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">

Configurar la ventana de inicio


El archivo App.xaml y el archivo de código subyacente correspondiente se crean de forma
predeterminada en un proyecto WPF. Este archivo contiene recursos de nivel de aplicación que
pueden ser usados en cualquier documento de la aplicación. Dentro de este archivo se
implementa la clase Application. La ventana de inicio se establece a través de la propiedad
StartupUri.

<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>

Propiedades de la Clase Window


AllowsTransparency: Obtiene o establece un valor que indica si se puede ajustar la opacidad
del formulario
Background: Obtiene o establece un pincel (Brush) que describe el fondo de un control.
BorderBrush: Obtiene o establece un pincel (Brush) que describe el fondo del borde de un
control.
BorderThickNess: Obtiene o establece el grosor del borde de un control.
Cursor: Obtiene o establece el cursor que se muestra cuando el puntero del mouse se sitúa
sobre este elemento.
Foreground: Obtiene o establece un pincel (Brush) que describe el color de primer plano,
generalmente la letra.
Height: Obtiene o establece el alto sugerido del elemento, expresados en unidades
independientes de dispositivo (1/96 de pulgada por unidad).
Icon: Obtiene o establece el icono de una ventana.
IsEnabled: Obtiene o establece si la ventana permite recibir entradas del usuario.
Left: Obtiene o establece la posición del borde izquierdo de la ventana con respecto al
escritorio.
ResizeMode: Obtiene o establece el modo de cambio de tamaño.
ShowInTaskBar: Obtiene o establece un valor que indica si la ventana tiene un botón de barra
de tareas.
SizeToContent: Obtiene o establece un valor que indica si una ventana ajustará
automáticamente su tamaño al de su contenido.
Title: Obtiene o establece el título de una ventana.
Top: Obtiene o establece la posición del borde superior de la ventana con respecto al
escritorio.
TopMost: Obtiene o establece un valor que indica si una ventana aparece en el punto más alto
en el orden Z.
Width: Obtiene o establece el ancho del elemento, expresados en unidades independientes de
dispositivo (1/96 de pulgada por unidad).
WindowStartupLocation: Obtiene o establece la posición de la ventana cuando se muestra por
primera vez.
WindowState: Obtiene o establece un valor que indica si una ventana está restaurada,
minimizada o maximizada.
WindowStyle: Obtiene o establece el estilo de borde de una ventana.

Controles WPF y su equivalente con Controles Windows Forms


Windows Forms WPF Comentarios

Button Button

CheckBox CheckBox

CheckedListBox ListBox Compuestos

ColorDialog

29
ComboBox ComboBox

ContextMenuStrip ContextMenu

DataGridView Algunas funcionalidades de DataGridView


se pueden reproducir usando los controles
ListView y GridView.
DateTimePicker

ErrorProvider

FlowLayoutPanel WrapPanel o StackPanel

FolderBrowserDialog

FontDialog

Form Window

GroupBox GroupBox

HelpProvider No tiene ayuda con F1. Se puede


reemplazar esta funcionalidad con
ToolTips
ImageList

Label Label

LinkLabel Se puede utilizar la clase Hyperlink.

ListBox ListBox

ListView ListView Solo contiene la vista de detalle en


formato de solo.
MaskedTextBox

MenuStrip Menu

MonthCalendar

NotifyIcon

NumericUpDown TextBox y dos RepeatButton

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

WebBrowser Frame, The Frame control can host HTML pages.


System.Windows.Controls.WebBr
owser

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>

Recursos Estáticos y Dinámicos


Los recursos pueden ser referenciados en forma estática o dinámica. Esto se hace usando
StaticResource o DynamicResource y el nombre del recurso que se está referenciando. La
32
referencia estática procesa la clave buscando el valor en todos los recursos disponibles, esto
ocurre durante la carga del proceso. En cambio la referencia dinámica procesa la clave
creando una expresión que permanece sin ser evaluada hasta el momento de la ejecución,
durante la ejecución evalúa la expresión y devuelve el valor.
Cuando se va a crear una referencia a un recurso, las siguientes consideraciones deberían
tenerse en cuenta para elegir cual de las dos opciones va a usarse:
 Donde está creado el recurso, si es un recurso de ventana, de aplicación, de control,
etc.
 La funcionalidad de la aplicación
 El comportamiento del recurso

Cuando elegir StaticResource:


 La mayoría de los recursos son de ventana o aplicación. Los recursos no son re
evaluados durante la ejecución mejorando la performance.
 Se está estableciendo alguna propiedad del objeto en Freezable
 Se están creando recursos que serán compilados en una dll aparte.
 Se están definiendo recursos que van a ser usados en Temas
 Se están usando recursos con muchas propiedades de dependencia.

Cuando elegir DynamicResource:


 El valor del recurso depende de una condición que solo se conoce durante la
ejecución.
 Se están creando o referenciando Temas para un control personalizado
 Se está intentando ajustar el contenido de ResourceDictionary
 Cuando se usan recursos compilados
 Cuando se referencia recursos pesados y el uso de ese recurso no es inmediato, o sea
no se usa durante la carga de la ventana. Los recursos estáticos se cargan con la
carga de la ventana, en cambio los dinámicos cuando se necesitan
 Se aplican recursos a elemento que dependen de elementos padres. Al cambiar el
elemento padre es posible que sea necesario cambiar el objeto hijo también.

<Button Background="{StaticResource MyBrush}"/>


<Ellipse Fill="{StaticResource MyBrush}"/>

2- Controles de Diseño

Sistema de Diseño de Interfase de Usuario


El término diseño describe el proceso de medir y organizar los integrantes de la colección
Children de un elemento contenedor y, a continuación, dibujarlos en la pantalla. Se trata de un
proceso intensivo y, cuanto mayor es la colección Children, mayor es el número de cálculos
realizados. También se puede incluir mayor complejidad según el comportamiento de diseño
definido por el elemento contenedor que posee la colección. Un diseño relativamente sencillo,
como Canvas, puede producir un rendimiento excelente si no se requiere un Panel más
complejo como Grid.
Cada vez que un elemento secundario cambia su posición, tiene el potencial de desencadenar
un nuevo paso del sistema de diseño. Como a tal, es importante entender los eventos que
pueden invocar el sistema del diseño, puesto que la invocación innecesaria puede deteriorar el
rendimiento de la aplicación.
En su versión más simple, el diseño es un sistema recursivo que conduce a la configuración del
tamaño, posición y representación de un elemento en la pantalla. El sistema de diseño
completa dos pasos por cada miembro de la colección Children: un paso de medida y un paso
de organización. Se trata de la serie de eventos que se producen cada vez que se invoca el
sistema de diseño.
33
 Un elemento secundario comienza el proceso de diseño midiendo, en primer lugar,
propiedades básicas.
 Se evalúan las propiedades de tamaño, como Width, Height y Margin.
 Se aplica la lógica concreta del contenedor, como la dirección de Dock o la orientación
(Orientation) de apilado.
 El contenido se organiza después de medir todos los elementos secundarios.
 Se dibuja la colección Children en la pantalla.
 Se invoca el proceso de nuevo si se agregan Children adicionales a la colección.
Al pensar en el diseño de una aplicación en Windows Presentation Foundation (WPF), es
importante entender el rectángulo de selección que rodea todos los elementos. Esta
abstracción ayuda a comprender el comportamiento del sistema de diseño. Cada elemento
utilizado por el sistema de diseño se puede considerar como un rectángulo que se inserta en
una partición del diseño. El sistema, calculando el espacio de pantalla disponible, determina el
tamaño de ese rectángulo, el tamaño de cualquier restricción, las propiedades específicas del
diseño, como el margen y el relleno, y el comportamiento individual del elemento primario. Al
procesar estos datos, el sistema puede calcular la posición de todos los elementos secundarios
de un contenedor determinado. Es importante recordar que las características de tamaño
definidas en el elemento primario (como Border) afectan a sus elementos secundarios.
Cuando se representa el contenido de un objeto Window, se invoca el sistema del diseño
automáticamente. Para mostrar el contenido, el Content de la ventana debe definir un panel
raíz que sirve para definir un marco que permite organizar los miembros de Children en la
pantalla.
A continuación se detallan los controles disponibles como paneles o contenedores.

Border
Dibuja un borde, un fondo o ambos alrededor de otro elemento.

En el siguiente ejemplo se muestra un control Border que contiene un CheckBox.

<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.

En el siguiente ejemplo se muestra el uso de un control DockPanel con varios controles


contenidos. La propiedad DockPanel permite posicionar los objetos.

<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.

<Grid VerticalAlignment="Top" HorizontalAlignment="Left" ShowGridLines="True"


Width="250" Height="100" Canvas.Left="80" Canvas.Top="67"
Background="LightYellow" >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>

<TextBlock FontSize="20" FontWeight="Bold" Grid.ColumnSpan="3"


Grid.Row="0">Productos Comprados</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1"
Grid.Column="0">Enero</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1"
Grid.Column="1">Febrero</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1"
Grid.Column="2">Marzo</TextBlock>
36
<TextBlock Grid.Row="2" Grid.Column="0" TextAlignment="Right"
>50,000</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1"
TextAlignment="Right">100,000</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="2"
TextAlignment="Right">150,000</TextBlock>
<TextBlock FontSize="16" FontWeight="Bold" Grid.ColumnSpan="3"
Grid.Row="3">Total Units: 300,000</TextBlock>
</Grid>

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.

En el siguiente ejemplo se muestra el uso del control StackPanel. La propiedad Orientation


organiza los RadioButtons en forma vertical.

<StackPanel Height="40" Width="115" Orientation="Vertical" >


<RadioButton Name="rb1" Checked="rb_Checked" >Si</RadioButton>
<RadioButton Name="rb2" Checked="rb_Checked" >No</RadioButton>
</StackPanel>

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.

<GroupBox Width="100" Height="100" Background="Beige" >


<GroupBox.Header>
<Label>Employee Data</Label>
</GroupBox.Header>
<StackPanel>
<RadioButton HorizontalAlignment="Left" Height="16" Name="Opcion1"
Width="120">Opción 1</RadioButton>
<RadioButton HorizontalAlignment="Left" Height="16" Name="Opcion2"
Width="120">Opción 2</RadioButton>
</StackPanel>
</GroupBox>
37
Expander
Representa el control que muestra un encabezado que tiene una ventana contraible que
muestra el contenido. Su propiedad de contenido es Content y su propiedad de encabezado es
Header. Si el contenido de la ventana expandida es demasiado grande para la ventana, puede
ajustar el contenido de Expander en un control ScrollViewer para proporcionar el contenido
desplazable. Expander no proporciona automáticamente la función de desplazamiento.

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.

<Expander Name="myExpander" HorizontalAlignment="Left" Header="Expandir"


ExpandDirection="Down" IsExpanded="True" Width="100">
<TextBlock TextWrapping="Wrap">
Representa el control que muestra un encabezado que tiene una ventana
contraible que muestra el contenido.
</TextBlock>
</Expander>

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.

<InkCanvas Name="ic" Background="LightSteelBlue" Canvas.Top="0" Canvas.Left="0"


Height="200" Width="200" />

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.

<Menu Name="menu1" Width="300" HorizontalAlignment="Left"


VerticalAlignment="Top" >
<MenuItem Header="_Archivo" >
<MenuItem Header="A_brir" IsCheckable="true" />
<MenuItem Header="_Cerrar" IsCheckable="true" IsChecked="True" />
<MenuItem Header="_Guardar" >
<MenuItem.Icon>
<Image Source="setup.ico" Width="16" Height="16" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="A_gregar" >
<MenuItem Header="Proyecto" />
<MenuItem Header="Archivo" />
</MenuItem>
</MenuItem>
</Menu>

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.

<Frame Name="miFrame" Source="http://www.microsoft.com" />

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.

<Border HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Black"


BorderThickness="2">
<WrapPanel Background="LightBlue" Width="200" Height="100">
<Button Width="200">Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
<Button>Button 4</Button>
</WrapPanel>
</Border>

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.

En el siguiente ejemplo al presionar Alt+D, el foco se posiciona en TextBox2

<DockPanel Margin="0,10,3,3" Grid.Column="0" Grid.Row="5">


<TextBox Name="textBox2" Width="100" Height="20"/>
<Label Target="{Binding ElementName=textBox2}">
<AccessText FontSize="12" FontWeight="Bold">E_ditar</AccessText>
</Label>
</DockPanel>
<TextBox Name="textBox3" Width="100" Height="20" Canvas.Top="30" />

TextBox
Representa un control que se puede utilizar para mostrar o editar texto sin formato.

<TextBox Name="tbSelectSomeText" Width="100" >


Algún Texto</TextBox>

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.

<TextBlock Name="textBlock1" TextWrapping="Wrap" FontSize="14">


<Bold>TextBlock</Bold> esta diseñado para ser <Italic>liviano</Italic>
</TextBlock>

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.

En el siguiente ejemplo vemos el uso de un ListBox sencillo.

<ListBox Name="lb" Width="100" Height="55" SelectionChanged="PrintText"


SelectionMode="Single">
<ListBoxItem>Item 1</ListBoxItem>
<ListBoxItem>Item 2</ListBoxItem>
<ListBoxItem>Item 3</ListBoxItem>
</ListBox>

Estos controles permiten también el uso de listas con contengan CheckBox, imágenes, etc.
como controles contenidos para cada ítem.

<ListBox Name="lb" Width="251" Height="119" SelectionMode="Single"


SelectionChanged="lb_SelectionChanged">
<ListBoxItem Background="LightCoral" FontFamily="Verdana" FontSize="12"
FontWeight="Bold">
<CheckBox Name="chk1">
<StackPanel Orientation="Horizontal">
<Image Source="ms.jpg" Height="30"></Image>
<TextBlock Text="Item 1" Width="174"></TextBlock>
</StackPanel>
</CheckBox>
</ListBoxItem>
<ListBoxItem Background="Green" FontFamily="Verdana" FontSize="12"
FontWeight="Bold">
<CheckBox Name="chk2">
42
<StackPanel Orientation="Horizontal">
<Image Source="nextover.jpg" Height="30"></Image>
<TextBlock Text="Item 2" Width="174"></TextBlock>
</StackPanel>
</CheckBox>
</ListBoxItem>
</ListBox>

<ComboBox Height="25" Width="150" HorizontalAlignment="Center"


VerticalAlignment="Top" IsEditable="True">
<CheckBox Name="Item1">Buenos Aires</CheckBox>
<CheckBox Name="Item2">Córdoba</CheckBox>
<CheckBox Name="Item3">Mendoza</CheckBox>
</ComboBox>

CheckBox
Representa un control que un usuario puede activar y desactivar. Pueden tener tres estados:
activado, desactivado e indeterminado.

<CheckBox Name="Prueba" IsChecked="True">Prueba</CheckBox>

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.

<StackPanel Height="40" Canvas.Left="35" Canvas.Top="43" Width="115">


<RadioButton Name="rb1" Checked="rb_Checked">Si</RadioButton>
<RadioButton Name="rb2" Checked="rb_Checked">No</RadioButton>
<RadioButton Name="rb3" Checked="rb_Checked">No sabe/no
contesta</RadioButton>
</StackPanel>

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.

Creación dinámica de controles


Todos los controles pueden ser también generados dinámicamente en tiempo de ejecución.

// Crea la ventana principal de la aplicación


mainWindow = new Window();
mainWindow.Title = "Grid Sample";
// Crea el control Grid
Grid myGrid = new Grid();
myGrid.Width = 250;
myGrid.Height = 100;
45
myGrid.HorizontalAlignment = HorizontalAlignment.Left;
myGrid.VerticalAlignment = VerticalAlignment.Top;
myGrid.ShowGridLines = true;
// Define la columnas
ColumnDefinition colDef1 = new ColumnDefinition();
ColumnDefinition colDef2 = new ColumnDefinition();
ColumnDefinition colDef3 = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(colDef1);
myGrid.ColumnDefinitions.Add(colDef2);
myGrid.ColumnDefinitions.Add(colDef3);
// Define las filas
RowDefinition rowDef1 = new RowDefinition();
RowDefinition rowDef2 = new RowDefinition();
RowDefinition rowDef3 = new RowDefinition();
RowDefinition rowDef4 = new RowDefinition();
myGrid.RowDefinitions.Add(rowDef1);
myGrid.RowDefinitions.Add(rowDef2);
myGrid.RowDefinitions.Add(rowDef3);
myGrid.RowDefinitions.Add(rowDef4);

// Agrega el texto a la primera celda


TextBlock txt1 = new TextBlock();
txt1.Text = "Productos Comprados";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetColumnSpan(txt1, 3);
Grid.SetRow(txt1, 0);
..................................

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.

<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>

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>

En el siguiente ejemplo se muestra una declaración de estilo que afectará a la propiedad


Background de una clase Control. La asignación del estilo a un control se hace en forma
explícita.

<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>

En el siguiente ejemplo el estilo se aplica al control StackPanel y sus controles contenidos y no


a toda la ventana.

<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>

<Button Height="50" Width="50">


<StackPanel>
<TextBlock>Play</TextBlock>
<Polygon Points="0,0 0,26 17,13"
Fill="Black" />
</StackPanel>
</Button>
</StackPanel>

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.

En el siguiente ejemplo se ha especificado un botón en forma de círculo con el icono de


reproducción en el centro, colocando el icono de reproducción sobre un elemento elipse. El
nuevo botón de plantilla aparece junto a un botón normal.

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>

Otro tipo de desencadenador es EventTrigger, que inicia un conjunto de acciones en función


de la aparición de un evento. Los objetos EventTrigger inician un conjunto de propiedades
Actions cuando se produce un evento enrutado especificado. Por ejemplo, quizá desee usar
EventTrigger para iniciar un conjunto de animaciones cuando el puntero del mouse esté sobre

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.

2- Crear Controles de Usuario

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"

donde UC es el nombre que se va a dar al namespace del control. Si el control estuviera en


otro ensamblado la línea a ingresar es

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>

Personalizando Controles de Usuario


Cuando se crean controles muchas veces es necesario generar código no solo para poder
interactuar con ellos, sino para encapsular funcionalidad. Al crear los controles descubrirá que
es necesario implementar las propiedades, métodos y/o eventos para administrar tanto la
apariencia del control como su comportamiento en el tiempo de ejecución. Por ejemplo, sería
importante en el control creado en el ejemplo anterior obtener y establecer el color del icono.
Para ello, se puede crear una propiedad como se muestra a continuación.

Brush _ColorIcono = Brushes.Black;


public Brush ColorIcono
{
get { return _ColorIcono; }
set
{
_ColorIcono = value;
IconoPlay.Fill = _ColorIcono;
}
}

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>

public partial class controlPrueba : Button


{
public controlPrueba()
{
InitializeComponent();
}
}

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.

Modos de Enlazar – Propiedad Mode


La etiqueta Binding permite también el atributo Mode. El atributo Mode define el modo de
enlace que determinará el flujo de datos entre el origen y el destino.
59
 OneWay: los datos fluyen desde el origen hasta el destino cada vez que se realiza un
cambio en el origen. Es el modo predeterminado
 OneTime: Envía datos desde el origen al destino; sin embargo, hace esto sólo cuando
se inicia la aplicación o cuando cambia el DataContext y, como resultado, no espera
notificaciones de cambio en el origen.
 OneWayToSource: Envía datos desde el destino al origen.
 TwoWay: Envía los datos del origen al destino y si hay cambios en el valor de la
propiedad de destino, éstos volverán a enviarse al origen.
La selección del modo de enlace apropiado es muy importante. Con frecuencia se emplea
OneWay cuando se desea mostrar los datos a un usuario. Se usa TwoWay cuando se desea
que el usuario pueda cambiar los datos en el control y que ese cambio se refleje en el origen
de datos (por ejemplo un DataSet, objeto, XML u otro control de enlace). Se usa
OneWayToSource cuando se desea permitir a un usuario cambiar el origen de datos sin que el
origen de datos tenga que volver a enlazar sus datos al destino y se usa OneTime cuando se
desea cargar datos de solo lectura al cargar la pantalla y que estos permanezcan sin cambios
aunque se efectúen cambios en el origen de datos.

DataContext="{Binding ElementName=Customer,Path=Items,Mode=OneWay}"

Momento del Enlace


Por defecto el momento del enlace esta predefinido en cada control. Por ejemplo en el caso del
ListBox el enlace se hace al momento de seleccionar un elemento, en cambio en el caso de un
TextBox el enlace ocurre al perder el foco. Para cambiar el evento que provoca la devolución
de los datos al origen, puede especificar un valor para el atributo UpdateSourceTrigger, que
es la propiedad de enlace que define cuándo debe actualizarse el origen. Hay cuatro valores
que pueden configurarse para UpdateSourceTrigger:
 Default: se actualizará en el evento asociado por defecto al control
 Explicit: el origen no se actualizará a menos que se llame desde el código al método
UpdateSource de la clase BindingExpression.
 LostFocus: indica que el origen se actualizará cuando el control del destino pierda el
foco.
 PropertyChanged: indica que el destino actualizará el origen cada vez que cambie la
propiedad enlazada del control del destino. Esta configuración es útil si desea
establecer el momento del enlace de forma personalizada.

En este ejemplo a medida que escribe en el TextBox se va escribiendo en el TextBlock, ya que


el enlazado está sobre la propiedad Text y el momento establecido con la opción
PropertyChanged es la modificación de esa propiedad:

<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>

Si modifica el valor de UpdateSourceTrigger a Default, el TextBlock se actualizará al perder el


foco del TextBox, ya que ese es el evento predeterminado para ese control.
En cambio si establece esta propiedad en Explicit deberá invocar al método UpdateSource de
la clase BindingExpression.

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:

private void Button_Click(object sender, RoutedEventArgs e)


{
BindingExpression be =
txtNombre.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}

Propiedad Asociadas al Enlace de Datos


BindsDirectlyToSource: Esta propiedad establece un valor que indica si se va a evaluar la
propiedad Path con respecto al elemento de datos o al objeto ObjectDataProvider. El valor por
defecto de esta propiedad es false. Cuando es false evalúa la ruta de acceso con respecto al
elemento de datos propiamente dicho; de lo contrario, cuando es verdadero, evaluará la ruta de
acceso respecto al objeto ObjectDataProvider. En el ejemplo anterior debe estar en true, ya
que la propiedad Path tiene el valor de una propiedad de
ObjectDataProvider(MethodParameters) y no del elemento de datos.

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.

Enlace entre Controles


Es posible enlazar dos controles de manera de sincronizar los valores entre ellos.
En el siguiente ejemplo se enlaza la propiedad Text de un TextBlock a un elemento
seleccionado de un control ListBox.

<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:

<TextBlock Width="248" Height="24"


Text="{Binding ElementName=lbProvincias, Path=SelectedItem.Content}" />

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.

public class Paises


{
public DataTable TraerPaises()
{
using (DataTable dt = new DataTable())
{
using (SqlConnection cn = new
SqlConnection(WpfCSharp.Properties.Settings.Default.AdvWorks))
{
using (SqlDataAdapter da = new SqlDataAdapter("Select *
from Person.CountryRegion", cn))
{
da.Fill(dt);
}
}
return dt;
}
}
}

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.

public class Provincias


{
public DataTable TraerProvincias(string Cod)
{
using (DataTable dt = new DataTable())
{
using (SqlConnection cn = new
SqlConnection(WpfCSharp.Properties.Settings.Default.AdvWorks))

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;
}
}
}

Usando ObjectDataProvider generamos la referencia a este método y con MethodParametes


definimos su parámetro. Luego asociamos ese parámetro a un TextBox de manera de poder
seleccionar distintos valores y mostramos la lista de provincias 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"
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.

En el siguiente ejemplo usa DataTemplate para modificar la apariencia de la lista de manera


que el nombre del país se vea en negrita.

<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:" />

<TextBox Grid.Column="1" Grid.Row="0" Height="23" Name="txtCodigo"


Text="{Binding CountryRegionCode}"/>

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>

Controles ListView - Gridview


WPF no tiene un control GridView pero este puede generarse usando un control ListView.
ListView contiene una propiedad View que provee un objeto GridView. La propiedad Columns
de un GridView contiene elementos del tipo GridViewColumn. La propiedad
AllowsColumnReorder permite establecer si la columnas soportan ordenado o no.

En el siguiente ejemplo usando la misma clase Países declarada en el ejemplo anterior


generamos un ListView y organizamos las columnas del mismo usando el objeto GridView.

<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.

public class Persona


{
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;
}
}

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>

public partial class Window1 : Window


{
Persona oPersona;
public Window1()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)


{
oPersona = new Persona("Juan", 10);
txtNombre.Text = oPersona.Nombre;
txtEdad.Text = oPersona.Edad.ToString();
}

private void btnCumple_Click(object sender, RoutedEventArgs e)


{
oPersona.Edad++;
MessageBox.Show("Feliz Cumpleaños");
// Actualizamos manualmente la propiedad
txtEdad.Text = oPersona.Edad.ToString();
}
}

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.

public class Persona: INotifyPropertyChanged


{
private string _Nombre;
public string Nombre
{
get { return _Nombre; }
set {
_Nombre = value;
Notificar ("Nombre");
}
}

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;
}

public event PropertyChangedEventHandler PropertyChanged;


protected void Notificar (string NombrePropiedad)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(NombrePropiedad));
}
}
}

El código de la ventana quedaría de la siguiente manera. Atajamos el evento disparado por la


clase Persona y además necesitaríamos también un mecanismo que detecte los cambios en la
interfase gráfica, y los propague hacia el objeto, de manera que la sincronización sea en ambos
sentidos. Para ellos atajamos el evento TextChanged de los TextBox y modificamos el valor de
las propiedades. De esta manera mantenemos ambos valores sincronizados
permanentemente.

72
public partial class Window1 : Window
{
Persona oPersona;
public Window1()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)


{
oPersona = new Persona("Juan", 10);
txtNombre.Text = oPersona.Nombre;
txtEdad.Text = oPersona.Edad.ToString();
oPersona.PropertyChanged += new
System.ComponentModel.PropertyChangedEventHandler(
Persona_PropertyChanged);
}

private void btnCumple_Click(object sender, RoutedEventArgs e)


{
oPersona.Edad++;
MessageBox.Show("Feliz Cumpleaños");
}

private void Persona_PropertyChanged(object sender,


System.ComponentModel.PropertyChangedEventArgs e)
{
switch( e.PropertyName )
{
case "Nombre":
txtNombre.Text = oPersona.Nombre;
break;
case "Edad":
txtEdad.Text = oPersona.Edad.ToString();
break;
}
}

private void txtNombre_TextChanged(object sender, TextChangedEventArgs e)


{
oPersona.Nombre = txtNombre.Text;
}

private void txtEdad_TextChanged(object sender, TextChangedEventArgs e)


{
oPersona.Edad = System.Convert.ToInt32(txtEdad.Text);
}
}

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.

Por ejemplo si necesitamos enlazar un valor verdadero o falso a la propiedad Visibility


debemos convertir el dato, ya que esta propiedad es una enumeración que contiene tres
valores: Visible, Collapsed o Hidden.
Como primer paso escribiremos una clase que implemente a la interfase IValueConverter y que
implemente el método Convert de manera de generar la conversión necesaria. En este ejemplo
no se está permitiendo la conversión inversa.

using System.Windows.Data;
using System.Globalization;
using System.Windows;

public class ConversionBooleanToVisibility: IValueConverter


{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
return value;
}

public object ConvertBack(object value, Type targetType, object parameter,


CultureInfo culture)
74
{
throw new NotImplementedException();
}
}

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>

<CheckBox x:Name="chkPrueba" Content="Prueba Visibilidad" />


<StackPanel x:Name="Detalle"
Visibility="{Binding IsChecked, ElementName=chkPrueba,
Converter={StaticResource boolToVisible}}">
<Button Height="23" Width="50" Content="Prueba"></Button>
</StackPanel>
</StackPanel>
</Window>

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 "";
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo


culture)
{
string strValue = (string)value;
DateTime fecha;
if (DateTime.TryParse(strValue, out fecha))
return fecha;
else
throw new NotImplementedException();
}
}

<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>

El atributo ValueConversion permite especificar el tipo de dato de entrada y el de salida, es


útil para documentar el tipo de conversión que estamos realizando, pero no es obligatorio su
uso para que la conversión funcione correctamente.

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.

public class ReglaEdad: ValidationRule


{
private int _minEdad;
public int MinEdad
{
get { return _minEdad; }
set { _minEdad = value; }
}

private int _maxEdad;


public int MaxEdad
{
get { return _maxEdad; }
set { _maxEdad = value; }
}

public override ValidationResult Validate(object value, CultureInfo cultureInfo)


{
int iEdad = 0;
try
{
if (((string)value).Length > 0)
iEdad = Int32.Parse((String)value);
}
catch (Exception e)
{
return new ValidationResult(false, "Carácter incorrecto o " + e
.Message);
}

if ((iEdad < MinEdad) || (iEdad > MaxEdad))


{
return new ValidationResult(false,
"Por favor ingrese la edad en el rango: " + MinEdad + " - " +
MaxEdad + ".");
}
else
{
return new ValidationResult(true, null);
}
}
}

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.

<TextBox Name="tbEdad" DataContext="{StaticResource Datos}"


Width="100">
<TextBox.Text>
<Binding Path="Edad" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<svc:ReglaEdad MinEdad="1" MaxEdad="80" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

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>

<Style x:Key="EstiloError" TargetType="TextBox">


<Setter Property="Margin" Value="4,4,10,4" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource
TextBoxErrorTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>

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;
}

public string Error


{
get { throw new NotImplementedException(); }
}

public string this[string columnName]


{
get
{
string result = null;
if (columnName == "Nombre")
{
if (String.IsNullOrEmpty(Nombre))
result = "Debe ingresar el nombre";
else if (Nombre.Length < 3)
result = "El nombre debe tener al menos 3
carácteres";
}
else if (columnName == "Edad")
{
// Verifica rango
if ((Edad < 1) || (Edad > 80))
{
result="El edad debe estar entre 1 y 80";
}

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>

ValidatesOnExceptions indica si debe incluirse o no ExceptionValidationRule.


ValidatesOnDataErrors indica si debe incluirse o no DataErrorValidationRule, por lo tanto el
código usado en ambos TextBox es idéntico, con diferente sintaxis.

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.

Cómo implementar colecciones


Es posible enumerar cualquier colección que implementa la interfase IEnumerable. Sin
embargo, para configurar enlaces dinámicos de modo que las inserciones o eliminaciones que
se realicen en la colección actualicen la interfase de usuario de forma automática, la colección
debe implementar la interfase INotifyCollectionChanged. Esta interfase expone un evento que
debe provocarse siempre que se realicen cambios en la colección subyacente.

public event PropertyChangedEventHandler PropertyChanged;

WPF proporciona la clase ObservableCollection<T>, que es una implementación integrada de


una recolección de datos que expone la interfase INotifyCollectionChanged. Observe que para
permitir totalmente la transferencia de valores de datos de los objetos de origen a los destinos,
cada objeto de la colección que admite propiedades enlazables debe implementar también la
interfase INotifyPropertyChanged.
Antes de implementar su propia colección, considere la posibilidad de utilizar
ObservableCollection<T> o una de las clases de colección existentes, como List<T>,
Collection<T> y BindingList<T>, entre otras muchas. Si cuenta con un escenario avanzado y
desea implementar su propia colección, considere la posibilidad de utilizar IList, que
proporciona una colección no genérica de objetos a los que se puede obtener acceso
individualmente por índice y, por consiguiente, proporciona el máximo rendimiento.

En el siguiente ejemplo se muestra un ListBox enlazado a una objeto List<>.

private void Window_Loaded(object sender, RoutedEventArgs e)


{
List<string> Lista = new List<string>();
Lista.Add("Buenos Aires");
Lista.Add("Córdoba");
Lista.Add("Mendoza");
stpProv.DataContext = Lista;
}
83
<StackPanel Name="stpProv">
<ListBox Name="lstProv" ItemsSource="{Binding}" />
</StackPanel>

En el siguiente ejemplo se implementa una clase personalizada que implementa la interfase


INotifyPropertyChanged que se usará para armar una colección enlazable.

using System.ComponentModel;
using System.Collections.ObjectModel;

public class Empleado : INotifyPropertyChanged


{
public event PropertyChangedEventHandler PropertyChanged;

private string _Nombre;


public string Nombre
{
get { return _Nombre; }
set {
_Nombre = value;
OnPropertyChanged("Nombre");
}
}

private string _Apellido;


public string Apellido
{
get { return _Apellido; }
set {
_Apellido = value;
OnPropertyChanged("Apellido");
}
}

public Empleado(string pNombre, string pApellido)


{
Nombre = pNombre;
Apellido = pApellido;
}

protected void OnPropertyChanged(string info)


{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
84
{
handler(this, new PropertyChangedEventArgs(info));
}
}

public override string ToString()


{
return Apellido.ToString() + ", " + Nombre.ToString();
}
}

Ahora creamos otra clase que deriva de ObservableCollection<> y desde el constructor


ingresamos los datos a la colección.

public class Empleados : ObservableCollection<Empleado>


{
public Empleados()
{
Add(new Empleado("Juan", "Perez"));
Add(new Empleado("Jose", "Lopez"));
Add(new Empleado("Maria", "Rodriguez"));
}
}

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.

Cómo crear una vista


Una manera de crear y utilizar una vista es crear directamente una instancia del objeto de vista
y utilizar a continuación esa instancia como el origen del enlace. Para crear otra vista para la
misma colección, puede crear otra instancia de CollectionViewSource y asignarle un nombre
x:Key diferente.
Especificar una vista de colección como origen de enlace es una forma de crear y utilizar una
vista de colección. WPF también crea una vista de colección predeterminada para cada
colección utilizada como origen de enlace. Si enlaza directamente a una colección, WPF enlaza
a su vista predeterminada. Tenga en cuenta que todos los enlaces a una misma colección
comparten esta vista predeterminada, de modo que si se realiza un cambio en una vista
predeterminada a través de un control enlazado o mediante código (como un cambio de
ordenación o en el puntero de elemento actual, que se describe más adelante), éste se refleja
en el resto de los enlaces a la misma colección. Para obtener la vista predeterminada, se utiliza
el método GetDefaultView.

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

public class Empleado : INotifyPropertyChanged


{
public event PropertyChangedEventHandler PropertyChanged;

private string _Nombre;


public string Nombre
{
get { return _Nombre; }
set {
_Nombre = value;
OnPropertyChanged("Nombre");
}
87
}

private string _Apellido;


public string Apellido
{
get { return _Apellido; }
set {
_Apellido = value;
OnPropertyChanged("Apellido");
}
}

private string _Ciudad;


public string Ciudad
{
get { return _Ciudad; }
set
{
_Ciudad = value;
OnPropertyChanged("Ciudad");
}
}

public Empleado(string pNombre, string pApellido, string pCiudad)


{
Nombre = pNombre;
Apellido = pApellido;
Ciudad = pCiudad;
}

protected void OnPropertyChanged(string info)


{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}

public override string ToString()


{
return Apellido.ToString() + ", " + Nombre.ToString();
}
}

public class Empleados : ObservableCollection<Empleado>


{
public Empleados()
{
Add(new Empleado("Juan", "Perez", "Buenos Aires"));
Add(new Empleado("Jose", "Lopez","Mendoza"));
Add(new Empleado("Maria", "Rodriguez", "Buenos Aires"));
Add(new Empleado("Carlos", "Diaz", "Buenos Aires"));
Add(new Empleado("Mariana", "Fernandez", "Mendoza"));

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>

public partial class Window2 : Window


{
CollectionViewSource EmplFilter;
public Window2()
{
InitializeComponent();
EmplFilter = (CollectionViewSource)(this.Resources["EmplFiltrados"]);
}

private void filtroP(object sender, FilterEventArgs e)


{
Empleado oItem = (Empleado) e.Item;
if (oItem.Apellido.StartsWith("P"))
e.Accepted = true;
else
e.Accepted = false;
}

private void chkFiltro_Checked(object sender, RoutedEventArgs e)


{
if (EmplFilter != null)
EmplFilter.Filter += new FilterEventHandler(filtroP);
}

private void chkFiltro_Unchecked(object sender, RoutedEventArgs e)


{

90
if (EmplFilter != null)
EmplFilter.Filter -= new FilterEventHandler(filtroP);
}
}

Punteros de elemento actual


Las vistas admiten también la noción de elemento actual. Puede navegar por los objetos en
una vista de colección. A medida que navega por los objetos, mueve un puntero de elemento
que le permite recuperar el objeto ubicado concretamente en esa posición en la colección. Al
enlazar a una vista, el carácter de barra diagonal ("/") de un valor Path designa el elemento
actual de la vista. En el ejemplo siguiente, el contexto de datos es una vista de colección. La
primera línea enlaza a la colección. La segunda línea enlaza al elemento actual de la colección.
La tercera línea enlaza a la propiedad Description del elemento actual de la colección.

<Button Content="{Binding }" />


<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

La noción de elemento actual no es sólo útil para la navegación de elementos en una


colección, sino también para el escenario de enlace Cabecera-Detalle.

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.

<StackPanel Name="stp" >


<TextBlock Text="{Binding Nombre}" Width="100" Height="23"/>
<TextBlock Text="{Binding Apellido}" Width="100" Height="23"/>
<StackPanel Orientation="Horizontal" >
<Button Content="&lt;&lt;" Name="btnPrimero" Width="30"
Click="btnPrimero_Click" />
<Button Content="&lt;" Name="btnAnterior" Width="30"
Click="btnAnterior_Click" />
<Button Content="&gt;" Name="btnSiguiente" Width="30"
Click="btnSiguiente_Click" />
<Button Content="&gt;&gt;" Name="btnUltimo" Width="30"
Click="btnUltimo_Click" />
91
</StackPanel>
</StackPanel>

private void Window_Loaded(object sender, RoutedEventArgs e)


{
// Crea la colección como DataContext del StackPanel
stp.DataContext = new Empleados();
}

private void btnAnterior_Click(object sender, RoutedEventArgs e)


{
CollectionView Vista =
(CollectionView)CollectionViewSource.GetDefaultView(stp.DataContext);
Vista.MoveCurrentToPrevious();
if (Vista.IsCurrentBeforeFirst)
{
Vista.MoveCurrentToLast();
}
}

private void btnSiguiente_Click(object sender, RoutedEventArgs e)


{
CollectionView Vista =
(CollectionView)CollectionViewSource.GetDefaultView(stp.DataContext);
Vista.MoveCurrentToNext();
if (Vista.IsCurrentAfterLast)
{
Vista.MoveCurrentToFirst();
}
}

private void btnPrimero_Click(object sender, RoutedEventArgs e)


{
CollectionView Vista =
(CollectionView)CollectionViewSource.GetDefaultView(stp.DataContext);
Vista.MoveCurrentToFirst();
}

private void btnUltimo_Click(object sender, RoutedEventArgs e)


{
CollectionView Vista =
(CollectionView)CollectionViewSource.GetDefaultView(stp.DataContext);
Vista.MoveCurrentToLast();
}

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.

2- Documentos Dinámicos (FlowDocuments)


Definición
Los documentos dinámicos o FlowDocuments son documentos que pueden ser creados y
mostrados en una aplicación WPF. Consisten primordialmente en textos, con figuras y otros
elementos incluidos en el entorno. Consisten de dos diferentes tipos de elementos:
 Elementos en bloque, los cuales definen bloques de secciones de texto, siempre tienen
un salto de línea entre cada elemento
 Elementos en línea, que proveen formatos y efectos de texto en línea.
Para crearlos se usa el elemento FlowDocument. Estos documentos deben ser creados dentro
de un contenedor, además los contenedores nos proveen de Zoom, Paginación, columnas y
cuando se cambia de tamaño, el texto se adecua al tamaño del elemento.
94
Contenedores para Documentos Dinámicos
Existen 3 tipos de contenedores:
 FlowDocumentScrollViewer: Este contenedor cuadra todo su contenido de acuerdo a
su tamaño y agrega barras de desplazamiento. Es el contenedor más sencillo, aunque
este también permite Zoom. Para añadir el Zoom al FlowDocumentScrollViewer, solo
debemos dejar la propiedad de IsToolBarVisible a True.
 FlowDocumentPageViewer: Automáticamente divide el contenido en páginas, el
tamaño de la página está determinado por el tamaño del contenedor, incluye también
un control para la paginación, y un elemento Slider para el Zoom
 FlowDocumentReader: Es el contenedor que más opciones tiene. Nos permite cambiar
las vistas para la paginación, adiciona barras de desplazamiento, columnas, una barra
de búsqueda y Zoom.

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>

Formateando Documentos Dinámicos


Todos los elementos en línea y en bloques tienen muchas propiedades que podemos usar para
formatear el texto, como por ejemplo el fondo, el color, tipo, tamaño, estilo de la letra entre
otras.
Los cambios hechos a dichas propiedades son aplicados a todo el texto que esta encerrado en
el elemento, hay otras propiedades que solo aplican para los elementos en bloque y son: color,
grosor del borde, espaciamiento del texto, márgenes, alineación del texto.

Si en el primer ejemplo cambiamos la siguiente línea a

<Paragraph Background="yellow" BorderBrush="Red" BorderThickness="4"


Foreground="Green" >

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.

<List MarkerStyle="Decimal" >

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.

Nota: El diseñador de WPF no soporta el ingreso del elemento <PageContent>. El programa


ejecuta pero el diseñador genera un error que dice:
Property 'Pages' does not support values of type 'PageContent'.
Es un bug que está documentado y que probablemente va a ser solucionado en la versión
2010.

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.

public class Persona


{
public Persona()
{}
public Persona(string pNombre, string pApellido)
{
Nombre = pNombre;
Apellido = pApellido;
}

private string _Nombre;


public string Nombre
{
get { return _Nombre; }
set { _Nombre = value; }
}

private string _Apellido;


public string Apellido
{
get { return _Apellido; }
set { _Apellido = value; }
}
}

public class Personas


{
public List<Persona> TraerPersonas()
{
List<Persona> lista = new List<Persona>();
lista.Add(new Persona("Juan","Perez"));
lista.Add(new Persona("Jose","Fernandez"));
lista.Add(new Persona("Maria","Lopez"));
lista.Add(new Persona("Pedro","Rodriguez"));
return lista;
}
}

<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>

Al igual que cualquier otro objeto puede crearse escribiendo código

private void Window_Loaded(object sender, RoutedEventArgs e)


{
FixedDocument fd = new FixedDocument();
PageContent pc = new PageContent();
FixedPage fp = new FixedPage();
((IAddChild)pc).AddChild(fp);
fd.Pages.Add(pc);

Canvas cv = new Canvas();


TextBlock tb = new TextBlock();
tb.Text = "Esta es una prueba del elemento FixDocument creado por código";
103
tb.FontSize = 16;
Canvas.SetLeft(tb, 10);
Canvas.SetTop(tb, 10);
cv.Children.Add(tb);
fp.Children.Add(cv);

// lo muestra en la ventana
DocumentViewer dv = new DocumentViewer();
dv.Document = fd;
((IAddChild)this).AddChild(dv);
}

4- Empaquetado e Impresión de Documentos

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.

Documentos XPS (XPSDocument)

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.

Para el siguiente ejemplo de deberá agregar la referencia a System.Printing.dll

using System.Windows.Xps;
105
using System.Printing;

PrintDocumentImageableArea area = null;


XpsDocumentWriter xdw = PrintQueue.CreateXpsDocumentWriter(ref area);
if (xdw != null)
xdw.Write(fd);

Nota: fd es un FixedDocument creado en un ejemplo anterior

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>

private void Imprimir_Click(object sender, RoutedEventArgs e)


{
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintDocument(((IDocumentPaginatorSource)
DocPrueba).DocumentPaginator, "Impresión Documento Dinámico");
}
}

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>

Propiedades de las formas


 Fill: Pinta el interior. Tipo de dato Brush
 Stroke: Pinta el borde de la forma
 StrokeThickness: Establece el espesor del borde
108
 Stretch: Determina como llena el espacio vacío del contenedor

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.

<Path Stroke="Black" StrokeThickness="1" >


<Path.Data>
<LineGeometry StartPoint="10,20" EndPoint="100,130" />
</Path.Data>
</Path>

<Path Stroke="Black" StrokeThickness="1">


<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="1,10">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="150,200" Point2="300,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
109
Aparentemente, la clase Geometry y la clase Shape son bastante similares. Ambas se utilizan
para representar gráficos 2D y tienen clases concretas similares que se derivan de ellas, por
ejemplo, EllipseGeometry y Ellipse. Sin embargo, existen diferencias importantes entre estos
dos conjuntos de clases. En primer lugar, la clase Geometry carece de parte de la
funcionalidad de la clase Shape, como la capacidad de dibujarse a sí misma. Para dibujar un
objeto de geometría, deberá utilizarse otra clase, del tipo de DrawingContext, Drawing o Path
(cabe destacar que Path es una forma) para realizar la operación de dibujo. Las propiedades
de representación, tales como el relleno, el trazo y el grosor del trazo pertenecen a la clase que
dibuja el objeto de geometría, mientras que un objeto de forma contiene estas propiedades.
Podemos pensar en esta diferencia de la siguiente manera: un objeto de geometría define una
región, un círculo por ejemplo, mientras que un objeto de forma define una región, define cómo
se rellena y perfila esa región, y participa en el sistema de diseño.
Puesto que los objetos Shape se derivan de la clase FrameworkElement, utilizarlos puede
aumentar significativamente el consumo de memoria de la aplicación. Si realmente no necesita
las características de FrameworkElement para el contenido gráfico, es conveniente utilizar los
objetos Drawing, más ligeros.

Propiedad Clip
Obtiene o establece una región que limita la región de dibujo del gráfico.

<Button Content="Aceptar" Height="100" Width="100">


<Button.Clip>
<EllipseGeometry Center="50,50" RadiusX="50" RadiusY="20" />
</Button.Clip>
</Button>

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.

En el siguiente ejemplo vemos como rotar la imagen 90º

<Image Height ="150">


<Image.Source>
<TransformedBitmap Source="mslogo.jpg" >
<TransformedBitmap.Transform>
<RotateTransform Angle="90"/>
</TransformedBitmap.Transform>
</TransformedBitmap>
</Image.Source>
</Image>

En este ejemplo convertimos la imagen a tonos de gris.

<Image Width="150" >


<Image.Source>
<FormatConvertedBitmap Source="mslogo.jpg" DestinationFormat="Gray2" />
</Image.Source>
</Image>

113
En este ejemplo recortamos la imagen.

<Image Width="200" Source="mslogo.jpg">


<Image.Clip>
<EllipseGeometry Center="75,10" RadiusX="40" RadiusY="15" />
</Image.Clip>
</Image>

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.

<Button Height="50" Width="100" Foreground="White" FontWeight="Bold" FontSize="14" >


Aceptar
<Button.Background>
<ImageBrush ImageSource="Flower2.jpg" />
</Button.Background>
</Button>

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.

La propiedad TextureCoordinates especifica una colección de puntos (Point) que indica al


sistema de gráficos cómo asignar las coordenadas que determinan cómo trazar una textura en
los vértices de la malla. Las TextureCoordinates se especifican como un valor comprendido
entre cero y 1, incluidos. Como sucede con la propiedad Normals, el sistema de gráficos puede
calcular las coordenadas de textura predeterminadas, pero si lo desea puede establecer
coordenadas de textura diferentes a fin de controlar la asignación de una textura que incluya
parte de un patrón repetitivo.

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.

El siguiente ejemplo arma un gráfico 3D aplicando los objetos vistos

<Canvas Width="200" Height="201">


<!-- Viewport3D provee la superficie -->
<Viewport3D ClipToBounds="True" Width="150" Height="150" Canvas.Left="0"
Canvas.Top="10">
<!-- Define la cámara -->
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1" FieldOfView="60" />
</Viewport3D.Camera>
<Viewport3D.Children>
<!-- ModelVisual3D define la luz de la escena. sin luz los objetos 3D
no pueden verse. La dirección de la luz define las sombras. Se pueden crear
múltiples luces con diferentes colores que brillen en distintas direcciones -->
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<!-- La geometría especifica la forma del plano 3D. -->
<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>
<!-- El material especifica el material aplicado al objeto 3D. -->
<GeometryModel3D.Material>
<MaterialGroup>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>

119
</MaterialGroup>
</GeometryModel3D.Material>

<!-- Se puede aplicar transformaciones al objeto. -->


<GeometryModel3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,3,0" Angle="40" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
</Canvas>

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).

El siguiente ejemplo genera que el rectángulo se mueva a derecha e izquierda.

<Canvas Width="300" Height="201">


<Viewport3D Name="MyAnimatedObject"
ClipToBounds="True" Width="300" Height="150"
Canvas.Left="0" Canvas.Top="10">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="myPerspectiveCamera" Position="0,0,2"
LookDirection="0,0,-1"
FieldOfView="60" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
</ModelVisual3D.Content>

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.

Si se especifica una transformación de escala no uniforme (una transformación de escala


cuyos valores X, Y y Z no son iguales), se puede provocar el estiramiento o la contracción del
modelo en una o dos dimensiones sin afectar el resto. Por ejemplo, si se establece el valor de
ScaleX en 1, de ScaleY en 2 y de ScaleZ en 1, se provocaría la duplicación del alto del modelo
transformado pero los ejes X y Z permanecerían iguales.
De forma predeterminada, ScaleTransform3D provoca la expansión o contracción de los
vértices en el origen (0,0,0). Si el modelo que desea transformar no se dibuja partiendo del
origen, sin embargo, al escalar el modelo a partir del origen, no se escalará el modelo "en
contexto". En su lugar, cuando se multiplican los vértices del modelo por el vector de escala, la
operación de la escala tendrá el efecto de trasladar y escalar el modelo.

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.

<Canvas Width="321" Height="201">


<Viewport3D ClipToBounds="True" Width="150" Height="150" Canvas.Left="0"
Canvas.Top="10">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1" FieldOfView="60" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
</ModelVisual3D.Content>
</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>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup>
</GeometryModel3D.Material>
<GeometryModel3D.Transform>
<ScaleTransform3D ScaleX="2" ScaleY="0.2" ScaleZ="1" CenterX="0"
CenterY="0" CenterZ="0" />
</GeometryModel3D.Transform>
</GeometryModel3D>

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.

El siguiente ejemplo genera la rotación de la figura.

<Canvas Width="321" Height="201">


<Viewport3D Name="MyAnimatedObject"
ClipToBounds="True" Width="150" Height="150"
Canvas.Left="0" Canvas.Top="10">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="myPerspectiveCamera" Position="0,0,2"
LookDirection="0,0,-1"
FieldOfView="60" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="#FFFFFF" Direction="0.612372,-0.5,-0.612372" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
125
<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>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup>
</GeometryModel3D.Material>

<!-- 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>

<!-- Dispara la animación de rotación-->


<Viewport3D.Triggers>
<EventTrigger RoutedEvent="Viewport3D.Loaded">
<BeginStoryboard>
<Storyboard>
<!-- Esta animación anima la propiedad Angle de AxisAngleRotation3D
haciendo que el objeto rote de -60 a 60 -->
<DoubleAnimation
Storyboard.TargetName="myAngleRotation"
Storyboard.TargetProperty="Angle"
From="-60" To="60" Duration="0:0:4" AutoReverse="True"
RepeatBehavior="Forever"/>

<!-- Esta animación anima la propiedad Axis de AxisAngleRotation3D


haciendo que el objeto se tambalee mientras rota -->
<Vector3DAnimation

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>

MediaPlayer player = new MediaPlayer();


player.Open(new Uri(@"sampleMedia\xbox.wmv", UriKind.Relative));
VideoDrawing aVideoDrawing = new VideoDrawing();
aVideoDrawing.Rect = new Rect(0, 0, 100, 100);
aVideoDrawing.Player = player;
player.Play();

Modos Media Playback


Controla los diferentes modos que un medio puede ser reproducido. Tanto MediaElement como
MediaPlayer pueden usar ambos modos. Si Clock está en nulo el medio se reproduce en forma
independiente.
127
 Modo Independiente: Predeterminado. Se puede especificar la URI del medio, controlar
su reproducción, etc. Permite usar los métodos Play, Pause, Close y Stop.
 Modo Reloj: La reproducción se controla con MediaTimeline. Los métodos Play, Pause,
Close y Stop está inhabilitados.

<MediaElement Name="myMediaElement" >


<MediaElement.Triggers>
<EventTrigger RoutedEvent="MediaElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<!-- La MediaTimeline tiene RepeatBehavior="Forever" que genera que se
repita eternamente-->
<MediaTimeline Source="media\tada.wav"
Storyboard.TargetName="myMediaElement"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</MediaElement.Triggers>
</MediaElement>

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.

private void Window_Loaded(object sender, RoutedEventArgs e)


{
System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
sp.SoundLocation = txtArchivo.Text;
sp.Play();
}

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>.

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup,
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
130
<section name="WpfCSharp.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<connectionStrings>
<add name="WpfCSharp.Properties.Settings.AdvWorks" connectionString="Data
Source=.;Initial Catalog=AdventureWorks;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<userSettings>
<WpfCSharp.Properties.Settings>
<setting name="miClave" serializeAs="String">
<value>ElValor</value>
</setting>
</WpfCSharp.Properties.Settings>
</userSettings>
</configuration>

La configuración de aplicación consta de cuatro valores:


 Nombre: El nombre o identificación del valor configurado. Se lo usa para acceder al
valor.
 Tipo: El tipo de dato
 Ámbito: Puede ser Usuario o Aplicación

131
 Valor: El valor a devolver al leer la configuración

Recuperando información de app.config


Durante la ejecución de la aplicación necesitará acceder a los valores configurados en el
archivo app.config. Para ello podrá hacerlo desde el código escribiendo un código similar al
siguiente, siendo AdvWorks el nombre del valor configurado:

string sc = Properties.Settings.Default.AdvWorks;

Almacenar información en app.config en run-time


Se puede almacenar información en el archivo de configuración durante la ejecución solo para
valores configurados de ámbito de usuario. Para cambiar el valor simplemente asígnele un
nuevo valor y luego llame el método Save.

Properties.Settings.Default.miValor = "Otro Valor";


Properties.Settings.Default.Save();

2- Distribución de Aplicaciones WPF


Una vez generadas las aplicaciones de Windows Presentation Foundation (WPF), es preciso
implementarlas. Windows y .NET Framework incluyen varias tecnologías de implementación:
 XCopy.
 Microsoft Windows Installer.
 Implementación de ClickOnce.

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.

Microsoft Windows Installer


Windows Installer permite empaquetar las aplicaciones como aplicaciones ejecutables
autónomas que se pueden distribuir con facilidad a los clientes y ejecutar. Además, Windows
Installer se instala con Windows y habilita la integración con el escritorio, el menú Inicio, y el
panel de control Agregar o quitar programas.
Windows Installer simplifica la instalación y desinstalación de aplicaciones, pero no proporciona
los medios para asegurarse de que las aplicaciones instaladas se mantengan actualizadas
desde el punto de vista de su versión.
132
ClickOnce
ClickOnce habilita la implementación de aplicaciones de tipo web para aplicaciones no web: las
aplicaciones se publican e inician desde servidores web. Aunque ClickOnce no admite la gama
completa de características de cliente que sí poseen las aplicaciones instaladas con Windows
Installer, admite un subconjunto que incluye lo siguiente:
 Integración con el menú Inicio y el panel de control Agregar o Quitar Programas, para
aplicaciones independientes.
 Control de versiones, reversión y desinstalación.
 Modo de instalación en línea, que siempre inicia una aplicación desde la ubicación de
implementación.
Para hacer que una aplicación ClickOnce esté disponible para los usuarios, deberá publicarla
en un servidor Web, recurso compartido de archivo o en medios extraíbles. Puede publicar la
aplicación utilizando el Asistente para publicación. Antes de ejecutar el Asistente para
publicación, debe establecer las propiedades de publicación adecuadamente.

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

1. En el Explorador de soluciones, seleccione el proyecto de la aplicación.


2. Haga clic con el botón secundario en el nodo del proyecto y haga clic en Publicar. Aparecerá
el Asistente para publicación. También puede llamar al asistente de publicación desde la
solapa Publicar explicada anteriormente.

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.

Recurso compartido de archivos


3. En la página ¿Dónde desea publicar la aplicación?, escriba una ruta de acceso válida con el
formato \\computername\applicationname y, a continuación, haga clic en Siguiente.
4. En la página Instalación de la aplicación, seleccione la ubicación donde los usuarios
instalarán la aplicación:
- Si los usuarios van a instalar la aplicación desde un sitio web, haga clic en Desde un
sitio web y escriba una dirección URL que corresponda a la ruta de acceso del archivo
escrita en el paso anterior. Haga clic en Siguiente. (Normalmente, se utiliza esta opción
134
cuando se especifica una dirección FTP como la ubicación de la publicación. No se
admite la descarga directa desde FTP. Por lo tanto, aquí debe escribir una dirección
URL.)
- Si los usuarios instalan la aplicación directamente desde el recurso compartido de
archivos, haga clic en Desde una ruta de acceso UNC o un recurso compartido de
archivos y luego haga clic en Siguiente. (Esto es para ubicaciones de publicación en el
formato c:\deploy\myapp o \\server\myapp.)
- Si los usuarios van a instalar la aplicación desde medios extraíbles, haga clic en
Desde un CD-ROM o un DVD-ROM y, a continuación, haga clic en Siguiente.
5. 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.
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.

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.

Comparación entre ClickOnce y Microsoft Windows Installer


Característica ClickOnce Windows Installer
Actualización automática SI SI
Deshacer cambios tras la instalación SI NO
Actualizar desde el Web SI NO
No afecta a componentes compartidos u otras
SI NO
aplicaciones
Se conceden permisos de seguridad Sólo concede los Concede plena
permisos necesarios confianza de forma
para la aplicación predeterminada
(más seguro) (menos seguro)
Permisos de seguridad requeridos Zona Internet o Administrador
intranet (plena
135
confianza para la
instalación de CD-
ROM)
Firma de manifiestos de aplicación e
SI NO
implementación
Interfaz de usuario del proceso de instalación Asistente de varias
Indicador único
partes
Instalación de ensamblados a petición SI NO
Instalación de archivos compartidos NO SI
Instalación de controladores Sí (con acciones
NO
personalizadas)
Instalación en la caché de ensamblados global NO SI
Instalación para varios usuarios NO SI
Agregar la aplicación al menú Inicio SI SI
Agregar la aplicación al grupo Inicio NO SI
Agregar la aplicación al menú Favoritos NO SI
Registrar tipos de archivos NO SI
Acceso al Registro durante la instalación Limitado SI
Revisión de archivos binarios NO SI
Ubicación de instalación de aplicaciones Caché de
Carpeta Archivos de
aplicaciones de
programa
ClickOnce

136
Links Relacionados

Introducción a WPF
http://msdn.microsoft.com/es-ar/library/aa970268.aspx

Ejemplos Aplicaciones WPF:


http://msdn.microsoft.com/es-ar/library/ms771449.aspx
http://msdn.microsoft.com/es-ar/library/ms771542.aspx

Información sobre controles


http://msdn.microsoft.com/es-es/library/ms590941(v=VS.90).aspx

Eventos y Comandos enrutados


http://msdn.microsoft.com/es-es/magazine/cc785480.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

También podría gustarte