Etiquetado: WinForms

¿Hay vida después del Trace?

A los programadores nos suele gustar mucho escribir en la consola lo que están haciendo nuestras funciones.
Algunas veces es porque simplemente nos gusta ver que el interior funciona, como cuando abríamos un coche de scalextric o desmontábamos un reloj, pero normalmente es para saber qué está ocurriendo dentro del código sin hacer interminables sesiones de debug.
Cuando la programamos con múltiples hilos, eventos y cientos de controles, poner puntos de debug se vuelve una auténtica locura. Unas pocas líneas llamando a métodos de la clase System.Diagnostics.Trace nos pueden ayudar mucho. Pero, ¿qué pasa cuando no tenemos enganchado el debugger del VS.Net?, ¿adónde van los mensajes de Trace?
El mejor amigo del programador es aquí la clase System.Diagnostics.TraceListener. Heredando de ella podemos crear fácilmente nuestra propia ventana de Trace sin necesidad de tener un debugger para poder verlos. Simplemente hemos de implementar dos métodos que escriban en nuestra ventana:

He aquí una muestra de código, os dejo para vosotros la implementación de la TraceWindow:

/// <summary>
///
Trace Listener que dirige el trace a una ventana.
///
</summary>
public class WindowTraceListener :
TraceListener
{

private TraceWindow traceWin;

/// <summary>
///
Crea una instancia de WindowTraceListenerClass
///
</summary>
public WindowTraceListener() : base()
{
ShowTraceWindow();
}

/// <summary>
///
Abre la ventana de Trace.
///
</summary>
public void ShowTraceWindow()
{
ThreadPool.QueueUserWorkItem(
new WaitCallback(ShowTraceWindowInThread));
}

/// <summary>
///
Inicializa una nueva instancia de la clase WindowTraceListener
///
usando el nombre espcificado.
///
</summary>
/// <param name=”name”>nombre para el listener.
</param>
public WindowTraceListener(string name) : base(name)
{
ShowTraceWindow();
}

// ejecuta la ventana en otro thread para evitar atascar nuestra aplicacion
private void ShowTraceWindowInThread(object o)
{
if (traceWin == null)
{
traceWin = new TraceWindow();
traceWin.FormClosed += new FormClosedEventHandler(traceWin_FormClosed);
traceWin.ShowDialog();
}
}

void traceWin_FormClosed(object sender, FormClosedEventArgs e)
{
traceWin.Dispose();
traceWin = null;
}

/// <summary>
///
Escribe un mensaje a la ventana TraceWindow.
///
</summary>
/// <param name=”message”>el mensaje a escribir.
</param>
public override void Write(string message)
{
traceWin.Write(message);
}

/// <summary>
///
Escribe un mensaje a la ventana TraceWindow.
///
</summary>
/// <param name=”message”>el mensaje a escribir.
</param>
public override void WriteLine(string message)
{
traceWin.WriteLine(message);
}

}

Anuncios

Rendimiento, contadores y mediciones

Detectar puntos que degradan la velocidad de una aplicación no siempre es fácil. Si preguntais por ahí normalmente os dirán que useis contadores de rendimiento; eso está muy bien para detectar cuellos de botella en aplicaciones servidor, pero en otros escenarios como Windows Forms o para medir operaciones concretas son muy poco manejables.
Para estos últimos podemos comprarnos un buen profiler o hacerlo a la antigua contando cuanto tiempo de procesador usa cada llamada. En el .Net framework tenemos una clase que nos facilitará algo esas mediciones dentro del namespace System.Diagnostics llamada Stopwatch:

System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
presuntaLlamadaCostosa();
System.Diagnostics.Trace.WriteLine( string.Format(“La llamada tardó {0}ms ({1}ticks)”, sw.ElapsedMilliseconds, sw.ElapsedTicks) );

Pesadillas con el BindingContext

Para todos aquellos incautos que penséis que podéis heredar de la clase BindingContext… olvidadlo.
No entiendo muy bien porqué se diseñó así esta clase:

  • El evento CollectionChanged jamás se ejecuta.
  • El método sobrecargable AddCore no tiene el parámetro datamember y además no es llamado nunca.
  • Los indexer no son virtual, así que no es posible sobrecargarlos (si se declaran con un new no sirve puesto que las llamadas se hacen a través de la clase base).
  • No se puede cambiar la lista interna que maneja la colección de bindings.

En fin, que la única manera de manejar lo que haya dentro del BindingContext es iterando dentro de sus items en algún momento después del constructor y después rezar para que nadie añada nada a la lista más tarde.

Acelerando WinForms

Recientemente tuvimos que revisar una aplicación winforms que se había vuelto muy lenta, cada vez que se instanciaba una nueva ventana o usercontrol tardaba varios segundos en abrirse.
Tras revisar el código vimos que algunos controles se inicializaban en los métodos OnLoad y OnCreateControl. Es un error muy común, ya que hasta que el control no se ha creado no podemos saber si todas sus propiedades tienen el valor correcto y a veces hay propiedades que dependen de otras.
Como sabemos no podemos controlar el orden de creación de los controles en un formulario y no podemos asegurar que un control siempre se creará antes que otro o que una propiedad se asignará antes que otra.
Por nuestra experiencia sabíamos que eso podía ser un problema así que decidimos cambiar algunos controles e implementar en ellos el interface ISupportInitialize.
Este interface es soportado directamente por el entorno de diseño y nos permite ejecutar código en el método EndInit con la garantía de que todos los controles en el formulario ya han sido creados.
El truco está en sustituir el código que había en el OnLoad y moverlo al método EndInit, de esta manera el código se ejecuta dentro del constructor con una ganancia de casi el 50% en velocidad.

Al final de los cambios llegamos a las siguientes conclusiones:

  • Siempre que sea posible realizar la inicialización en el constructor.
  • Evitar iteraciones sobre los controles durante los métodos OnLoad y OnCreateControl. Son mucho más rápidas si se realizan dentro del constructor, incluso usando reflection.