¡Bienvenid@ a La bloguera.net! Iniciar sesión | ÚNETE a la web | Ayuda

Cuando se presentó WPF hace ya algunos años, éste venía con un nuevo modelo de eventos que resultaban muy potentes, los eventos enrutados. Podemos definir los eventos enrutados como un tipo de evento que puede invocar controladores o varios agentes de escucha en un árbol de elementos, en lugar de simplemente en el objeto que lo desencadenó. Este modelo surge por necesidad al poder incluir controles dentro de otros, por ejemplo, si incluimos una imagen dentro de un  botón, éste conservará su comportamiento al hacer clic sobre la imagen. Windows Forms no dispone de este modelo de eventos pero podemos crear un comportamiento similar a los eventos enrutados de WPF que nos puede ser muy útil para propagar por varios elementos un evento que se haya producido.

En primer lugar vamos a definir la interfaz que nos va a permitir que un objeto pueda procesar un evento y/o dejarlo como trabajo para otros. La propiedad EventSuccesor define el elemento al que se le enviará el evento en caso de que no se procese en el objeto actual, la propiedad EventManager nos proporciona acceso al objeto que gestiona los eventos instanciados para el objeto y, por último, el método HandleBubbleEvent es el que se encarga de procesar un evento.

public interface IRoutedEventHandler

{

    IRoutedEventHandler EventSuccesor { get; set; }

 

    RoutedEventManager EventManager { get; }

 

    void HandleBubbleEvent(string eventName, RoutedEventArgs e);

}

La clase RoutedEventArgs, que extiende a la clase EventArgs que se utiliza como clase base para pasar información a cualquier evento define, principalmente, dos propiedades Source y Handled. Con la primera indicaremos el objeto origen donde se produce el evento y con la segunda indicamos si el evento ha sido manejado o no. En caso de que la propiedad Handled se defina como true el evento dejará de propagarse por la jerarquía establecida.

public class RoutedEventArgs : EventArgs

{

    private object _source;

    private bool _handled;

    private object _param;

 

    public RoutedEventArgs(object source)

    {

        _source = source;

    }

 

    public object Source

    {

        get { return _source; }

    }

 

    public bool Handled

    {

        get { return _handled; }

        set { _handled = value; }

    }

 

    public object Param

    {

        get { return _param; }

        set { _param = value; }

    }

}

Mediante la clase RoutedEventManager proporcionamos un almacén para guardar los eventos a los que responderá el objeto que implemente la interfaz IRoutedEventHandler previamente definida. Simplemente se proporciona un mecanismo que asocia a un nombre una lista de delegados de tipo RoutedEventDelegate, los cuales apuntan a los métodos que establezcamos como manejadores. Utilizamos una lista en vez de un único delegado para permitir que un evento tenga varios manejadores, de igual modo que podemos hacer con un evento de siempre.

public delegate void RoutedEventDelegate(Object sender, RoutedEventArgs e);

 

public class RoutedEventManager

{

    private Dictionary<string, List<RoutedEventDelegate>> _container;

 

    public RoutedEventManager()

    {

        _container = new Dictionary<string, List<RoutedEventDelegate>>();

    }

 

    public void AddHandler(string eventName, RoutedEventDelegate dlg)

    {

        List<RoutedEventDelegate> list;

 

        if (_container.ContainsKey(eventName))

        {

            list = _container[eventName];

            list.Add(dlg);

        }

        else

        {

            list = new List<RoutedEventDelegate>();

            list.Add(dlg);

            _container.Add(eventName, list);

        }

    }

 

    public bool RemoveHandler(string eventName)

    {

        if (_container.ContainsKey(eventName))

            return _container.Remove(eventName);

        else

            return false;

    }

 

    public bool RemoveHandler(string eventName, RoutedEventDelegate dlg)

    {

        List<RoutedEventDelegate> list;

 

        if (_container.ContainsKey(eventName))

        {

            list = _container[eventName];

 

            return list.Remove(dlg);

        }

        else

            return false;

    }

 

    public List<RoutedEventDelegate> GetHandler(string eventName)

    {

        if (_container.ContainsKey(eventName))

            return _container[eventName];

        else

            return null;

    }

A continuación implementamos el modelo de evento definido para un botón que por defecto al hacer clic sobre él, cambia de color. Aunque definamos un manejador enrutado dentro del propio código de la clase también podemos hacerlo desde cualquier otra que tenga acceso a una instacia de MyButton, de igual modo que ocurre con un evento.

public class MyButton : Button, IRoutedEventHandler

{

    private RoutedEventManager _eventManager;

    private IRoutedEventHandler _succesor;

 

    public MyButton()

        : base()

    {

        _eventManager = new RoutedEventManager();

 

        EventManager.AddHandler("Click", new RoutedEventDelegate(ClickHandler));

 

        this.Click += new EventHandler(MyButton_Click);

    }

 

    void MyButton_Click(object sender, EventArgs e)

    {

        RoutedEventArgs re = new RoutedEventArgs(this);

        re.Param = e;

 

        HandleBubbleEvent("Click", re);

    }

 

    public void ClickHandler(object sender, RoutedEventArgs re)

    {

        if (sender == this)

        {

            if (BackColor == DefaultBackColor)

                BackColor = Color.Red;

            else

                BackColor = DefaultBackColor;

        }

        else

        {

            if (re != null)

            {

                if (BackColor == DefaultBackColor)

                    BackColor = Color.Red;

                else

                    BackColor = DefaultBackColor;

 

                MessageBox.Show("Origen del evento: " + re.Source);

            }

        }

    }

 

    #region IRoutedEventHandler Members

 

    public IRoutedEventHandler EventSuccesor

    {

        get { return _succesor; }

        set { _succesor = value; }

    }

 

    public RoutedEventManager EventManager

    {

        get { return _eventManager; }

    }

 

    public void HandleBubbleEvent(string eventName, RoutedEventArgs e)

    {

        List<RoutedEventDelegate> dlgList = EventManager.GetHandler(eventName);

 

        if (dlgList != null)

        {

            foreach (RoutedEventDelegate dlg in dlgList)

            {

                if (!e.Handled)

                    dlg.Invoke(e.Source, e);

                else

                    break;

            }

        }

 

        if (!e.Handled && EventSuccesor != null)

            EventSuccesor.HandleBubbleEvent(eventName, e);

    }

 

    #endregion

}

Si nos fijamos en cómo definimos los eventos, tenemos que el manejador para el evento enrutado referencia al método ClickHandler, el cual implementa la lógica que aplicamos al evento Click, y por otro, definimos el evento Click del propio botón sobre el método MyButton_Click que simplemente realiza una llamada sobre HandleRoutedEvent para procesar el evento. Si en lugar de hacerlo de este modo incluimos la lógica en el propio evento Click y el evento enrutado hace referencia al mismo manejador entraríamos en un bucle que acabaría produciendo un desbordamiento de pila, pues la llamada a HandleRoutedEvent desencadenaría en una llamada al mismo método de forma recursiva.

 

Dados los últimos acontecimientos que llevamos padeciendo durante las últimas semanas me veo obligado a transmitir mi descontento con la nueva ley que el gobierno quiere aprobar para que sea una comisión particular y no el poder judicial quien tenga potestad para determinar el cierre de sitios web. Uno de los problemas que ha originado todo este asunto es que en este país se tiene la mala costumbre de crear leyes a golpe de impulso, mal y tarde que llevan a un alto grado de desacuerdo y descontento con la ciudadanía del país. Como agravante podríamos incluir el hecho de que sea la industria musical principalmente, que no la única, quienes presionan al gobierno para seguir adelante con el proyecto de ley y ellos se bajan los pantalones para cumplir sus deseos. El otro grave problema es que las industrias citadas no se quieren adaptar a los nuevos tiempos de la red y quieren perpetuar un modelo de negocio que se quedó obsoleto hace ya unos años.

Para ello, y entre otras cosas, es la propia industria la que ha ido sacando a la luz diferentes informes donde se manipula la información o simplemente se dan datos falsos para dar una visión totalmente falsa de la realidad y presentar una visión catastrofista del futuro de los músicos del país o de nuestro cine, pero le dan poco ruido a que año tras año mejoran beneficios. Con más detalle sobre estos informes, espero que leáis el siguiente artículo que me ha parecido bastante interesante.

Como respuesta a esta situación, han sido muchas las personas que han mostrado su descontento, desagrado, oposición, etc. tanto de forma individual como colectiva para defender nuestros derechos.  Incluso declarando la guerra. No somos el problema, y esto es algo que nos debe preocupar a todos, únete.

Nota de humor.

PD: muchos deberían de estar más en la actualidad como este señor de 78 años.

 

Consideramos imprescindible la retirada de la disposición final primera de la Ley de Economía Sostenible por los siguientes motivos:

1 -Viola los derechos constitucionales en los que se ha de basar un estado democrático en especial la presunción de inocencia, libertad de expresión, privacidad, inviolabilidad domiciliaria, tutela judicial efectiva, libertad de mercado, protección de consumidoras y consumidores, entre otros.

2 - Genera para la Internet un estado de excepción en el cual la ciudadanía será tratada mediante procedimientos administrativos sumarísimos reservados por la Audiencia Nacional a narcotraficantes y terroristas.

3 - Establece un procedimiento punitivo “a la carta” para casos en los que los tribunales ya han manifestado que no constituían delito, implicando incluso la necesidad de modificar al menos 4 leyes, una de ellas orgánica. Esto conlleva un cambio radical en el sistema jurídico y una fuente de inseguridad para el sector de las TIC (Tecnología de la Información y la Comunicación). Recordamos, en este sentido, que el intercambio de conocimiento y cultura en la red es un motor económico importante para salir de la crisis como se ha demostrado ampliamente.

4 - Los mecanismos preventivos urgentes de los que dispone la ley y la judicatura son para proteger a toda ciudadanía frente a riesgos tan graves como los que afectan a la salud pública. El gobierno pretende utilizar estos mismos mecanismos de protección global para beneficiar intereses particulares frente a la ciudadanía. Además la normativa introducirá el concepto de "lucro indirecto", es decir: a mí me pueden cerrrar el blog porque "promociono" a uno que "promociona" a otro que linka a un tercero que hace negocios presuntamente ilícitos

5 - Recordamos que la propiedad intelectual no es un derecho fundamental contrariamente a las declaraciones del Ministro de Justicia, Francisco Caamaño. Lo que es un derecho fundamental es el derecho a la producción literaria y artística.

6 - De acuerdo con las declaraciones de la Ministra de Cultura, esta disposición se utilizará exclusivamente para cerrar 200 webs que presuntamente están atentando contra los derechos de autor. Entendemos que si éste es el objetivo de la disposición, no es necesaria, ya que con la legislación actual existen procedimientos que permiten actuar contra webs, incluso con medidas cautelares, cuando presuntamente se esté incumpliendo la legalidad. Por lo que no queda sino recelar de las verdaderas intenciones que la motivan ya que lo único que añade a la legislación actual es el hecho de dejar la ciudadanía en una situación de grave indefensión jurídica en el entorno digital.

7 - Finalmente consideramos que la propuesta del gobierno no sólo es un despilfarro de recursos sino que será absolutamente ineficaz en sus presuntos propósitos y deja patente la absoluta incapacidad por parte del ejecutivo de entender los tiempos y motores de la Era Digital.

La disposición es una concesión más a la vieja industria del entretenimiento en detrimento de los derechos fundamentales de la ciudadanía en la era digital.

La ciudadanía no puede permitir de ninguna manera que sigan los intentos de vulnerar derechos fundamentales de las personas, sin la debida tutela judicial efectiva, para proteger derechos de menor rango como la propiedad intelectual. Dicha circunstancia ya fué aclarada con el dictado de inconstitucionalidad de la ley Corcuera (o ley de patada en la puerta). El Manifiesto en defensa de los derechos fundamentales en Internet, respaldado por más de 200 000 personas, ya avanzó la reacción y demandas de la ciudadanía antes la perspectiva inaceptable del gobierno.

Para impulsar un definitivo cambio de rumbo y coordinar una respuesta conjunta, el 9 de enero se ha constituido la "Red SOStenible" una plataforma representativa de todos los sectores sociedad civil afectados. El objetivo es iniciar una ofensiva para garantizar una regulación del entorno digital que permita expresar todo el potencial de la Red y de la creación cultural respetando las libertades fundamentales.

En este sentido, reconocemos como referencia para el desarrollo de la era digital, la Carta para la innovación, la creatividad y el acceso al conocimiento, un documento de síntesis elaborado por más de 100 expertos de 20 países que recoge los principios legales fundamentales que deben inspirar este nuevo horizonte.

En particular, consideramos que en estos momentos es especialmente urgentes la implementación por parte de gobiernos e instituciones competentes, de los siguientes aspectos recogidos en la Carta:

1 - Las/os artistas como todos los trabajadores tienen que poder vivir de su trabajo (referencia punto 2 "Demandas legales", párrafo B. "Estímulo de la creatividad y la innovación", de la Carta);

2 - La sociedad necesita para su desarrollo de una red abierta y libre (referencia punto 2 "Demandas legales", párrafo D "Acceso a las infraestructuras tecnológicas", de la Carta);

3 - El derecho a cita y el derecho a compartir tienen que ser potenciado y no limitado como fundamento de toda posibilidad de información y constitutivo de todo conocimiento (referencia punto 2 "Demandas legales", párrafo A "Derechos en un contexto digital", de la Carta);

4 - La ciudadanía debe poder disfrutar libremente de los derechos exclusivos de los bienes públicos que se pagan con su dinero, con el dinero publico (referencia punto 2 "Demandas legales", párrafo C "Conocimiento común y dominio público", de la Carta);

5 -Consideramos necesaria una reforma en profundidad del sistema de las entidades de gestión y la abolición del canon digital (referencia punto 2 "Demandas legales", párrafo B. "Estímulo de la creatividad y la innovación", de la Carta).

Por todo ello hoy se inicia la campaña INTERNET NO SERA OTRA TELE y se llevarán a cabo diversas acciones ciudadanas durante todo el periodo de la presidencia española de la UE.

Consideramos particularmente importantes en el calendario de la presidencia de turno española el II Congreso de Economía de la Cultura (29 y 30 de marzo en Barcelona), Reunión Informal de ministros de Cultura (30 y 31 de marzo en Barcelona) y la reunión de ministros de Telecomunicaciones (18 a 20 de abril en Granada).

La Red tiene previsto reunirse con representantes nacionales e internacionales de partidos políticos, representantes de la cultura y legaciones diplomáticas.

Firmado Red SOStenible

La Red Sostenible somos todo. Si quieres adherirte a este texto, cópialo, blogguéalo, difúndelo.

Enlaces de interés:

Red SOStenible.

A debate los derechos de autor en ‘59 segundos’.

Sobre el anteproyecto de ley para cerrar webs, por Javier de la Cueva y David Bravo.

Movilízate.

Internet no será otra tv.

Una forma sencilla para crear una instancia de un objeto existente en una DLL cargada dinámicamente es mediante el uso de AppDomain.CreateInstance. Si disponemos de una interfaz que implementa la clase del objeto (en tiempo de compilación) podemos crear el objeto en tiempo de ejecución y acceder directamente a los métodos declarados en la interfaz sin tener que recurrir a reflexión pura y dura.

System.Runtime.Remoting.ObjectHandle handle;

string assemblyName = string.Empty; //assembly donde esta la clase

string classToLoad = string.Empty; //nombre de la clase <namespace>.<nombre>

Assembly callingAssembly = null;

AppDomain appDomain = System.Threading.Thread.GetDomain();

Assembly[] assemblies = appDomain.GetAssemblies();

 

foreach (Assembly assembly in assemblies)

{

    if (assemblyName.Equals(assembly.FullName))

        callingAssembly = assembly;

}

 

if (callingAssembly == null)

