Go to content Go to navigation Go to search

Como cambiar el texto de los botones de un MessageBox – C#

June 13th, 2010 by JuanK

Por razones que aún no me son del todo claras, el .NET Framework no tiene textos localizables para los MessageBox, razón por la cual no siempre se muestran en el lenguaje que necesitamos, sino que se muestran en el lenguaje del Framework instalado.

 

Puede que nuestra aplicación este en español pero probablemente nuestros MessageBox siempre salgan con los labels de los botones en ingles:

image

Además no siempre los labels de los batones tienen textos convenientes para nuestras aplicación.

El Framework no nos ofrece ninguna manera de modificar dichos textos más allá de las opciones predeterminadas, ¿Qué podemos hacer?

No es una tarea tan sencilla como uno se imaginaria inicialmente, requiere del uso de la API de Windows y un poco de ingenio.

He creado una utilidad que te ahorrará mucho trabajo y podrás cambiar los textos de los botones de manera muy sencilla tal como te lo muestra el siguiente ejemplo:

MsgBoxUtil.HackMessageBox("SI","NO", "CANCELAR");
            MessageBox.Show("hola", "hola", MessageBoxButtons.YesNoCancel);
            
            MsgBoxUtil.HackMessageBox("REINTENTAR", "CANCELAR");
            MessageBox.Show("hola2", "hola2", MessageBoxButtons.RetryCancel);
            
            MsgBoxUtil.HackMessageBox("Descartar", "Reintentar", "Ignorar");
            MessageBox.Show("hola3", "hola3", MessageBoxButtons.AbortRetryIgnore);
            
            MsgBoxUtil.UnHackMessageBox();
 
            MessageBox.Show("Normal", "Normal", MessageBoxButtons.AbortRetryIgnore);

 

El código completo de la utilidad lo puedes descargar aquí: MsgBoxUtil.cs

 

Cómo se hace?

Bueno hay que hacer las cosas al derecho, primero lo primero.

Análisis

Planteamiento inicial

  • Para modificar los textos hay que encontrar la manera de llegar hasta los botones del MessageBox
  • Para llegar a los botones del MessageBox primero hay que llegar al MessageBox

 

Dado que el .NET Framework no nos permite manipular la ventana del MessageBox debemos recurrir a la API de Windows.

Para Windows, todo control que se dibuje en pantalla es una ventana ( si, así es), así que si tuviéramos una forma, esta es una ventana que contiene paneles, los panales son también ventanas que contienen por ejemplo GroupBox que a su vez son ventanas que contienen botones , que a su vez también son ventanas.

Con base a estos puntos entonces detallemos un poco más el planteamiento.

  • Se deben buscar las ventanas hijas de la forma actual
  • Cuando se encuentre la ventana del MessageBox se deben buscar los botones
  • Con base a la ventana del  MessageBox se deben buscar sus ventanas hijas
  • A las ventanas que sean botones se les debe cambiar el texto
  • Dado que el MessageBox es una clase estática, la podemos dar por creada desde un comienzo ( el CLR la creara a la primera referencia que se haga).

Referencias a la API de Windows

Dado que esta tarea requiere de la API de Windows, y de acuerdo a nuestro planteamiento detallado, estas son las funciones de la API que utilizaremos:

  • EnumThreadWindows: Ejecuta un proceso por cada una de las ventanas del hilo actual, según se le indique. Lo usaremos para encontrar cual es la ventana del MessageBox.

  • GetCurrentThreadId: Obtiene el id del Thread en ejecución.

  • GetClassName: Obtiene el nombre de la clase a partir de un handler. Se utilizara junto con EnumThreadWindows para determinar si la ventana procesada es un MessageBox, dado que se desconoce el nombre que tendrán las ventanas del MessageBox, se debe determinar utilizando el ClassName de las ventanas. Más adelante veremos como hallar el ClassName de un MessageBox.

  • EnumChildWindows: Con base al handler de una ventana recorre cada una de sus ventanas hijas y ejecuta un proceso. Solo enumera las ventanas de primer nivel. Lo usaremos para encontrar los botones del MessageBox.

  • SetWindowText: Establece el texto asociado a una ventana, lo usaremos para modificar el texto de los Botones.

 

