En contra de la Cruz Roja …

Buenas

semana más que complicada entre manos, y antes que nadie piense cosas raras quiero aclarar que todavía no tengo nada en contra la Cruz Roja, GreenPeace, etc. El problema con el que estuve lidiando esta semana estaba más relacionado con la cruz roja de la imagen (popularmente conocida como la BIG RED X, o BIG RED CROSS). 

Se que hay muchas personas que pasan directamente del desarrollo de aplicaciones Windows, personalmente a mi también me gusta mucho más el desarrollo de las capas de servicios o negocios de una solución, la integración con diferentes sistemas, etc. Pero debo reconocer que el desarrollo de UIs Windows suele ser un reto bastante interesante. Más aún cuando son aplicaciones de tiempo real, que intearccionan con varias fuentes de información, se actualizan utilizando complicados sistemas de varios hilos, etc.

En uno de estos escenarios, cuando se actualiza la información de un control desde varios threads, suele comenzar a aparecer la Big Red Cross. El tema de la programación Mutithreading es un tema que da para un libro completo, pero en este caso es parte de un problema que se termina reflejando en el evento Paint() de los controles Windows. Cuando se produce una excepción dentro de este evento, por lo gral es una excepción no controlada que suele ser dificil de capturar y procesar.

Aqui tengo que hacer un paréntesis para comentar como se tratan este tipo de excepciones en el Paint de cada control. Los controles propios de la librería System.Windows.Forms permiten capturar y procesar los errores en el dibujado de los mismos y además es posible, utilizar el StackTrace de la excepción para conocer el origen de la misma. Sin embargo, en algunas librerías de terceros, este tipo de excepciones se procesan de manera diferente y en algunos casos se omite completamente el detalle de toda la excepción. Es en esos casos donde, hay que tirar un poco de la inventiva y tratar de encontrar una solución que nos permita llegar el fondo del problema.

 

Reproduciendo la Big Red Cross

Para simular este escenario, he creado una clase que hereda de PictureBox y posee una propiedad que define si durante el pintado de la misma se dispara una excepción personalizada:

1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace WindowsApplication4 6 { 7 public class MyPictureBox : System.Windows.Forms.PictureBox 8 { 9 private bool _throwPaintException = false; 10 11 /// <summary> 12 /// Gets or sets a value indicating whether [throw paint exception]. 13 /// </summary> 14 /// <value><c>true</c> if [throw paint exception]; otherwise, <c>false</c>.</value> 15 public bool ThrowPaintException 16 { 17 get { return _throwPaintException; } 18 set { _throwPaintException = value; } 19 } 20 21 /// <summary> 22 /// Raises the <see cref="E:System.Windows.Forms.Control.Paint"></see> event. 23 /// </summary> 24 /// <param name="e">A <see cref="T:System.Windows.Forms.PaintEventArgs"></see> that contains the event data.</param> 25 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) 26 { 27 base.OnPaint(e); 28 if (_throwPaintException) 29 throw new System.Exception("Paint Exception !!!!"); 30 } 31 } 32 }

 

He agregado este PictureBox personalizado dentro de un formulario con 2 botones y un CheckBox que definirá si se producirá una excepción en el dibujado del control. Para esto he agregado el siguiente código al formulario en el click del boton Paint Panel:

1 private void btnPaintPanel_Click(object sender, EventArgs e) 2 { 3 this.myPictureBox1.ThrowPaintException = chkThrowExc.Checked; 4 this.myPictureBox1.Image = WindowsApplication4.Properties.Resources.Goku; 5 }

De esta forma, si el check esta en True, se disparará una excepción en el click del Botón.

 

Capturando la Excepción

Una opción para poder capturar y procesar la excepción en la aplicación, es suscribirse al evento Application.ThreadException() y procesar la excepción dentro del manejador de este evento. En el ejemplo lo he hecho en el startUp de la aplicación, ya que es necesario suscribirse al evento antes de iniciar ningún proceso de creación y gestión de controles visuales:

1 namespace WindowsApplication4 2 { 3 static class Program 4 { 5 /// <summary> 6 /// The main entry point for the application. 7 /// </summary> 8 [STAThread] 9 static void Main() 10 { 11 // Add the event handler for handling UI thread exceptions to the event. 12 Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); 13 14 // Set the unhandled exception mode to force all Windows Forms errors to go through 15 // our handler. 16 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 17 18 // Add the event handler for handling non-UI thread exceptions to the event. 19 AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); 20 21 Application.EnableVisualStyles(); 22 Application.SetCompatibleTextRenderingDefault(false); 23 Application.Run(new Form1()); 24 } 25 26 27 static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 28 { 29 ProcessException((Exception)e.ExceptionObject); 30 } 31 32 static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) 33 { 34 ProcessException(e.Exception); 35 } 36 37 public static void ProcessException(Exception e) 38 { 39 MessageBox.Show(e.ToString()); 40 } 41 } 42 }

 

De esta forma si se produce un error, podremos ver el detalle del mismo en un messageBox como se muestra en la línea 39.

 

 

Reiniciando el estado del Control

Hasta aquí toda la programación habitual que ya conocemos, sin embargo por lo general, frente a las BIG RED X la única salida que poseemos es reiniciar la aplicación para que los controles vuelvan a un estado normal.

No existe ningún metodo convencional para refrescar el estado a “cero” de un control en una aplicación. Por suerte podemos tirar un poco de algunas opciones de Reflection y a partir de las mismas reiniciar el estado del control:

1 [ReflectionPermission(SecurityAction.Demand, MemberAccess = true)] 2 void ResetControlState(Control control) 3 { 4 typeof(Control).InvokeMember("SetState", BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, control, new object[] { 0x400000, false }); 5 } 6 7 private void btnReserUserControl_Click(object sender, EventArgs e) 8 { 9 ResetControlState(this.myPictureBox1); 10 }

 

De esta forma, en el boton Reset User Control invoca a esta rutina y reinicia el estado del control para que luego pueda ser utilizado el mismo, como si nunca hubiese aparecido la BIG RED X.

El código fuente completo del ejemplo se puede descargar desde aquí (gracias Geeks.ms) y a continuación un video con el ejemplo de utilización, y tampoco me olvido de agradecer a Jaume, que fue el que me llevo por el camino de la sabiduría para descargar los últimos capítulos de héroes lidiar con este error.


Video: Windows Forms – Paint Exception, Big Red X

 

Saludos

El Bruno

Crossposting from ElBruno.com

3 comentarios en “En contra de la Cruz Roja …”

  1. Hola,
    Me ha gustado mucho tu artículo, ya que tengo un problema de este tipo en una aplicación. Sin embargo, no me ha acabado de servir. Verás, la aplicación es una librería de clases la cual tiene varios timers del tipo Threading.Timer . En los procedimientos que ejecutan estos Timers se lanzan excepciones, y al hacerlo aparece el famoso error UnhandledException, ya que los timers se inician de forma automática en threads diferentes. Yo necesito que esas excepciones las capture la aplicación que usa esta librería y que a continuación la aplicación se cierre.
    Probé a poner en el constructor de la clase que tiene los timers la siguiente linea “AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);” y nada, no se dispara el evento, sigue dando el mismo error. Entonces quise poner las otras 2 instrucciones de tu ejemplo, pero el evento Application.ThreadException no existe y la propiedad Application.SetUnhandledExceptionMode tampoco.
    Quizás NO debería hacer que los procedimientos que lanzan los timers lanzaran excepciones, pero no se me ocurre otra forma de hacerlo.
    Si sabes la solución te lo agradecería.
    Gracias de antemano.

  2. Saludos, excelente tip, muchas gracias por su ayuda, tenía el mismo problema con al realizar drag and drop en un treelist, y funcionó perfectamente. Estimado bruno mis felicitaciones, ya que tu aporte ha sido de mucha ayuda.
    Atentamente
    Juan Carlos Salazar
    jcsmguitar@hotmail.com
    Quito – Ecuador
    Sudamérica

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *