Go to content Go to navigation Go to search

C# – Cuando la precisión que da el StopWatch no es suficiente…

February 2nd, 2010 by JuanK

Medir tiempo en nanosegundos

Hola!

He observado que es muy frecuente cuando alguien quiere hacer una prueba de rendimiento (sobre todo a nivel académico) que la resolución que da el objeto StopWatch (System.Diagnostics.Stopwatch) de Milisegundos resulta no ser siempre suficiente.

En esos casos lo mejor es recurrir a las funciones de la API para crear algo más de acuerdo a nuestras necesidades, de tal forma que podamos medir el tiempo transcurrido con una precisión mayor a la que da  – por alguna razón – el objeto StopWatch, así que creare algo sencillo que permitirá lograr la precisión deseada, pero primero – como siempre –algo de teoría al respecto.

 

CÓMO CALCULAR EL TIEMPO

Para calcular el tiempo dentro de un computador debemos valernos de la información que nos brinda el procesador, como todos sabemos el procesador posee un atributo llamado frecuencia, el cual nos indica cuantos ciclos de reloj realiza el procesador cada segundo. De esta forma encontramos que hay procesadores 1 Ghz (un millon de ciclos de reloj por segundo) y hay de muchos diferentes valores.

Por otro lado un procesador posee un contador de ciclos es decir un registro el cual informa de cuantos ciclos ha procesado.

Así que tenemos dos fuentes de información que utilizaremos para calcular el tiempo transcurrido ya que si dividimos la cantidad de ciclos que han pasado de un momento a otro entre la frecuencia, obtendremos el tiempo transcurrido con una precisión bastante grande (double).

 

Tenemos:

Frecuencia= ciclos por segundo

Ticks= ciclos procesados de un instante a otro

Tiempo = Ticks / Frecuencia (   ciclos / ciclos por segundo  )

De tal forma que las unidades resultantes serán: segundos.

 

Que!!! segundos? si pero esos segundos están expresados con una gran precisión decimal por lo cual podemos llegar a la precisión de nanosegundos tan solo multiplicando por 1.000‘000.000 (mil millones), y con un tipo de dato double tenemos espacio mas que suficiente para manejar estas cifras.

 

DE DONDE OBTENEMOS LOS DATOS?

Para ello utilizaremos dos funciones de la API de Windows:

  • QueryPerformanceCounter: Retorna el valor almacenado en el registro contador de ciclos del procesador en un momento dado
  • QueryPerformanceFrequency: Retorna la velocidad del procesador

 

Como ven ya esta todo lo necesario, ahora la implementación.

 

IMPLEMENTACIÓN

Lo primero es poder hacer uso de las funciones API  para lo cual nos ayudaremos con DllImport, ya saben la mejor fuente para saber como hacer declaraciones de la API de manera rápida es: http://www.pinvoke.net/. Todo esto lo hare dentro de la clase NanoTemporizador

using System;
using System.Runtime.InteropServices;
 
public class Temporizador
{
    /// <summary>
    /// Obtiene la frecuencia del procesador 
    /// </summary>
    /// <param name="frequency" />variable donde retorna la frecuencia
    /// <returns>True si el procesador tiene contador de frecuencia, false sino</returns>
    [DllImport"kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceFrequency(out long frequency);
 
    /// <summary>
    /// Obtiene l evalor actual del contador de alto rendimiento del ptrocesador
    /// </summary>
    /// <param name="lpPerformanceCount" />variable donde retorna el valor
    /// <returns>True si todo salio OK, false sino</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
}

Ahora en el constructor de nuestra clase no haremos nada :) . Vale la pena recordar que siempre se deben crear componentes eficientes – según yo :P – por lo que es mejor que tengamos un constructor estático ya que realmente la frecuencia del procesador no cambiara nunca, así que solo es necesario calcularla una sola vez para todas las instancias.