Diseño

Tenemos una única clase, utilicemos algo sencillo que ilustre el proceso de manera general, con esto bastará.

Diseno

  • Iniciaremos el proceso con EnumThreadWindows al cual le pasaremos un delegado que internamente revisará cada ventana enumerada para determinar si es o no un MessageBox.

  • Sino es un MessageBox revisará la siguiente ventana enumerada hasta que lo encuentre. Sino encuentra es que no hay MessageBox.
  • Cuando encuentra el MessageBox verifica a través de sus ventanas hijas con EnumChildWindows al cual se se pasa un delegado que internamente revisará cada ventana enumerada para determinar si es o no un Botón.

  • Cuando encuentra que es un botón modificará el texto asociado.

 

Implementación

Lo primero que debemos hacer es preparar las funciones a las que accederemos a través de interoperabilidad:

public class MsgBoxUtil
{
    #region Interoperabilidad
    private delegate bool EnumWindowDelegate(IntPtr handler, IntPtr longPointer);
 
    [DllImport("user32.dll")]
    private static extern bool SetWindowText(IntPtr handler, string texto);
 
    [DllImport("user32.dll")]
    private static extern int GetClassName(IntPtr handler, StringBuilder nombre, int tamañoMaximo);
 
    [DllImport("user32.dll")]
    private static extern bool EnumChildWindows(IntPtr handler, EnumWindowDelegate callback, IntPtr longPointer);
 
    [DllImport("user32.dll")]
    private static extern bool EnumThreadWindows(int threadID, EnumWindowDelegate callback, IntPtr longPointer);
 
    [DllImport("kernel32.dll")]
    private static extern int GetCurrentThreadId();
 
    #endregion Interoperabilidad
}

 

No hay nada nuevo en lo que acabamos de ver, son las funciones que hemos mencionado desde el comienzo,  es de destacar únicamente EnumWindowDelegate que es un delegado (puntero a una función) que debemos usar cuando llamemos a EnumChildWindows y a EnumThreadWindows.

 

HackMessageBox

Acto seguido crearemos el método HackMessageBox, el cual recibe como parámetro las cadenas de texto que representan los labels de los botones del MessageBox. Esta función guarda una referencia estática a ese array de textos para poderlos usar en otros lugares de la clase, también se determina si hay o no un windows form y de ser así inicia el procesamiento llamando asíncronamente a EnumThreadWindow, sin embargo no llamaremos directamente a EnumThreadWindows porque requiere demasiados parámetros lo cual nos complica un poco el paso de parámetros en el llamado asíncrono (BeginInvoke), en su lugar llamaremos a BuscaMessageBox que es una función sin parámetros que llamará internamente a EnumThreadWindows con los parámetros necesarios.Dado que BeginInvoque requiere enviar un delegado, debemos definirlo previamente.

 

En el llamado a EnumThreadWindows le debemos pasar el ThreadId el cual obtenemos con un llamado a GetCurrentThreadId, el segundo parámetro es una función ProcesaMessageBoxEnForms que se ejecutará cada por cada ventana que se enumere, el tercer parámetro no lo necesitamos así que será Zero.

private static string[] textoBotones;
    private delegate void BuscarMsgBoxDelegate();
 
    public static void HackMessageBox(params string[] textoBotones)
    {
        MsgBoxUtil.textoBotones = textoBotones;
 
        if (Application.OpenForms.Count > 0)
            Application.OpenForms[0].BeginInvoke(new BuscarMsgBoxDelegate(BuscaMessageBox));
    }
 
    private static void BuscaMessageBox()
    {
        EnumThreadWindows(GetCurrentThreadId(), ProcesaMessageBoxEnForms, IntPtr.Zero);
    }

 

ProcesaMessageBoxEnForms

