79

Click here to load reader

guia c# 01

Embed Size (px)

DESCRIPTION

Programacion c#

Citation preview

Page 1: guia c# 01

Formularios MDI con C# (C Sharp)

EJERCICIO 01

Un formulario MDI es un formulario (llamado "Padre" o "Parent") que puede contener otros

formularios (llamados "Hijos" o "Child"). Para trabajar con este tipo de formularios se

ejecuta el Visual C# y se crea un nuevo proyecto del tipo "Windows Application". De forma

automática se crea un formulario con nombre "Form1". En la ventana Propiedades, se busca

la propiedad "IsMDIContainer" y se cambia a "True". Luego en el menú se elige Proyectos ->

Agregar Windows Form. Aparecerá la ventana de "Agregar Nuevo Elemento". Se elige

"Windows Form".

El nuevo formulario se crea con el nombre Form2. A este control le agregamos 3 controles

tipo textBox que se dejarán con sus nombres por defecto: textBox1, textBox2 y textBox3.

En la ventana "Explorador de Soluciones", se le hace clic derecho al icono de Form1 y

clicamos en "Ver Código". El código a añadir será:

Lo que se está haciendo es crear un objeto de la clase "Form2" (C# es orientado a objetos,

y todo está hecho en base a clases y namespaces). El objeto se llamará "Frm2". En las tres

Page 2: guia c# 01

líneas dentro de Form1_Load, le decimos a Form2 (a través de Frm2) que esté contenido

dentro de Form1.

"Frm2" nos permite tener control sobre Form2. Mas existe la condición de que todo lo que

queramos hacerle a Form2 esté dentro de métodos públicos. Form1 y Form2 son dos clases

separadas (ambas están dentro del namespace "Semana07"), por lo que sólo podemos

acceder a Form2 desde Form1 a través de sus métodos públicos.

En el código de Form2 añadimos:

Page 3: guia c# 01

Luego a Form1 le añadimos un control MenuStrip, jalándolo desde la caja de herramientas:

Luego se le hace clic a la etiqueta "limpiar" y se añade el código:

Esto permite borrar las cajas de texto en Form2 haciendo click a la opción "limpiar" en el

menú de Form1.

Se pueden hacer más cosas, por ejemplo, añadí esto al menuStrip:

Page 4: guia c# 01

Junto con este código:

Esto permite minimizar y maximizar Form2 clickeando la opción correspondiente en el

menú.

Por último, siempre es bueno revisar que en el código del archivo Program.cs, dentro del