    throw new NullReferenceException("No se encuentra el ensamblado.");

 

handle = appDomain.CreateInstance(callingAssembly.FullName, classToLoad, false, 0,

    null, new Object[] { /*parametros para el constructor*/ }, null, null, null);

 

MyInterface obj = (MyInterface) handle.Unwrap();

 

//obj.Metodo()...

Definiendo el ensamblado donde se encuentra la clase a instanciar (assemblyName) y la clase a instanciar (classToLoad) nos basta enlazar un objeto, hay que tener en cuenta que al especificar el nombre de la clase debemos incluir el namespace en el que ésta se encuentra sino, se producirá una excepción porque no se encontrará la clase.

El método CreateInstance consta de una serie de argumentos que utiliza para crear la instancia. Necesita que especifiquemos al menos el ensamblado y la clase a instanciar, y un array de objetos para especificar los argumentos del constructor (o null si no necesita). Además, podemos espeficicar indicadores de enlace, información específica de la referencia cultural utilizada para interpretar argumentos, atributos de activación y autorización para crear el tipo.

Leyendo por la red he encontrado una curiosa entrada en la que se nos muestra cómo jugar a juego de rol “de mesa” mediante Surface, algo que en estos momentos está limitado a unos pocos debido a su precio prohibitivo.

 

 

Por otro lado, podemos contemplar este otro juego de rol “de mesa” en un ReacTable. Para los que no conozcan este dispositivo, decir que es un invento español, concretamente tiene su origen en la Universidad Pompeu Fabra de Barcelona y su función original es la de un instrumento musical colaborativo inspirado en los sintetizadores de hace unos años.

 

 

Aquí un video de su funcionamiento como sintetizador.

 

 

Enjoy it.

Si diponeis de una HTC Magic o similiar que use el SO Android, al conectarlo via USB a un ordenador con Windows os lo reconocera, en un principio, pero no podréis usarlo. Para ello necesitais instalar el driver necesario que podéis descargar desde la siguiente dirección: http://dl.google.com/android/android_usb_windows.zip. Luego simplemente tenéis que seguir los siguientes paso:

1. Descomprimir el driver.
2. Conectar el teléfono con Android al PC via USB  y al hacerlo se nos preguntará si queremos montarlo, le decimos que si.
3. Cuando Windos diga que ha encontrado un hardware nuevo y que si quieres que busque los drivers, le decimos que NO.
4. En la siguiente pantalla ponemos que buscaremos nosotros mismo la ubicación de los drivers.
5. Buscas la carpeta donde hays descomprimido el driver.
6. Haces clic en Finalizar o Aceptar.

Tarde o temprano a cualquier programador le toca lidiar con la programación multihilo para hacer un uso más intensivo de los recursos disponibles y/o mejorar la respuesta de aplicación ante una determinada acción o tarea. En una de las cosas que más lo podemos notar es en la respuesta de la interfaz de usuario en una aplicación de escritorio donde, si la aplicación está llevando a cabo una tarea de cierta carga, el usuario tendrá la sensación de que la aplicación se ha quedado colgada y no hace nada, cuando en realidad, sigue a lo suyo.  Por lo tanto, la programación multihilo seguro que es el pan de cada día de más de uno… además, con los procesadores que llevan un tiempo en el mercado con más de un núcleo parece que es la moda lo de usar “hilos”.

Pues bien, lanzar hilos a diestro y siniestro sin control es relativamente sencillo, pero algo habrá que hacer con ellos, es decir, en algún momento nos puede interesar “manejar” un hilo. Principalmente lo que vamos a poder hacer es acabar con la ejecución de un hilo de una forma más o menos controlada. Existen varias formas de enfocar el como terminar la ejecución de un hilo.

La primera de ellas, es disponer de una variable que determine cuando termina un bucle donde se realiza el trabajo que realiza el subproceso. Para este esquema, tenemos que pensar en como dividir el trabajo del subproceso en diferentes subtrabajos que se corresponderán con las diferentes iteraciones del bucle. Esto nos proporciona una ventaja y es que en caso de querer reanudar esta tarea en un nuevo subproceso, dicha tarea se encontrará en un estado “seguro” puesto que los datos con los que trabajemos serán coherentes y podremos continuar sin problemas. Como principal desventaja tenemos que la ejecución del subproceso no termina de forma inmediata.

private Thread t;

private volatile bool terminado;

 

private void LanzarHilo()

{

    t = new Thread(new ThreadStart(DoWork));

    terminado = false;

 

    t.Start();

}

 

private void DetenerHilo()

{

    terminado = true;

}

 

private void DoWork()

{

    while (!terminado)

    {

        //realizar subtrabajo...

        //si antes de detener el hilo, termina

        //con su tarea salimos

        if (concluido)

            terminado = true;

    }

}

El código no creo que tenga mayor complicación para entenderlo, puntualizar que la variable que determina cuando finaliza el while está declarada como volatile, por lo que se eliminan las optimizaciones que pueda establecer el compilador para el uso por un solo subproceso y se garantiza que siempre se acceda al valor más actualizado de la variable por cualquier subproceso.

El otro esquema que podemos utilizar para terminar con la ejecución de un hilo es mediante el uso de la llama a Abort(), que termina de forma abrupta con la ejecución del hilo. Esta llamada lanza una excepción especial del tipo ThreadAbortException que se produce en el subproceso, por lo tanto el código definido en el método o función que ejecuta el subproceso debe de estar dentro de un bloque try-catch y capturar, al menos, este tipo de excepción para saber cuando se produce la cancelación y realizar las operaciones que creamos convenientes. Tendríamos algo así:

private void DoWork()

{

    try

    {

        //subprocesamiento...

    }

    catch (ThreadAbortException e)

    {

        //tratamiento de la excepcion

    }

    finally

    {

 

    }

}

Esto tiene dos implicaciones, la primera es que si pensábamos que al llamar a Abort() la cancelación del hilo se produce de forma inmediata no es algo del todo correcto, puesto que podríamos seguir ejecutando código de forma ilimitada dentro de los bloques catch-finally. La otra implicación viene dada por ese carácter especial que le hemos dado antes a la excepción ThreadAbortException, y es que la particularidad de esta excepción viene dada porque una vez que finaliza el bloque try-catch donde se captura la excepción producida por la llamada al método Abort(), se vuelve a producir de forma la misma excepción de forma automática. De este modo si existiera más código después del bloque finally, éste no se ejecutaría.

Tenemos la posibilidad de que esta excepción no se vuelva a producir mediante la llamada a Thread.ResetAbort(), este método anula la cancelación del subproceso.

private void DoWork()

{

    try

    {

        //subprocesamiento...

    }

    catch (ThreadAbortException e)

    {

        Thread.ResetAbort();

    }

    finally

    {

        //…

    }

 

    //mas codigo…

}

En este caso, el código que sigue al bloque finally si que se ejecutaría puesto que hemos llamado a ResetAbort(). Además, conviene incluir la llamada en el bloque catch, donde tenemos las certeza que se ha llamado a Abort(), ya que si la llamada a ResetAbort() se realiza cuando aun no se ha lanzado la excepción ThreadAbortException, esta condición podría darse en el bloque finally, se produce una excepción del tipo ThreadStateException.

Una vez que se ha ejecutado el bloque finally, podríamos seguir ejecutando más código, pero ¿qué ocurre si se llama de nuevo a Abort()? No se producirá ninguna nueva excepción, independientemente de si hemos llamado a ResetAbort() o no. Una vez que llamamos a Abort(), el thread pasará a un estado llamado AbortRequested, indicando que se está esperando a que se produzca la excepción ThreadAbortException para que se produzca la finalización del hilo. Luego sucesivas llamadas a Abort() no tienen efecto ninguno y ya no disponemos de la posibilidad de abortar el hilo nuevamente.

Para evitar el perder la posibilidad de volver a cancelar el thread, podemos “relanzarlo” en el momento de capturar la excepción producida por la llamada a Abort() mediante un delegado que apunte al mismo método que ejecuta el hilo. 

private delegate void MyDelegate();

private MyDelegate myDlg;

 

public void LanzarHilo()

{

    t = new Thread(new ThreadStart(DoWork));

 

    myDlg += new MyDelegate(DoWork);

}

 

private void DetenerHilo()

{

    t.Abort(/*data...*/);

}

 

private void DoWork()

{

    try

    {

        //asignamos el hilo a la ref global para manejarla posteriormente

        t = Thread.CurrentThread;

 

        //subprocesamiento...

    }

    catch (ThreadAbortException e)

    {

        Thread.ResetAbort();

 

        //podemos utilizar el objeto que pasamos en la

        //llamada a Abort() para determinar que hacer

 

        //if (e.ExceptionState...)

        //dejar datos en un estado coherente

        myDlg.BeginInvoke(null, null);

    }

}

La llamada Abort() puede tomar como argumento un objeto que luego podemos recuperar mediante la propiedad ExceptionState de la excepción para determinar las condiciones que han producido que se cancele el hilo y así determinar si queremos relanzarlo o dejarlo que muera. Cuando se produce la llamada a BeginInvoke del delegado en caso de que queramos relanzar el hilo, se va a crear un nuevo thread que volverá a ejecutar el método DoWork(). Si analizamos el thread que se ejecuta al llamar a BeginInvoke podemos ver que se ejecuta en background y además, que es un hilo perteneciente al ThreadPool de .Net, por lo que la capacidad de realizar este esquema o patrón repetidas veces de forma simultanea estará limitada al número de hilos que se puedan ejecutar del forma simultanea mediante el ThreadPool.

¿En qué casos nos puede resultar este modelo útil? Podemos disponer de una subtarea potencialmente bloqueante y que, si transcurrido un determinado tiempo (determinado, por ejemplo, mediante timer) no se ha completado, la cancelemos  y la relanzemos el número de veces que creamos convenientes.

Los asserts nos permiten comprobar el resultado de las pruebas unitarias. Podemos destacar que existe un buen soporte para trabajar con genéricos, algo que nos puede facilitar mucho el trabajo a realizar a la hora de escribir nuestras pruebas. Existe un buen numero de asserts pero algunos son la negación o lo contrario de otro (por ejemplo: True-False, Null-NotNull...):

