Colorear marco de textbox o combobox

Iniciado por NoaC, 25 Junio 2017, 19:47 PM

0 Miembros y 1 Visitante están viendo este tema.

NoaC

Hola!
Estoy realizando una aplicación con muchos formularios muy distintos en C#. Y se me ha ocurrido crear una clase que contenga todos los métodos para validar cualquier campo de cada formulario, incluso utilizando expresiones regulares para dni, correo, etc...
Bueno, rizando un poco el rizo, también me gustaría que si un campo no pasa la validación, dicho campo(siendo textbox o combobox, o label para radiobutton o checkbox) cambiase de color(la letra en los labels y el borde de los textbox o combobox).
Por lo que veo no es tan sencillo, además de que aún no domino C#.

Cogiendo trozos de funciones que encuentro por la red he llegado al siguiente código:
Código C#:
Código (csharp) [Seleccionar]

//para los textbox
private void Form1_Paint(TextBox nomControl)
{
    nomControl.BorderStyle  = BorderStyle.None;
    Pen p = new Pen(Color.Red);
    Graphics g = //....aqui me he quedado porque en la funcion original se usaba
//un PaintEventArgs e como argumento y la línea de instrucción seguia ...e.Graphics.
//luego continua...
    int variance = 3;
    g.DrawRectangle(p, new Rectangle(nomControl.Location.X - variance, nomControl.Location.Y - variance, nomControl.With + variance, nomControl.Height + variance));
}

Vamos, que de lo único de lo que realmente me entero es de que le he pasado el control y que creo que no debe ser tan difícil...

Alguien me puede decir si voy por buen camino y qué es lo que me falta??

Saludos!!

Eleкtro

#1
Cita de: NoaC en 25 Junio 2017, 19:47 PMutilizando expresiones regulares para dni, correo, etc...

La forma más segura de validar una dirección de email, es mediante un proveedor de validación de dominios (ya sea online, o mediante una bd offline), pero a falta de esto, mejor que una expresión regular es más recomendable utilizar los miembros de la clase System.Uri para validar la estructura/sintaxis de la dirección de e-mail ( puedes combinar esta metodología con una evaluación RegEx para cubrir otros aspectos de validación sobre el string, pero en mi opinión eso sería perder el tiempo, puesto que nada es 100% efectivo, y esto ya cubre varios aspectos importantes de validación de direcciones e-mail ):

Código (csharp) [Seleccionar]
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Validates a mail address.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <example> This is a code example.
/// <code>
/// Dim isValid As Boolean = ValidateMail("Address@Gmail.com")
/// Dim isValid As Boolean = ValidateMail("mailto:Address@Gmail.com")
/// </code>
/// </example>
/// ----------------------------------------------------------------------------------------------------
/// <param name="address">
/// The mail address.
/// </param>
/// ----------------------------------------------------------------------------------------------------
/// <returns>
/// <see langword="true"/> if the mail address is valid, <see langword="false"/> otherwise.
/// </returns>
/// ----------------------------------------------------------------------------------------------------
[DebuggerStepThrough()]
public static bool ValidateMail(string address) {

if (!address.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase)) {
address = address.Insert(0, "mailto:");
}

if (!Uri.IsWellFormedUriString(address, UriKind.Absolute)) {
return false;
}

Uri result = null;
if (Uri.TryCreate(address, UriKind.Absolute, result)) {
return (result.Scheme == Uri.UriSchemeMailto);

} else {
return false;

}

}

/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Validates a mail address.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <example> This is a code example.
/// <code>
/// Dim isValid As Boolean = ValidateMail(New MailAddress("Address@Gmail.com"))
/// Dim isValid As Boolean = ValidateMail(New MailAddress("mailto:Address@Gmail.com"))
/// </code>
/// </example>
/// ----------------------------------------------------------------------------------------------------
/// <param name="address">
/// The mail address.
/// </param>
/// ----------------------------------------------------------------------------------------------------
/// <returns>
/// <see langword="true"/> if the mail address is valid, <see langword="false"/> otherwise.
/// </returns>
/// ----------------------------------------------------------------------------------------------------
[DebuggerStepThrough()]
public static bool ValidateMail(MailAddress address) {
return MailUtil.ValidateMail(address.Address);
}


El código original, este de aquí abajo, lo he extraido de mi framework comercial Elektrokit para programadores de .NET (el cual lo puedes encontrar en mi firma, por si te interesa)...




Cita de: NoaC en 25 Junio 2017, 19:47 PM
Código (csharp) [Seleccionar]

   Graphics g = //....aqui me he quedado porque en la funcion original se usaba