método Main, sea a Form1 al que se llame en la línea de Application.Run (los programas en

C# siempre empiezan en el método Main).

Page 5: guia c# 01

EJERCICIO 02

En el ejemplo vamos a crear una aplicación MDI en la que tendremos un formulario principal

en el que añadiremos un menú con opciones para:

Crear nuevos documentos, abrir un fichero de texto para mostrar en el documento activo,

una opción para guardar el fichero del documento activo, otra para cerrar el documento

activo y una lista con los documentos que están abiertos.

En el formulario que usaremos para cada nuevo documento simplemente tendremos una caja

de textos multilínea en la que mostraremos el fichero abierto.

Las opciones del formulario principal se basará en el documento activo, es decir que se

trata del documento activo es para que quede claro que cualquier acción que hagamos con

las opciones de los menús se basarán en el documento o formulario secundario que tenga el

foco, es decir, el activo.

Creamos un nuevo proyecto, se añade un formulario llamado Form3, le dejamos ese nombre.

El form se muestra por defecto, por tanto vamos a la ventana de propiedades (si no está

activa, puedes pulsar F4), buscamos la propiedad IsMdiContainer y le asignamos el valor

True (por defecto tiene el valor False).

A partir de ahora ya tenemos un formulario MDI principal, y notaremos que es así porque el

fondo del formulario cambia de color usando el que tengamos predeterminado en el

sistema, normalmente es un fondo gris oscuro.

Para añadir el formulario hijo, usaremos el Form2 que creamos en el ejercicio anterior.

Asignamos la propiedad MultiLine a True en el último TextBox creado, el tipo de letras lo

puedes poner como Courier New, también debes asignar a la propiedad Dock el valor

Bottom (el botón que está en la parte inferior de las opciones que te muestra al seleccionar

esa propiedad desde la ventana de propiedades).

Page 6: guia c# 01

Añadir los menús al formulario MDI principal

Ahora añadimos un menú principal, del Cuadro de herramientas seleccionamos MenuStrip, y

agregamos los siguientes menús: (Entre paréntesis está el nombre del menú)

&Ficheros (mnuFic)

o &Nuevo (mnuNuevo)

o -

o &Abrir... (mnuAbrir)

o &Guardar como... (mnuGuardarComo)

o -

o &Cerrar ventana (mnuCerrar)

o -

o &Salir (mnuSalir)

&Ventanas (mnuVentanas)

o &Minimizar todas (mnuMinimizar)

En los separadores no he puesto el nombre porque no los usamos en el código, pero yo suelo

nombrarlos al estilo de mnuFicSepN donde N es un número que voy incrementando.

El siguiente es el código del proyecto:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Semana07 { public partial class Form3 : Form { Form2 frm2; public Form3() { InitializeComponent(); } private void mnuNuevo_Click(object sender, EventArgs e) { // Crear una nueva ventana hija frm2 = new Form2(); frm2.MdiParent = this; // Para mostrarlo maximizado: frm2.WindowState = FormWindowState.Maximized; frm2.Show(); } private void mnuSalir_Click(object sender, EventArgs e) { // Cerrando la ventana principal se cierran todas this.Close(); //Application.Exit() } private void mnuCerrar_Click(object sender, EventArgs e) { // Cerrar la ventana hija que tiene el foco

Page 7: guia c# 01

// Asignar la ventana que tiene el foco Form2 frm2 = (Form2)this.ActiveMdiChild; if (frm2 != null) { frm2.Close(); } } private void mnuAbrir_Click(object sender, EventArgs e) { Form2 frm2 = ((Form2)this.ActiveMdiChild); if (frm2 != null) { // Seleccionar el fichero que queremos abrir OpenFileDialog ofD = new OpenFileDialog(); if (ofD.ShowDialog() == DialogResult.OK) { // Leemos el contenido de ese fichero System.IO.StreamReader sr = new System.IO.StreamReader(ofD.FileName, System.Text.Encoding.Default, true); // Lo asignamos al TextBox1 del Form2: frm2.textBox3.Text = sr.ReadToEnd(); sr.Close(); } } } private void mnuGuardarComo_Click(object sender, EventArgs e) { Form2 frm2 = ((Form2)this.ActiveMdiChild); if (frm2 != null) { SaveFileDialog sfD = new SaveFileDialog(); if (sfD.ShowDialog() == DialogResult.OK) { System.IO.StreamWriter sw = new System.IO.StreamWriter(sfD.FileName, false, System.Text.Encoding.Default); sw.WriteLine(frm2.textBox3.Text); sw.Close(); } } } private void mnuMinimizarTodas_Click(object sender, EventArgs e) { for (int i = 0; i < this.MdiChildren.Length; i++) { this.MdiChildren[i].WindowState = FormWindowState.Minimized; } } }

}

Por último, siempre es bueno revisar que en el código del archivo Program.cs, dentro del

método Main, sea a Form3 al que se llame en la línea de Application.Run (los programas en

C# siempre empiezan en el método Main).

Page 8: guia c# 01

CICLOS

EJERCICIO 03

Replique el siguiente ejercicio.

Código

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Semana07 { public partial class Form4 : Form { public Form4() { InitializeComponent(); } private void btnDia_Click(object sender, EventArgs e) { String numero; switch (int.Parse(txtDia.Text)) { case 1:

Page 9: guia c# 01

numero = "Domingo"; break; case 2: numero = "Lunes"; break; case 3: numero = "Martes"; break; case 4: numero = "Miercoles"; break; case 5: numero = "Jueves"; break; case 6: numero = "Viernes"; break; case 7: numero = "Sabado"; break; default: numero = "No existe ese Dia"; break; } lblDia.Text = "Día: " + numero; } private void btnFactorial_Click(object sender, EventArgs e) { int valor = 0; valor = int.Parse(txtFactorial.Text); lblFactorial.Text = "El factorial es: " + Factorial(valor); } public static long Factorial(int n) { if (n < 0) { return -1; } //para evitar errores asignamos cualquier numero. if (n > 256) { return -2; } if (n == 0) { return 1; } // Calcula el factorial. long tempResult = 1; for (int i = 1; i <= n; i++) { tempResult *= i; } return tempResult; } private void btnMultiplicar_Click(object sender, EventArgs e) { int reng = 0; int val = int.Parse(txtMultiplicar.Text); lbxMultiplicar.Items.Clear(); while (reng <= 10) { int cal = 0; cal = val * reng; lbxMultiplicar.Items.Add(reng.ToString() + " x " + val + " = " + cal); reng++; }; } } }

Page 10: guia c# 01

01-Nombre de Aplicación: Manejo del Control Listbox

Foto:

Descripción: Pequeña aplicación que nos enseñara como utilizar el Control Listbox,

agregaremos items provenientes de un Textbox y podremos eliminar un ítem posicionando

nos en él y dando clic derecho.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio01 : Form { public Ejercicio01() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { listBox1.Items.Add(textBox1.Text); textBox1.Text = ""; textBox1.Focus(); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void eliminarSeleccionadoToolStripMenuItem_Click(object sender, EventArgs e) { listBox1.Items.Remove(listBox1.SelectedItem); }

Page 11: guia c# 01

private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 12: guia c# 01

02-Nombre de Aplicación: Cargar Imagen en PictureBox

Foto:

3

Descripción: Pequeña aplicación para cargar una Imagen en un Picturebox utilizando el

OpenFileDialog.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio02 : Form { public Ejercicio02() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { OpenFileDialog BuscarImagen = new OpenFileDialog(); BuscarImagen.Filter = "Archivos de Imagen|*.jpg"; BuscarImagen.FileName = ""; BuscarImagen.Title = "Programa de Contabilidad Casero"; BuscarImagen.InitialDirectory = "C:\\"; BuscarImagen.FileName = this.textBox1.Text; if (BuscarImagen.ShowDialog() == DialogResult.OK) { /// Si esto se cumple, capturamos la propiedad File Name y la guardamos en el control this.textBox1.Text = BuscarImagen.FileName; //Pueden usar tambien esta forma para cargar la Imagen solo activenla y comenten la linea donde se cargaba anteriormente String Direccion = BuscarImagen.FileName; this.pictureBox1.ImageLocation = Direccion; //this.pictureBox1.ImageLocation = textBox1.Text; pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;

Page 13: guia c# 01

} } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 14: guia c# 01

03-Nombre de Aplicación: Convertidor de Grados Centigrados en Farenheit

Foto:

Descripción: Aplicación que nos sirve para hacer una conversión de Grados C a F o

Viceversa, el botón reconocerá donde se han ingresado datos.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio03 : Form { private TextBox objTextBox = null; public Ejercicio03() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { try { double grados; // Estp valida en que caja de texto se escribio para mandar el resultado a la otra if (objTextBox == textBox2) { grados = Convert.ToDouble(textBox2.Text) * 9.0 / 5.0 + 32.0; // Como la variable es de tipo double puede arrojar decimales le decimos aqui // que redondee el resultado a unicamente 2 decimales. textBox1.Text = string.Format("{0:F2}", grados); } if (objTextBox == textBox1) { grados = (Convert.ToDouble(textBox1.Text) - 32.0) * 5.0 / 9.0; // Mostrar el resultado redondeado a dos decimales textBox2.Text = string.Format("{0:F2}", grados);

Page 15: guia c# 01

} } catch (FormatException) { textBox2.Text = "0,00"; textBox1.Text = "32,00"; } } private void textBox2_KeyPress(object sender, KeyPressEventArgs e) { objTextBox = (TextBox)sender; } private void textBox1_KeyPress(object sender, KeyPressEventArgs e) { objTextBox = (TextBox)sender; } private void Form1_Load(object sender, EventArgs e) { textBox1.Focus(); linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void textBox2_MouseClick(object sender, MouseEventArgs e) { TextBox ObjTextBox = (TextBox)sender; ObjTextBox.SelectAll(); } private void textBox1_MouseClick(object sender, MouseEventArgs e) { TextBox ObjTextBox = (TextBox)sender; ObjTextBox.SelectAll(); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 16: guia c# 01

04-Nombre de Aplicación: Fecha y Hora Actualizados

Foto:

Descripción: Aplicación que usa un Timer para ir actualizando la hora cada segundo.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio04 : Form { public Ejercicio04() { InitializeComponent(); timer1.Enabled = true; } private void timer1_Tick(object sender, EventArgs e) { label1.Text = DateTime.Now.ToString(); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 17: guia c# 01

05-Nombre de Aplicación: Evitar Cierre Equivocado

Foto:

Descripción: Aplicación que sirve para controlar el Evento Closing de una aplicación y

poder cancelar el cierre.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio05 : Form { public Ejercicio05() { InitializeComponent(); } private void Form1_FormClosing(object sender, System.ComponentModel.CancelEventArgs e) { DialogResult dialogo = MessageBox.Show("¿ Desea Salir de la Aplicacion S/N ?", "Salir de Aplicacion", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (dialogo == DialogResult.OK) { } else { e.Cancel = true; } } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 18: guia c# 01

06-Nombre de Aplicación: Form con Transparencia

Foto:

Descripción: Sencilla Aplicación para poner transparencia a una Forma.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio06 : Form { public Ejercicio06() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { this.Opacity = 0.70; linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 19: guia c# 01

07-Nombre de Aplicación: Mostrar Varios tipos de Mensajes

Foto:

Descripción: Aplicación que nos muestra diferentes formas de mostrar un Mensaje en una

aplicación.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio07 : Form { public Ejercicio07() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { label1.Text = "Mostrando un Mensaje por Medio de un Label"; } private void button2_Click(object sender, EventArgs e) { MessageBox.Show("Este es un Ejemplo de Mensaje usando MessageBox"); } private void button3_Click(object sender, EventArgs e) { textBox1.Text = "Esta es Otra manera de Enviar texto a un control"; } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) {

Page 20: guia c# 01

linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 21: guia c# 01

08-Nombre de Aplicación: Link Label

Foto:

Descripción: Solo nos muestra cómo utilizar un Link Label.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio08 : Form { public Ejercicio08() { InitializeComponent(); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 22: guia c# 01

09-Nombre de Aplicación: Mini Reproductor de Video

Foto:

Descripción: un poco mas complejo es hacer uso de los componentes del Wmplayer para

crear una aplicación que reproduce Video en formatos MPEG y Avi.

using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Text; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio09 : Form { [DllImport("winmm.dll")] private static extern long mciSendString(string strCommand, StringBuilder strReturn, int iReturnLength, IntPtr hwndCallback); string ruta; string comandoMCI; public Ejercicio09() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { // Liberar si ya se encontraba cargado un video mciSendString("stop miVideo", null, 0, IntPtr.Zero); mciSendString("close miVideo", null, 0, IntPtr.Zero); OpenFileDialog abrirArchivo = new OpenFileDialog(); abrirArchivo.Filter = "Archivos de video|*.avi;*.mpeg;"; abrirArchivo.ShowDialog();

Page 23: guia c# 01

ruta = abrirArchivo.FileName; //Abrir el dispositivo MCI comandoMCI = string.Format("open \"{0}\" type mpegvideo alias miVideo wait", ruta); long respuesta = mciSendString(comandoMCI, null, 0, IntPtr.Zero); comandoMCI = "window miVideo handle " + this.pictureBox1.Handle.ToString(); mciSendString(comandoMCI, null, 0, IntPtr.Zero); comandoMCI = "put miVideo destination at 0 0 " + this.pictureBox1.Width.ToString() + " " + this.pictureBox1.Height.ToString() + " wait"; mciSendString(comandoMCI, null, 0, IntPtr.Zero); } private void button2_Click(object sender, EventArgs e) { comandoMCI = string.Format("play miVideo"); mciSendString(comandoMCI, new StringBuilder(), 0, IntPtr.Zero); } private void button3_Click(object sender, EventArgs e) { comandoMCI = string.Format("pause miVideo"); mciSendString(comandoMCI, new StringBuilder(), 0, IntPtr.Zero); } private void button4_Click(object sender, EventArgs e) { comandoMCI = "seek miVideo to start"; mciSendString(comandoMCI, null, 0, IntPtr.Zero); } private void linkLabel1_LinkClicked(object sender, System.Windows.Forms.LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 24: guia c# 01

10-Nombre de Aplicación: Mini Stock

Foto:

Descripción: Aplicación que nos da un ejemplo de cómo podemos agregar productos a un

Stock si desarrollamos algún proyecto de punto de Venta.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio10 : Form { public Ejercicio10() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { int precio, cantidad, total; precio = Convert.ToInt32(textBox1.Text); cantidad = Convert.ToInt32(textBox2.Text); total = precio * cantidad; label4.Text = "$" + Convert.ToString(total); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 25: guia c# 01

11-Nombre de Aplicación: Modificar Propiedades Controles

Foto:

Descripción: aplicación que nos enseñara a jugar un poco con las propiedades de los

controles.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio11 : Form { public Ejercicio11() { InitializeComponent(); } private void button2_Click(object sender, EventArgs e) { textBox1.Visible = false; } private void button1_Click(object sender, EventArgs e) { textBox1.Visible = true; } private void button4_Click(object sender, EventArgs e) { textBox1.Enabled = false; } private void button5_Click(object sender, EventArgs e)

Page 26: guia c# 01

{ textBox1.Enabled = true; } private void button6_Click(object sender, EventArgs e) { textBox1.Text = textBox2.Text; } private void button3_Click(object sender, EventArgs e) { try { textBox1.ForeColor = System.Drawing.Color.FromName(comboBox1.Text); } catch { MessageBox.Show ("Debe seleccionar un color de la lista");} } private void button7_Click(object sender, EventArgs e) { try { textBox1.BackColor = System.Drawing.Color.FromName(comboBox1.Text); } catch { MessageBox.Show("Debe seleccionar un color de la lista"); } } private void button8_Click(object sender, EventArgs e) { textBox1.Text = ""; } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 27: guia c# 01

12-Nombre de Aplicación: Mover Form sin Bordes

Foto:

Descripción: Nos enseña cómo mover un form que no tiene bordes.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio12 : Form { [DllImport("user32.DLL", EntryPoint = "ReleaseCapture")] private extern static void ReleaseCapture(); [DllImport("user32.DLL", EntryPoint = "SendMessage")] private extern static void SendMessage(System.IntPtr hWnd, int wMsg, int wParam, int lParam); public Ejercicio12() { InitializeComponent(); } private void Form1_MouseDown(object sender, MouseEventArgs e) { ReleaseCapture(); SendMessage(this.Handle, 0x112, 0xf012, 0); } private void button1_Click(object sender, EventArgs e) { Application.Exit(); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); }

Page 28: guia c# 01

private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 29: guia c# 01

13-Nombre de Aplicación: Proveedor de Errores

Foto:

Descripción: Esta aplicación nos enseña a usar el control ErrorProvider verán así sus

ventajas y desventajas (No conozco a nadie que le guste usarlo pero por si quieren saber

cómo se usa)

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio13 : Form { private double datoTextBox; public Ejercicio13() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { textBox1.Focus(); linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void CajaTexto_Validating(object sender, CancelEventArgs e) { TextBox objTextBox = (TextBox)sender; try { datoTextBox = Convert.ToDouble(objTextBox.Text); } catch (Exception) { e.Cancel = true; textBox1.SelectAll(); proveedorError.SetError(objTextBox, "Tiene que ser numérico"); } } private void CajaTexto_Validated(object sender, EventArgs e) { Debug.WriteLine("Caja de texto validada"); proveedorError.Clear(); }

Page 30: guia c# 01

private void button1_Click(object sender, EventArgs e) { textBox1.SelectAll(); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 31: guia c# 01

14-Nombre de Aplicación: Realizar un Clic

Foto:

Descripción: Nos enseña como ejecutar un clic presionando la tecla enter estando dentro de

un Textbox.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio14 : Form { public Ejercicio14() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { MessageBox.Show("Me haz dado un Clic"); } private void textBox1_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)(Keys.Enter)) { e.Handled = true; button1.PerformClick(); } } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } }

Page 32: guia c# 01

}

Page 33: guia c# 01

15-Nombre de Aplicación: Realizar copia y cambiar formato

Foto:

Descripción: Muy útil quiero hacer una versión más completa cuantos no hemos necesitado

cambiar el formato de una imagen y a veces necesitamos algo sumamente simple para

hacerlo, es una aplicación que carga una imagen en un picturebox y nos deja realizar una

copia en C: con el formato que escojamos.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio15 : Form { public Ejercicio15() { InitializeComponent(); } private void button5_Click(object sender, EventArgs e) { OpenFileDialog BuscarImagen = new OpenFileDialog(); BuscarImagen.Filter = "Archivos de Imagen|*.jpg"; BuscarImagen.FileName = ""; BuscarImagen.Title = "Realizar Copia de Imagen"; BuscarImagen.InitialDirectory = "C:\\"; BuscarImagen.FileName = this.txtImagen.Text; if (BuscarImagen.ShowDialog() == DialogResult.OK) { this.txtImagen.Text = BuscarImagen.FileName; this.pictureBox1.ImageLocation = txtImagen.Text; pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; }

Page 34: guia c# 01

} private void button1_Click(object sender, EventArgs e) { String NombreArchivo; NombreArchivo = System.IO.Path.GetFileNameWithoutExtension(txtImagen.Text); Bitmap Picture = new Bitmap(txtImagen.Text); Picture.Save(@"C:\" + NombreArchivo + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg); MessageBox.Show("Se guardo la Copia correctamente en C:"); } private void button2_Click(object sender, EventArgs e) { String NombreArchivo; NombreArchivo = System.IO.Path.GetFileNameWithoutExtension(txtImagen.Text); Bitmap Picture = new Bitmap(txtImagen.Text); Picture.Save(@"C:\" + NombreArchivo + ".bmp", System.Drawing.Imaging.ImageFormat.Bmp); MessageBox.Show("Se guardo la Copia correctamente en C:"); } private void button3_Click(object sender, EventArgs e) { String NombreArchivo; NombreArchivo = System.IO.Path.GetFileNameWithoutExtension(txtImagen.Text); Bitmap Picture = new Bitmap(txtImagen.Text); Picture.Save(@"C:\" + NombreArchivo + ".png", System.Drawing.Imaging.ImageFormat.Png); MessageBox.Show("Se guardo la Copia correctamente en C:"); } private void button4_Click(object sender, EventArgs e) { String NombreArchivo; NombreArchivo = System.IO.Path.GetFileNameWithoutExtension(txtImagen.Text); Bitmap Picture = new Bitmap(txtImagen.Text); Picture.Save(@"C:\" + NombreArchivo + ".gif", System.Drawing.Imaging.ImageFormat.Gif); MessageBox.Show("Se guardo la Copia correctamente en C:"); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 35: guia c# 01

16-Nombre de Aplicación: Recorrer con Enter

Foto:

Descripción: Para aplicaciones que requieran captura de datos sin usar el Mouse.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio16 : Form { public Ejercicio16() { InitializeComponent(); } private void textBox1_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)(Keys.Enter)) { e.Handled = true; SendKeys.Send("{TAB}"); } } private void textBox2_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)(Keys.Enter)) { e.Handled = true; SendKeys.Send("{TAB}"); } } private void textBox3_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)(Keys.Enter)) { e.Handled = true; SendKeys.Send("{TAB}"); } } private void textBox4_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)(Keys.Enter))

Page 36: guia c# 01

{ e.Handled = true; SendKeys.Send("{TAB}"); } } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 37: guia c# 01

17-Nombre de Aplicación: Valor DateTimePicker

Foto:

Descripción: Obtener el valor de un datetimepicker y pasarlo a otro control.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace Semana07B { public partial class Ejercicio17 : Form { public Ejercicio17() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { label2.Text = dateTimePicker1.Text; } } }

Page 38: guia c# 01

18-Nombre de Aplicación: Valor MonthCalendar

Foto:

Descripción: Lo mismo que el anterior pero usando un MonthCalendar.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio18 : Form { public Ejercicio18() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { textBox1.Text = monthCalendar1.SelectionStart.Day.ToString() + "/" + monthCalendar1.SelectionStart.Month.ToString() + "/" + monthCalendar1.SelectionStart.Year.ToString(); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } }

Page 39: guia c# 01

}

Page 40: guia c# 01

19-Nombre de Aplicación: Uso del SaveFile Dialog

Foto:

Descripción: Saca una captura de pantalla y nos da la posibilidad de guardarla donde le

indiquemos.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio19 : Form { public Ejercicio19() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Bitmap bmpCaptura = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); Graphics Captura = Graphics.FromImage(bmpCaptura); //se toma la impresion de pantalla Captura.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size); pictureBox1.Image = bmpCaptura; } private void button2_Click(object sender, EventArgs e) { SaveFileDialog saveFile = new SaveFileDialog(); saveFile.Filter = "Imagenes BMP|*.bmp"; if (saveFile.ShowDialog() == DialogResult.OK) { Bitmap picture = new Bitmap(pictureBox1.Image); picture.Save(saveFile.FileName, System.Drawing.Imaging.ImageFormat.Bmp); } }

Page 41: guia c# 01

private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 42: guia c# 01

20-Nombre de Aplicación: Validar texto Introducido

Foto:

Descripción: Nos enseña a usar el evento Keypress y validarlo para indicarle que letras

queremos que acepte un control.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace Semana07B { public partial class Ejercicio20 : Form { public Ejercicio20() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { if (textBox3.Text == "") { MessageBox.Show("No debe dejar espacios en Blanco"); } } private void textBox1_KeyPress(object sender, KeyPressEventArgs e) { if (Char.IsLetter(e.KeyChar)) { e.Handled = false; } else if (Char.IsControl(e.KeyChar)) { e.Handled = false; } else if (Char.IsSeparator(e.KeyChar)) { e.Handled = false; } else { e.Handled = true; } } private void textBox2_KeyPress(object sender, KeyPressEventArgs e) {

Page 43: guia c# 01

if (char.IsDigit(e.KeyChar)) { e.Handled = false; } else if (char.IsControl(e.KeyChar)) { e.Handled = false; } else { e.Handled = true; } } } }

Page 44: guia c# 01

21-Nombre de Aplicación: Convertir String a Int

Foto:

Descripción: Minicalculadora que nos enseñara a convertir el valor de un textbox a Int.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Semana07B { public partial class Ejercicio21 : Form { public Ejercicio21() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { try { int v1, v2; double resultado; v1 = Convert.ToInt32(textBox1.Text); v2 = Convert.ToInt32(textBox2.Text); resultado = v1 + v2; label2.Text = Convert.ToString(resultado); } catch { MessageBox.Show("Debe indicar ambos valores y asegurese que sean valores numericos"); } } private void button2_Click(object sender, EventArgs e) { try { int v1, v2; double resultado; v1 = Convert.ToInt32(textBox1.Text); v2 = Convert.ToInt32(textBox2.Text); resultado = v1 - v2; label2.Text = Convert.ToString(resultado); } catch { MessageBox.Show("Debe indicar ambos valores y asegurese que sean valores numericos"); } }

Page 45: guia c# 01

private void button3_Click(object sender, EventArgs e) { try { int v1, v2; double resultado; v1 = Convert.ToInt32(textBox1.Text); v2 = Convert.ToInt32(textBox2.Text); resultado = v1 * v2; label2.Text = Convert.ToString(resultado); } catch { MessageBox.Show("Debe indicar ambos valores y asegurese que sean valores numericos"); } } private void button4_Click(object sender, EventArgs e) { try { int v1, v2; double resultado; v1 = Convert.ToInt32(textBox1.Text); v2 = Convert.ToInt32(textBox2.Text); resultado = v1 / v2; label2.Text = Convert.ToString(resultado); } catch { MessageBox.Show("Debe indicar ambos valores y asegurese que sean valores numericos"); } } } }

Page 46: guia c# 01

22-Nombre de Aplicación: Dar Formato a Texto

Foto:

Descripción: Nos ayuda a usar los controles para cambiar la fuente y el color de un texto

dentro de un Textbox.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Semana07B { public partial class Ejercicio22 : Form { public Ejercicio22() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { ColorDialog color = new ColorDialog(); if (color.ShowDialog() == DialogResult.OK) { richTextBox1.ForeColor = color.Color; } } private void button2_Click(object sender, EventArgs e) { FontDialog font = new FontDialog(); font.Font = richTextBox1.Font; if (font.ShowDialog() == DialogResult.OK) { richTextBox1.Font = font.Font; } } } }

Page 47: guia c# 01

23-Nombre de Aplicación: Mayúsculas y Minúsculas

Foto:

Descripción: Nos ayudara a cambiar el texto de un control a Mayúsculas o Minúsculas, sirve

de vez en cuando.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Semana07B { public partial class Ejercicio23 : Form { public Ejercicio23() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { String MyString = label1.Text; label1.Text = MyString.ToUpper(); } private void button2_Click(object sender, EventArgs e) { String MyString = label1.Text; label1.Text = MyString.ToLower(); } } }

Page 48: guia c# 01

24-Nombre de Aplicación: Usar RadioButton y Checkbox

Foto:

Descripción: Una aplicación que nos enseña cómo usar los Radiobutton y Checkbox muy

util y sencilla.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace Semana07B { public partial class Ejercicio24 : Form { public Ejercicio24() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { label3.Visible = true; label4.Visible = true; if (radioButton1.Checked) label3.Text = "Hombre"; if (radioButton2.Checked) label3.Text = "Mujer"; if (checkBox1.Checked) label4.Text = checkBox1.Text; if (checkBox2.Checked) label4.Text = checkBox2.Text; if (checkBox3.Checked) label4.Text = checkBox3.Text; } private void Form1_Load(object sender, EventArgs e) { } } }

Page 49: guia c# 01

25-Nombre de Aplicación: CambiaEstiloVentana

Foto:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio25 : Form { [DllImport("UxTheme")] private static extern void SetWindowTheme(IntPtr hWnd, string pszSubAppName, string pszSubIdList); [DllImport("UxTheme")] private static extern void SetWindowTheme(IntPtr hWnd, int pszSubAppName, int pszSubIdList); public Ejercicio25() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { SetWindowTheme(this.Handle, "", ""); } private void button2_Click(object sender, EventArgs e) { SetWindowTheme(this.Handle, 0, 0); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } } }

Page 50: guia c# 01

26-Nombre de Aplicación: EjecutarSonidos

Foto:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Diagnostics; namespace Semana07B { public partial class Ejercicio26 : Form { public Ejercicio26() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { System.Media.SystemSounds.Beep.Play(); } private void button2_Click(object sender, EventArgs e) { System.Media.SystemSounds.Exclamation.Play(); } private void button3_Click(object sender, EventArgs e) { System.Media.SystemSounds.Asterisk.Play(); } private void button4_Click(object sender, EventArgs e) { System.Media.SystemSounds.Hand.Play(); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProcessStartInfo sInfo = new ProcessStartInfo(e.Link.LinkData.ToString()); Process.Start(sInfo); } private void Form1_Load(object sender, EventArgs e) { linkLabel1.Links.Add(0, linkLabel1.Text.Length, "http://www.cfallasd.com/"); } } }

Page 51: guia c# 01

Desarrollo de aplicaciones con .NET y WPF

Andrés Marzal Departamento de Lenguajes y Sistemas Informáticos

Universitat Jaume I

Decharlas, 24 de mayo de 2010

¿Qué es .NET? ¿Qué es C#? ¿Qué es WPF? ¿Qué es Visual

Studio? ¿Qué es Expression Blend? odemos simplificar mucho y decir que .NET es la respuesta de Microsoft a Java. .NET ofrece un entorno

de ejecución con máquina virtual para un lenguaje de máquina propio: IL, por Intermediate Language.

Diferentes lenguajes se traducen a ese lenguaje de máquina y un compilador de última hora genera

código nativo, que es lo que realmente se ejecuta.

.NET sigue un estándar ECMA: “Standard ECMA-335, Common Language Infrastructure (CLI)”. La

implementación de Microsoft del CLI se conoce por CLR (Common Language Runtime). Hay una

implementación libre de CLI desarrollada por Novell: Mono. Acompaña al entorno un conjunto de

librerías gigantesco, aspecto en el que .NET va significativamente por delante de Mono.

El lenguaje de preferencia para .NET es C# (se lee “C Sharp”), un lenguaje que se diseñó para superar

algunos problemas de Java. En particular, la diferencia sustancial entre valores y objetos y la carencia

de delegados que facilitaran la implementación del patrón observador/observable. C# ha evolucionado

mucho desde su aparición, pero mantiene una coherencia en el diseño que lo hace fácil de aprender.

Aunque es un lenguaje con herencia simple, implementación de interfaces y memoria con

recolección automática, como Java, se diferencia de éste en numerosos aspectos importantes. C# ha

integrado eficazmente varios conceptos de la programación funcional, como las funciones anónimas

y las clausuras. Cuenta además con un mini-lenguaje para efectuar consultas a fuentes de datos, LINQ,

que facilita mucho la gestión de información proveniente de bases de datos, de colecciones en

memoria, de ficheros XML, etcétera. Lo cierto es que LINQ facilita el trabajo con cualquier objeto que

proporcione una enumeración de elementos. Las enumeraciones son muy corrientes en .NET, pues C#

facilita su diseño e implementación mediante estructuras de control como “yield return”. C# evita,

además, la verbosidad del patrón de consulta y asignación de valor a campos (“getters & setters”) propia

de Java mediante las denominada propiedades. Finalmente cabe advertir que la implementación de

tipos genéricos en C# es mucho más sólida que la de Java, pues conserva información de tipos y

distingue entre valores y objetos en el parámetro de tipo, a diferencia de lo que ocurre en Java, que

basa su implementación de genéricos en el borrado de tipos. C# está estandarizado y su definición se

encuentra en “Standard ECMA-334 – C# Language Specification”. Va por la versión 4.0 tanto en .NET

como en Mono.

WPF son las siglas de Windows Presentation Foundation. Es un conjunto de librerías para

implementar aplicaciones interactivas. Arrancó con el nombre en clave “Avalon”. Presenta muchos

P

Page 52: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

¿Q

ué e

s .N

ET?

¿Qu

é e

s C

#?

¿Qu

é e

s W

PF?

¿Qu

é e

s V

isu

al Stu

dio

? ¿Q

ué e

s Exp

ress

ion

Ble

nd

?

2

aspectos interesantes: separación de apariencia y lógica,

soporte del patrón “orden” (command), fácil conexión a

fuentes de datos vía ligaduras (bindings), simplificación

de trabajo con objetos observables mediante

propiedades de dependencia, herencia de valores para

propiedades por relación jerárquica entre componentes,

acceso directo a hardware gráfico, animaciones,

personalización completa de componentes mediante

plantillas, etcétera.

La P de WPF viene de “Presentation” y es importante.

WPF soporta el patrón arquitectónico Modelo-Vista-

Presentador (frente al clásico Modelo-Vista-Controlador).

La versión WPF de este patrón es la que se conoce por

Modelo-Vista-Modelo de la Vista, o MVVM por

Model-View-ViewModel.

Hay una versión ligera de WPF diseñada para correr

incrustada en navegadores (aunque también puede

ejecutarse fuera del navegador): Silverlight. El proyecto

arrancó con el nombre en clave WPF/E, por WPF

Everywhere, y muchas veces se habla de él en términos

de competencia directa con Flex y Flash. Mono ofrece

una implementación libre de Silverlight: Moonlight

(aunque suele ir retrasada con respecto a la de Microsoft:

La versión actual de Silverlight es la 3.0, con la 4.0 a

punto de salir, y Moonlight implementa la funcionalidad

de la 2.0 y buena parte de 3.0).

WPF propone separar apariencia de lógica y lo lleva al

extremo de ofrecer una herramienta para diseñadores

gráficos que se integra en el proceso de desarrollo.

Cuando el programador crea una interfaz gráfica se

concentra en los elementos desde el punto de vista lógico y en cómo se comunican entre sí y con los

datos de la aplicación. Los ficheros generados son directamente accesibles con Microsoft Expression

Blend. Allí, el diseñador encuentra una aplicación con la que es sencillo cambiar el aspecto visual de los

elementos, aplicar efectos y diseñar animaciones. Blend es parte de la suite Microsoft Expression, que

incluye más herramientas orientadas a diseñadores gráficos (como Microsoft Expression Design, una

herramienta en la línea de Adobe Freehand).

Visual Studio es la plataforma de desarrollo por excelencia para .NET. Su última versión es Visual Studio

2010 (VS 2010) y ofrece soporte nativo para lenguajes .NET como C#, Visual Basic, F# (un lenguaje

funcional de la familia de Ocaml) y para proyectos “clásicos” con C o C++. VS 2010 es extensible y

cuenta con soporte (de terceras partes) para herramientas como Subversion o Mercurial. El proyecto

Mono cuenta con su propia plataforma de desarrollo: MonoDevelop. Y hay otra plataforma abierta,

SharpDevelop, aunque de uso marginal.

.NET y Software Libre

El principal problema de .NET para la

comunidad no es de carácter técnico, sino el

estigma de ser obra de Microsoft. Muchas de

las herramientas libres de uso común en Java

están disponibles para .NET: NHibernate,

NAnt, NUnit, Spring.NET, etcétera. Microsoft

abrió en 2009 CodePlex, un espacio para dar

soporte a proyectos de software libre y

algunos de sus proyectos recientes se

distribuyen con licencias que permite acceder

al código fuente (MEF e IronPython, por

ejemplo). Microsoft apoya oficiosamente la

iniciativa Moonlight, de Mono, con la que se

está desarrollando una versión abierta de

Silverlight.

Aunque CLI o C# se han publicado como

estándares ECMA y la actitud de Microsoft

ante la comunidad de software libre ha

evolucionado mucho, la comunidad mira con

recelo cualquier innovación que provenga de

Microsoft. Por ejemplo, Miguel de Icaza, líder

del proyecto Mono, es frecuentemente

insultado o menospreciado por personas o

asociaciones respetadas en la comunidad del

software libre. (Se puede obtener información

sobre uno de los últimos rifi-rafes en

http://www.fsf.org/blogs/rms/microsoft-

codeplex-foundation,

http://www.linuxtoday.com/news_story.php3

?ltsn=2009-09-21-028-35-OP-CY-EV).

Parece que ahora le toca a Microsoft sufrir

una campaña de FUD como las que montaba

hace tiempo.

Page 53: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pri

mero

s p

aso

s co

n W

PF

3

Primeros pasos con WPF onstruyamos una aplicación WPF extremadamente sencilla para empezar a entender algunos de los

elementos básicos de WPF y C#. Nuestra aplicación mostrará un formulario en el que el usuario puede

poner nombre y primer apellido. Tras pulsar un botón, se mostrará un cuadro de diálogo modal con un

saludo personalizado.

Empezamos iniciando VS 2010 (Figura 1a). Con la opción FileNewProject… creamos un nuevo

proyecto de tipo “WPF Application” al que denominamos HolaMundo (Figura 1b).

Esto crea un directorio HolaMundo y una estructura de

ficheros y directorios que facilita el desarrollo de la

aplicación. El explorador de soluciones muestra esta

estructura (Figura 2). Encontramos una carpeta para

propiedades, otra para referencias a DLLs y “ficheros

lógicos” de aplicación que pueden desplegarse en uno o

más ficheros físicos.

Los ficheros App.* contienen el punto de entrada a la

aplicación y definen/construyen una instancia de la clase

Application. Los ficheros MainWindow.* definen la ventana

principal de la aplicación y App.* contiene una indicación de

que MainWindow.* es la ventana principal o URI de inicio.

Los ficheros que nos interesan tienen extensión “.xaml” o

“.cs”. Los primeros son ficheros en un formato XML

denominado XAML (eXtensible Application Markup

Language); los segundos son ficheros C#.

XAML sigue un esquema XML cuyos elementos se inscriben en el espacio de nombres

{http://schemas.microsoft.com/winfx/2006/xaml}. XAML es un lenguaje de marcado diseñado para

facilitar la instanciación de objetos .NET y la definición de sus propiedades (o “atributos”, en jerga XML).

Hay una versión de XAML con un esquema cuyos elementos están en el espacio de nombres

C

(a) (b)

Figura 1. (a) Pantalla inicial de Visual Studio 2010. (b) Cuadro de diálogo para crear un nuevo proyecto.

Figura 2. Explorador de soluciones con el

proyecto WPF recién creado.

Page 54: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pri

mero

s p

aso

s co

n W

PF

4

{http://schemas.microsoft.com/winfx/2006/xaml} que permite instanciar objetos WPF y definir sus

propiedades.

XML es un formato jerárquico y, por tanto, los objetos en XAML siempre forman un árbol. Esa

estructura es natural en una interfaz de usuario. Veamos el aspecto de App.xaml:

<Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>

El fichero instancia un objeto de la clase App (que es nuestra y hereda de otra, Application, propia de

WPF) en el espacio de nombres C# HolaMundo (lo indica el atributo x:Class). Define los dos espacios de

nombres XML que se usan: XAML y WPF y señala que la aplicación empieza en la URI

MainWindow.xaml. A continuación, define una sección para los recursos de la aplicación, pero no los

hay. La sintaxis es especial y merece que nos detengamos: la marca Application.Resources corresponde,

en realidad, a un atributo “Resources” de la marca “Application”. Es decir, en principio (pero sólo en

principio), podríamos haber encontrado algo de este estilo:

<Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" Resources="…"> </Application>

¿Por qué se usa esa otra sintaxis, extraña en el mundo XML (aunque siga el estándar)? Porque así es

posible que el atributo Resources contenga nuevos elementos XML y no sólo una simple cadena. Esta

sintaxis alternativa es muy corriente en los ficheros XAML y conviene acostumbrarse a ella.

El fichero App.xaml.cs no contiene gran cosa:

using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; namespace HolaMundo { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { } }

Page 55: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Bo

ton

es

y e

ven

tos

5

La clase App, en el espacio de nombres HolaMundo, es una clase parcial (adjetivo “partial”), lo que

significa que parte de su código está definido en otro fichero. Ese otro fichero contiene el código auto-

generado por VS 2010.

La clase está vacía (en nuestro fichero, pero no en el auto-generado). En nuestra parte podríamos

definir, por ejemplo, comportamientos relacionados con el ciclo de vida de la aplicación (creación,

activación, minimización, cierre, etcétera).

Veamos ahora qué contiene MainWindow.xaml:

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> </Grid> </Window>

Se crea una instancia de un objeto de la clase MainWindow, que por herencia es también de la clase

Window. Se definen algunos atributos: el título (Title), la altura (Height) y la anchura (Width). Las

unidades en que se indican las medidas son independientes de la pantalla. Una pulgada corresponde

siempre a 96 unidades. (Se escogió esta unidad de medida por facilitar la medida en monitores

convencionales, que suelen presentar una resolución de 96 dpi, es decir, 96 puntos por pulgada.)

Dentro de la ventana hay un Grid. Es un elemento de maquetación. Más adelante lo estudiaremos con

detenimiento.

Ejecutemos la aplicación (pulsamos la tecla F5) y aparece una ventana vacía (Figura 3). VS 2010 sigue

estando accesible, pero no permite editar el contenido del proyecto. Cerramos la aplicación pulsando

en el botón de cierre de la ventana (o pulsando Shift-F5 en VS 2010) y volvemos a VS 2010.

Botones y eventos camos a crear un botón con el texto “Saluda” y haremos que al pulsarlo aparezca una ventana modal

con un saludo:

<Window x:Class="HolaMundo.MainWindow" … Title="MainWindow" Height="350" Width="525"> <Grid> <Button Click="Button_Click"> Saluda </Button> </Grid> </Window>

El texto “Saluda” es el valor que se asigna a una

propiedad por defecto: Content. Es decir, este

código XAML es equivalente:

V

Figura 3. Ventana de la aplicación en ejecución.

Page 56: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Bo

ton

es

y e

ven

tos

6

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button Content="Saluda" Click="Button_Click" /> </Grid> </Window>

Algunos objetos tienen campos privilegiados en tanto que el contenido de la marca XML (el texto o

código XML que va entre las marcas de apertura y cierre) se les asigna automáticamente. En un botón,

el campo especial es Content, en una caja de texto, el campo especial es Text.

El atributo Click corresponde a un evento. Cada vez que se pulse en el elemento de tipo Button con el

botón izquierdo del ratón y se levante en el interior del elemento, se invocará automáticamente al

método Button_Click. El asistente de VS 2010 nos ha preparado el método Button_Click en

MainWindow.xaml.cs:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace HolaMundo { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { } } }

El parámetro sender de Button_Click contendrá una referencia al propio botón y el parámetro de tipo

RoutedEventArgs contendrá ciertos datos relativos al evento cuando éste ocurra. Vamos a rellenar el

método con la invocación al diálogo modal:

private void Button_Click(object sender, RoutedEventArgs e) {

Page 57: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

7

MessageBox.Show("Hola"); }

El código que acompaña al XAML se denomina “código trasero” (code behind). Y aunque ahora

recurrimos a él, veremos que MVVM permite eliminar buena parte de él (si no todo).

Ejecutemos la aplicación (F5). El botón ocupa toda el área de trabajo (Figura 5a). Al pulsar el botón

aparece la ventana modal que bloquea el acceso a la ventana MainWindow (Figura 5b).

Maquetación con paneles eamos qué ocurre si tratamos de añadir elementos gráficos nuevos, como una etiqueta o una caja para

texto:

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Label>Nombre:</Label> <TextBox></TextBox> <Button Click="Button_Click">Saluda</Button> </Grid> </Window>

En la ventana sólo vemos el último elemento. En realidad están

todos, pero uno encima del otro (véase la Figura 5). Es cosa del

elemento Grid, que dejamos para luego por ser complejo: si dos o

más elementos están en la misma celda de un Grid, se

superponen.

V

Figura 5. Los elementos se tapan

unos a otros en el Grid, por lo que

sólo se ve el botón, que está encima

del todo.

(a) (b)

Figura 4. (a) Ventana con el botón “Saluda”, que ocupa toda la superficie de la ventana. Ventana de diálogo

modal que aparece al ejecutar el método Button_Click.

Page 58: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

8

Empezamos por un elemento de maquetación más sencillo:

StackPanel. Un StackPanel apila vertical u horizontalmente sus

elementos.

<StackPanel> <Label>Nombre:</Label> <TextBox></TextBox> <Button Click="Button_Click"> Saluda </Button> </StackPanel>

La Figura 6 muestra el resultado de la nueva disposición de

elementos en el StackPanel: uno sobre el otro, por orden de

aparición en el fichero XAML.

Cada elemento WPF contiene decenas de atributos. Podemos

recurrir a un formulario para asignar valores distintos de los

“por defecto”, pero lo cierto es que a la larga resulta

conveniente usar el editor de XAML.

Hay más elementos de maquetación y se pueden incorporar

otros definidos por programadores. Los paneles que vienen de

serie son:

Grid: distribución de elementos en una tabla, con la

posibilidad de fundir filas y columnas.

StackPanel: distribución de elementos en sucesión

vertical u horizontal.

DockPanel: distribución de elementos con anclaje a

punto cardinal y posible expansión del último al área

sobrante.

WrapPanel: distribución de elementos en sucesión

vertical u horizontal en “líneas” (como el texto, que

fluye de una línea a la siguiente).

UniformGrid: distribución de elementos en una matriz

cuadrada.

Canvas: ubicación precisa de elementos.

(Algunos paneles definidos por programadores disponen

elementos gráficos de formas novedosas u ofrecen

animaciones para el desplazamiento o selección de sus

elementos. Se pueden encontrar ejemplos en

http://www.codeproject.com/Articles/37348/Creating-

Custom-Panels-In-WPF.aspx,

http://www.wpftutorial.net/CustomLayoutPanel.html o

http://www.codeproject.com/KB/WPF/Panels.aspx.)

Figura 6. Los tres elementos se muestran

uno sobre otro gracias al StackPanel.

Propiedades

Los elementos XAML tienen

numerosos atributos y al principio

cuesta un poco manejarse con

tantos. Puede venir bien invocar el

panel de edición de atributos. Con el

cursor en el elemento XAML cuyos

atributos se desea editar, aparece un

panel de propiedades al pulsar F4.

No obstante, a la larga es más

productivo usar el editor de XAML en

VS 2010, que asiste al programador

con los menús Intellisense.

Page 59: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

9

Las maquetas más complejas suelen formarse combinando diferentes paneles. En nuestro caso,

podemos crear un StackPanel vertical en el que apilar dos StackPanels adicionales, estos horizontales, y

el botón.

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel> <StackPanel Orientation="Horizontal"> <Label>Nombre:</Label> <TextBox></TextBox> </StackPanel> <StackPanel Orientation="Horizontal"> <Label>Apellido:</Label> <TextBox></TextBox> </StackPanel> <Button Click="Button_Click">Saluda</Button> </StackPanel> </Window>

La ventana que hemos creado tiene espacio muerto bajo el botón “Saluda”. Esto es así porque hemos

creado la ventana con unas dimensiones fijas: 525 por 350 puntos. El problema es fácil de corregir:

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" SizeToContent="Height">

El atributo SizeToContent puede tomar los valores

Manual (que es el que toma por defecto), Height,

Width y WidthAndHeight. El efecto de seleccionar

Height para SizeToContent es una ventana con

anchura fija y altura ajustada al contenido de la

ventana (Figura 7a).

Hay un par de problemas adicionales. Por una

parte, los campos de texto son pequeños (aunque

crecen automáticamente conforme tecleamos

texto en ellos); por otra, el alineamiento de los

campos de texto no es perfecto y depende del

tamaño de las etiquetas (Figura 7b).

El elemento de maquetación Grid permite

solucionar los dos problemas. Un Grid consta de

una sección de declaración de filas y columnas. En

nuestro caso definiremos 3 filas y 2 columnas. La

primera fila contendrá la etiqueta “Nombre:” y su

campo de texto; la segunda, la etiqueta “Primer

apellido:” y su campo de texto; y la tercera, el

botón “Saluda”. Las etiquetas se dispondrán en la

Figura 8. Ventana con espacio indeseado.

(a)

(b)

Figura 7. (a) La ventana con altura ajustada a su

contenido. (b) Efecto de mal alineamiento cuando las

etiquetas tienen texto de diferentes longitudes.

Page 60: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Maq

ueta

ció

n c

on

pan

ele

s

10

primera columna y los campos de texto en la segunda. El botón “Saluda” ocupará una columna que será

resultado de fundir las dos. La primera columna se ajustará al contenido y la segunda ocupará el resto

del espacio. Nuestra tabla presentará, pues, esta estructura:

Etiqueta Campo de texto ----------------------------------------------------------------------------------

Etiqueta Campo de texto ----------------------------------------------------------------------------------

--------------------------------------------------------- Botón ------------------------------------------------

El código XAML se complica un poco:

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" SizeToContent="Height"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0">Nombre:</Label> <TextBox Grid.Column="1" Grid.Row="0"></TextBox> <Label Grid.Column="0" Grid.Row="1">Primer apellido:</Label> <TextBox Grid.Column="1" Grid.Row="1"></TextBox> <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> Saluda </Button> </Grid> </Window>

Por fin vemos lo conveniente de fijar atributos con la sintaxis Elemento.Atributo: no es apropiado definir

los atributos RowDefinitions y ColumnDefinitions con una simple cadena de texto, pues son en realidad

listas de elementos XML, algunos con sus propios atributos.

Hay un nuevo elemento sintáctico. Hay atributos (no elementos, como antes) con la sintaxis

Elemento.Atributo. Examinemos, por ejemplo, esta línea:

<Label Grid.Column="0" Grid.Row="1">Primer apellido:</Label>

El atributo Grid.Column permite asignar un valor a una propiedad Column definida en Grid, no en Label.

Los elementos de tipo Label no saben nada de los de tipo Grid y, aun así, pueden asociar un valor a una

propiedad de Grid. Los elementos WPF mantienen un diccionario que permite asociar valores a claves

(propiedades) de las que nada sabe. En este caso, esas propiedades permiten ubicar el elemento en una

fila/columna del Grid.

Si nosotros definiésemos un panel propio, digamos que con una clase ClockPanel, en el que hubiese

que ubicar los elementos alrededor de una circunferencia, por ejemplo, necesitaríamos que cada

Page 61: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Co

nte

nid

o r

ico

, d

iseñ

o g

ráfi

co y

an

imaci

on

es

11

elemento especificase los grados en los que debe aparecer en la esfera del reloj. Podríamos, entonces,

usar una etiqueta como ésta:

<Label ClockPanel.Angle="90">Primer apellido:</Label>

Nótese que Label no sabe nada de ClockPanel (de hecho, ClockPanel es una invención nuestra y, de

momento, ni siquiera existe). Pese a ello, podemos gestionar atributos de este tipo, que reciben el

nombre de “propiedades pegadas” (attached properties).

Contenido rico, diseño gráfico y animaciones PF sigue un modelo de contenido rico. En muchos sistemas de construcción de aplicaciones con interfaz

gráfica de usuario hay serias limitaciones al contenido de los elementos. En algunas, los botones sólo

pueden contener, por ejemplo, texto y, opcionalmente, un icono. Escapar de esta restricción, cuando es

posible, obliga a construir nuevos elementos, lo que supone un incremento de complejidad enorme.

WPF, sin embargo, permite que muchos componentes contengan a otros componentes en su interior, lo

que facilita el diseño de aplicaciones con un acabado gráfico espectacular (si se trabaja codo con codo

con diseñadores gráficos, claro está).

Podemos probar a añadir un smiley al botón “Saluda”. Lo haremos con ayuda de Miorosoft Expression

Blend. Arrancamos Blend y desde su menú FileOpen Project/Solution… abrimos el proyecto VS 2010

HolaMundo y nos encontramos con la aplicación como se muestra en la Figura 9. La interfaz de Blend es

bastante compleja y no la analizaremos en detalle. Sólo queremos llevarnos una impresión acerca de su

uso y ver que es una herramienta especializada en adaptar la apariencia de nuestra aplicación, y no en

la lógica.

Podemos editar código XAML desde Microsoft

Expression Blend del mismo modo que hacemos

con VS 2010. Basta con pulsar el icono “<>” que

hay en la parte superior derecha del panel central

(el que contiene la ventana de nuestra aplicación).

Con el editor XAML de Microsoft Expression Blend

hemos escrito este texto en el fichero de texto

MainWindow.xaml, es decir, en el mismo fichero

que hemos estado editando en Visual Studio y

que forma parte del proyecto HolaMundo:

<Window x:Class="HolaMundo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" SizeToContent="Height"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/>

W

Figura 9. Microsoft Expression Blend 4 tras abrir el

proyecto HolaMundo.

Page 62: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Co

nte

nid

o r

ico

, d

iseñ

o g

ráfi

co y

an

imaci

on

es

12

<ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox Grid.Column="1" Grid.Row="0"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox Grid.Column="1" Grid.Row="1"/> <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> <StackPanel> <TextBlock TextAlignment="Center">Saluda</TextBlock> <Canvas Width="64" Height="64"> </Canvas> </StackPanel> </Button> </Grid> </Window>

El botón contiene ahora un StackPanel con dos elementos: un bloque de texto y un panel de tipo

Canvas de tamaño 64x64. Ahí dibujaremos el smiley. Con ayuda de la paleta de herramientas y el panel

de propiedades creamos el gráfico que se muestra en la Figura 10.

Blend permite crear efectos gráficos y animaciones. Vamos a

hacer que cuando el ratón entre en la región del botón el

smiley dé una vuelta.

Empezamos creando una animación. Seleccionamos el

Canvas y en el icono “+” del panel Objects and Timeline

seleccionamos New… y creamos una historia (storyboard) a

la que denominamos ZoomStoryboard. Con la marca de

tiempo (línea amarilla) en el instante 0, fijamos a 0 la propiedad Angle en el panel Transform del Canvas

que contiene al smiley, y fijamos a 360 su valor en el instante 1. Con eso conseguiremos que el smiley

dé una vuelta completa en un segundo.

Seleccionamos ahora el botón Saluda y seleccionamos el activo de tipo Behaviors denominado

ControlStoryBoardAction. En su panel, seleccionamos la historia ZoomStoryboard y el evento MouseEnter.

Podemos probar a ejecutar la aplicación y comprobar que cada vez que el cursor entra en el botón, se

ejecuta la animación. Bueno, no sólo entonces: también se dispara cuando cargamos la aplicación.

Luego eliminaremos este efecto indeseado (que aunque podemos eliminar desde Blend, eliminaremos

desde VS 2010).

Todo lo que hemos hecho con Blend se podría haber hecho directamente con VS 2010, pero hubiese

supuesto un esfuerzo considerablemente mayor, como comprobaremos en breve al analizar el XAML

generado.

Figura 10. Botón con dibujo creado con el

editor de Blend.

Page 63: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Más

sob

re X

AM

L: re

curs

os,

dis

para

do

res,

tra

nsf

orm

aci

on

es

y r

efe

ren

cia

s

13

Más sobre XAML: recursos, disparadores, transformaciones y

referencias s hora de volver a VS 2010. Cerramos Blend y volvemos a VS 2010, que detectará que hubo cambios en

el proyecto y solicita, por tanto, recargarlo.

Analicemos el XAML que se ha generado desde Blend:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="HolaMundo.MainWindow" Title="MainWindow" Width="525" SizeToContent="Height"> <Window.Resources> <Storyboard x:Key="ZoomStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="canvas"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Window.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard Storyboard="{StaticResource ZoomStoryboard}"/> </EventTrigger> </Window.Triggers> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox Grid.Column="1" Grid.Row="0"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox Grid.Column="1" Grid.Row="1"/> <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseEnter"> <ei:ControlStoryboardAction Storyboard="{StaticResource ZoomStoryboard}"/> </i:EventTrigger> </i:Interaction.Triggers> <StackPanel> <TextBlock TextAlignment="Center"><Run Text="Saluda"/></TextBlock> <Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/>

E

Page 64: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Más

sob

re X

AM

L: re

curs

os,

dis

para

do

res,

tra

nsf

orm

aci

on

es

y r

efe

ren

cia

s

14

</TransformGroup> </Canvas.RenderTransform> <Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8" Stroke="Black" Canvas.Top="8" Width="48"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="20" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="36" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <ed:Arc ArcThickness="0" ArcThicknessUnit="Pixel" EndAngle="-90" Fill="#FFF4F4F5" Height="12" Canvas.Left="20" Stretch="None" Stroke="Black" StartAngle="90" Canvas.Top="36.12" Width="23.5"/> </Canvas> </StackPanel> </Button> </Grid> </Window>

Complejo. Pero podemos analizar su contenido y comprobar que todo lo hecho con Blend, acaba

codificándose como texto en el fichero XAML.

Por una parte tenemos una sección de recursos, que es código XAML asignado a la propiedad Resources

de Window.

<Window.Resources> <Storyboard x:Key="ZoomStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="canvas"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:1" Value="360"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources>

La sección de recursos es un diccionario que permite asociar elementos WPF a claves. La historia es que

hemos creado tiene por clave ZoomStoryboard y por valor una instancia de la clase

DoubleAnimationUsingKeyFrames. Las animaciones en WPF se forman con elementos simples. Una

DoubleAnimation, por ejemplo, es una animación consistente en el cambio de un valor de tipo de

double a lo largo del tiempo. Si vincuamos el valor de una propiedad de un elemento de la interfaz

gráfica a esa animación (como la escala, la altura, la opacidad…), su valor afectará al aspecto visual de

ese elemento. Una DoubleAnimationUsingKeyFrames es eso mismo, pero fijando tramas clave (key

frames) en las que el double debe tomar ciertos valores. En nuestro caso, en el instante 0 debe tener

valor 0 y en el instante 0:0:1 (un segundo después), debe valer 360. La animación tiene efecto sobre una

propiedad del Canvas en el que hemos puesto el smiley: el ángulo de rotación.

Después de la sección de recursos hay otra con disparadores (triggers). Los disparadores permiten

asociar acciones a eventos (entre otras cosas).

<Window.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard Storyboard="{StaticResource ZoomStoryboard}"/> </EventTrigger> </Window.Triggers>

Page 65: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Más

sob

re X

AM

L: re

curs

os,

dis

para

do

res,

tra

nsf

orm

aci

on

es

y r

efe

ren

cia

s

15

Este disparador es el responsable de que tan pronto se carga la ventana se inicie la animación

ZoomStoryboard. Los valores de atributos entre llaves son especiales. En este caso se indica que la

historia que debe ejecutarse se encuentra almacenada en el diccionario de recursos con la clave

ZoomStoryboard. Si eliminamos ese disparador (o, para el caso, su sección completa), eliminaremos la

animación indeseada.

Seguimos analizando el XAML. El botón tiene este código que fija el valor de algunas “propiedades

pegadas”:

<Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseEnter"> <ei:ControlStoryboardAction Storyboard="{StaticResource ZoomStoryboard}"/> </i:EventTrigger> </i:Interaction.Triggers>

Los elementos en los espacios de nombres con sufijos i y ei son propios de Microsoft Expression Blend.

No entramos en detalles. Baste saber que los comportamientos (behaviors) que podemos fijar en Blend

se han incrustado en el XAML así.

El botón contiene un StackPanel y su primer elemento es un TextBlock, un bloque de texto. Su único

componente es una instancia de Run.

<TextBlock TextAlignment="Center"><Run Text="Saluda"/></TextBlock>

Hay varios tipos de elemento que podemos poner en un TextBlock y entre ellos destacamos Run (texto

normal), Italic (texto en cursiva) y Bold (texto en negrita). Pero no son los únicos. Y si ponemos texto a

pelo, WPF sabe que queríamos poner una marca Run y la pone por nosotros. Mucha de la

infraestructura de WPF nos permite eliminar algo de verbosidad en el código XAML (que aún así es muy

verboso).

Y llegamos por fin al Canvas:

<Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5"> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Canvas.RenderTransform> <Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8" Stroke="Black" Canvas.Top="8" Width="48"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="20" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <Ellipse Fill="#FFFF1700" Height="7.5" Canvas.Left="36" Stroke="Black" Canvas.Top="21.12" Width="7.5"/> <ed:Arc ArcThickness="0" ArcThicknessUnit="Pixel" EndAngle="-90" Fill="#FFF4F4F5" Height="12" Canvas.Left="20" Stretch="None" Stroke="Black" StartAngle="90" Canvas.Top="36.12" Width="23.5"/> </Canvas>

Page 66: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Acc

eso

a p

rop

ied

ad

es

desd

e c

ód

igo

tra

sero

16

Hay un atributo interesante con identificador x:Canvas. Permite asociar un nombre a un componente y

así poder referenciarlo desde otros puntos. De hecho, ya hemos referenciado a éste desde uno recurso:

<Window.Resources> <Storyboard x:Key="ZoomStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="canvas">

Así es como aplica la historia a un componente concreto: fijando como valor del objetivo el

identificador o nombre del elemento. En el Canvas hay un grupo de elementos asignados al atributo

RenderTransform: con componentes que aplican una transformación afín a un elemento en el momento

de visualizarse. La expresión

(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)

Estaba seleccionando el tercer componente (índice 2) del grupo de transformación, es decir, la rotación,

y centrando el interés en la propiedad Angle, que corresponde al ángulo. La rotación tiene lugar

centrada en el centro del Canvas gracias a este otro atributo:

<Canvas x:Name="canvas" Width="64" Height="64" RenderTransformOrigin="0.5,0.5">

El Canvas contiene tres elipses y un arco, elemento que forma parte de un espacio de nombres propio

de Microsoft Expression.

Cabe señalar otro aspecto interesante de los valores de ciertos atributos. En las elipses podemos ver

que hay varias formas de expresar un color:

<Ellipse Fill="#FFFDFF00" Height="48" Canvas.Left="8" Stroke="Black" Canvas.Top="8" Width="48"/>

Una forma es con #AARRGGBB (alfa, rojo, verde, azul) y otra con el propio nombre del color. WPF sabe

interpretar apropiadamente el valor que se desea usar a partir de una cadena. Para ello usa un rico

juego de conversores. Con unidades de medida, por ejemplo, sabe que “48” es media pulgada, que

también podríamos expresas con “0.5in”. Y “1cm” representa un centímeto, o lo que es lo mismo,

“10mm”.

¡Ah! Y fijémonos en los atributos Canvas.Left y Canvas.Top, que permite fijar las coordenadas X e Y de la

esquina superior izquierda del rectángulo de inclusión de la elipse en el Canvas que la contiene. Ya

vimos algo parecido con Grid.Column y Grid.Row.

Acceso a propiedades desde código trasero amos a acceder desde código trasero al valor que el usuario teclee en las cajas de texto. Para ello

necesitaremos poder acceder a las cajas de texto y deberán tener un identificador. En el código XAML

escribimos esto:

<Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox x:Name="nombre" Grid.Column="1" Grid.Row="0"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox x:Name="apellido" Grid.Column="1" Grid.Row="1"/>

V

Page 67: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

En

lace

s

17

Volvamos al código trasero. En particular, al método que se invoca al pulsar el botón. Hagamos que el

método quede así:

private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show("¡Hola, " + nombre.Text + " " + apellido.Text + "!"); }

Estamos accediendo a la propiedad Text de los elementos “nombre” y “apellido”. Decimos propiedad y

no campo porque Text no es un campo de tipo cadena, sino una “propiedad C#”. Las propiedades C#

son pares de métodos que permiten acceder o asignar un valor a, en principio, un campo. Lo cierto es

que detrás puede haber un campo o no haberlo, pero el programador que usa la propiedad tiene la

ilusión de que lo hay. En realidad se ejecutará código que podría calcular el valor que se quiere leer a

partir de uno o más campos (o de ninguno). El acceso a la propiedad Text no es una mera consulta a un

campo: probablemente consista en el acceso a un diccionario y en la aplicación de operaciones que nos

permitan ver el resultado como una cadena. Pero no hay de qué preocuparse: es todo tarea de WPF y

para nosotros se crea la ilusión de acceso a un simple campo.

Enlaces odemos enlazar diferentes elementos gráficos. Probemos a asociar el título de la ventana con el nombre

que se introduce en el formulario. Para eso hemos de asignar un nombre a la ventana y crear un enlace

(binding) que ligue su valor al del campo de texto que deseemos (y que ha de tener un nombre):

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" … Title="{Binding ElementName=nombre, Path=Text}" Width="525" SizeToContent="Height">

El enlace indica que hemos vincular el valor del campo Title de la ventana al del campo Text del

elemento llamado “nombre”. La sintaxis de las llaves puede reemplazarse por esta otra:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" … Width="525" SizeToContent="Height"> <Window.Title> <Binding ElementName="nombre" Path="Text"/> </Window.Title>

Al ejecutar la aplicación podemos

comprobar que título de ventana y

contenido de la caja de texto con el

nombre coinciden en todo

momento. Conforme tecleamos los

caracteres del nombre, el título de la

ventana se va actualizando.

P

Figura 11. El título de la ventana está vinculado al contenido de la

primera caja de texto.

Page 68: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Vis

tas

y m

od

elo

s (o

casi

)

18

Vistas y modelos (o casi) o usual en una aplicación interactiva bien diseñada es que haya una clara separación entre la

representación de un objeto (la vista) y el propio objeto (el modelo). De hecho, lo ideal es que el

modelo dependa lo menos posible de la interfaz gráfica. Vamos a hacerlo ahora en nuestra aplicación,

aunque resultará un tanto impostado por ser ésta muy sencilla.

Creamos una nueva clase Persona. En el menú contextual del proyecto HolaMundo seleccionamos

AddClass… y creamos la clase Persona, que pasa a definirse así:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace HolaMundo { class Persona : INotifyPropertyChanged { private string _nombre; private string _apellido; public string NombreCompleto { get { return Nombre + " " + Apellido; } } public string Nombre { get { return _nombre; } set { if (value != _nombre) { _nombre = value; NotifyChange("Nombre"); NotifyChange("NombreCompleto"); } } } public string Apellido { get { return _apellido; } set { if (value != _apellido) { _apellido = value; NotifyChange("Apellido"); NotifyChange("NombreCompleto"); } } } void NotifyChange(string id)

L

Page 69: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Vis

tas

y m

od

elo

s (o

casi

)

19

{ if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(id)); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } }

La idea ahora es que el campo Nombre y el campo Apellido de una instancia de Persona estén siempre

sincronizados con las cajas de texto correspondientes en el formulario. Hemos creado cierta

infraestructura al efecto. Por una parte, Persona implementa la interfaz INotifyPropertyChanged. La

interfaz obliga a que se implemente un evento que se disparará cada vez que alguien modifique el valor

de una propiedad, avisando así a los suscriptores del evento. Nótese que el cambio del nombre o el

apellido no sólo cambia Nombre y Apellido, respectivamente: también cambia NombreCompleto.

Hemos de crear una instancia de Persona e indicar que el “contexto de datos” (DataContext) de la

ventana principal es esa instancia:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace HolaMundo { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public readonly Persona LaPersona; public MainWindow() { InitializeComponent(); LaPersona = new Persona { Nombre = "Tu nombre", Apellido = "Tu apellido" }; DataContext = LaPersona; } private void Button_Click(object sender, RoutedEventArgs e)

Page 70: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pro

pie

dad

es

de d

ep

en

den

cia

20

{ MessageBox.Show("¡Hola, " + LaPersona.NombreCompleto + "!"); } } }

Y ahora, vinculemos el contenido de las cajas de texto con los de LaPersona. De paso, vincularemos el

título con el nombre completo:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="HolaMundo.MainWindow" Width="525" SizeToContent="Height"> <Window.Title> <Binding Path="NombreCompleto"/> </Window.Title> …

<Label Grid.Column="0" Grid.Row="0" Content="Nombre:"/> <TextBox x:Name="nombre" Grid.Column="1" Grid.Row="0" Text="{Binding Nombre}"/> <Label Grid.Column="0" Grid.Row="1" Content="Primer apellido:"/> <TextBox x:Name="apellido" Grid.Column="1" Grid.Row="1" Text="{Binding Apellido}"/>

Y ya está. Nuestra ventana tiene por título el nombre completo y la instancia LaPersona siempre está

sincronizada con el formulario.

Propiedades de dependencia PF ofrece una herramienta muy interesante para crear propiedades que notifican automáticamente de

los cambios que experimentan: las propiedades de dependencia. De hecho, las propiedades de los

elementos WPF son realmente propiedades de dependencia. Estas propiedades no sólo notifican de los

cambios que experimentan: tienen valores por defecto, se pueden heredar sus valores en la jerarquía de

objetos, se pueden ligar a otras propiedades de dependencia, pueden usarse en animaciones y, lo que

quizá es más importante: no consumen memoria si no se les asigna un valor. Las propiedades de

dependencia se almacenan en un diccionario cuando se les asigna un valor. Si no lo tienen, WPF se

encarga de acceder al valor por defecto automáticamente. Se trata de un factor muy importante si

tenemos en cuenta que un elemento WPF puede tener más de medio centenar de propiedades.

Convirtamos nuestra persona en un objeto con propiedades de dependencia y, de momento, olvidemos

la sincronización del título de la ventana con el nombre completo. Más tarde recuperaremos esa

funcionalidad.

La nueva definición de Persona es ésta:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Windows;

W

Page 71: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Pro

pie

dad

es

de d

ep

en

den

cia

21

namespace HolaMundo { public class Persona : DependencyObject { public string NombreCompleto { get { return Nombre + " " + Apellido; } } public string Nombre { get { return (string)GetValue(NombreProperty); } set { SetValue(NombreProperty, value); } } public static readonly DependencyProperty NombreProperty = DependencyProperty.Register("Nombre", typeof(string), typeof(Persona), new UIPropertyMetadata("")); public string Apellido { get { return (string)GetValue(ApellidoProperty); } set { SetValue(ApellidoProperty, value); } } public static readonly DependencyProperty ApellidoProperty = DependencyProperty.Register("Apellido", typeof(string), typeof(Persona), new UIPropertyMetadata("")); } }

Complicado, ¿no? Afortunadamente VS 2010 nos ayuda con los denominados snippets, plantillas con

fragmentos de código fácilmente utilizables. Si tecleamos propdp (por “property: dependency property”),

el editor nos ofrece una plantilla como ésta:

public int MyProperty { get { return (int)GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } } // Using a DependencyProperty as the backing store for MyProperty… public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new UIPropertyMetadata(0));

Con la ayuda del tabulador podemos asignar valor a los cuatro campos de la plantilla (que aparecen

con fondo verde en este documento).

La clase Persona hereda de DependencyObject, clase que ofrece la infraestructura necesaria para

soportar propiedades de dependencia. Una propiedad de dependencia es un objeto estático que se

registra en un diccionario con el método DependencyProperty.Register. Las instancias de un

DependencyObject pueden acceder al valor de su propiedad de dependencia con el método GetValue,y

asignarle un valor con SetValue (ambos heredados de DependencyObject). La propiedad C# que da

acceso a estos métodos nos facilita el acceso a su lógica.

Page 72: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Un

a in

tro

du

cció

n a

l p

atr

ón

arq

uit

ect

ón

ico

MV

VM

22

No hemos tenido que notificar los cambios porque las propiedades de dependencia ya se ocupan de

ello automáticamente. Como NombreCompleto no es una propiedad de dependencia y ya no notifica de

los cambios, hemos perdido esa funcionalidad. No costaría nada recuperarla volviendo a implementar el

notificador de cambios en propiedades. Pero es mejor que pasemos a hablar de un patrón de diseño

importante en el mundo WPF: el patrón Modelo-Vista-Modelo de la Vista, o MVVM, por Model-View-

ViewModel.

Una introducción al patrón arquitectónico MVVM a separación entre vista y modelo que hemos hecho no es buena. Hemos acabado por tocar el modelo

para ajustarlo a la vista hasta el punto de construirlo con componentes de WPF, y eso es muy mala

práctica. Normalmente el modelo nos viene dado y tenemos poca capacidad de influencia sobre él.

Este problema es crucial en el diseño de aplicaciones

interactivas, salvo en las más triviales. Desde el inicio de la

programación de aplicaciones interactivas se plantearon la

cuestión de la separación de responsabilidades en este tipo

de sistemas. Un patrón exitoso es el conocido como

Modelo-Vista-Controlador o MVC, por Model-View-

Controller, que divide el sistema en tres componentes: el

modelo, la vista y el controlador. Podemos representar

gráficamente el concepto como se muestra en la Figura 12.

Un patrón de diseño, como MVC, no es una receta estructa

acerca de cómo implementar cierta funcionalidad, sino una

serie de criterios que deben considerarse al diseñar la

arquitectura de la aplicación e implementarla.

El usuario interactúa con dispositivos que “hablan” con un

controlador, el cual manipula un modelo (los datos) cuyo cambio fuerza la actualización de una vista,

que es lo que percibe el usuario. Hoy día el modelo presenta ciertas dificultades y se considera

superado por otros, como el denominado MVP, por Model-View-Presenter. También es un patrón con

tres componentes. En este caso se trata del modelo, la vista y el presentador. Vista y modelo parece

claro lo que son, pero ¿qué es el presentador? Es una capa entre la vista y el modelo. Cuando la vista

necesita algo del modelo, se lo solicita al presentador, que ofrece métodos que hacen cómodo para la

vista el acceso a los datos relevantes del modelo. Supongamos que el modelo tiene la fecha de

nacimiento de una persona, pero no la edad. El presentador podría ofrecer un método o propiedad

Edad que accediese a la fecha de nacimiento y al día actual para proporcionar el dato deseado. Y en

sentido inverso, cuando la vista necesita modificar el modelo, lo hace a través del presentador, que sabe

cómo “traducir” elementos de la vista en datos del modelo. También modelo y presentador interactúan:

el presentador lee y escribe sus datos y cuando el modelo cambia “espontáneamente” (es decir, por

eventos no relacionados con la interacción con el usuario), notifica al presentador de los cambios y éste

se encarga de actualizar la vista.

El patrón arquitectónico MVVM es una versión especializada de MVP para WPF. Establece mecanismos

propios de WPF para la comunicación Vista-Presentador (que aquí se denomina Modelo de la Vista). En

particular y limita las herramientas que podemos usar en cada capa.

L

Figura 12. Diagrama del patrón Modelo-

Vista-Controlador. (Imagen extraída de

Wikipedia.)

Page 73: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Un

a in

tro

du

cció

n a

l p

atr

ón

arq

uit

ect

ón

ico

MV

VM

23

La vista es XAML 100% (o casi).

El modelo de la vista expone lógica vía órdenes (de las que nos ocupamos en breve), expone el

modelo mediante ligaduras entre propiedades y propiedades de dependencia y mantiene

información de estado de la interacción (pero sólo de la interacción).

El modelo mantiene los datos y sus operaciones, pero no lógica dependiente de cómo se usa.

(Si cambia espontáneamente, implementa un notificador de cambios en propiedades.)

Ahora vamos a seguir el patrón MVVM para que la aplicación vuelva a funcionar. Devolvamos el modelo

a una versión minimalista. El fichero Persona pasa a contener este texto:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace HolaMundo { public class Persona { public string Nombre { get; set; } public string Apellido { get; set; } public string NombreCompleto { get { return Nombre + " " + Apellido; } } } }

El modelo no “sabe” nada de WPF ni de cómo se usará en la aplicación. Se limita a mantener un par de

datos. Podría tener, además, operaciones para serializar el objeto, almacenarlo en disco, etcétera.

Preparemos una clase para el modelo de la vista: MainWindowViewModel. Recordemos que su papel es

hacer de puente entre la vista y el modelo.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace HolaMundo { public class MainWindowViewModel : INotifyPropertyChanged { public Persona Model; public string Nombre { get { return Model.Nombre;} set { Model.Nombre = value; NotifyChange("Nombre", "NombreCompleto"); } } public string Apellido { get { return Model.Apellido; } set { Model.Apellido = value; NotifyChange("Apellido", "NombreCompleto"); } }

Page 74: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Un

a in

tro

du

cció

n a

l p

atr

ón

arq

uit

ect

ón

ico

MV

VM

24

public string NombreCompleto { get { return Model.Nombre + " " + Model.Apellido; } } void NotifyChange(params string[] ids) { if (PropertyChanged != null) foreach (var id in ids) PropertyChanged(this, new PropertyChangedEventArgs(id)); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } }

Hemos de preocuparnos ahora de vincular Vista, Modelo y Modelo de la Vista. Lo haremos modificando

en App.xaml el arranque de la aplicación. Su contenido actual es éste:

<Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>

Y pasa a ser este otro:

<Application x:Class="HolaMundo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Startup="Application_Startup"> <Application.Resources> </Application.Resources> </Application>

El método Application_Startup se definirá en App.xaml.cs así:

using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; using System.Windows.Input; namespace HolaMundo { /// <summary> /// Interaction logic for App.xaml /// </summary>

Page 75: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Órd

en

es:

el p

atr

ón

Co

mm

an

d

25

public partial class App : Application { private void Application_Startup(object sender, StartupEventArgs e) { var model = new Persona { Nombre = "Tu nombre", Apellido = "Tu apellido" }; var viewModel = new MainWindowViewModel {Model = model}; var view = new MainWindow {DataContext = viewModel}; view.Show(); } } }

Hemos creado un modelo, que se almacena en el modelo de la vista tan pronto se crea, y hemos creado

una vista cuyo contexto de datos es el modelo de la vista. La última acción consiste en mostrar la vista,

que es la ventana principal.

Para que la aplicación funciones hemos de eliminar las referencias a LaPersona o sus campos. Una de

ellas está en el método Button_Click, asociado al evento Click del Button. Vamos a deshacernos de los

eventos, pues no son recomendables en una aplicación MVVM.

Órdenes: el patrón Command l uso de eventos no es, en general, recomendable en aplicaciones de tamaño moderado o grande. Los

eventos crean referencias entre objetos que pueden prolongar la vida de éstos más allá de lo que el

programador supone. Es la causa principal de las fugas de memoria en aplicaciones .NET, por lo que

conviene tomar medidas profilácticas.

WPF soporta el patrón de diseño “orden” (command) que permite asociar lógica a acciones interactivas.

Y no sólo eso: permite también habilitar o deshabilitar la interacción de ciertos componentes en función

del estado de los datos.

En principio hemos de definir una clase que implemente la interfaz ICommand para cada orden del

sistema. Pero resulta más cómodo usar una clase única a la que suministrar, mediante delegados o

funciones anónimas, la lógica que deseamos. Esta clase suele denominarse RelayCommand o

DelegateCommand. Esta es nuestra versión:

public class RelayCommand : ICommand { #region Fields readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion // Fields #region Constructors public RelayCommand(Action<object> execute) : this(execute, null) { }

E

Page 76: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Órd

en

es:

el p

atr

ón

Co

mm

an

d

26

public RelayCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion #region ICommand Members [DebuggerStepThrough] public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } #endregion }

Antes de estudiarla, veamos cómo usarla. Creemos una orden que se dispare cuando pulsamos el botón

“saluda”. El lugar natural para las órdenes es el modelo de la vista. Este es el código que le corresponde:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace HolaMundo { public class MainWindowViewModel : INotifyPropertyChanged { public Persona Model; private readonly ICommand _saludaCommand; public ICommand SaludaCommand { get { return _saludaCommand; } } public MainWindowViewModel()

Page 77: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Órd

en

es:

el p

atr

ón

Co

mm

an

d

27

{ _saludaCommand = new RelayCommand( o => MessageBox.Show("¡Hola, " + NombreCompleto + "!"), o => !string.IsNullOrEmpty(NombreCompleto.Trim())); } public string Nombre { get { return Model.Nombre;} set { Model.Nombre = value; NotifyChange("Nombre", "NombreCompleto"); } } public string Apellido { get { return Model.Apellido; } set { Model.Apellido = value; NotifyChange("Apellido", "NombreCompleto"); } } public string NombreCompleto { get { return Model.Nombre + " " + Model.Apellido; } } void NotifyChange(params string[] ids) { if (PropertyChanged != null) foreach (var id in ids) PropertyChanged(this, new PropertyChangedEventArgs(id)); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion } }

Al construir el RelayCommand proporcionamos dos funciones (anónimas en este caso): una que indica

qué hacer y la otra qué nos permite saber si la orden puede ejecutarse. En nuestro caso, el qué hacer es

lo de siempre: mostrar el diálogo modal. Y la orden podrá ejecutarse siempre que el nombre completo

contenga algún carácter no blanco.

El ICommand que se ejecutará al pulsar el botón se expone como propiedad de sólo lectura. Falta

vincular la orden a la pulsación del botón en MainWindow.xaml:

<Button Grid.Row="2" Grid.ColumnSpan="2" Command="{Binding SaludaCommand}">

Y ya está. Ejecutemos la aplicación y

comprobemos que el botón funciona.

Y comprobemos también que cuando

no hay texto en las cajas de texto no

podemos pulsar el botón, como se

muestra en la Figura 13.

Figura 13. El botón aparece deshabilitado porque no hay texto en

las cajas.

Page 78: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

n h

ay m

ás,

pero

no

para

ho

y

28

Podría pensarse que no hemos ganado mucho con las órdenes, pero el patrón orden aisla

efectivamente la lógica de la vista y la encapsula en una entidad que reúne la acción con un método

que permite saber cuándo es posible ejecutarla. Si ahora quisiésemos que otros elementos (opciones de

menú, gestos de teclados, etcétera) disparasen esa misma lógica, bastaría con asociarlos al mismo

RelayCommand. Todos esos elementos se activarían y desactivarían gracias al método CanExecute y

dispararían, cuando fuera posible, la misma acción vía Execute.

Aún hay más, pero no para hoy s imposible presentar todos los elementos que conforman WPF en una simple charla. He pretendido

mostrar algunos elementos básicos e introducir, aunque sea someramente, el patrón arquitectónico de

preferencia: MVVM. Entre lo que nos dejamos en el tintero:

Infinidad de controles.

Diferentes tipos de animaciones.

Estilos y plantillas que permiten personalizar prácticamente todo.

Gestión de colecciones que facilita la interacción con listas o árboles de datos.

La conexión a fuentes de datos provenientes de bases de datos o XML.

El modelo de navegación.

La versión empotrable en páginas web (Silverlight).

Los componentes multimedia.

Los efectos de bitmap.

Diseño de controles y paneles personalizados.

El trabajo con elementos 3D.

El diseño de pruebas unitarias para componentes MVVM.

Librerías de ayuda para el diseño de aplicaciones MVVM.

Uso de inyección de dependencias para facilitar la asociación entre elementos de la tríada

MVVM.

Extensiones para tinta, tacto, etcétera.

Conversores.

Eventos y órdenes enrutadas.

Espero haber despertado la curiosidad por WPF y C# para que acudáis ahora a las fuentes bibliográficas

o los blogs temáticos para aprender más.

E

Page 79: guia c# 01

Desarrollo de Aplicaciones con .NET y WPF Andrés Marzal

Fu

en

tes

bib

lio

grá

fica

s re

com

en

dab

les

29

Fuentes bibliográficas recomendables ólo queda recomendar algunos libros que permiten profundizar en WPF.

Windows Presentation Foundation Unleashed, de Adam Natham.

(Sacará una nueva edición en breve para cubrir WPF 4.0.)

Programming WPF, 2ª edición, de Chris Sells.

Essential Windows Presentation Foundations, de Chris Anderson.

Applications = Code + Markup: A Guide to the Microsoft Windows

Presentation Foundation, de Charles Petzold.

Hay también varios blogs recomendables. Entre ellos, os cito estos:

http://blogs.msdn.com/llobo/

http://sachabarber.net/

http://joshsmithonwpf.wordpress.com/

http://blogs.msdn.com/jgoldb/default.aspx

http://houseofbilz.com/Default.aspx

http://jesseliberty.com/

S