Por desgracia el framework de .NET no incluye un control con esta funcionalidad. Podemos suplir esta carencia creándonos un control de usuario con un TextBox y un Button, pero vamos a ver cómo podemos modificar el TextBox para añadirle esta funcionalidad, creando un nuevo control que herede del control TextBox.
Definiendo la funcionalidad del control
Vamos a crear un nuevo control TextBoxButton que se visualizará como un control TextBox normal con un botón situado en la parte derecha del control.
El nuevo control tendrá todas las propiedades, métodos y eventos del control TextBox estándar y además:
- Una propiedad ButtonImage que permita indicar la imagen a mostrar en el botón.
- Un evento ButtonClick que se producirá cuando se realice click sobre el botón.
Creando el Proyecto para el Control
Para crear el control voy a crear un nuevo proyecto TextBoxButtonControl en Visual Studio utilizando la plantilla para bibliotecas de clases.
Eliminamos la clase Class1 que genera el Visual Studio por defecto y creamos una nueva clase TextBoxButton.
Para que nuestro control tenga toda la funcionalidad de un TextBox clásico vamos a hacer que nuestra clase herede de la clase TextBox de System.Windows.Forms. Para ello necesitamos agregar la referencia a esta librería. En el menú Proyecto vamos a la opción Agregar referencia, desplegamos Ensamblados y vamos a la opción Framework. En la lista de ensamblados seleccionamos System.Windows.Forms y pulsamos Aceptar.
Hacemos que nuestra clase TextBoxButton herede de System.Windows.Forms.TextBox, y con esto ya tendríamos la base de nuestro propio control con toda la funcionalidad del TextBox clásico.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TextBoxButtonControl { public class TextBoxButton: TextBox { } }
Imports System.Windows.Forms Public Class TextBoxButton Inherits TextBox End Class
Creando el proyecto de prueba
Para poder probar nuestro control vamos a agregar un nuevo proyecto PruebaControl a la solución utilizando la plantilla Aplicación de Windows Forms.
Para poder utilizar el nuevo control desde el proyecto de pruebas tendremos que añadir una referencia al proyecto TextBoxButtonControl. En el menú Proyecto vamos a la opción Agregar referencia, desplegamos Solución y vamos a la opción Proyectos. Seleccionamos el proyecto TextBoxButtonControl y pulsamos Aceptar.
Si abrimos en modo diseño el formulario Form1 que Visual Studio ha creado automáticamente al crear el proyecto PruebaControl, veremos que en la ventana Cuadro de herramientas aparece nuestro nuevo control TextBoxButton.
Si no puedes ver el control, compila la Solución (Menú Compilar > Compilar Solución) para que aparezca.
A continuación arrastramos el control al área de diseño del formulario y veremos que, como esperábamos, la apariencia es la misma que la de un control TextBox y, mirando la ventana Propiedades, que ha heredado todas las propiedades y eventos de éste.
Establecemos el proyecto PruebaControl como proyecto de inicio (Menú Proyecto > Establecer como proyecto de inicio) y arrancamos la aplicación para comprobar que el funcionamiento de nuestro TextBoxButton es idéntico al de cualquier otro TextBox.
Añadiendo el botón al TextBoxButton
Para añadir el botón voy a crear un control Button en el constructor de la clase TextBoxButton y lo añadiré a la colección Controls de éste.
También voy crearé un método PosicionarBoton que se encargará de establecer el ancho y alto del botón al valor de la altura del TextBoxButton y de alinearlo a la derecha del TextBox. Llamaré a este método en la creación del control y cada vez que se genere el evento Resize indicando que el control ha cambiado de tamaño.
Para poder utilizar las clases Size y Point deberemos añadir una referencia al ensamblado System.Drawing de la misma forma que lo hicimos con System.Windows.Forms.
using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TextBoxButtonControl { public class TextBoxButton: TextBox { private readonly Button _button; public TextBoxButton() { _button = new Button { Cursor = Cursors.Default, TabStop = false }; this.Controls.Add(_button); PosicionarBoton(); } protected override void OnResize(EventArgs e) { base.OnResize(e); PosicionarBoton(); } private void PosicionarBoton() { _button.Size = new Size(this.ClientSize.Height, this.ClientSize.Height); _button.Location = new Point(this.ClientSize.Width - _button.Width, 0); } } }
Imports System.Windows.Forms Imports System.Drawing Public Class TextBoxButton Inherits TextBox Private ReadOnly _button As Button Public Sub New() _button = New Button() With { _ .Cursor = Cursors.Default, _ .TabStop = False} Me.Controls.Add(_button) PosicionarBoton() End Sub Protected Overrides Sub OnResize(e As EventArgs) MyBase.OnResize(e) PosicionarBoton() End Sub Private Sub PosicionarBoton() _button.Size = New Size(Me.ClientSize.Height, Me.ClientSize.Height) _button.Location = New Point(Me.ClientSize.Width - _button.Width, 0) End Sub End Class
Si compilamos el proyecto y vamos a nuestro formulario de prueba veremos que ya aparece el botón a la derecha de nuestro control. Si modificamos el tamaño del control comprobaremos que el botón se redimensiona para ajustarse al tamaño del contenedor y, si arrancamos el proyecto PruebaControl veremos cómo se muestra correctamente en el formulario.
Sin embargo, si introducimos texto en el control, veremos que cuando el texto llega a la altura del botón el texto se sigue escribiendo debajo de éste.
Por desgracia el Framework de .NET no nos proporciona herramientas para evitar este problema así que tendremos que recurrir a la API de Windows. Para establecer los margenes de un control de edición deberemos enviar el mensaje EM_SETMARGINS.
private void PosicionarBoton() { _button.Size = new Size(this.ClientSize.Height, this.ClientSize.Height); _button.Location = new Point(this.ClientSize.Width - _button.Width, 0); SendMessage(this.Handle, 0xd3, (IntPtr)2, (IntPtr)(_button.Width << 16)); } [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
Private Sub PosicionarBoton() _button.Size = New Size(Me.ClientSize.Height, Me.ClientSize.Height) _button.Location = New Point(Me.ClientSize.Width - _button.Width, 0) SendMessage(Me.Handle, &HD3, 2, _button.Width << 16) End Sub Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _ ByVal hWnd As IntPtr, ByVal msg As IntPtr, ByVal wp As IntPtr, _ ByVal lp As IntPtr) _ As IntPtr
Si arrancamos de nuevo nuestro formulario de prueba e introducimos texto en el control comprobaremos que el espacio de edición finaliza justo antes de llegar al botón.
Añadiendo funcionalidad al botón
Ya tenemos nuestro control de texto con botón incrustado, pero no tenemos forma de añadirle funcionalidad al botón ya que no podemos controlar cuando es pulsado por el usuario.
Para solucionarlo vamos a propagar el evento Click del botón a través de un evento ButtonClick en el nuevo control. Para ello creamos el evento ButtonClick y lo configuramos de forma que, cuando se añada un controlador al evento, se lo añadimos al evento Click del botón. Y cuando se elimine un controlador, lo eliminamos igualmente del evento Click del botón.
[Category("Action")] public event EventHandler ButtonClick { add { _button.Click += value; } remove { _button.Click -= value; } }
<Category("Action")> _ Public Custom Event ButtonClick As EventHandler AddHandler(value As EventHandler) AddHandler _button.Click, value End AddHandler RemoveHandler(value As EventHandler) RemoveHandler _button.Click, value End RemoveHandler RaiseEvent(sender As Object, e As EventArgs) End RaiseEvent End Event
He añadido un atributo Category al evento para que se muestre en la ventana de Propiedades del Visual Studio junto con el resto de eventos de la categoría Acción.
Si en el formulario de prueba añadimos un controlador para el evento podremos probar la nueva funcionalidad.
private void textBoxButton1_ButtonClick(object sender, EventArgs e) { MessageBox.Show("Ha pulsado el botón del control"); }
Private Sub TextBoxButton1_ButtonClick(sender As Object, e As EventArgs) Handles TextBoxButton1.ButtonClick MessageBox.Show("Ha pulsado el botón del control") End Sub
Añadiendo una imagen al botón
Ya tenemos el control con la funcionalidad deseada. Únicamente nos faltaría mejorar la presentación. Para ello añadiré una propiedad que permita al desarrollador especificar una imagen a mostrar en el botón e incluiré una imagen en el proyecto que se mostrará por defecto en caso de que el desarrollador no indique ninguna.
Primero voy a añadir la imagen que mostraré por defecto en el proyecto. Para ello abrimos las propiedades del proyecto (Menú Proyecto > Propiedades de TextBoxButtonControl...) y seleccionamos la opción Recursos. Si nos aparece un mensaje indicando que no existe ningún archivo de recursos para el proyecto, hacemos click en el enlace para generar uno. Seleccionamos Imágenes como tipo de recurso y Agregar archivo existente... en Agregar recurso.
Esta es la imagen que voy a añadir.
Esta imagen ha sido creada originalmente por Google y descargada de www.flaticon.com. La imagen se encuentra bajo licencia CC BY 3.0.
Una vez añadida la imagen establecemos el nombre del recurso como "ellipsis":
A continuación voy a modificar la inicialización del botón para establecer la imagen por defecto.
_button = new Button { Cursor = Cursors.Default, TabStop = false, BackgroundImage = Properties.Resources.ellipsis, BackgroundImageLayout = ImageLayout.Zoom };
_button = New Button() With { _ .Cursor = Cursors.Default, _ .TabStop = False, _ .BackgroundImage = My.Resources.ellipsis, _ .BackgroundImageLayout = ImageLayout.Zoom}
Si arrancamos la aplicación podremos ver el efecto del cambio:
Ya sólo falta darle al desarrollador la posibilidad de cambiar la imagen del botón. Así que voy a añadir al control una propiedad ButtonImage.
private Image _buttonImage; [Category("Appearance"), Description("Imagen del botón")] public Image ButtonImage { get { return _buttonImage; } set { _buttonImage = value; if (_buttonImage == null) _button.BackgroundImage = Properties.Resources.ellipsis; else _button.BackgroundImage = _buttonImage; } }
Private _buttonImage As Image <Category("Appearance"), Description("Imagen del botón")> _ Public Property ButtonImage() As Image Get Return _buttonImage End Get Set(ByVal value As Image) _buttonImage = value If _buttonImage Is Nothing Then _button.BackgroundImage = My.Resources.ellipsis Else _button.BackgroundImage = _buttonImage End If End Set End Property
He creado una variable privada _buttonImage para almacenar la imagen elegida por el desarrollador. Si el desarrollador no establece ninguna imagen se utiliza la imagen por defecto.
Si compilo y vuelvo al formulario de prueba podemos ver la nueva propiedad del control.
Para probar la nueva propiedad voy a asignarle a la propiedad ButtonImage una imagen con una lupa:
Al arrancar la aplicación podemos ver el efecto del cambio.
El código completo del ejemplo, tanto en C# como en Visual Basic, está disponible en:
Códigos de muestra - Ejemplos MSDN
maestro super bueno su control, pero lo utilice para incluirlo como una nueva columna en un datagridview y no se como llamar desde ahi al evento del click del boton ya que no me aparece en las opciones de eventos como cuando se usa como control directo, no se si me podrías ayudar con este tema, porque necesito generar una grilla con esta opción y no quiero recurrir a los paquetes de controles porque quedo atado a sus librerías.
ResponderEliminargracias
Sí, no eres el primero que me lo plantea. A ver si saco un poco de tiempo y preparo un ejemplo de cómo podría hacerse.
EliminarDependiendo de la funcionalidad que quieras implementar podría enfocarse de dos formas diferentes:
- Crear un DataGridViewColumn que utilice el control como editor y propagar el evento ButtonClick del control a la columna. Sería la solución más flexible.
- Encapsular el comportamiento que se desee que tenga el botón dentro del propio editor y modificar su comportamiento a través de propiedades de la columna
yo cree la nueva columna utilizando el TextBoxButton como base pero no se en que parte debería agregar el nuevo evento, no puedo copiar aca todo el código, pero hice la pregunta en msdn y nade me pudo responder, te dejo el link porque ahi esta todo el código
EliminarLink
Hola, me puedes decir como hiciste para ponerlo en el datagridview?. Lo necesito.
EliminarGracias
Saludos Asier, que buen ejemplo, te felicito, tengo una pregunta.
ResponderEliminarComo hago para limitar el espacio del boton hacia el lado izquierdo ya que con este codigo siguiente limitas el lado derecho
//Usando un api para limitar el tamaño del texto
Win32Api.SendMessage(this.Handle, 0xd3, (IntPtr)2, (IntPtr)(_button_Izquierda.Width << 16));
Como seria para el lado izquierdo
Hola George,
Eliminarsiento el retraso pero me acabo de tomar unos días de vacaciones.
Si quieres establecer el botón a la izquierda no tienes más que establecer su posición como 0,0 y establecer el margen izquierdo en lugar del derecho.
Para esto último debes establecer el tercer parámetro del método SendMessage con el valor 1 (EC_LEFTMARGIN) y establecer el valor del ancho en el low-word del double word que se pasa como cuarto parámetro, lo que en la práctica significa que no necesitas realizar el desplazamiento de 16 bits:
_button.Location = new Point(0, 0);
SendMessage(this.Handle, 0xd3, (IntPtr)1, (IntPtr)_button.Width);
Puedes encontrar más información sobre el mensaje EM_SETMARGINS en:
https://msdn.microsoft.com/es-es/library/bb761649(v=vs.85).aspx
Un saludo
Hola buenas una consulta, tengo 2 formularios A y B, en el B tengo un formulario que genera números aleatorios al presionar un botón que le puse, pero yo necesito poner un botón en el formulario A para ejecutar desde ahí los números aleatorios del formulario B, como lo podría hacer..
ResponderEliminarAsier buena tarde, para establecer ambos margenes de que forma hay que llamar a la API SendMessage?
ResponderEliminarAsier buen día, muy buen aporte, me podrías orientar para establecer ambos margenes en el control (derecho e izquierdo)
ResponderEliminarHola Asier, soy Álvaro:
ResponderEliminarTu control es perfecto, va genial. Felicidades. Tengo una pregunta: ¿Es posible cargar tu control sin necesidad de añadir todo el proyecto a la solución? Estoy intentando cargar únicamente la DLL que genera tu proyecto pero no consigo visualizarlo en el cuadro de herramientas.
Muchas gracias.
Está muy bueno como siempre Asier.
ResponderEliminarDime una cosa, como le cambio el alto al control?
Lo intento con MiminumSize y heigth pero no puedo ponerlo en un alto de 22
Gracuas
Asier no me reconoce el comando Properties...
ResponderEliminarEsta sentencia:
_button.BackgroundImage = Properties.Resources.ellipsis;
Estoy en Net 4.5 con C# 2015
Gracias
Buen día
ResponderEliminarcomo se puede cambiar el tamaño del boton
Una pregunta, se puede crear un control de usuario, agregando como referencia dll externa de un proveedor cualquiera por ejemplo Leadtools.
ResponderEliminarEstoy intentando hacer un visor de imagenes TIF.
Hola, he utilizado tu TextBoxButtom pero al llamar a un form desde un panel se esconde el boton. Me podrías echar una mano. Pásame un mail y te envío los pantallazos. Mil Gracias
ResponderEliminar