Ir al contenido principal

TextBox con Icon/Imagen

Bien, continuando con este articulo: TextBox con borde personalizado, ahora le dare la funcionalidad de poder mostrar un icono o imagen dentro del Control TextBox.

Existen dos maneras de hacer esto:
  • Pintar el icono/imagen dentro del control o
  • Pintar el icono/imagen dentro del Non-Client Area del control.
Pintar el icono/imagen dentro del control.

Antes de escribir el código decidi googlear un poco, para ver si alguien más ya habia tenido la misma idea de usar el mensaje EM_SETMARGINS para dejar el espacio necesario para pintar el icono o imagen ya sea a la derecha o izquierda y me he encontrado con este articulo.


Pintar el icono/imagen dentro del Non-Client Area del control.
Usando el Non-Client Area no encontre resultados googleando, así que es la forma que usare para dibujar un icono o imagen dentro de un control TextBox.

En el control TextEditor que escribí, utilizo esta manera para pintar el icono o imagen dentro del TextBox, así que, si cambian el estilo del borde a "FixedSingle" vera un pequeño problema que no considere al escribir este control, pero!!, veremos la solución a este problema.   ;-)

Imagen del problema.



Si leyeron el articulo anterior a este (TextBox con borde personalizado), veran que menciono la solución al problema, pero no esta completa, no.... como en este articulo no modifique el Non-Client Area para pintar el borde de un color diferente, la solución no esta completa, pero la completaremos en este articulo.

Así que empecemos.

WM_NCCALCSIZE: Con este mensaje dejaremos el espacio a la izquierda o derecha para pintar el icono o imagen.

C#
private bool WmNCCalcSize(ref Message m)
{
    if (this.Image == null)
    {
        this.intClientArea = Rectangle.Empty;
        return true;
    }

    if (m.WParam == (IntPtr)1)
    {
        NativeMethods.NCCALCSIZE_PARAMS ncParams = (NativeMethods.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NativeMethods.NCCALCSIZE_PARAMS));
        int top = 0, right = 0, bottom = 0, left = 0;
        if (this.BorderStyle == BorderStyle.Fixed3D)
        {
            top = right = bottom = left = 2;
        }

        if (this.ImageAlign == ImageAlign.Left)
        {
            left += this.Image.Width + 2;
            left += this.BorderStyle != BorderStyle.Fixed3D ? 2 : 0;
        }
        else
        {
            right += this.Image.Width + 2;
            right += this.BorderStyle != BorderStyle.Fixed3D ? 2 : 0;
        }

        ncParams.rgrc0.Top += top;
        ncParams.rgrc0.Left += left;
        ncParams.rgrc0.Right -= right;
        ncParams.rgrc0.Bottom -= bottom;

        this.intClientArea = new Rectangle(left, top, ncParams.rgrc0.Right - ncParams.rgrc0.Left, ncParams.rgrc0.Bottom - ncParams.rgrc0.Top);

        Marshal.StructureToPtr(ncParams, m.LParam, false);
        return false;
    }

    return true;
}

La idea de este método privado es, si hemos seleccionado una imagen para mostrar en el control, según donde queremos que la imagen aparezca,  con este mensaje modificare el Non-Client Area del control para pintar la imagen luego en el mensaje wm_ncpaint, ademas definido el valor para la variable "intClientArea" tipo Rectangle, este sera el rectángulo a excluir para evitar el parpadeo del texto.

WM_NCPAINT: En este mensaje pintaremos la imagen.

C#
private bool WmNCPaint(ref Message m)
{
    if (!this.IsHandleCreated || 
         this.IsDisposed || 
         (this.Image == null && this.BorderColor.IsEmpty))
        return true;

    Rectangle winRect = this.WindowRect;
    if (winRect.IsEmpty)
        return true;

    Rectangle bounds = new Rectangle(0, 0, winRect.Width, winRect.Height);
    if (this.BorderStyle == BorderStyle.Fixed3D && this.BorderColor.IsEmpty)
        bounds = new Rectangle(2, 2, winRect.Width - 4, winRect.Height - 4);

    IntPtr hDC = NativeMethods.GetDCEx(this.Handle, IntPtr.Zero, NativeMethods.DCXFlags.DCX_WINDOW | NativeMethods.DCXFlags.DCX_CACHE | NativeMethods.DCXFlags.DCX_CLIPSIBLINGS);
    if (hDC != IntPtr.Zero)
    {
        Rectangle excludeClip = new Rectangle(2, 2, bounds.Width - 4, bounds.Height - 4);
        if (!this.intClientArea.IsEmpty)
            excludeClip = new Rectangle(this.intClientArea.Left, this.intClientArea.Top, this.intClientArea.Right - this.intClientArea.Left, this.intClientArea.Bottom - this.intClientArea.Top);

        NativeMethods.ExcludeClipRect(hDC, excludeClip.Left, excludeClip.Top, excludeClip.Right, excludeClip.Bottom);
        using (BufferedGraphics bg = BufferedGraphicsManager.Current.Allocate(hDC, bounds))
        {
            using (SolidBrush brush = new SolidBrush(this.BackColor))
            {
                bg.Graphics.FillRectangle(brush, bounds);
            }
            this.OnNCPaint(bg.Graphics, bounds);
            bg.Render(hDC);
        }

        NativeMethods.ReleaseDC(this.Handle, hDC);
    }

    if (this.BorderColor.IsEmpty)
        base.DefWndProc(ref m);

    return false;
}