/// <summary>Almacena la frecuencia del contador de alto rendimiento</summary>
    private static long _frecuencia;
 
    static NanoTemporizador()
    {
        if (!QueryPerformanceFrequency(out _frecuencia))
            throw new NullReferenceException(
                "Este componente se hizo para utilizar contadores de alto rendimiento. Como no los hay mejor utiliza StopWatch"
             );
    }

Para que funcione realmente como un contador de tiempo necesitamos poder establecer si el contador esta andando o no, para lo cual crearemos una propiedad. Adicionalmente en el método Start del temporizador vams a calcular el valor de contador actual y a cambiar el valor de nuestro indicador a true:

/// <summary>Almacena el valor de conteo inicial</summary>
    private long _conteoInicial;
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    private bool _isRunning = false;
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    public bool IsRunning { get { return _isRunning; } }
 
    public void Start()
    {
        if (!_isRunning)
        {
            QueryPerformanceCounter(out _conteoInicial);
            _isRunning = true;
        }
    }

De igual forma se establece el método Stop:

/// <summary>Almacena el valor de conteo final</summary>
    private long _conteoFinal;
 
    public void Stop()
    {
        if (_isRunning)
        {
            QueryPerformanceCounter(out _conteoFinal);
            _isRunning = false;
        }
    }

Finalmente se crea una propiedad a travez de la cual podamos hallar el valor en nanosegundos:

///<summary>Retorna la cantidad de nanosegundos contados</summary>
    public double ElapsedNanoseconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) * 1000000000L
                   / (double)_frecuencia;
        }
    }

 

Perfecto, eso es todo. Esta es la versión completa:

using System;
using System.Runtime.InteropServices;
 
public class NanoTemporizador
{
    /// <summary>
    /// Obtiene la frecuencia del procesador 
    /// </summary>
    /// <param name="frequency">variable donde retorna la frecuencia</param>
    /// <returns>True si el procesador tiene contador de frecuencia, false sino</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceFrequency(out long frequency);
 
    /// <summary>
    /// Obtiene l evalor actual del contador de alto rendimiento del ptrocesador
    /// </summary>
    /// <param name="lpPerformanceCount">variable donde retorna el valor</param>
    /// <returns>True si todo salio OK, false sino</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
 
    /// <summary>Almacena la frecuencia del contador de alto rendimiento</summary>
    private static long _frecuencia;
    
    /// <summary>Almacena el valor de conteo inicial</summary>
    private long _conteoInicial;
    /// <summary>Almacena el valor de conteo final</summary>
    private long _conteoFinal;
 
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    private bool _isRunning = false;
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    public bool IsRunning { get { return _isRunning; } }
 
    /// <summary>Valor por el cual se multiplican segundos para pasarlos a nanosegundos</summary>
    private const long NANOSEGUNDOS = 1000000000L;
 
    /// <summary>Valor por el cual se multiplican segundos para pasarlos a milisegundos</summary>
    private const long MILISEGUNDOS = 1000L;
    
    static NanoTemporizador()
    {
        if (!QueryPerformanceFrequency(out _frecuencia))
            throw new NullReferenceException(
               "Este componente se hizo para utilizar contadores de alto rendimiento. Como no los hay mejor utiliza StopWatch."
            );
    }
 
    /// <summary>Inicia el conteo del temporizador</summary>
    public void Start()
    {
        if (!_isRunning)
        {
            QueryPerformanceCounter(out _conteoInicial);
            _isRunning = true;
        }
    }
    
    /// <summary>Detiene el conteo del temporizador</summary>
    public void Stop()
    {
        if (_isRunning)
        {
            QueryPerformanceCounter(out _conteoFinal);
            _isRunning = false;
        }
    }
 
    ///<summary>Retorna la cantidad de nanosegundos contados</summary>
    public double ElapsedNanoseconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) * NANOSEGUNDOS
                   / (double)_frecuencia;
        }
    }
 
    ///<summary>Retorna la cantidad de milisegundos contados</summary>
    public double ElapsedMilliseconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) * MILISEGUNDOS
                   / (double)_frecuencia;
        }
    }
 
    ///<summary>Retorna la cantidad de segundos contados</summary>
    public double ElapsedSeconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) / (double)_frecuencia;
        }
    }
}

CÓMO USARLO?

Bien este es un ejemplo tontisimo, pero muy ilustrativo:

using System;
 
namespace Prueba
{
    class Program
    {
        static void Main(string[] args)
        {
            NanoTemporizador temporizador = new NanoTemporizador();
 
            Probador(temporizador, 1000);
            Probador(temporizador, 1000);
            Probador(temporizador, 5000);
            Probador(temporizador, 2358);
            Probador(temporizador, 3541);
            Probador(temporizador, 10000);
 
            Console.ReadLine();
        }
 
        private static void Probador(NanoTemporizador temporizador, int espera )
        {
            temporizador.Start();
            System.Threading.Thread.Sleep(espera);
            temporizador.Stop();
 
            Console.WriteLine("Tiempo transcurrido: {0} ns> ", temporizador.ElapsedNanoseconds);
            Console.WriteLine("Tiempo transcurrido: {0} ms> ", temporizador.ElapsedMilliseconds);
            Console.WriteLine("Tiempo transcurrido: {0} sg> ", temporizador.ElapsedSeconds);
            Console.WriteLine("=====================================================");
        }
    }
}

Hasta pronto.

Bookmark and Share

Originally posted 2009-07-26 14:11:55.

C# – Conseguir un Handler a la Ventana de un Proceso

February 2nd, 2010 by JuanK

Cuando se esta jugando con la API de Windows, especialmente con el tema de las ventanas esta función puede resultar de muchísima utilidad. Sin embargo no existe, así que hay que implementarla.

 

En resumen explicare los pasos necesarios para crear una función GetProcessWindowHandler, la cual devuelve un handler a la ventana principal de un proceso.

 

Necesitaremos recuperar el handler de la ventana (cuando la encontremos),así que creare una clase que  utilizare como LPARAM a algunas funciones de la API, capaz de contener tanto el id del proceso como el handler de la ventana.

 

/// <summary>Almacena el ID de proceso y el handler de una ventana</summary> 
    private class AuxInfo
    {
        public int processID;
        public IntPtr handler;
    }

 

Para lograrlo debemos recurrir a la función EnumWindows, la utilizaremos para recorrer las ventanas existentes en búsqueda de  una ventana cuyo id de proceso coincida con el proceso que acabamos de iniciar.

Como EnumWindows requiere como parámetro un delegado que se ejecutara para las ventanas enumeradas, entonces la función de búsqueda debe tener el signature de EnumWindowsProc, declarado en la API de Windows y que acá lo declararo como un delegado.

/// <summary>
    /// Delegado para hacer de callback
    /// </summary>
    /// <param name="hwnd" />handler de la ventana
    /// <param name="lParam" />paramétro con la informacion necesaria para el proceso
    /// <returns>Valor de retorno del proceso</returns>
    private delegate bool EnumWindowsProc(IntPtr hwnd, AuxInfo lParam);

Y acá la definición de EnumWindows

/// <summary>
    /// Recorre las ventanas y ejecuta un proceso para cada una de ellas
    /// </summary>
    /// <param name="lpEnumFunc" />Delegado con el proceso a utilizar para cada ventana
    /// <param name="lParam" />paramétro con la informacion necesaria para el proceso
    /// <returns>Retorna true si se recorren todas las ventanas, de lo contrario false o segun determine el usuario a trabes del callback</returns>
    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, AuxInfo lParam);

 

Para poder determinar el id de proceso de cada una de las ventanas enumeradas haré uso de GetWindowThreadProcessId:

/// <summary>
    /// Devuelve el ID del proceso al que pertenece el hilo de la ventana
    /// </summary>
    /// <param name="hwnd" />handler de la ventana
    /// <param name="lpdwProcessId" />ID del proceso (parámetro de salida)
    /// <returns>ID del Thread que creó la ventana</returns>
    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out int lpdwProcessId);

Ya con esta información mi función delegada para encontrar el handler de ventana ( la que se ejecutara por cada ventana hallada por EnumWindows )es esta:

/// <summary>
    /// Obtiene el handler de la ventana asociada a un proceso
    /// Este procedimiento es solo de utileria para usarse con EnumWindows 
    /// y no deberia ser invocado directamente
    /// </summary>
    /// <param name="hwnd" />handler de la ventana actual
    /// <param name="info" />informacion auxiliar para el proceso
    /// <returns>false si encuentra la ventana, true sino</returns>
    private static bool _GetProcessWindowHandler(IntPtr hwnd, AuxInfo info)
    {
        int processID;
        GetWindowThreadProcessId(hwnd, out processID);
 
        if (processID == info.processID)
        {
            info.handler = hwnd;
            return false;
        }
        else
        {
            info.handler = IntPtr.Zero;
            return true;
        }
    }

Estando ya definida mi función de callback entonces llamaré a EnumWindows y crearé con ella una función GetProcessWindowHandler, la cual como su nombre lo indica será la que usaré para devolver el handler de la ventana del proceso. Sin embargo el tema no es tan fácil como pareciera a simple vista.

 

Si utilizo la función para traer un handler a la ventana de un proceso ya abierto no tengo ningún problema, pero si el proceso recién lo estoy lanzando desde mi aplicación, por ejemplo con Process.Start(), se debe esperar a que el sistema operativo cree y muestre por primera vez la ventana, de lo contrario no habrá manera de hallarla con EnumWindows, así que debo llamar a EnumWindows hasta que se cumplan estas dos condiciones:

 

  1. Encontró una ventana asociada al proceso
  2. Dicha ventana ya ha sido mostrada por el sistema operativo

 

Para la primera condición, y de acuerdo a como creamos nuestra función de callback (_GetProcessWindowHandler), basta con preguntar si el handler es válido y para la segunda se debe determinar si la ventana de dicho proceso ya ha sido mostrada lo cual lo hacemos con IsWindowVisible:

/// <summary>
    /// Indica si una ventana es o no visible
    /// </summary>
    /// <param name="hWnd" />handler de la ventana
    /// <returns>Indicador de si la v entana es o no visible</returns>
    [DllImport("user32.dll")]
    private static extern bool IsWindowVisible(IntPtr hWnd);

 

Así que la función internamente debe tener un proceso iterativo para poder hallar el handler

 

/// <summary>
    /// Devuelve el handler de la ventana asociada al proceso
    /// </summary>
    /// <param name="pid" />Id del proceso
    /// <returns>handler de la ventana</returns>
    public static IntPtr GetProcessWindowHandler(int pid)
    {
        //Delegado con el proceso auxiliar de búsqueda
        EnumWindowsProc getHandlerVentana = new EnumWindowsProc(_GetProcessWindowHandler);
        //Informacion auxiliar
        AuxInfo informacion = new AuxInfo();
        informacion.processID = pid;
 
        /*Repetir bucle hasta que este presente la ventana del proceso
         *(puede que la enumeracion se realice y windows  aún no haya creado 
         *la primera ventana del proceso o bien no la haya hecho visible, 
         *por lo cual se debe repetir el bucle hasta encontrala)*/
        do
        {
            /*Enumerar las ventanas buscando la que coincida con
             *el id de proceso contenido en informacion */
            EnumWindows(getHandlerVentana, informacion);
        } while (informacion.handler == IntPtr.Zero || !IsWindowVisible(informacion.handler));
 
        return informacion.handler;
    }

Bien, he encapsulado la funcionalidad en la clase Win32APITools y el método GetProcessWindowHandler es el único método expuesto, asi que la implementación completa queda así:

using System;
using System.Runtime.InteropServices;
 
 
class Win32APITools
{
    /// <summary>Almacena el ID de proceso y el handler de una ventana</summary> 
    private class AuxInfo
    {
        public int processID;
        public IntPtr handler;
    }
 
    /// <summary>
    /// Delegado para hacer de callback
    /// </summary>
    /// <param name="hwnd" />handler de la ventana
    /// <param name="lParam" />paramétro con la informacion necesaria para el proceso
    /// <returns>Valor de retorno del proceso</returns>
    private delegate bool EnumWindowsProc(IntPtr hwnd, AuxInfo lParam);
 