Revisemos ProcesaMessageBoxEnForms, como esta funcion es llamada por EnumThreadWindows, esta le pasa siempre como parámetro el handler de la ventana que se esta enumerando, así que con ese handler buscaremos el nombre de la clase de la ventana enumerada para así poder compararlo con el nombre de la clase de un MessageBox, sino es un MessageBox entonces se devuelve true y así EnumThreadWindows vuelve a llamar a ProcesaMessageBoxEnForms pero pasando como parámetro la siguiente ventana enumerada, el procedimiento se repite hasta que se logra hallar la ventana del MessageBox. En ese momento se establece la variable indiceTexto en 0, esta variable la utilizaremos para recorrer el array de textos de acuerdo a los botones que encontremos dentro del MessageBox.

Seguidamente llamamos a EnumChildWindows pasándole como parámetro el handle de la ventana que desde luego es el MessageBox que acabamos de encontrar, así que las ventanas que se enumeraran son las subventanas del MessageBox entre ellas… los botones. Como ya encontro el MessageBox y realizo el proceso de busqueda de los botones se retorna false, de tal forma que EnumThreadWindows no siga enumerando ventanas.

private static int indiceTexto;    
    private const string MBOX_CLASSNAME = "#32770";
    private const int STRING_BUILDER_CAPACITY = 256;
    
    private static bool ProcesaMessageBoxEnForms(IntPtr handler, IntPtr longPointer)
    {
        StringBuilder nombreClase = new StringBuilder(STRING_BUILDER_CAPACITY);
        GetClassName(handler, nombreClase, nombreClase.Capacity);
 
        if (nombreClase.ToString() != MBOX_CLASSNAME)
            return true;
        else
        {
            indiceTexto = 0;
            EnumChildWindows(handler, CambiaTextoBotonMessageBox, IntPtr.Zero);
            return false;
        }
    }

 

BREAK> Y de dónde saqué que MBOX_CLASSNAME = “#32770″?

Algunos, o espero que todos los que no sepan se estarán haciendo esa pregunta.

Hay que utilizar herramientas que nos permitan hacer ingeniería inversa, una de estas herramientas es Spy++ que viene incluida con Visual Studio, no haremos un curso completo de Spy++ pero veremos como usarlo para obtener el ClassName de un MessageBox.

  1. Abrir un MessageBox de un programa de .NET Framework
  2. Abrir Spy++
  3. Ahora en Spy++ presionaremos esta tecla Spy

  4. Esto hace que se despliegue el siguiente dialogo:Spy  2

  5. Allí damos click sostenido en la figura señalada en rojo y la arrastramos hasta la ventana del MessageBox, teniendo cuidado de no seleccionar los botones u otras figuras, solo el marco principal. Soltamos el click y damos OK.
  6. Se abre un cuadro de dialogo,vamos a la pestaña Class y allí esta!!! ClassName = #32770.

Spy  3

END BREAK>

CambiaTextoBotonMessageBox

Una vez se ha encontrado el MessageBox se hace el llamado a EnumChildWindows pasándole como parámetro el handler del MessageBox, el segundo parámetro es la función CambiaTextoBotonMessageBox, el tercer parámetro es Zero.

 

EnumChildWindows invoca a CambiaTextoBotonMessageBox por cada una de las ventanas enumeradas, internamente CambiaTextoBotonMessageBox revisa si la ventana procesada es de clase Button, si no lo es entonces se devuelve true para que EnumChildWindow envie la siguiente ventana a proceso, esto continua así hasta encontrar tantos botones como cadenas de texto se hallan enviado en HackMessageBox como parámetro, a cada uno de los botones encontrados se le asigna su cadena de texto en el orden correspondiente.

private const string BUTTON_CLASSNAME = "Button";
 
    private static bool CambiaTextoBotonMessageBox(IntPtr handler, IntPtr longPointer)
    {
        StringBuilder nombreClase = new StringBuilder(STRING_BUILDER_CAPACITY);
        GetClassName(handler, nombreClase, nombreClase.Capacity);
 
        if (nombreClase.ToString() == BUTTON_CLASSNAME && indiceTexto < textoBotones.Length)
        {
            SetWindowText(handler, textoBotones[indiceTexto]);
            indiceTexto++;
        }
        return true;
    }

Ejemplo de uso

