lunes, 8 de agosto de 2011

Pensando en Lambda: Lambda Expressions con EventHandlers

Cuantas veces extendiendo funcionalidad de un control no hemos querido definir un eventHandler, sin tener que crear un método para ello? Bueno, no muchas veces pero a mí se me ocurren un par de usos bastante interesantes. Yo quería hacer un Extension Method para soportar que un botón mostrar una imagen distinta cuando está deshabilitado. El código sería muy sencillo usando lambda expresions, además de ser una solución bastante elegante:

jueves, 30 de diciembre de 2010

Ruta del Framework

Para obtener la ruta donde se encuentra el framework de .Net que se está utilizando, basta con hacer una llamada a la siguiente instrucción:



Luego podemos acceder a por ejemplo el directorio bin. Para hacer eso podemos utilizar la función Path.Combine. El método Combine, combina las rutas de dos folders, de manera que no nos tenemos que preocupar por si ya uno de los paths incluye un '\' al final o no, y si la siguiente empieza o termina con el mismo caracter. El ejemplo:

Editar un ResX

Una vez me enfrenté a un reto interesante. En efecto no sabía cómo resolver el problema, pero estaba seguro que no podía ser muy difícil. En realidad no lo fue. La cuestión que necesitaba era editar un imagelist que se encontraba serializado en un archivo de recursos (ResX). Lo bueno es que ésto puede servir para cualquier tipo de objeto que se encuentre serializado en un archivo de recursos, y aunque voy a hacer el ejemplo con el imagelist, tengan presente que se puede hacer con cualquier objeto.

La primera cosa que se necesita saber es que un archivo de recursos no es otra cosa más que un archivo XML (En este caso estoy rosando peligrosamente lo obvio). El primer paso entonces es cargar el archivo ResX en un documento Xml.


La estructura del xml no es muy compleja, por lo que es fácil deducir qué información necesitamos y cómo extraerla. Usando XPATH hacemos una consulta sobre el documento XML para obtener el item que deseamos modificar o deserializar. En este caso es un nodo value, hijo de un nodo data cuyo attributo name sea imgLstLogos.ImageStream. Luego extraemos el texto del nodo que es la representación del objeto.


Ahora a lo que vinimos. El nodo con el que estamos trabajando es algo similar a la siguiente imágen (click para agrandar):


Como pueden observar, el formato en que está serializado el objeto no es binario. Hasta cierto punto si lo es, pero es un tipo especial de binario. El formato que se utiliza es un string base 64, que se forma con caracteres ascii. Pueden ver más información técnica al respecto en Wikipedia. Pero hay un truco, dado que en el xml el string está dividido en bloques de 80 caracteres, tendremos que reemplazar con expresiones regulares algunos caracteres como fin de linea y espacios, a fin de tener la hilera de caracteres en una sola línea y un solo bloque.



Ya ahora sí podemos convertir la hilera en un arreglo de bytes, y utilizando las clases MemoryStream y BinaryFormatter, podemos obtener el objeto y castearlo. Para el caso del imagelist, lo que se guarda realmente es un ImageListStreamer. Basta con crear una nueva instancia de la clase ImageList y luego asignar la propiedad ImageStream con el valor deserializado para poder trabajar con las imágenes que estaban almacenadas.



Fácilmente podemos hacer modificaciones al objeto a nuestro antojo y posteriormente volverlo a encriptar. Siempre debemos tomar en cuenta que el objeto se debe guardar en string base 64 y en bloques de 80 caracteres. Como ejemplo vamos a eliminar la primera imagen del ImageList:



Luego vamos a convertir el objeto a su representación en binario. Para eso volvemos a usar las clases MemoryStream y el BinaryFormatter.



Ahora hay que convertirlo a su representación en String Base 64:



Como resultado obtenemos un arreglo de caracteres que es justo lo que necesitamos. Ahora basta con dividirlo en bloques de 80 caracteres. Por razones de eficiencia, utilizamos un StringBuilder, que se supone que es un poco mejor que concatenar strings por si solos (Dado que si se hacen concatenaciones, se crea un nuevo string y se desechan los dos anteriores).



Por último, cambiamos el valor del Texto en el nodo de donde originalmente lo tomamos y ya tenemos el documento Xml que corresponde al archivo de recursos actualizado. Nada más hay que recordar guardarlo, ya sea en el archivo original o en otra ruta distinta.



La mayoría del tiempo no es necesario editar un archivo de recursos de esta forma, ni mucho menos generarlo (Se puede usar la herramienta ResGen que viene con visual studio). Pero para el problema particular que tenía que afrontar era necesario. Dejo aquí el código más como referencia futura para mí mismo, pero si a alguien le funciona, es un plus.

miércoles, 27 de octubre de 2010

Código para Leer y Escribir el Registro

Otra cosa que agrego aquí como referencia futura. Tenía que hacer una aplicación para leer del registro y no encontré un ejemplo que me respondiera las preguntas que tenía, así que hice el mío propio. Para leer la estructura de la imagen, se usa el código que se encuentra abajo:




viernes, 5 de febrero de 2010

Método Join de la clase Thread

Ohh Hilos!, amados, odiados, útiles y a veces un poco complicados. Los hilos nos permiten realizar simultáneamente varias acciones al mismo tiempo, en una aplicación. Cuando trabajamos con interfaces de usuario, nos encontramos un problema.

Empecemos con un ejemplo sencillo, un simple hilo. Una vez que se ejecute la instrucción Start, entonces se va a comenzar a ejecutar. Ésto es útil, porque así el programa puede continuar ejecutando otras acciones y así, al hacer varias cosas al mismo tiempo, se supone que debería terminar antes de que si se ejecutaran en un solo hilo (típico divide y vencerás).


Uno podría hacer que cuando ese hilo termine se notifique de alguna manera al hilo principal, al usuario, etc. Pero eso es otro tema que podría complicarnos más de la cuenta, así que mejor lo dejamos para otro post. Lo que si necesitamos saber es que si necesitamos que nuestro hilo principal espere hasta que termine la ejecución del hilo que arrancamos, simplemente usamos el Método Thread.Join().

Imaginemos la siguiente situación: Tenemos una ventana y cuando le damos click a un botón una acción se va a llevar a cabo. Qué pasa cuando esa acción toma demasiado tiempo?. Si, adivinaron, la pantalla no se va a poder refrescar, es decir no se va a pintar correctamente y va aparecer en blanco o si le ponemos una ventana encima va a quedar pintada, y la pantalla se va a pintar correctamente hasta que termine el hilo, por lo que un Join no nos funciona.

Ahí es útil un hilo, para que la pantalla continúe pintándose y la acción se lleve a cabo, pero qué pasa si tenemos que esperar a que la acción termine? Estaríamos fritos, es por eso que también existe una sobrecarga que nos permite ejecutar una acción cada cierta cantidad de milisegundos, en este caso cada 100 millisegundos se ejecuta un DoEvents, que lo que hace es atender la cola de mensajes y en pocas palabras pintar nuestra ventana.

Pero hay que tener cuidado con el DoEvents, porque permite al usuario ejecutar cualquier acción permitida en la ventana, lo cual podría provocar un race condition o efectos no deseados. Lo ideal sería deshabilitar los controles, o poner una ventana modal que contenga un progress bar, lo cuál solucionaría el problema para muchos. Pero por ciertas razones no era lo que yo necesitaba, y eso lleva a la verdadera razón de este post. Cómo filtro los mensajes del sistema operativo, para que no se ejecute ningún evento en la ventana, pero que si se pinte?

Pues tuve que implementar un MessageFilter, de esta manera filtré todos los mensajes excepto el que implica refrescar la pantalla. Aquí les dejo el código completo.


Mas que todo, escribo esto para mis propias referencias futuras, pero si a alguno le sirve todavía mejor.

domingo, 31 de enero de 2010

Firefox y Yo Rompimos: Una historia de la vida real

Dolorosamente Firefox y yo debemos separar nuestros caminos... Ya Firefox no es igual, ha cambiado y he de decir que para mal. Aquí les dejo el diálogo de mi triste ruptura:

YO: Firefox, tenemos que hablar
Firefox(FF): Claro, hablemos
YO: Firefox, las cosas han cambiado y lastimosamente he de decir que no ha sido para bien.
FF: Porqué?
YO: Últimamente he notado que cada vez que te llamo, tardas demasiado en reponder, me dejas colgado y luego como después de 3 minutos me respondes. Ese silencio me duele. Además, estás consumiendo 40MB de mi limitada cantidad de memoria, y no tengo ningun add-on más que FireBug, así que no tienes excusa. Por si fuera poco, cada cierto tiempo me dejas colgado y el sistema me dice Firefox no responde...
FF: Si, sé que he fallado...
YO: Lo siento Firefox, creo que es momento de separar nuestros caminos
FF: Yo no lo considero así
YO: FF, no hagas esto más difícil. Tengo que confesarte que conocí a otro navegador, se llama Chrome.
FF: Yo sabía que uds dos se traían algo
YO: Chrome es todo lo que siempre necesité y más...
FF: Estoy de acuerdo contigo, creo que es momento de separar nuestros caminos
YO: Que bueno que lo comprendas. Igual seguiremos siendo amigos, cuando requiera de FireBug, te llamaré... Y si tu necesitas que programe un plugin, también llámame con confianza.
FF: Me parece bien, quedemos como amigos.
YO: Por favor, no me preguntes más si deseo que seas mi navegador predeterminado... Creo que ya sabrás la respuesta.
FF: Entiendo
YO: Adiós
FF: Adiós

(Cualquier parecido con una conversación de la vida real, no es coincidencia :P )
 

viernes, 15 de enero de 2010

Obtener el UserName y el Dominio de un Proceso

Para realizar esta (a primera vista) sencilla operación, en .NET, resulta no ser tan sencillo. Después de Googlear un rato (Dios bendiga Google), encontré la solucion, que es utilizando un Query y el ManagementObjectSearcher. Creo que también hay maneras de invocarlo con llamadas al API, pero siendo sincero, si quisiera llamar a un API, entonces mejor seguiría usando VB6 ;). Como siempre, aquí les dejo código de ejemplo.



Aunque si lo que necesitan es el Usuario y el Dominio del Proceso actual, es muchísimo más fácil y eficiente usar el Enviroment.UserName y el Environment.UserDomainName.