Microsoft MVP

Email y Rss

email rss

Klout

Seguidores en facebook

Timeline de mi Twitter

Tienes preguntas?

Ideas de un Conejo
Más allá de los sistemas de información: (C#)=> videojuegos, soluciones a problemas interesantes y Sistemas Operativos
XNA
C#
Sistemas Operativos
Varios
Metro
Acerca de

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

June 13th, 2010 by JuanK

TweetFollow @JuanKRuiz

Share

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;
    }

[/code]

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.

Print Friendly
Share

TweetFollow @JuanKRuiz

  • 14 Comentarios »
  • Publicado en la categoría 'C#'

14 comentarios to “Como cambiar el texto de los botones de un MessageBox – C# ”


  • paola Says:
    April 16th, 2009 at 12:30 pm  

    ohh no!!!!!

  • Daniel (Heravar) Says:
    April 23rd, 2009 at 10:30 am  

    Muy buen post, muy útil y además bien detallado! Muchas gracias por compartirlo!

  • Miguel Ángel Says:
    May 12th, 2009 at 3:09 am  

    Hola!!

    Estoy intentando probarlo en una aplicación Windows Forms con .NET 3.5SP1 y no hay forma de que funcione… alguna pista? no me da error ni nada, pero no cambia el texto de los botones. Te dejo el código:

    MsgBoxUtil.HackMessageBox(“Desconectado”, “Parámetros”, “PRUEBA”);
    DialogResult res = MessageBox.Show(“No se puede establecer conexión con el servidor central.\n¿Desea trabajar en modo desconectado o cambiar la dirección de acceso al servidor?”, “Ofi”, MessageBoxButtons.AbortRetryIgnore);
    MsgBoxUtil.UnHackMessageBox();

    Un saludo y muchas gracias!!!

  • Como cambiar el texto de los botones de un MessageBox - C# - Ideas de un Conejo Says:
    August 18th, 2009 at 2:49 pm  

    [...] http://juank.black-byte.com/c-cambiar-texto-botones-messagebox/ [...]

  • Araxhiel Khy Says:
    April 9th, 2010 at 10:22 am  

    Hola, este, si no te molesta realice una implementación de tu código pero en VB.NET, si deseas te lo puedo enviar para que le des el visto bueno…

    Gracias por el excelente aporte que significa esta clase =D

    Saludos!

  • Manuel Arango Says:
    April 26th, 2010 at 10:50 am  

    Hoja Juan Carlos, buen día…

    Me ha sido de gran ayuda su ejemplo, lo utilizo y funciona perfecto para los textos de los botones de los mensajes… le hice una pequeña variación para que funcionara para los labels de un control ColorGrid. en esencia se cambia el valor de la constante BUTTON_CLASSNAME de “Button ” a “Static”.
    El problema que tengo es en cambiar los toolTips de un control PropertyGrid (“Por categorías” y “Alfabético” )… a cualquier texto…. Agradezco cualquier ayuda….

    mil gracias

  • yelinna Says:
    September 14th, 2010 at 1:05 pm  

    Las Apis, tenían que ser las Apis….
    Yo he corrido códigos fuente en C# con Monodevelop en Ubuntu 10, no hay que cambiar ni una línea de código y corren de lo más bien hasta que… se pone un p/Invoke como las Apis. Si se desea que nuestras aplicaciones sean multiplataforma (Praise Monodevelop!!) es mejor obviar las apis y diseñar nuestras propias msgbox en .Net solamente.

    Pero para Windows, es una excelente solución :D :D :D :D

  • JuanK Says:
    September 14th, 2010 at 1:12 pm  

    así es,
    el uso de las APIS trae tras de si el costo de perder la multiplataforma, es
    algo normal e inevitable…

  • Eduardo Says:
    October 15th, 2010 at 11:09 am  

    este aporte me ha servido mucho en vdd gracias

    igual si en esto agregamos un dateTimePicker1 tambien cambiaria la hora y fecha depende la region seleccionada gracias saludos

  • Carlos Says:
    April 9th, 2011 at 7:57 am  

    hola tengo un problema como puedo hacer para que mi form modal se cierre al hacer click fuera de el asi como si fuese un popup lo necesito urgente porfavor si me puedes ayudar te lo agredeceria mucho .

    Gracias de antemano.
    Saludos.

  • JuanK Says:
    April 9th, 2011 at 9:03 am  

    En ese caso intenta con el evento LostFocus quedo atento a ver como lo resolvemos.

  • Ed Says:
    August 19th, 2011 at 8:36 pm  

    Muy buen articulo y muy buen codigo

  • Famara Says:
    November 2nd, 2011 at 4:35 pm  

    Hola, quisiera saber si esta aplicación de botones personalizados en MessageBox es posible en .NET COMPACT FRAMEWORK o para una aplicación para pockets, muchas gracias.

  • JuanK Says:
    November 5th, 2011 at 8:58 am  

    Hola, lamentablemente no te se decir si puede ser compatible con compact framework, lo mejor seria probar.

Deja un comentario

Redes Sociales

Follow @JuanKRuiz
Answer Questions

Busca en el blog

Artículos Relacionados

  • C# – El extraño caso de la ventana sin borde que no se deja cambiar de tamaño
  • C# – WPF – Escalar el tamaño de la fuente al cambiar el tamaño de la ventana o control
  • Como abrir la puerta del cd rom desde C# y VB.NET
  • C# – Cmo modificar el comportamiento del botn minimizar, maximizar, etc.
  • C# – Cmo modificar la transparencia en la ventana de otro proceso?
  • Como crear un Servicio con C# y WCF
  • C# – XNA- Como Convertir Una Imagen a Escala de Grises
  • Artículos Relacionados

  • C# – El extraño caso de la ventana sin borde que no se deja cambiar de tamaño
  • C# – WPF – Escalar el tamaño de la fuente al cambiar el tamaño de la ventana o control
  • Como abrir la puerta del cd rom desde C# y VB.NET
  • C# – Cmo modificar el comportamiento del botn minimizar, maximizar, etc.
  • C# – Cmo modificar la transparencia en la ventana de otro proceso?
  • Como crear un Servicio con C# y WCF
  • C# – XNA- Como Convertir Una Imagen a Escala de Grises
  • Nube de Temas

    API - C# - codigo - Fiber - Forms - GeSHi - icon - IE - IE9 - imagenes - IT - Microsoft - MVP - Pinned - PowerShell - Proceso - rendimiento - RSS - sistema - Sistemas Operativos - Site - Thread - velocidad - Visual - WCF - Windows - WndProc - WPF - XML - XNA

    Blogs recomendados

  • VBCodigoPocketPC Espacio para tratar temas de programacion para dispositivos moviles, Pocket PC, Compact Framework, Embbeded Visual Basic, Visual Basic.NET , C# (C Sharp)
  • Róbinson Moscoso Estaré publicando acá cosas sobre tecnologia .NET, situacioines cotidianas de las que voy aprendiendo… sirve como extensión de memoria.
  • .Net C# Blog de Nelsón Venegas
  • Warnov Microsoft Developer Evangelist
  • IT LIfe Blog de mi Hermano que esta en el lado claro: IT
  • Sorey Garcia Una chica del común con la firme intención de no serlo
  • Black Byte videojuegos, modelado y animación 3d
  • Road to IT World Cosas interesantes de IT
  • Marcela Chitiva Un poco de esto… un poco de aquello
  • Surviving the Nigth El mejor blog para aquellos que nos gustan los “internals”
  • Meta

    1. Log in
    2. WordPress

    Ideas de un Conejo is powered by Wordpress. Theme designed by Juan Carlos Ruiz.