    /// <summary>
    /// Recorre las ventanas y ejecuta un proceso para cada una de ellas
    /// </summary>
    /// <param name="lpEnumFunc" />Delegado con el proceso a utilizar para cada ventana
    /// <param name="lParam" />paramétro con la informacion necesaria para el proceso
    /// <returns>Retorna true si se recorren todas las ventanas, de lo contrario false o segun determine el usuario a trabes del callback</returns>
    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, AuxInfo lParam);
 
    /// <summary>
    /// Devuelve el ID del proceso al que pertenece el hilo de la ventana
    /// </summary>
    /// <param name="hwnd" />handler de la ventana
    /// <param name="lpdwProcessId" />ID del proceso (parámetro de salida)
    /// <returns>ID del Thread que creó la ventana</returns>
    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out int lpdwProcessId);
 
    /// <summary>
    /// Indica si una ventana es o no visible
    /// </summary>
    /// <param name="hWnd" />handler de la ventana
    /// <returns>Indicador de si la v entana es o no visible</returns>
    [DllImport("user32.dll")]
    private static extern bool IsWindowVisible(IntPtr hWnd);
 
    /// <summary>
    /// Obtiene el handler de la ventana asociada a un proceso
    /// Este procedimiento es solo de utileria para usarse con EnumWindows 
    /// y no deberia ser invocado directamente
    /// </summary>
    /// <param name="hwnd" />handler de la ventana actual
    /// <param name="info" />informacion auxiliar para el proceso
    /// <returns>false si encuentra la ventana, true sino</returns>
    private static bool _GetProcessWindowHandler(IntPtr hwnd, AuxInfo info)
    {
        int processID;
        GetWindowThreadProcessId(hwnd, out processID);
 
        if (processID == info.processID)
        {
            info.handler = hwnd;
            return false;
        }
        else
        {
            info.handler = IntPtr.Zero;
            return true;
        }
    }
 
    /// <summary>
    /// Devuelve el handler de la ventana asociada al proceso
    /// </summary>
    /// <param name="pid" />Id del proceso
    /// <returns>handler de la ventana</returns>
    public static IntPtr GetProcessWindowHandler(int pid)
    {
        //Delegado con el proceso auxiliar de búsqueda
        EnumWindowsProc getHandlerVentana = new EnumWindowsProc(_GetProcessWindowHandler);
        //Informacion auxiliar
        AuxInfo informacion = new AuxInfo();
        informacion.processID = pid;
 
        /*Repetir bucle hasta que este presente la ventana del proceso
         *(puede que la enumeracion se realice y windows  aún no haya creado 
         *la primera ventana del proceso o bien no la haya hecho visible, 
         *por lo cual se debe repetir el bucle hasta encontrala)*/
        do
        {
            /*Enumerar las ventanas buscando la que coincida con
             *el id de proceso contenido en informacion */
            EnumWindows(getHandlerVentana, informacion);
        } while (informacion.handler == IntPtr.Zero || !IsWindowVisible(informacion.handler));
 
        return informacion.handler;
    }
}

Y este es un ejemplo de uso:

using System;
using System.Diagnostics;
 
namespace GetProcessWindowHandler
{
    class Program
    {
        static void Main(string[] args)
        {
            Process proc = new Process();
            ProcessStartInfo psi = new ProcessStartInfo("calc.exe");
            proc.StartInfo = psi;
 
            proc.Start();
 
            IntPtr handler = Win32APITools.GetProcessWindowHandler(proc.Id);  
 
            Console.WriteLine("El Handler obtenido para la ventana de este proceso es: {0}", handler);
            Console.ReadLine();
        }
    }
}

Hasta Pronto.

Bookmark and Share

Originally posted 2009-10-03 21:03:31.

C# – El extraño caso de la ventana sin borde que no se deja maximizar ni minimizar

February 2nd, 2010 by JuanK