//un PaintEventArgs e como argumento y la línea de instrucción seguia ...e.Graphics.

Esto que voy a explicar no te conviene hacerlo, tan solo me estoy limitando a responder a tu pregunta para aclararte la duda que tienes:

El objeto "e" es del tipo Graphics, por ende, la función equivalente que estás buscando es Control.CreateGraphics(), o bien Graphics.FromHwnd() (ambas tienen el mismo efecto)


Te muestro un ejemplo de uso genérico que he elaborado con extensiones de método:
Código (csharp) [Seleccionar]
public static class ControlExtensions {

/// <summary>
/// Draws a solid border of the specified color with 1 pixel width around the bounds of the source <see cref="Control"/>.
/// </summary>
public static void DrawBorder(this Control sender, Color color) {
ControlExtensions.DrawBorder(sender, color, 1, ButtonBorderStyle.Solid, sender.ClientRectangle);
}

/// <summary>
/// Draws a solid border of the specified color and with around the bounds of the source <see cref="Control"/>.
/// </summary>
public static void DrawBorder(this Control sender, Color color, int width) {
ControlExtensions.DrawBorder(sender, color, width, ButtonBorderStyle.Solid, sender.ClientRectangle);
}

/// <summary>
/// Draws a border of the specified color, with and style around the bounds of the source <see cref="Control"/>.
/// </summary>
public static void DrawBorder(this Control sender, Color color, int width, ButtonBorderStyle style) {
ControlExtensions.DrawBorder(sender, color, width, style, sender.ClientRectangle);
}

/// <summary>
/// Draws a border of the specified color, with and style around the specified bounds of the source <see cref="Control"/>.
/// </summary>
public static void DrawBorder(this Control sender, Color color, int width, ButtonBorderStyle style, Rectangle bounds) {

switch (sender.GetType()) {

// You can implement here a custom case for selective painting...
       // case typeof(TextBox):
       // case typeof(RichTextBox):
       // etc.

default:
using (Graphics g = sender.CreateGraphics()) { // or also: Graphics.FromHwnd(sender.Handle)

ControlPaint.DrawBorder(g, bounds,
                   color, width, style,  // left
                   color, width, style,  // top
                   color, width, style,  // right
                   color, width, style); // bottom
}
break;
}

}

}


Modo de empleo:
Código (csharp) [Seleccionar]
myControl.DrawBorder(Color.Red, 1, ButtonBorderStyle.Solid);

Código original, escrito en VB.NET:




Cita de: NoaC en 25 Junio 2017, 19:47 PMme gustaría que si un campo no pasa la validación, dicho campo(siendo textbox o combobox, o label para radiobutton o checkbox) cambiase de color(la letra en los labels y el borde de los textbox o combobox).

Para el tratamiento de validación de entrada en controles de edición (o Edit-controls) te conviene utilizar el componente ErrorProvider, sin más, una vez lo hayas configurado de forma correcta (controlando las validaciones de cada campo/control) si un control no pasa la validación entonces el componente mostrará un icono (personalizable) que indicará un fallo de validación en "X" campo(s), y de esta manera evitarás tener que reinventar la rueda para ir intentando modificar el color de los bordes de todos los controles del Form en caso de fallo de validación...







Cita de: NoaC en 25 Junio 2017, 19:47 PMlo único de lo que realmente me entero es de que le he pasado el control y que creo que no debe ser tan difícil...

Alguien me puede decir si voy por buen camino y qué es lo que me falta??

No, no vas por buen camino, por varios motivos...

La solución que considero más óptima ya te la expliqué más arriba, el componente ErrorProvider, y todo lo que voy a decir desde este momento hasta el final del post no contribuye a la solución recomendada, por que la solución más óptima para tus necesidades ya se te explicó cual es (insisto), ahora, con la intención de intentar enseñarte y que aprendas algo básico que te podría servir de ayuda, déjame explicarte que los controles por defecto de Microsoft/.NET Framework se renderizan a partir de los estilos de ventana (también conocidos como Window Styles, y los estilos de ventana extendidos o Extended Window Styles) y de los estilos visuales definidos para el tipo de control (a su vez, los estilos visuales del control están definidos por el tema visual/windows theme que esté actualmente activo en el sistema operativo), quienes controlan el aspecto resultante del control. Puedes aplicar un estilo de ventana y también un estilo visual de control específico, por ejemplo puedes asignarle el estilo visual de un button a un picturebox (todo esto de forma administrada), lo que literálmente convertirá el aspecto del control sin realizar mayor esfuerzo, e incluso puedes desactivar los estilos visuales en toda la aplicación, pero eso es otra historia...

Por si te interesa para el futuro:

Sigamos con las explicaciones...

Dependiendo del control en cuestión, éste puede requerir un tratamiento específico y "especial" para poder aplicarle personalizaciones de diseño como por ejemplo añadir un borde de un color específico, pues los controles se encargan de controlar por si mismos sus eventos de dibujado y con ello se encargan de administrar el cómo, el cuando, y el qué deben dibujar ( en general todos los controles exponen el evento Control.Paint, pero otros controles disponen de varios eventos adicionales de dibujado, los que internámente administran los mensajes de ventana: WM_PAINT, WM_PRINTCLIENT, etc ), y en muchos controles la complejidad de "dibujado manual" aumenta considerablemente, por ejemplo en un ListView, si queremos personalizar su diseño entonces deberemos encargarnos de administrar el dibujado del fondo o background, y de la rejilla o grid, las columnas, el texto de cada campo de cada fila y su posicionamiento/alineación, etc; son muchos factores a tener en cuenta.

Personalizar el diseño de un control no es un tema sencillo, se requiere entendimiento, pues de hecho es MUY denso (hay muchos conceptos que no he tratado para no alargar más las explicaciones, y otros tantos conceptos que ni conoceré), y sobre todo se necesita PRÁCTICA para comprender, por que tarde o tempranos las pasaremos canutas puesto que algunos controles no permiten una administración guiada o sencilla por parte del usuario para este tipo de personalizaciones... y tendremos que activar una propiedad conocida como OwnerDraw y/o activar el UserPaint para "obligar" a que el control deje de administrar el dibujado para darnos el relevo y encarganos nosotros, pero en algunos controles esto será ignorado internamente, y entonces deberemos recurrir a la API de Windows para administrar el dibujado del control, todo esto se debe a que la tecnología de WindowsForms (a diferencia de WPF) no está pensada para satisfacer las necesidades exquisitas de este tipo de personalizaciones avanzadas de diseño de controles... el soporte es muy limitado en este aspecto.

Por último, y quizás lo más importante que debes saber despues de todo este rollo...

La forma en la que estás intentando dibujar un borde sobre un control, es incorrecta (a parte de estar usando el método DrawRectangle que es bastante expensivo y esto puede generar flickering), por que como ya hemos aclarado previamente, los controles se encargan de administrar el cómo, el cuando, y el qué deben dibujar, esto quiere decir que si tú en un momento determinado "X" le modificas el borde al control, ese cambio no persistirá demasiado... ya que la próxima vez que el control vuelva a controlar su dibujado, éste desechará el cambio que le aplicaste (o mejor dicho, el borde que tu dibujaste se "limpiará"), esto puedes comprobarlo sin ir más lejos con un Button, cuando pasas el puntero del mouse por encima del button se disparan varios eventos del mouse y del foco, y con ello el botón se redibujará... y perderás el borde que hayas aplicado de la manera en la que lo estás intentando aplicar.

Para solucionar ese problema, tienes dos opciones, la primera y la más deficiente es seguir con el código que estabas escribiendo (o con el código que yo te mostré con las extensiones de método) y controlar desde tu Form todos y cada uno de los eventos de "X" control que impliquen un redibujado de ese control, para llamar al método encargado de dibujar el borde en cada redibujado (esto es muy ineficiente y no siempre dará el resultado esperado).

La segunda opción, la más óptima, sería heredar la clase del control para controlar el dibujado. Según el control que sea, podrá ser tan sencillo como ampliar la implementación de invalidación (u overriding) del método heredado Control.OnPaint(), en otros casos hará falta hacer lo mismo en otros métodos heredados relacionados con el dibujado, y en los casos más tediosos o quizás "complejos" deberemos procesar manualmente los mensajes de dibujado de ventana mediante el método de procedimiento de ventana (WndProc), y según la sofisticación que queramos obtener, adicionálmente recurrir a la API de Windows a las funciones BeginPaint y EndPaint para reducir el efecto de flickering... entre otras técnicas que también podemos combinar para el mismo propósito.

En fin, para acabar te muestro un ejemplo sencillo y básico de como se podría implementar la funcionalidad de modificar el color del borde de un control de tipo Button:

Código (csharp) [Seleccionar]
[DisplayName("MyExtendedButton")]
[Description("A extended Button control.")]
[DesignTimeVisible(true)]
[DesignerCategory("UserControl")]
[ToolboxBitmap(typeof(Button), "Button.bmp")]
[ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Require)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ComVisible(true)]
[DefaultProperty("Text")]
[DefaultEvent("Click")]
public sealed class MyExtendedButton : Button {

[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Localizable(false)]
       [Category("(Extended) Appearance")]
[Description("The border color of the control.")]
[DefaultValue(typeof(Color), "ControlLight")]
public Color BorderColor { get; set; }

[DebuggerNonUserCode()]
public MyExtendedButton() {
base.SuspendLayout();

base.SetStyle(ControlStyles.DoubleBuffer, true);
// base.SetStyle(ControlStyles.AllPaintingInWmPaint, ... );
// base.SetStyle(ControlStyles.ResizeRedraw, ... );
// base.SetStyle(ControlStyles.UserPaint, ... );
// base.SetStyle(ControlStyles.SupportsTransparentBackColor, ... );

base.ResumeLayout(performLayout: false);
}

[DebuggerStepThrough()]
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
}

[DebuggerStepThrough()]
protected override void OnPaintBackground(PaintEventArgs e) {
base.OnPaintBackground(e);
}

[DebuggerStepperBoundary()]
protected override void WndProc(ref Message m) {
base.WndProc(m);

switch (m.Msg) {
case 15: // WM_Paint
this.DrawBorder();
break;
}
}

[DebuggerStepperBoundary()]
private void DrawBorder() {
using (Graphics g = base.CreateGraphics()) {
ControlPaint.DrawBorder(g, base.ClientRectangle,
                                   this.BorderColor, 1, ButtonBorderStyle.Solid,  // left
                                   this.BorderColor, 1, ButtonBorderStyle.Solid,  // top
                                   this.BorderColor, 1, ButtonBorderStyle.Solid,  // right
                                   this.BorderColor, 1, ButtonBorderStyle.Solid); // bottom

}
}

}


Código original, escrito en VB.NET:

PD: Logicamente ese código es un ejemplo, carente de muchas funcionalidades básicas, por ejemplo se podría adaptar para aplicar un color distitno en el caso de que el botón tenga foco o no lo tenga, o cuando está pulsado o no lo está.

Saludos.








Serapis

#2
Lo que comenta Elektro es totalmente correcto, sin embargo también es complicarse en exceso para alguien que está empezando y no conoce a fondo el lenguaje.

Tú puedes tener una función (o varias sobrecargas, de hecho mejor así, una para textos otra para números, etc... siempre que resulte cómodo y simple) que validen tus datos, correcto... pero no le veo mucho sentido a ir modificando cada vez el campo que no pase la validación (luego deberás volverlo a su estado original).