Listo ya la funcionalidad esta terminada, pero solo para utilizarla dentro de Windows Forms, ya que si lo tratamos de usar por consola fallará. Sin embargo eso es fácil de solucionar y lo haremos en un próximo artículo. Esta es la manera correcta de utilizarlo.

MsgBoxUtil.HackMessageBox("SI","NO", "CANCELAR");
            MessageBox.Show("hola", "hola", MessageBoxButtons.YesNoCancel);
            
            MsgBoxUtil.HackMessageBox("REINTENTAR", "CANCELAR");
            MessageBox.Show("hola2", "hola2", MessageBoxButtons.RetryCancel);
            
            MsgBoxUtil.HackMessageBox("Descartar", "Reintentar", "Ignorar");
            MessageBox.Show("hola3", "hola3", MessageBoxButtons.AbortRetryIgnore);

 

Les dejo este MessageBox con Botones Personalizados:

MsgBoxUtil.HackMessageBox("Acepto", "Lo Pensare", "Olvidalo");
            MessageBox.Show("hola3", "hola3", MessageBoxButtons.AbortRetryIgnore);

 

MsgBox2

Reversar el MessageBox a su estado original también es posible de hacer, pero lo dejaremos para el próximo artículo, mientras pueden intentarlo si quieren.

 

Cordial saludo a Todos.

Bookmark and Share

Como abrir la puerta del cd rom desde C# y VB.NET

June 13th, 2010 by JuanK

Dando solución a una de las inquietudes más comunes de las personas en los foros he creado este post para mostrarles la funcionalidad de la clase ExpulsarCDRom para explicarles como se hizo.

 

CÓMO UTILIZAR ESTA CLASE?

Para utilizarla, las explicaciones sobran, este es un código de ejemplo que muestra como abrir todas las unidades de CDRom en el PC ,he utilizado linq y DriveInfo para obtener el listado de unidades de CDRom disponibles y seguidamente utilizo el método ExpulsarCDRom.Expulsar:

class Program
{
    static void Main(string[] args)
    {
        //Obtener la lista de dispositivos de CDRom   
        var CDRomDrives = from drive in System.IO.DriveInfo.GetDrives()
                          where drive.DriveType == System.IO.DriveType.CDRom
                          select drive;
 
        
        //A cada uno de ellos hacerlo abrir   
        foreach (DriveInfo cdRom in CDRomDrives)
            ExpulsarCDRom.Expulsar(cdRom.Name);             
    }
}

Esta es la implementación final de ExpulsarCDRom en C#

//Constantes usadas en la API 
/// <summary>Indica que se se hara lectura genérica del archvo</summary>
const uint GENERICREAD = 0x80000000;
/// <summary>Indica que se debe abrir un archivo existente, no crear uno nuevo</summary>
const uint OPENEXISTING = 3;
/// <summary>Comando enviado al dispositivo para abrir la puerta</summary>
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// <summary>Indica que la operaciónj no finalizo adecuadamente </summary>
const int INVALID_HANDLE = -1;
 
 
/// <summary>Puntero que se usara para apuntar al archivo (unidad) de CDRom</summary>
private static IntPtr fileHandle;
/// <summary>Indica el número de bytes leidos cmo rspuesta de un proceso</summary>
private static uint returnedBytes;
 
/// <summary>
/// esta función sirve para crear archivos pero también para abrir archivos existentes, 
/// así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema 
/// global de archivos así que podemos llegar a ella desde este medio
/// </summary>
/// <returns>Puntero que sirve como manejador del archivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
                                uint desiredAccess,
                                uint shareMode,
                                IntPtr attributes,
                                uint creationDisposition,
                                uint flagsAndAttributes,
                                IntPtr templateFile);
 
/// <summary>
/// Cierra un manejador a un objeto, 
/// en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile 
/// </summary>
/// <param name="driveHandle" />
/// <returns>Entero que indica la respuesta del proceso</returns>
[DllImport("kernel32", SetLastError = true)]
static extern int CloseHandle(IntPtr driveHandle);
 
 
/// <summary>
/// Nos permite enviar comandos de I/O a un dispositivo. 
/// </summary>
/// <returns>Indica si fue o no enviado el comando al dispositivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
                                    uint IoControlCode,
                                    IntPtr lpInBuffer,
                                    uint inBufferSize,
                                    IntPtr lpOutBuffer,
                                    uint outBufferSize,
                                    ref uint lpBytesReturned,
                                    IntPtr lpOverlapped);
 