En algunas aplicaciones llega a ser necesario tener una ventana sin borde en algún momento, una ventana sin borde se logra estableciendo la propiedad FormBorderStyle = None en el diseñador de Windows Forms o a través de código:

 
this.FormBorderStyle = FormBorderStyle.None;

Hasta ahí todo esta bien y no hay ningún problema al respecto, hasta que nos damos cuenta que una ventana sin borde no se deja maximizar ni minimizar.

De esto tratare en este artículo, el porqué de esta situación y como solucionarlo.

 

Como sabe una ventana que se debe maximizar o minimizar?

El bucle de mensajes

Las ventanas – y los demás controles – funcionan gracias a un bucle de mensajes, todo lo que manejamos nosotros como eventos : click del mouse, mover, cerrar, cambiar tamaño, maximizar etc, realmente es controlado por un bucle en donde se envían diferentes mensajes a la ventana, esta a su vez tiene un procedimiento que recibe estos mensajes y con base a los mensajes recibidos puede hacer una u otra cosa según se programe.

 

Si, para algunos esto ya debe estar sonando a cuento, pero las cosas son así por debajo de lo que usamos tradicionalmente. El tema del artículo no es explicar como funciona un ciclo de mensajes así que por el momento lo dejaremos hasta allí y quien quiera profundizar puede consultar esta fuente en internet http://www.winprog.org/tutorial/message_loop.html

 

Por el momento lo que si nos interesa del bucle de mensajes es que algunos de esos mensajes se utilizan para maximizar y minimizar las ventanas, es decir cuando uno utiliza alguna funcionalidad para minimizar una ventana, lo que ocurre realmente es que se envía el mensaje que dice: hey! minimízate y ya el manejador de la ventana hará lo necesario para minimizarse.

El problema de la ventana sin borde.

Resulta que cuando se crea una ventana el sistema de ventanas se encarga de asignar ciertas características de acuerdo a sus parámetros de creación, una de esas características es incluir llamados a las funciones internas de Windows Forms que minimizan y maximizan ventanas, pero cuando se esta creando una ventana sin borde, al no tener esta los botones de minimizar o maximizar simplemente se pasa por alto la necesidad de incluir llamados a esas funciones.

Tan es así que las ventanas sin borde ni siquiera reciben mensajes relacionados con maximizar y minimizar desde la barra de tareas de Windows, esto lo podemos verificar así:

  • Crear una ventana con borde en el diseñador
  • Sobre escribir el método WndProc ( que es el que procesa la cadena de mensajes enviados a la ventana)
  • Interceptar el mensaje de minimizar la ventana y lanzar un MessageBox:

 

Lo que sucederá es que el mensaje se mostrará, pero si creamos desde un comienzo la ventana como ventana sin borde nos daremos cuenta que el mensaje nunca se lanza puesto que la ventana nunca recibe el mensaje indicando que se minimize, y aunque lo recibiera no haría nada.

 

Este es el código de como se debe dejar el WndProc en la forma para hacer las pruebas con y sin borde.

 
const int WM_SYSCOMMAND = 0x112;
const int SC_MINIMIZE = 0xF020;
 
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_SYSCOMMAND)
    {
        if (m.WParam == (IntPtr)SC_MINIMIZE)
            MessageBox.Show"Hacer lo que quieras en vez de minimizar");
 
        base.WndProc(ref m);
    }
    else
        base.WndProc(ref m);
}

Esto se convierte en un problema, pero como hacer para solucionarlo?

 

*Próximamente escribiré un breve artículo profundizando un poco más en la interceptación de mensajes y en el caso particular expuesto de minimizar la ventana.

Cambiar el comportamiento de la ventana

Como deshacernos de este problema y poder minimizar la ventana sin borde?

La respuesta esta escondida en mis líneas anteriores:

 

resulta que cuando se crea una ventana el sistema de ventanas se encarga de asignar ciertas características de acuerdo a sus parámetros de creación