  • Equal<T>: Comprueba si dos objetos sean iguales.
  • NotEqual<T>: Comprueba que dos objetos no sean iguales.
  • NotSame: Comprueba que dos objetos no sean la misma instancia.
  • Same: Comrpueba que dos objetos sean la misma instancia.
  • Contains, Contains<T>: Verifica que una colección contiene un objeto dado.
  • DoesNotContain, DoesNotContain<T>: Verifica que una colección no contiene un objeto dado.
  • DoesNotThrow: Comprueba que un bloque de código no lanza ninguna excepción.
  • Throws, Throws<T>: Comprueba que un bloque de código lance una determinada excepción.
  • InRange<T>: Verifica que un objeto se encuentra dentro de un rango.
  • NotInRange<T>: Verifica que un objeto no se encuentra dentro de un rango.
  • IsAsignableFrom, IsAsignableFrom<T>: Comprueba que un objeto sea del tipo dado o de uno derivado.
  • Empty: Comprueba que una colección no contenga elementos.
  • False: Verifica si una condición es falsa.
  • IsNotType, IsNotType<T>: Verifica que un objeto no es exactamente del tipo dado.
  • IsType, IsType<T>: Verifica que un objeto es exactamente del tipo dado.
  • NotEmpty: Comprueba que una colección tenga algún elemento.
  • NotNull: Verifica que una referencia a un objeto no sea nula.
  • Null: Verifica que una referencia a un objeto no sea nula.
  • True: Verifica que una condición es verdadera.

Los asserts a los que podemos prestarles más atención puede que sean Equal, InRange y Throws. Para la aserción Equal podemos pensar que no tiene mayor misterio pero, ¿qué ocurre si comparamos dos objetos de una clase que hemos creado? Para verlo con un ejemplo he creado una clase FichaDomino que simplemente contiene dos valores enteros para representar una ficha de dominó.

[Fact]

public void TestFichasIguales()

{

    FichaDomino fich1 = new FichaDomino(6, 6);

    FichaDomino fich2 = new FichaDomino(6, 6);

    Assert.Equal(fich1, fich2);

}

Al jecutar este test esperamos que pase pero el resultado obtenido es el siguiente:

TestCase 'Tests.Test.TestFichasIguales' failed: Assert.Equal() Failure

Expected: 6:6

Actual:   6:6