/// <summary>   
/// Expulsa el drive de acuerdo a la letra asignada   
/// </summary>   
/// <param name="driveLetter" />Letra del drive   
public static void Expulsar(string driveLetter)
{
    //Modificar el nombre de la unidad de acuerdo a como lo entiende el 
    //sistema de archivos
    driveLetter = @"\\.\" + driveLetter.Substring(0, 2);
    try
    {
        //Crea el puntero al archivo (dispositivo)   
        fileHandle = CreateFile(driveLetter, GENERICREAD, 0,
                                IntPtr.Zero, OPENEXISTING,
                                0, IntPtr.Zero);
 
        //Si es una unidad valida   
        if (fileHandle.ToInt32() != INVALID_HANDLE)
        {
            //Intenta expulsar el dispositivo   
            DeviceIoControl(fileHandle, IOCTL_STORAGE_EJECT_MEDIA,
                            IntPtr.Zero, 0, IntPtr.Zero, 0,
                            ref returnedBytes, IntPtr.Zero);
        }
    }
    catch
    {
        //Sino lo pudo expulsar   
        throw new Exception(Marshal.GetLastWin32Error().ToString());
    }
    finally
    {
        //Asegurarse de siempre cerrar el puntero del archvo   
        CloseHandle(fileHandle);
        fileHandle = IntPtr.Zero;
    }
}
//Constantes usadas en la API 
/// <summary>Indica que se se hara lectura genérica del archvo</summary>
const uint GENERICREAD = 0x80000000;
/// <summary>Indica que se debe abrir un archivo existente, no crear uno nuevo</summary>
const uint OPENEXISTING = 3;
/// <summary>Comando enviado al dispositivo para abrir la puerta</summary>
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// <summary>Indica que la operaciónj no finalizo adecuadamente </summary>
const int INVALID_HANDLE = -1;
 
 
/// <summary>Puntero que se usara para apuntar al archivo (unidad) de CDRom</summary>
private static IntPtr fileHandle;
/// <summary>Indica el número de bytes leidos cmo rspuesta de un proceso</summary>
private static uint returnedBytes;
 
/// <summary>
/// esta función sirve para crear archivos pero también para abrir archivos existentes, 
/// así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema 
/// global de archivos así que podemos llegar a ella desde este medio
/// </summary>
/// <returns>Puntero que sirve como manejador del archivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
                                uint desiredAccess,
                                uint shareMode,
                                IntPtr attributes,
                                uint creationDisposition,
                                uint flagsAndAttributes,
                                IntPtr templateFile);
 
/// <summary>
/// Cierra un manejador a un objeto, 
/// en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile 
/// </summary>
/// <param name="driveHandle" />
/// <returns>Entero que indica la respuesta del proceso</returns>
[DllImport("kernel32", SetLastError = true)]
static extern int CloseHandle(IntPtr driveHandle);
 
 
/// <summary>
/// Nos permite enviar comandos de I/O a un dispositivo. 
/// </summary>
/// <returns>Indica si fue o no enviado el comando al dispositivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
                                    uint IoControlCode,
                                    IntPtr lpInBuffer,
                                    uint inBufferSize,
                                    IntPtr lpOutBuffer,
                                    uint outBufferSize,
                                    ref uint lpBytesReturned,
                                    IntPtr lpOverlapped);
 