Al caso, es más fácil tener un control oculto, con el formato ya 'hecho' (por ejemplo con un borde de otro color, un fondo de otro, color el texto resaltado en negrita... pero dicho control, lo que se hace simplemente es que cuando falle una validación se desplaza hasta la ubicación del campo que falla, y se hace del mismo tamaño que el campo que falla se escribe el mismo texto que contiene y acto seguido se le hace visible... y se le entrega el foco...

...et voilá, el usuario lo ve como tu quieres, sin tocar el original, ahora el usuario introduce el texto en ese control, y se intenta validar, se repite hasta que la cumpla o cancele la operación, si pasa la validación entrega el texto al campo original (que llamó la validación y sigue a la espera de la respuesta de su llamada) y se oculta....
Esto, es más sencillo, no requiere redibujar a cada instante, visualmente es equivalente, ya está todo hecho solo hay un Move y Size, y una asignación de texto... todo my simple.

En pseudocódigo sería más o menos así (3 bloques de código):

La lógica de validar todos los campos validables del formulario...
buleano TodoValidado = true
Por cada campo en camposValidables del formulario
    String Texto = Campo.Texto
   
    Si Validar(Campo, Texto) = TRUE luego
       Campo.texto = texto
    Si no
       TodoValidado = FALSE
       Salir del bucle // fue cancelado...
    Fin si
Fin por cada

Si TodoValidado = TRUE
  // Siguiente paso, que debas hacer en este caso
Si no
  // lo que debas hacer si se canceló.
Fin si


Esta función recoge la lógica de validar expresada más arriba.
Esta función debería tener sobrecargas si hay que validar diferentes cosas aparte de texto o con criterios dispares...
Buleano = Funcion Validar(control Ctl, String Texto)
   CtlOculto.MoveAndSize(ctl.Left, ctl.Top, ctl.Width, ctl.Height)
   
   //primero se valida el texto original, solo si falla aparecerá el trabajo del control oculto.
   Mientras ValidarP(Texto) = FALSE
       Mensaje de error: "el texto introducido no es válido, corrijalo y luego pulse intro (validar) o   ESC (cancelar)
       CtlOculto.Texto = Texto
       Ctl.Visible = TRUE
       Ctl.SetFocoModal // se da oportunidad al usuario de cambiar el texto y al pulsar intro o Escape  regresa aqui (= MODAL, el código queda a la espera hasta que se cierre el objeto llamado, básicamente podría ser un formulario, si no es modal el código continuará tras la llamada previa y eso no interesa)
           
       // Si se pulsó intro, se debió cerrar el ctlOculto y volver a ser validado su texto.
       Si Tecla = Escape luego
           ctlOculto.Visible = FALSE  //Si el ctlOculto es formulario o está dentro de un formulario, esto no sería necesario, se habrá cerrado el formulario Modal...
           Devolver FALSE // se sale de la función desde aquí
       Fin si
   Repetir
   Texto = ctlOculto.Texto
   ctlOculto.Visible = FALSE //Si el ctlOculto es formulario o está dentro de un formulario, esto no sería necesario, se habrá cerrado el formulario Modal...
   Devolver TRUE
Fin Funcion


El código que hace realmente la validación iría aquí:
Buleano = ValidarT(Texto) //T de Texto...
   // validación del texto, lo que tu hagas para validarlo
   // Devuelve TRUE si es válido y FALSE si no lo es
Fin Funcion


Y más o menos esto te simplifica mucho tu tarea... el control ctlOculto, ya está con el formato de 'error' así no hay ni que redibujar cada control ni luego volverlo a redibujar de nuevo a su estado original.

p.d.: Yendo un poco más lejos, ni siquiera tienes que reposicionar y redimensionar el control oculto, basta montarlo en un fomrulario nuevo , como te convenga y mostrarlo igual que se haría con un "MessageBox" (de hecho sería eso, un messagebox personalizado) y mostrarlo centrado en la pantalla y más grande si es preciso, ahora con dos botones explíctos OK y Cancelar, pero además puedes añadir una etiqueta indicando que falló o que se espera que se escriba en dicho campo.
Las modificaciones al pseudocódigo para esto son evidentes, así que no veo preciso añadir nada más... para adaptarlo mas concretamente.

NoaC

Gracias, gracias, mil gracias!!

Amb@s lleváis razón; para ser novatilla me estoy complicando demasiado la vida y voy a curarme en salud.

¿Qué pasa con las expresiones regulares? Pues que yo antes era programadora web y tengo toda clase de expresiones regulares para reutilizar en la validación de campos como dni, nie, cif, email... Y eso me va a quitar bastante trabajo en un futuro si aprendo realmente a utilizar Regex en C#. Voy probando y no creo que sea difícil(esto no).

Lo del coloreado y tal... Me gusta el componente ErrorProvider que comentas, Elektro, y creo que lo voy a estudiar e implementar.
La solución que me das, NEBIRE, es también muy válida y la usaré quizá para otros campos de validación y si me lo piden...

Ya sería la repera si en el icono que muestra el ErrorProvider pudiese ponerse un atributo para que al pasar el ratón por encima te mostrase una frase, así como hace el atributo title en php... 

Saludos!!

Eleкtro

#4
Cita de: NoaC en 27 Junio 2017, 18:07 PM
Ya sería la repera si en el icono que muestra el ErrorProvider pudiese ponerse un atributo para que al pasar el ratón por encima te mostrase una frase, así como hace el atributo title en php...  

Jeje, pues si, te refieres a mostrar un Tooltip, y eso es precisamente una de sus funcionalidades... :-)

Sencillamente debes utilizar el método ErrorProvider.SetError(), a este método le pasas como argumento el control que falló en la validación, y el mensaje de error...

Ejemplo:
Código (csharp) [Seleccionar]
if (string.IsNullOrWhiteSpace(this.TextBox1.Text)) {
this.ErrorProvider1.SetError(this.TextBox1, "Invalid username.");
}




Saludos








NoaC

Perfecto!!

También estoy haciendo un híbrido entre expresiones y programación para validar los nif, cif y nie, aprovechando funciones que tenía en javascript y traduciéndolas a C#.

Por cierto, he visto, oído o lo que sea, que .NET puede relacionarse con javascript, es cierto?

Un saludo y Gracias de nuevo!!

Eleкtro

#6
Cita de: NoaC en 29 Junio 2017, 09:43 AM.NET puede relacionarse con javascript, es cierto?

Depende de lo que interpretes por "relacionarse". En la tecnología WinForms puedes instanciar un control WebBrowser que te permita ejecutar código escrito en javascript, y con el framework adecuado de terceros (ej. Telerik) para Visual Studio, puedes programar directamente en HTML5 suplementado con código escrito en javascript para el desarrollo de algunas tecnologías ( Android, y web-apps/ASP.NET, no sé si alguna más ) de manera multiplataforma.

Saludos