       en Xunit.Assert.Equal[T](T expected, T actual, IComparer`1 comparer)

       en Xunit.Assert.Equal[T](T expected, T actual)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(116,0): en Tests.Test.TestFichasIguales()

Esto ocurre así porque la comparación se realiza a través del método Equals que se hereda de la clase Object que lo único que hace es comparar si las referencias de los objetos a comparar son la misma. La solución es sobrescribir el método Equals o, implementar la interfaz IComparable y/o IComparable<T> (ya que podemos hacer uso de genéricos...). Indagando un poco en la forma que trabaja xUnit, primero se va comprobar si se implementa la interfaz Icomparable<T> para llamar a CompareTo de forma genérica (es decir, con un tipo concreto), si no es así se comprueba si se implementa la interfaz IComparable para llamar a CompareTo donde el argumento a comparar es un object. Si no se implementa alguna de estas interfaces entonces es necesario sobrescribir el método Equals.

La aserción InRange verifica que un objeto esté dentro de un rango de valores. En este caso pasa tres cuartos de lo mismo que ocurre con el assert Equal, para tipos simples (enteros, strings, fechas...) la comparación es trivial pero para objetos no. Aquí necesitamos implementar IComparable o IComparable<T> para que al comparar si el objeto dado esta dentro de un determinado rango de valores el resultado sea el correcto. Como alternativa podríamos pasarle a este assert un comparador que compare objetos de la clase FichaDomino, aunque pienso que podrían producirse fallos si olvidamos que siempre tenemos que utilizar un comparador personalizado al no haber implementado IComparable. La implementación es esta:

public int CompareTo(FichaDomino other)

{

    int result = 0;

 

    if (this.numIzq < other.numIzq)

    {

        if (this.numDer < other.numDer || this.numDer == other.numDer)

            result = -1;

        else

            result = 1;

    }

    else if (this.numIzq == other.numIzq)

    {

        if (this.numDer < other.numDer)

            result = -1;

        else if (this.numDer == other.numDer)

            result = 0;

        else

            result = 1;

    }

    else

        result = 1;

 

    return result;

}

Por último, Throws<T> nos permite verificar si se produce una excepción de un tipo determinado. El parámetro que toma este assert es un delegado a la función o al código que queremos testar para que el assert pueda recoger la excepción si se produce. Vamos a crear una ficha de dominó con los valores 8:8, si la implementación de constructor es correcta debería de lanzar un excepción del tipo ArgumentException por estar esos valores fuera de los valores que puede tener una ficha de dominó

public FichaDomino(int numIzq, int numDer)

{

    this.numIzq = numIzq;

    this.numDer = numDer;

}

el test para verificarlo es:

[Fact]

public void TestFichaNueva()

{

    Assert.Throws<ArgumentException>(

        delegate

        {

            FichaDomino fich1 = new FichaDomino(8, 8);

        });

}

y el resultado es:

TestCase 'Tests.Test.TestFichaNueva'

failed: Assert.Throws() Failure

Expected: System.ArgumentException

Actual:   (No exception was thrown)

       en Xunit.Assert.Throws(Type exceptionType, ThrowsDelegate testCode)

       en Xunit.Assert.Throws[T](ThrowsDelegate testCode)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(122,0): en Tests.Test.TestFichaNueva()

Qué ocurre si tenemos un método que puede lanzar una misma excepción pero por diferentes causas, de esta forma no podemos determinar cuál es la causa por la que se ha producido la excepción. Para tener un control mayor sobre las excepciones que se pueden lanzar vamos a utilizar la clase Record para capturar una excepción y después poder operar con ella. Como ejemplo vamos a tomar la calculadora (utilizada en el post anterior) para obtener un número aleatorio dentro de un rango de valores [a,b]. Las causas para lanzar una excepción son que el rango no sea correcto (a > b) o que no exista rango (a = b). Teniendo la siguiente implementación para generar un número aleatorio dentro de un rango de valores:

public static double NumAleatorioEnRango(double min, double max)

{

    if (min > max)

        throw new ArgumentException("Rango no valido. El valor minimo > maximo");

    if (min == max)

        throw new ArgumentException("Rango no valido. El valor minimo = maximo");

 

    Random random = new Random(DateTime.Now.Millisecond);

 

    double aleatorio = random.NextDouble();

    double result = ((max - min) * aleatorio) + min;

    return result;

}

Vamos a lanzar la siguiente prueba donde queremos comprobar que se lanza una excepción cuando min > max:

[Fact]

public void TestNumAleatorio3()

{

    double min = 9.0;

    double max = 5.0;

 

    Exception recException = Record.Exception(

        delegate

        {

            Calc.NumAleatorioEnRango(min, max);

        });

 

    Assert.IsType<ArgumentException>(recException);

    Assert.Equal("Rango no valido. El valor minimo > maximo", recException.Message);

}

El resultado es que las dos aserciones son correctas y el test pasa como válido.

NOTA: adjunto va un zip con la demo de prueba.

 

Este post y siguientes tienen como finalidad hacer una introducción a xUnit y el desarrollo guiado por pruebas (TDD o del inglés Test Driven Development). El desarrollo guiado por pruebas es una práctica que forma parte de las metodologías ágiles para el desarrollo de software y que consiste en el uso de pruebas unitarias para escribir el código de una forma más limpia y con menos errores.

Lo que se pretende, entre otras cosas, es evitar el escribir código innecesario “por si en un futuro lo usamos” y con menos fallos. Muchas veces empezamos a escribir código e intentamos adaptarlo para posibles cambios que pueden darse lugar en un futuro pero que luego y de forma general no suelen llegar, con lo que trabajamos en algo que no nos va a servir de mucho. La idea es que por cada requerimiento que tengamos escribamos una prueba que inicialmente fallará. Si la prueba no falla puede ser porque la funcionalidad ya estaba implementada o que la prueba es errónea. Cada prueba unitaria se escribe con el código mínimo necesario y posteriormente se puede realizar una refactorización. De esta forma nos aseguramos que al hacer cambio en el código tenemos la forma o herramienta de probar que esos cambios son válidos y no se han introducido nuevos errores.

El ciclo del desarrollo guiado por pruebas consiste en primer lugar en elegir un requerimiento para el cual vamos a escribir una prueba, después comprobamos que falla (como ya hemos comentado antes) y entonces implementamos el código lo más simple posible para que funcione. Verificamos que todas nuestras pruebas unitarias son válidas y entonces hacemos una refactorización del código para eliminar partes duplicadas. Por último actualizamos la lista de requerimientos, añadiendo requisitos nuevos si han surgido y/o eliminando los ya implementados, y realizamos un nuevo ciclo. Esto es una introducción muy general al TDD, solo para que uno se haga una idea y podéis buscar más información a través de nuestro amigo google.

Ahora vayamos al lío, de entre todos los frameworks existentes para hacer test unitarios he decido usar xUnit, basado en nUnit (aunque tienen algunas diferencias claras) y cuyo autor es el mismo. Para poder usar xUnit (descargar desde aquí) tenemos varias opciones, por un lado tenemos xunit.console y xunit.gui (que vienen con xUnit) y por otro tenemos la posibilidad de integrar xUnit con Visual Studio por medio de TestDriven (que cuenta con edición personal gratuita) o con ReSapher (evaluación de 30 días). Yo voy a usar TestDriven y para ello, una vez instalado TestDriven, hay que activar el soporte de xUnit para TestDriven a través de xunit.installer.

xUnit.installer

En este momento es cuando podemos crear un nuevo proyecto en Visual Studio con el que vamos a realizar pruebas unitarias. Imaginemos que vamos a hacer una calculadora y vamos a tener una librería que de soporte para realizar operaciones con una calculadora. Tenemos una clase muy sencilla llamada Calc:

public class Calc

{

    public Calc()

    { }

 

    public int SumaNumeros(int a, int b)

    {

        throw new ArgumentException("No implementado");

    }

 

    public double DivideNumeros(double a, double b)

    {

        throw new ArgumentException("No implementado");

    }

}

Después creamos otro proyecto de librería de clases que es el que va a incluir todas nuestras pruebas unitarias y agregamos la referencia a la dll de xUnit y la de la librería sobre la que vamos a realizar las pruebas:

referencias

En la imagen podemos ver las referencias agregadas tanto al proyecto como a la clase que contiene las pruebas unitarias. También podemos ver que existe un método llamado ComprobarSuma que es el encargado de comprobar la operación suma de la calculadora. Para que xUnit identifique este método como una prueba unitaria hay que marcarlo con el atributo [Fact], de lo contrario no se ejecutará ningún test. Queda resaltar la sentencia Assert que establece la condición para determinar si la prueba unitaria es satisfactoria o no. Para ejecutar xUnit pinchamos con el botón derecho del ratón sobre el proyecto que contiene las pruebas unitarias y le damos a Run Test(s).

runtest

Si hacemos correr el test, el resultado obtenido es el siguiente (hay que recordar que como no estaba implementado el método de sumar números hacíamos que lanzara una excepción):

------ Test started: Assembly: Tests.dll ------

 

TestCase 'Tests.Test.ComprobarSuma' failed: System.ArgumentException : No implementado

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\DemoTDD\Calc.cs(14,0): en DemoTDD.Calc.SumaNumeros(Int32 a, Int32 b)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(17,0): en Tests.Test.ComprobarSuma()

 

0 passed, 1 failed, 0 skipped, took 0,72 seconds.

Una vez visto que la prueba falla, es cuando hay que implementar el método que estamos probando. Para ver otro ejemplo, vamos a hacer que el método SumaNumeros nos devuelva la suma de los dos argumentos más uno. En el resultado al ejecutar el test nos dice que el test ha fallado y nos indica el valor esperado y el valor obtenido:

------ Test started: Assembly: Tests.dll ------

 

TestCase 'Tests.Test.ComprobarSuma' failed: Assert.Equal() Failure

Expected: 7

Actual:   8

       en Xunit.Assert.Equal[T](T expected, T actual, IComparer`1 comparer)