/// <summary>   
/// Expulsa el drive de acuerdo a la letra asignada   
/// </summary>   
/// <param name="driveLetter" />Letra del drive   
public static void Expulsar(string driveLetter)
{
    //Modificar el nombre de la unidad de acuerdo a como lo entiende el 
    //sistema de archivos
    driveLetter = @"\\.\" + driveLetter.Substring(0, 2);
    try
    {
        //Crea el puntero al archivo (dispositivo)   
        fileHandle = CreateFile(driveLetter, GENERICREAD, 0,
                                IntPtr.Zero, OPENEXISTING,
                                0, IntPtr.Zero);
 
        //Si es una unidad valida   
        if (fileHandle.ToInt32() != INVALID_HANDLE)
        {
            //Intenta expulsar el dispositivo   
            DeviceIoControl(fileHandle, IOCTL_STORAGE_EJECT_MEDIA,
                            IntPtr.Zero, 0, IntPtr.Zero, 0,
                            ref returnedBytes, IntPtr.Zero);
        }
    }
    catch
    {
        //Sino lo pudo expulsar   
        throw new Exception(Marshal.GetLastWin32Error().ToString());
    }
    finally
    {
        //Asegurarse de siempre cerrar el puntero del archvo   
        CloseHandle(fileHandle);
        fileHandle = IntPtr.Zero;
    }
}

Para Acceder a la versión en VB.NET pueden hacer la conversión del código anterior en este link:
http://www.developerfusion.com/tools/convert/csharp-to-vb/

 

COMO SE HACE?

Hay que utilizar la API de Windows

Si, sucede que abrir la puerta de la unidad de CDRom es una actividad altamente dependiente del sistema operativo pues requiere acceder al dispositivo y esto en cada sistema operativo se hace de manera diferente incluso puede ser diferente de un dispositivo a otro por lo que utilizar la API del sistema operativo nos cae bastante bien para no caer en complejidades exageradas.

Qué cosas hay que usar de la API?

Para poder abrir la unidad de CD necesitamos lo siguiente

  • CreateFile = esta función sirve para crear archivos pero también para abrir archivos existentes, así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema global de archivos así que podemos llegar a ella desde este medio.
  • CloseHandle = Cierra un manejador a un objeto, en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile .
  • DeviceIoControl = Nos permite enviar comandos de I/O a un dispositivo.

 

Para poder utilizar estos llamados a la API de Windows desde C# o VB necesitamos utilizar el atributo DLLImport que se encuentra definido en System.Runtime.InteropServices y que nos sirve para importar librerías creadas en código nativo.

 

Sin embargo no es tan sencillo como usar las funciones y ya, así que sino tienes experiencia en el uso de componentes nativos, puedes pegarte una pasada en internet para entender todo de manera más completa.

 

Por el momento basta con decir que debemos definir los tipos de dato y constantes que se utilizan en la API para así hacer un uso correcto de las funciones importadas.

 

Cómo funciona / Cómo Crearla.

Creamos una clase ExpulsarCDRom en la cual importaremos las funciones necesarias de la API de Windows:

class ExpulsarCDRom
{
 
    /// <summary>
    /// esta función sirve para crear archivos pero también para abrir archivos existentes, 
    /// así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema 
    /// global de archivos así que podemos llegar a ella desde este medio
    /// </summary>
    /// <returns>Puntero que sirve como manejador del archivo</returns>
    [DllImport("kernel32", SetLastError = true)]
    static extern IntPtr CreateFile(string fileName,
                                    uint desiredAccess,
                                    uint shareMode,
                                    IntPtr attributes,
                                    uint creationDisposition,
                                    uint flagsAndAttributes,
                                    IntPtr templateFile);
 
    /// <summary>
    /// Cierra un manejador a un objeto, 
    /// en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile 
    /// </summary>
    /// <param name="driveHandle" />
    /// <returns>Entero que indica la respuesta del proceso</returns>
    [DllImport("kernel32", SetLastError = true)]
    static extern int CloseHandle(IntPtr driveHandle);
 
    
    /// <summary>
    /// Nos permite enviar comandos de I/O a un dispositivo. 
    /// </summary>
    /// <returns>Indica si fue o no enviado el comando al dispositivo</returns>
    [DllImport("kernel32", SetLastError = true)]
    static extern bool DeviceIoControl(IntPtr driveHandle,
                                        uint IoControlCode,
                                        IntPtr lpInBuffer,
                                        uint inBufferSize,
                                        IntPtr lpOutBuffer,
                                        uint outBufferSize,
                                        ref uint lpBytesReturned,
                                        IntPtr lpOverlapped);
 
}

 

