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#:
//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!!
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 ):
/// ----------------------------------------------------------------------------------------------------
/// <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)...
- https://pastebin.com/XhZ43vMU
Cita de: NoaC en 25 Junio 2017, 19:47 PM
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)
- Control.CreateGraphics() Function | MSDN (https://msdn.microsoft.com/en-us/library/system.windows.forms.control.creategraphics(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2)
- Graphics.FromHwnd() Function | MSDN (https://msdn.microsoft.com/en-us/library/system.drawing.graphics.fromhwnd(v=vs.110).aspx)
Te muestro un ejemplo de uso genérico que he elaborado con extensiones de método:
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:
myControl.DrawBorder(Color.Red, 1, ButtonBorderStyle.Solid);
Código original, escrito en VB.NET:
- https://pastebin.com/NsUxRNEm
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...
- System.Windows.Forms.ErrorProvider Class | MSDN (https://msdn.microsoft.com/en-us/library/system.windows.forms.errorprovider(v=vs.110).aspx)
(http://www.c-sharpcorner.com/article/using-error-provider-control-in-windows-forms-and-C-Sharp/Images/ErrorP1.gif)
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:
- System.Windows.Forms.VisualStyles.VisualStyleElement Class | MSDN (https://msdn.microsoft.com/en-us/library/system.windows.forms.visualstyles.visualstyleelement(v=vs.110).aspx)
- Window Styles | MSDN (https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx)
- Extended Window Styles | MSDN (https://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx)
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:
[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:
- https://pastebin.com/XH4eUnXX
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.
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.
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!!
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:
if (string.IsNullOrWhiteSpace(this.TextBox1.Text)) {
this.ErrorProvider1.SetError(this.TextBox1, "Invalid username.");
}
(http://i.imgur.com/98IQgFX.jpg)
Saludos
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!!
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