       en Xunit.Assert.Equal[T](T expected, T actual)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(19,0): en Tests.Test.ComprobarSuma()

 

 

0 passed, 1 failed, 0 skipped, took 0,76 seconds. 

También es interesante saber que podemos añadir parámetros al atributo [Fact] para contemplar dos casos, cuando una prueba tarda demasiado tiempo y cuando por el motivo que sea no queremos que una determinada prueba se ejecute en un momento concreto.

Si queremos saltarnos una prueba tenemos que añadir el parámetro Skip que toma un valor de tipo string:

[Fact(Skip = "Sin implementar")]

public void SkipTest()

{

    //implementar despues

} 

La salida obtenida es:

0 passed, 0 failed, 1 skipped, took 0,72 seconds

Si queremos que test falle cuando exceda de un determinado tiempo tenemos que añadir el parámetro Timeout que toma un valor entero en milisegundos:

[Fact(Timeout = 2000)]

public void TimeoutTest()

{

    Thread.Sleep(6000);

} 

La salida obtenida es:

TestCase 'Tests.Test.TimeoutTest' failed: Test execution time exceeded: 2000ms

Y por último dejo una lista con todas las aserciones que contempla xUnit para comprobar el resultado de una prueba unitaria y que me gustaría comentar en el siguiente post:

  • Equal<T>
  • NotEqual<T>
  • NotSame
  • Same
  • Contains, Contains<T>
  • DoesNotContain, DoesNotContain<T>
  • DoesNotThrow
  • Throws, Throws<T>
  • InRange<T>
  • NotInRange<T>
  • IsAsignableFrom, IsAsignableFrom<T>
  • Empty
  • False
  • IsNotType, IsNotType<T>
  • IsType, IsType<T>
  • NotEmpty
  • NotNull
  • Null
  • True

NOTA: la versión que he utilizado de xUnit es la 1.0.3 que parece presentar un bug (que ya ha sido reportado) en el que la salida por consola no aparece, por eso si añadís algún Console.WriteLine no esperéis a que os aparezca por consola.

NOTA 2: teneis adjunto el ejemplo de código para VS2008

¿Os acordáis de de la aplicación MSDN Video, aquella que se desarrolló para dar a conocer la tecnología .Net allá por el 2003? Pues bien, desde hace un tiempo sabía que estaban actualizando el contenido para adaptarlo al framework 3.5 y Visual Studio 2008, lo que me parecía algo estupendo. Pues bien, anoche antes de acostarme decidí dar un paseo por la web de desarrolla con MSDN y sorpresa la mía al encontrarme que ya han publicado MSDN Video 2008.
 

Esta vez las tecnologías de las que se harán uso son, entre otras, Ajax, Linq, WPF y WCF. Se pretende que sea un proyecto en constante evolución, en el cual cada uno de nosotros podemos aportar nuestro granito de arena. El código fuente del que tenemos disponibilidad es una versión beta y también disponemos de un primer video de introducción en Silverlight. A parte, parece que sacarán un curso online para aprender el modo en el que se han utilizado las nuevas tecnologías que incorpora el framework 3.5.

Espero que os resulte útil, un saludo.

 

No se si a alguno de vosotros os habrá pasado, pero para mi instalar Visual Studio 2008 se ha convertido en una odisea y después de varios intentos he conseguido realizar una instalación satisfactoria.

Primer punto a tener en cuenta y, como suele ser habitual... antes de instalar un nuevo programa es recomendable desinstalar las betas o directamente el programa  de instalación te avisa de que antes de instalar una nueva versión tienes que instalar las betas que tengas en tu máquina.

Partimos de que mi sistema operativo es un Vista Business con VS2005, SQL Server 2005, Office 2007 entre otros y que antes de instalar Visual Studio 2008 tenia instalada la beta 2. VS2008 no va a ser distinto a cualquier otro programa y al intentar instalarlo sobre alguna pre-release os saldrá un cuadro de dialogo indicando que desinstaleis un determinado numero de componentes (ver imagen inferior). Existe una lista y un orden en el que desinstalar esos productos que podéis visitar aquí pero no todos los componentes que tendrás que desinstalar tienen que aparecer en esa lista, por ejemplo, los componentes que a mi me pedía desinstalar no aparecen en esa lista.

 

 

 

Puede ser que desinstales los componentes que os aparezcan en el cuadro de dialogo anterior y que al reiniciar la instalación os vuelva a pedir que desinstaleis otros componentes. De hecho al segundo intento me pidió desinstalar los siguientes hotfixes desde windows update: (KB110806), (KB930264) y (KB929300). Bueno... esto puede ser un poco tortuoso: intentar instalar, desinstalar componentes, volver a instalar... pero esto no es lo más frustrante, ¿qué ocurre cuando quieres desinstalar un componente que aparentemente no tienes instalado? te diriges a Panel de control -> Programas y características y ese componente no aparece en el listado de programas instalados. Por lo visto, esta herramienta te muestra como programas instalados los que quiere.
Una posible solución es irte al registro y borrar las entradas a mano, algo poco recomendable y otra opción es desinstalar esos componentes con la herramienta  Windows Installer Clean Up. Con esta herramienta podréis desinstalar todos componentes necesarios para poder instalar VS2008.

 

 



Una vez desinstalados todos los malditos componentes que me daban problemas pude iniciar la instalación de VS2008 pero otro problema que encontré fue que, a mitad de la instalación, ésta producía un error y terminaba. Lo que se me ocurrió fue desinstalar Office 2007 porque ya había oído que daba problemas cuando se intenta instalar algún programa teniéndolo instalado. Después de esto si, por fin tenia instalado VS en mi portátil como se puede ver en la imagen!!

 

 



Aunque tengo que decir... que no todo fueron alegrías. Al crear un nuevo proyecto, ya fuera web, wpf o windows form, VS producía un error y no cargaba el diseñador gráfico. La forma en que resolví este problema, y con este el ultimo, fue instalar las actualizaciones que podemos encontrar en el DVD de VS2008 en la carpeta: WCU\dotNetFramework\dotNetMSP\x86.

 

 

 

Con todo esto conseguí instalar VS2008 y tener un funcionamiento correcto, así que si alguno de vosotros instala VS2008 y tiene alguno de estos problemas, espero que os sean útiles los pasos que yo he seguido para solucionar los problemas que me han ido surgiendo.


Hace unos cuantos unos (bueno, unas semanas... que no encuentro tiempo para postear!!) necesitaba imprimir unos datos que se encontraban en un formulario y lo lógico sería usar reportes para hacerlo, pero por la falta de tiempo y por no haber trabajado reportes antes necesitaba una solución más rápida, entonces se me ocurrió imprimir el formulario y buscando por la red encontré este artículo en la knowledge base de Microsoft.  Pues bien, en primer lugar lo que hice fue pasar ese código a  C# y segundo hacer unas pequeñas variaciones al código para que la impresión del formulario se ajustara más a lo que deseaba. Para probarlo nos creamos un nuevo proyecto para una aplicación windows (en C#) y añadimos la siguiente clase al proyecto (extraída del articulo anterior):

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Drawing.Printing;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Imaging;

using System.Runtime.InteropServices;

 

public class Win32APICall

{