Al seleccionar el estilo de borde "FixedSingle", tenemos el problema antes mencionado en el control TextEditor, pero para resolverlo tendremos que hacerle más cambios al código en el método WM_PAINT, veamos.


C#
private bool WmPaint(ref Message m)
{
    if (this.BorderStyle != BorderStyle.FixedSingle || 
        (this.Image == null && this.BorderColor.IsEmpty))
        return true;

    NativeMethods.PAINTSTRUCT ps = default(NativeMethods.PAINTSTRUCT);

    bool flag = false;
    IntPtr hDC = m.WParam;
    if (m.WParam == IntPtr.Zero)
    {
        hDC = NativeMethods.IntBeginPaint(new HandleRef(this, this.Handle), ref ps);
        flag = true;
    }

    // Excluimos el area del texto para evitar parpadeo.
    NativeMethods.ExcludeClipRect(hDC, this.ClientRectangle.Left + 1, this.ClientRectangle.Top + 1, this.ClientRectangle.Right - 1, this.ClientRectangle.Bottom - 1);
    using (BufferedGraphics bg = BufferedGraphicsManager.Current.Allocate(hDC, this.ClientRectangle))
    {
        using (SolidBrush brush = new SolidBrush(this.BackColor))
        {
            bg.Graphics.FillRectangle(brush, this.ClientRectangle);
        }
        this.PaintBorder(bg.Graphics, this.ClientRectangle);
        bg.Render();
    }

    // excluimos toda el area del cliente.
    NativeMethods.ExcludeClipRect(hDC, this.ClientRectangle.Left, this.ClientRectangle.Top, this.ClientRectangle.Right, this.ClientRectangle.Bottom);
    // creamos una nueva area de cliente para excluir el border pintado por nosotros
    // y evitar que lo pinte el control.
    IntPtr hRgn = NativeMethods.CreateRectRgn(this.ClientRectangle.Left + 1, this.ClientRectangle.Top + 1, this.ClientRectangle.Right - 1, this.ClientRectangle.Bottom - 1);
    // aplicamos la nueva area para que el control pinte su contenido o texto.
    NativeMethods.ExtSelectClipRgn(hDC, hRgn, 2);

    m.WParam = hDC;
    base.DefWndProc(ref m);

    if (flag)
        NativeMethods.IntEndPaint(new HandleRef(this, this.Handle), ref ps);

    return false;
}

Si leemos la documentación de este mensaje podemos leer que el parámetro wParam puede recibir un puntero a un "Device Context" y este sera utilizado para pintar el control en este.


For some common controls, the default WM_PAINT message processing checks the wParam parameter. If wParam is non-NULL, the control assumes that the value is an HDC and paints using that device context.


Así que para evitar el problema antes mencionado lo que hago es excluir el área donde se pintara el borde para evitar este problema, para lo cual he acudido al uso de otras API's del windows, forzando previamente a utilizar un "Device Context" que puedo controlar.

CreateRectRgn
ExtSelectClipRgn
IntBeginPaint
IntEndPaint

Obteniendo como resultado.




y listo... eso es todo por hoy, ahora tenemos un control TextBox al cual le podemos cambiar el color del borde y también podemos mostrar un icono/Imagen en este, cuyo valor agregado es muy útil para los usuarios de nuestras aplicaciones.

así que espero sea de utilidad para ustedes, cualquier critica o comentario, sera bienvenido.

Salu2,

Descargas:
CSharp.Windows.Forms.ControlEx(2).rar


Articulos Relacionados:
Nullable DateTimePicker