Técnicamente hablando podríamos crear una ventana con borde y una vez inicializada cambiarle el estilo para que ahora estando sin borde se deje minimizar… ERROR eso no es posible porque apenas cambiamos el estilo de la ventana se llama una rutina que inicializa toda su estructura nuevamente… y al hacerlo elimina de nuevo la funcionalidad de minimizar y maximizar. :(

 

Para lograrlo hacer hay que hallar la forma de ‘engañar’ al sistema de ventanas del Windows Forms y hacerle creer que tiene una ventana con borde pero que realmente sea sin borde. Es decir debemos desde el comienzo crear una ventana con borde y luego volverla sin borde, PERO haciendo que el sistema de ventanas de Windows Forms no se entere, como es eso? con nuestra amiga la API de Windows haciendo llamados directamente al manejador de ventanas del sistema operativo sin pasar por Windows Forms.

 

Cuando una forma en Windows Forms es creada, esta inicializa todas sus estructuras de acuerdo a las propiedades establecidas y esto lo hace en un  método llamado CreateParams quien es el que internamente esta haciendo llamados a la API, bueno realmente es una propiedad, así que si reemplazamos  esta propiedad podemos hacer creer a Windows Forms que esta creando una ventana con bordes pero ya nos hemos encargado de quitarle dichos bordes “a la mala”.

Los pasos a seguir son los siguientes:

 

  1. Crear la forma con el estilo normal que incluye los botones minimizar y maximizar
  2. Sobre Escribir el método CreateParams
  3. Introducir modificaciones al estilo de la ventana  pero no utilizando las propiedades de Windows Forms sino modificando los parámetros con los cuales Windows Forms le pedirá al sistema operativo que cree la nueva ventana.

Todos los pasos son fáciles, el que es un poco críptico es el paso 3, así que lo analizare en más detalle.

CreateParams

Esta propiedad tiene a su vez su propia estructura, y parte de esa estructura es el campo Style de tipo int, cuando Windows esta inicializando la forma se revisa ese campo para determinar el estilo de la ventana, y de hecho cada vez que modificamos el estilo de la ventana esta recrea su apariencia modificando no solo el valor de Style sino también modificando comportamientos como ya lo hemos visto anteriormente. Sin embargo desde el propio manejador de ventanas de Windows cambiar el estilo no implica cambiar de una vez el comportamiento – como ya vimos que si sucede en Windows Forms  – así que podemos cambiar el valor de Style sin necesidad de cambiar nada más.

 

Como nuestra forma justo antes de comenzar el paso 3 ya esta lista para minimizarse y maximizarse, lo que haremos en el paso 3 será modificar la propiedad Style de CreateParams para suprimirle ‘la caja de titulo’ y ‘el borde de cambiar tamaño’, como hacemos esto si Style es un tipo int? pues lo haremos a través de mascaras como si fuera una enum en Windows Forms.  He definido WS_THICKFRAME nada más para preservar la definición inicial que se da en la API de Windows. El código quedaría así:

 

const int WS_CAPTION    = 0xC00000;
const int WS_THICKFRAME = 0x00040000;
const int WS_SIZEBOX    = WS_THICKFRAME;
protected override CreateParams CreateParams
{
  get
  {
      CreateParams p = base.CreateParams;
      p.Style &= ~(WS_CAPTION | WS_SIZEBOX);
      return p;
  }
}

Simplemente estamos tomando los valores originales de CreateParams los cuales incluyen una ventana con bordes, pero reasignamos la propiedad Style para dejarla como estaba pero quitándole el borde de Resize y la barra de titulo.

Y Listo!!!

Eso es todo, ahora la ventana no tiene bordes y adicionalmente recibe el mensaje de minimizar, es más podemos combinar este código con el que veíamos en la primera parte y veremos como ahora si podemos interceptar el mensaje de minimizar !! ;)

 

Para profundizar un poco más acerca de como modificar el comportamiento de las ventanas les recomiendo revisar este link:

http://juank.black-byte.com/c-modificar-boton-minimizar-maximizar/

 

Happy Learning!

Bookmark and Share

Originally posted 2009-08-22 12:38:51.

« Previous Entries