Una vez hecho esto, continuamos creando las constantes que necesitamos para llamar las funciones y desde luego las variables que nos permitirán controlar la respuesta de cada una de ellas, así que agregamos:

class ExpulsarCDRom
{
 
    //Constantes usadas en la API 
    /// <summary>Indica que se se hara lectura genérica del archvo</summary>
    const uint GENERICREAD = 0x80000000;
    /// <summary>Indica que se debe abrir un archivo existente, no crear uno nuevo</summary>
    const uint OPENEXISTING = 3;
    /// <summary>Comando enviado al dispositivo para abrir la puerta</summary>
    const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
    /// <summary>Indica que la operaciónj no finalizo adecuadamente </summary>
    const int INVALID_HANDLE = -1;
 
    /// <summary>Puntero que se usara para apuntar al archivo (unidad) de CDRom</summary>
    private static IntPtr fileHandle;
    /// <summary>Indica el número de bytes leidos cmo rspuesta de un proceso</summary>
    private static uint returnedBytes;   
 
...
...

 

Tenemos los preparativos para comenzar a codificar, creamos una función estática llamada ExpulsarUnidad la cual vemos a continuación y explicamos más abajo:

/// <summary>   
/// Expulsa el drive de acuerdo a la letra asignada   
/// </summary>   
/// <param name="driveLetter" />Letra del drive   
public static void Expulsar(string driveLetter)   
{   
    //1. Modificar el nombre de la unidad de acuerdo a como lo entiende el 
    //sistema de archivos
    driveLetter = @"\\.\" + driveLetter.Substring(0, 2);   
    try  
    {   
        //2.Crea el puntero al archivo (dispositivo)   
        fileHandle = CreateFile(driveLetter, GENERICREAD, 0, 
                                IntPtr.Zero, OPENEXISTING,
                                0,IntPtr.Zero);   
 
        //3.Si es una unidad valida   
        if (fileHandle.ToInt32() != INVALID_HANDLE)   
        {   
            //4.Intenta expulsar el dispositivo   
            DeviceIoControl(fileHandle, IOCTL_STORAGE_EJECT_MEDIA,   
                            IntPtr.Zero, 0, IntPtr.Zero, 0,   
                            ref returnedBytes,IntPtr.Zero);   
        }   
    }   
    catch  
    {   
        //5.Sino lo pudo expulsar   
        throw new Exception(Marshal.GetLastWin32Error().ToString());   
    }   
    finally  
    {   
        //6.Asegurarse de siempre cerrar el puntero del archvo   
        CloseHandle(fileHandle);   
        fileHandle = IntPtr.Zero;   
    }   
}

 

  1. Lo primero que hacemos es modificar el nombre del archivo que vamos a abrir, ya que por facilidad de consumo hemos creado la función para que reciba como parámetro el nombre de la unidad a abrir, pero este no es el nombre que entiende el sistema de archivos, así que modificamos levemente el nombre de la unidad para poder trabajar más cómodamente en adelante.
  2. Abrimos el archivo con ayuda de la función CreateFile de la API de Windows, lo cual nos devuelve el manejador del archivo. Los parámetros pasados son el nombre del archivo (la unidad de CD), la forma en que se abrirá el archivo, en  este caso solo lectura, y le indicamos que el archivo ya existe es decir que no debería crear uno nuevo, los demás parámetros no los necesitamos por lo cual envió 0 o apuntador a cero.
  3. Verificamos que en efecto que el sistema haya podido abrir el archivo (es decir direccionar un manejador a la unidad de CD), si no pudo el valor devuelto será INVALID_HANDLE (una de nuestras constantes creadas) en cuyo caso no continuaremos con el programa y no haremos nada.
  4. Si la respuesta no fue INVALID_HANDLE procedemos a enviar un comando a la unidad de CDRom, esto lo hacemos con la función DeviceIoControl , pasándole como parámetros: el manejador del archivo que representa al dispositivo (filehandle), el comando IOCTL_STORAGE_EJECT_MEDIA  (otra de nuestras constantes) y los demás parámetros que nuevamente no representan importancia para nuestro caso.
  5. Nos aseguramos de hacer un control básico de las excepciones que se pueden presentar .
  6. Y siempre, sin importar lo que suceda, intentamos cerrar el manejador al archivo y establecemos ese manejador en cero.

Para desarrolladores en Windows Vista y posteriores (sistema seguros)

Deben crear un manifestó en su aplicación, pues por defecto UAC requiere elevar privilegios para que este código funcione, entonces en su proyecto agreguen un nuevo manifestó que indique elevar privilegios para efectuar esta operación.

 

Hay más para decir al respecto, por lo cual a algunos no les funcionará, pero esperemos las preguntas :P

 

Happy Learning!





Bookmark and Share

C# – Creating Toolbars, Buttons and Menus for Internet Explorer

April 19th, 2010 by JuanK

Hi!,

Recently I was looking for documentation to be able to create components for IE, traditional things like toolbars, buttons and menus.

 

The problem

In the beginning I was excited, but once I caught the appropriate documentation I found something that could be unfortunate, to create this kind of components for IE I had to make use of COM technology, so I had two possible ways:

  1. Program this Add-ons using C++
  2. Program the Add-ons using C# but using COM interoperability

But then came the great disappointment. Whatever the choice, programming using COM technology seemed inevitable. Bad news.

 

Not that COM is an impossible subject, what happens is that I believe one has to assess these two aspects:

  1. Effort / Benefit proportion: How long will I spend creating the solution and How much should I charge?
  2. Maintenance time, How much time must I invest in supporting the solution once it is delivered?

In my case if I don’t use COM but use a managed component, both aspects  above would be playing for me. However, I think it is clear for any .NET developer, that if I use COM both aspects above would be playing against me.

 

The Solution

I was set to find across the internet some product that would allow me create Add-ons for IE using managed components, the pleasant surprise was when I found one called  “Add-In Express for IE” I stopped for a while to review what kind of things can I can do using this Tool and I realized that the tool was exactly I was looking for, the solution to my problems.

 

What does “Add-In Express for IE”?

A few paragraphs back I mentioned two important aspects where clearly I specified that cost/benefit is something very important to take in account.

I want to invite you to review some articles that show How to create Add-ons for IE using COM:

 

Creating Add-ons for Internet Explorer: Toolbar Buttons

Creating Custom Explorer Bars, Tool Bands, and Desk Bands

 

Summarize the most troublesome and annoying issues to just to create a button and a toolbar:

  1. Create and modify registry keys
  2. I must write code using some ‘Service Providers’
  3. I must use in my code some things to ‘Query Interfaces’

What???

Yes, this is what I asked too, Why I must  concerned about all those items if I just want to create two of the most common and simple things in Windows UI development?

 

For sure there exist some heavy reasons to do that, but neither me nor my client are interested in those heavy reasons.

 

“Add-In Express for IE” has rich functionality, very interesting and very useful, but for now I will sum up all in one:

  • You can create a complete solution just coding what you really need.

 

I mean, if I want just to create a button in a toolbar, all I have to do in my code is to handle the click event over the button, nothing else. I don’t need to take care of registry keys, service providers or query interfaces of I don’t know…what… Simply I code what I need, everything else is done by Add-In Express for me.

 

Great!!!

 

And then…

“Add-In Express for IE” is one of those tools that make you give thanks to God ( whatever your God ) for having found it, with this tool you can create  solutions in a very fast way concentrating in What you need but not on How you do it.

 

Among other things that makes this too pay off, you can find:

  • Creating an IE Add-on in a record time
  • Version neutrality
  • Support for 32 and 64 bit  Add-ons
  • Ease to test your solution in IE without necessity to go into details of the full installation process of your Add-on
  • Simplicity of creating a solution installer, it is done automatically.

 

In my next article I would expose the ease of create a solution, I mean we’ll come into the tech! Meanwhile I invite you to check these illustrative videos available in the Add-In express site:

http://www.add-in-express.com/programming-internet-explorer/video.php

 

I hope this article make your life easy ;)

 

Greetings.

Bookmark and Share

« Previous Entries