Comentarios

  1. Hola Marvín: Este control textbox que mencionas (imagen y borde), lo ofreces para descargar? no veo el link..

    Saludos y felicidades por tu trabajo!

    Luis Escobar

    ResponderEliminar
    Respuestas
    1. mmm..... fijate que aún no tenia que publicarlo ya que aún no tengo el código en VB.NET, pero creo que en lugar de guardar el borrador lo publique....

      pero bien, el código que si tengo listo porque primero lo escribo en C# es el de C#, solo dejame subirlo para crear el Link y lo puedas descargar.

      Salu2,

      Eliminar
    2. Listo Luis, ya puedes descargar el código fuente en C#, pronto subiré el de VB.NET

      Salu2,

      Eliminar
  2. Sos toro man, yo aun vengo aprendiendo c#. Gracias, seguí así.

    ResponderEliminar
  3. como enlazo en el formulario el textbox a la clase

    ResponderEliminar
  4. Muchas gracias Marvin por tu trabajo.
    Una cuestión:
    ¿Sería posible capturar el evento click de la imagen?
    ¿Como se haría?
    Creo que sería un valor añadido al control.
    De nuevo muchas gracias por tu trabajo.

    ResponderEliminar
  5. Hola Marvin excelnte trabajo, gracias por compartir la fuente, pero como se puede capturar el click de la imagen, nos ayudaria bastante a tus seguidores.

    ResponderEliminar
  6. Como disminuyes o en donde disminuyes el margen para que la imagen entre en su totalidad

    ResponderEliminar

Publicar un comentario

Entradas populares de este blog

NetBarControl

Actualización.
NetBarControl - Description Item Style (New)




NetBarControl (Outlook Bar) es uno de los controles que muchas veces buscamos para usar en nuestras aplicaciones, pero siempre encontramos en internet versiones pagables y tal vez no contamos con el presupuesto esperado como para comprar uno y las versiones gratis que se logran encontrar, pues como son gratis no implementan en su totalidad la funcionalidad que esperamos encontrar en un control de este tipo. Antes de comenzar a escribir este control dedique tiempo en buscar uno por internet que tuviera toda la funcionalidad o por lo menos una interfaz disponible en modo de diseño, pero, no logre encontrar uno, así que me propuse crearlo como a mí me gustaría que funcionara uno gratis y al final llegue a la conclusión de ¿Porque no hay uno completo, gratis y que incluya el código fuente? y la respuesta es:No es fácil, pero tampoco es cosa de otro mundo. Con esto no digo que otro programador no lo pueda hacer o que los ejemplos en…

ComboBox-MultiColumns

ComboBox-MultiColumns Update


Como bien sabemos el Set de Controles de Windows de .Net Framework incluye el control ComboBox pero este no soporta mostrar multiples columnas, en su lugar muestra una lista simple ya sea agregada manual mente usando la propiedad Items o según el origen de datos definido a traves de la propiedad DataSource, usando la propiedad DisplayMember para indicar la propiedad o campo a mostrar en la lista desplegable. Bien… la ventaja de los controles de .Net Framework es que los podes extender para mejorar su uso en nuestros desarrollos, en este caso extenderemos el Control ComboBox para reemplazar la lista desplegable por nuestra propia versión que mostrara “N” cantidad de columnas que necesitemos, para esto haremos uso de las siguientes clases ToolStripDropDown y ToolStripControlHost. En la ayuda de MSDN podemos encontrar un ejemplo sobre como usar la clase ToolStripDropDown para mostrar un TreeView. parte de este ejemplo lo he tomado para crear este Control ComboBo…

TextEditor

Este control nace a raíz de una pregunta en el foro de Visual Basic .Net, sobre como cambiar el borde de un TextBox a un borde personalizado y además andaba buscando cambiar la apariencia del control NetBarControl para poder aplicarle una nueva propiedad para cambiar el estilo. ejemplo:


Aun que esta imagen solo es un pre-formato de cómo funcionara el control NetBarControl, solo que me distraje creando este nuevo control TextEditor. Así que le echaré mano al terminado este nuevo control.







Bien, la idea inicial era solo agregar botones a un control TextBox, pero luego usando el Mozilla, al descargar unos archivos me percate de un control en la parte inferior de esta venta de descargar, aun que se suele ver mucho en las páginas Web, pero me llamo la atención aquí en el Mozilla y en el Window Live Messenger.


Entonces… me entro el gusano de la curiosidad, agregar la imagen luego de agregar los botones ya no era la parte difícil, la parte curiosa es mostrar el Texto como marca de agua, enco…