    public const int DIB_RGB_COLORS = 0;

    public const int BI_RGB = 0;

    public const int WHITENESS = 16711778;

 

    [DllImport("user32.dll", EntryPoint = "PrintWindow", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern UInt32 PrintWindow(IntPtr hWnd, IntPtr hDC, int dwFlags);

 

    [DllImport("gdi32.dll", EntryPoint = "CreateDIBSection", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFOHEADER pbmi,

        Int32 iUsage, IntPtr ppvBits, IntPtr hSection, Int32 dwOffset);

 

    [DllImport("gdi32.dll", EntryPoint = "PatBlt", SetLastError = true, CharSet =

        CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern bool PatBlt(IntPtr hDC, Int32 nXLeft, Int32 nYLeft, Int32 nWidth,

        Int32 nHeight, Int32 dwRop);

 

    [DllImport("gdi32.dll", EntryPoint = "SelectObject", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObj);

 

    [DllImport("GDI32.dll", EntryPoint = "CreateCompatibleDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr CreateCompatibleDC(IntPtr hRefDC);

 

    [DllImport("GDI32.dll", EntryPoint = "DeleteDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern bool DeleteDC(IntPtr hDC);

 

    [DllImport("GDI32.dll", EntryPoint = "DeleteObject", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern bool DeleteObject(IntPtr hObj);

 

    [DllImport("User32.dll", EntryPoint = "ReleaseDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

 

    [DllImport("User32.dll", EntryPoint = "GetDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr GetDC(IntPtr hWnd);

   

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

    public struct BITMAPINFOHEADER

    {

        public Int32 biSize;

        public Int32 biWidth;

        public Int32 biHeight;

        public Int16 biPlanes;

        public Int16 biBitCount;

        public Int32 biCompression;

        public Int32 biSizeImage;

        public Int32 biXPelsPerMeter;

        public Int32 biYPelsPerMeter;

        public Int32 biClrUsed;

        public Int32 biClrImportant;

    }

}

 
 

Ahora diseñamos el formulario que queremos imprimir y le añadimos un objeto PrintDocument printDocument1 y PrintDialog printDialog1, nos puede quedar algo como esto, un formulario que muestre información sobre libros (si, ya se que es feo, pero para el propósito que tiene es suficiente):

 

 

Después tendríamos que controlar el evento click del botón imprimir para que cuando pulsemos en el boton se imprima el formulario y a parte, tenemos que añadir a nuestro código el método que se encargará de imprimir el formulario (también extraído del articulo del que he hablado más arriba) llamado OnPrintPage:

 

private void buttonImprimir_Click(object sender, EventArgs e)

{

    printDialog1.Document = printDocument1;

    if ((printDialog1.ShowDialog() == DialogResult.OK))

    {

        printDocument1.PrintPage += new PrintPageEventHandler(this.OnPrintPage);

        printDocument1.Print();

    }

}
 

private void OnPrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)

{

    IntPtr hwndForm;

    hwndForm = this.Handle;

    IntPtr hdcDIBSection;

    IntPtr hdcRef;

    IntPtr hbmDIBSection;

    IntPtr hbmDIBSectionOld;

    Win32APICall.BITMAPINFOHEADER BMPheader;

    hdcRef = Win32APICall.GetDC(IntPtr.Zero);

    hdcDIBSection = Win32APICall.CreateCompatibleDC(hdcRef);

    Win32APICall.ReleaseDC(IntPtr.Zero, hdcRef);

    BMPheader.biBitCount = 24;

    BMPheader.biClrImportant = 0;

    BMPheader.biClrUsed = 0;

    BMPheader.biCompression = Win32APICall.BI_RGB;

    BMPheader.biSize = 40;

    BMPheader.biHeight = this.Height;

    BMPheader.biPlanes = 1;

    BMPheader.biSizeImage = 0;

    BMPheader.biWidth = this.Width;

    BMPheader.biXPelsPerMeter = 0;

    BMPheader.biYPelsPerMeter = 0;

    hbmDIBSection = Win32APICall.CreateDIBSection(hdcDIBSection, ref BMPheader,

        Win32APICall.DIB_RGB_COLORS, IntPtr.Zero, IntPtr.Zero, 0);

    hbmDIBSectionOld = Win32APICall.SelectObject(hdcDIBSection, hbmDIBSection);

    Win32APICall.PatBlt(hdcDIBSection, 0, 0, this.Width, this.Height,

        Win32APICall.WHITENESS);

    Win32APICall.PrintWindow(hwndForm, hdcDIBSection, 0);

    Win32APICall.SelectObject(hdcDIBSection, hbmDIBSectionOld);

    Bitmap imageFrm;

    imageFrm = Image.FromHbitmap(hbmDIBSection);

    e.Graphics.DrawImage(imageFrm, 0, 0);

    Win32APICall.DeleteDC(hdcDIBSection);

    Win32APICall.DeleteObject(hbmDIBSection);

}

 

Bien con esto ya podemos imprimir nuestro formulario, sencillo, pero vamos a ir un poquito más allá, la verdad es que imprimir el formulario así tal cual puede quedar un poquito feo, entonces lo que podemos hacer es modificar la apariencia del formulario para que se imprima lo que nosotros queramos que se vea. Para eso solo tenemos que cambiar la apariencia antes de imprimir el formulario y después de realizar la impresión devolver al formulario su apariencia original. Para el formulario que hemos creado podríamos, por ejemplo, ocultar el botón de impresión, el marco de la ventana, eliminar el borde de los textboxs y cambiar el color de fondo. El código final para el método buttonImprimir_Click sería:

 

private void buttonImprimir_Click(object sender, EventArgs e)

{

    Color colorFondo = this.BackColor;

    try

    {

        printDialog1.Document = printDocument1;

        if ((printDialog1.ShowDialog() == DialogResult.OK))

        {

            // ocultamos el borde del formulario

            this.FormBorderStyle = FormBorderStyle.None;

            //cambiamos el color de fondo del formulario

            this.BackColor = Color.White;

            //quitamos el borde a los texbox

            textBox1.BorderStyle = BorderStyle.None;

            textBox2.BorderStyle = BorderStyle.None;

            textBox3.BorderStyle = BorderStyle.None;

            textBox4.BorderStyle = BorderStyle.None;

            textBox5.BorderStyle = BorderStyle.None;

            textBox6.BorderStyle = BorderStyle.None;

            //ocultamos el boton de imprimir

            buttonImprimir.Visible = false;

            printDocument1.PrintPage += new PrintPageEventHandler(this.OnPrintPage);

            printDocument1.Print();

        }

    }

    catch (Exception)

    {

        MessageBox.Show("Error al imprimir", "Error",

            MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

    finally

    {

        //restauramos las propiedades de los controles

        this.FormBorderStyle = FormBorderStyle.Sizable;

        this.BackColor = colorFondo;

        textBox1.BorderStyle = BorderStyle.FixedSingle;

        textBox2.BorderStyle = BorderStyle.FixedSingle;

        textBox3.BorderStyle = BorderStyle.FixedSingle;

        textBox4.BorderStyle = BorderStyle.FixedSingle;

        textBox5.BorderStyle = BorderStyle.FixedSingle;

        textBox6.BorderStyle = BorderStyle.FixedSingle;

        buttonImprimir.Visible = true;

        //refrescamos el form

        this.Refresh();

    }

}

 

Y por si quereis trastear con el código os lo dejo adjunto más abajo, un saludo!!!

 

Siguiendo en la linea del post anterior, vamos a comentar dos de los puntos que fueron mencionados, los problemas con operaciones aritméticas y de comparación que, aunque de conocimiento básico no viene nada mal recordar.
 

- Operaciones aritméticas. Supongamos que tenemos un determinado objeto, como pueda ser una imagen, que tiene dimensiones (ancho y alto) y que queremos redimensionarla de tal forma que necesitamos obtener los porcentajes en que se redimensionan para el ancho (width) y alto (height), pues basta con dividir las nuevas dimensiones por las originales:

 

//valores de ejemplo

int widthOrigen = 2698;

int heightOrigen = 1780;

int width = 1024;

int height = 768;

 

float porcentajeW, porcentajeH;

 

porcentajeW = (width / widthOrigen);

porcentajeH = (height / heightOrigen);

 

pero este código no es del todo correcto porque lo que se produce es una división de enteros, por lo que el resultado será un entero aunque luego lo almacenemos en un float, para que el resultado de la división sea correcto bastaría con hacer un casting de uno de los operandos, posteriormente el compilador "promociona" el valor de menor precisión al del tipo de mayor precisión, es decir, de int a float:

 

porcentajeW = (width / (float)widthOrigen);

porcentajeH = (height / (float)heightOrigen);

 

- Operaciones de comparación. Esto es algo básico en la programación orientada a objetos, si intentamos comparar dos objetos con el operador de comparación (==) pues no obtendremos el resultado deseado porque lo que hacemos es comparar sus referencias a memoria, solo es válido para los tipos básicos(int, float, string...). Para comparar objetos podemos y/o debemos usar el método Equals(object obj), pero no basta con hacer una llamada a este método, pues obtendríamos el mismo resultado que al usar el operador de comparación. Este método pertenece a la clase object y debe ser sobrescrito en nuestras clases para que compare dos objetos de forma correcta. Supongamos que tenemos una clase llamada Complejo como la siguiente:

 

public class Complejo

{

    public int a;

    public int b;

 

    public Complejo(int a, int b)

    {

        this.a = a;

        this.b = b;

    }

 

    //...

 

    public override bool Equals(object obj)

    {

        try

        {

            Complejo aux = (Complejo)obj;

 

            if (this.a == aux.a)

                if (this.b == aux.b)

                    return true;

        }

        catch (InvalidCastException)

        {

            // es un objeto de otro tipo

        }

 

        return false;

    } 

}
 

Podemos destacar el método Equals(object obj), que no tiene ninguna complejidad, el cual se encarga de sobrescribir la forma predeterminada de comparar dos objetos de tipo Complejo. Al compilar, obtendremos una advertencia por no sobrescribir el método GetHashCode() así que o lo sobrescribís o pasáis de la advertencia. Por último y para ver la diferencia entre una simple comparación y el método Equals() lo mejor es verlo con un ejemplo:

 

 

Complejo c1 = new Complejo(2, 5);

Complejo c2 = new Complejo(2, 5);

Complejo c3 = c1;

 

Console.WriteLine("c1 = c2: " + (c1 == c2));

Console.WriteLine("c1 = c3: " + (c1 == c3));

Console.WriteLine("c2 = c3: " + (c2 == c3));

 

Console.WriteLine("c1.Equals(c2): " + c1.Equals(c2));

Console.WriteLine("c1.Equals(c3): " + c1.Equals(c3));

Console.WriteLine("c2.Equals(c3): " + c2.Equals(c3));

 

c3.a = 7;

 

Console.WriteLine("c1 = c2: " + (c1 == c2));

Console.WriteLine("c1 = c3: " + (c1 == c3));

Console.WriteLine("c2 = c3: " + (c2 == c3));

 

Console.WriteLine("c1.Equals(c2): " + c1.Equals(c2));

Console.WriteLine("c1.Equals(c3): " + c1.Equals(c3));

Console.WriteLine("c2.Equals(c3): " + c2.Equals(c3));

 
 

¿Cuál es el resultado?

 

Muchas veces cometemos errores que se podrían evitar simplemente prestando un poquito más de atención. Aquí van una serie de errores que podemos cometer con cierta asiduidad, desde mi punto de vista, y que podríamos minimizar con un poco de atención:

- Copy/paste: es muy habitual copiar partes de código que pueden repetirse, por ejemplo, de un método a otro y al que  después le realizamos alguna pequeña modificación. Sobre esta base, empezamos a modificar dicho código y de una idea vamos a otra que a su vez nos conduce a otra, que hace que nos olvidemos de la primera idea y de quitar esas linea de código que esta vez nos resultan inútiles. Hay estudios que afirman que las personas no pueden realizar más de 7 tareas a la vez, ¿será verdad?

- Aritmética: las prisas, que no son nada buenas pueden llevarnos a cometer errores en las operaciones aritméticas  pues podemos pasar por alto realizar algún casting necesario con el que sin él, obtendríamos un resultado diferente al esperado.

- Caracteres especiales: hay que tener especial cuidado al escribir código que haga uso de caracteres especiales. Estas escribiendo una capa de acceso a datos para una base de datos cuando haces un ... LIKE '*algo*'... y la consulta select no devuelve los datos esperados... ¿estás seguro de que ese carácter especial para indicar una cadena de uno o más caracteres es *?¿y si es %, o cualquier otro? no está de más dar un vistazo rápido a la referencia de la bbdd con la que se está trabajando.

- Comparación de objetos: no es lo mismo hacer una comparación ( == ) que hacer un objecet.Equals(otroObject), el  resultado producido puede no ser el deseado de nuevo.

- Error de sintaxis: posiblemente con mayor incidencia en C# o Java que en VB puesto que éste último no es Case Sensitive, ¿a quién no se le olvida poner una coma? ¿o poner una palabra en minúscula cuando su primera letra es mayúscula? escribir una palabra de forma correcta... por el amor de Dios!! si llevamos ¿20? ¿30? ¿40...? años escribiendo y hablando nuestra lengua materna, como es el castellano en mi caso, y seguimos cometiendo faltas de ortografía... cómo no las vamos a cometer cuando pikamos código!!!


Éstos son algunos errores habituales (tomados con algo de humor) a la hora de programar, si ahora mismo os viene alguno más a la mente dejarlo caer por aquí y... ¡¡cuidado al pikar código!!!

 

 

 

website statistics

Haciendo referencia al post que ha publicado Thempra hoy mismo, os dejo una serie de enlaces con recursos para Windows Server 2008 de los que he sabido no hace mucho, espero que os sean útlies:

 http://blogs.technet.com/davidcervigon/archive/2007/09/27/cursos-online-y-libros-gratuitos-de-windows-server-2008.aspx 

http://blogs.technet.com/davidcervigon/archive/2007/09/25/como-instalar-windows-server-virtualization-en-la-rc0-de-windows-server-2008.aspx

http://blogs.technet.com/davidcervigon/archive/2007/09/21/como-conseguir-betas-y-productos-de-evaluacion.aspx

 

Más envíos Página siguiente >