Pues eso… que lo pases genial en la despedida y que la entrada no sea muy resacosa que te tengo preparado un tocho para leer que…
¡Nos vemos en el 2019!
Guillermo
Pues eso… que lo pases genial en la despedida y que la entrada no sea muy resacosa que te tengo preparado un tocho para leer que…
¡Nos vemos en el 2019!
Guillermo
Esta sería la versión 2.0 del artículo que publiqué hace un par de días: Cambiar el tamaño de los controles de un formulario automáticamente, en esta ocasión lo que te voy a enseñar es cómo interceptar en los restantes formularios de la aplicación esos cambios y actuar consecuentemente, es decir, que también cambie el tamaño de sus controles. Y todo esto aunque el resto de formularios ya estén abiertos.
Nota:
Por si no te lo dije en el artículo anterior (es que es muy largo y me da pereza tener que leerlo al completo 🙂 ), para que los controles cambien de tamaño, el tipo de fuente debe ser la predeterminada, si usas una fuente definida de forma explícita, ese control no cambiará, salvo que hayas definido la propiedad Anchor.
En este segundo artículo lo comprobarás en uno de los controles, en el que el tamaño de la fuente está asignado de forma explícita.
Lo que hay que hacer para que esto funcione es definir un evento en el formulario principal (en el ejemplo que te mostraré aquí dicho formulario se llama Form1. Por tanto, si tu formulario principal se llama de otra forma, cambia Form1 por el nombre de tu formulario principal.
En Visual Basic se crea de forma predeterminada una colección con todos los formularios que tenemos en nuestro proyecto y no es necesario instanciarlos para acceder a ellos; pero como eso no es así en C# y te voy a mostrar el código tanto para C# como para Visual Basic, pues… el código lo escribiré para que sea re-utilizable en C#. Además de que de esta forma, creando y accediendo a los formularios por medio de una variable que nosotros instanciemos, nos evitamos el problema de tener (sin darnos casi cuenta) el mismo formulario abierto dos (o más) veces. Esto último debería resaltarlo, ya que creo que muchos usuarios de Visual Basic le puede ocurrir eso de tener el mismo formulario abierto más de una vez, una de forma automática al llamarlo directamente,. por ejemplo Form2.Show y la otra si previamente hemos creado una variable para acceder a dicho Form2. Ahora verás el ejemplo.
Si necesitamos acceder a la instancia en ejecución del formulario principal (en mi caso Form1) lo que yo hago es definir una variable compartida (Shared en VB y static en C#) llamada Current que es del mismo tipo que el formulario y que instanciamos, por ejemplo en el constructor.
Veamos el código de ejemplo que hay que añadir en el formulario principal (Form1) para definir esa instancia predeterminada y de paso crear el evento y demás métodos que serán necesarios para comunicar al resto de formularios que la fuente del formulario principal ha cambiado.
Empezaré con el código de Visual Basic y después con el de C#, que es algo diferente, al menos en la definición del evento. Pero igualmente te explicaré qué hace cada línea de código 😉
Nota:
Doy por hecho que tienes el código que puse en el artículo anterior, ya que aquí solo te diré (y mostraré) dónde tienes que añadir el código nuevo. Al menos en el Form1, ya que voy a crear otro formulario nuevo (Form2) y ese si te mostraré lo que debes hacer.
El código de Visual Basic a añadir al principio del formulario (en realidad da igual donde lo pongas, pero…)
Public Class Form1 ''' <summary> ''' Instancia del formulario principal ''' </summary> Public Shared Current As Form1 ''' <summary> ''' Evento para detectar el cambio de fuente ''' y avisar a los oyentes. ''' </summary> Public Event FuenteCambiada(fnt As Font) ''' <summary> ''' Método privado para lanzar el evento ''' </summary> Protected Sub OnFuenteCambiada(fnt As Font) RaiseEvent FuenteCambiada(fnt) End Sub Private Sub Form1_FontChanged(sender As Object, e As EventArgs) Handles _ Me.FontChanged OnFuenteCambiada(Me.Font) End Sub
Este es el mismo código pero para C#, ya sabes, casi lo mismo pero con puntos y comas 😉
public partial class Form1 : Form { /// <summary> /// Instancia del formulario principal /// </summary> public static Form1 Current; /// <summary> /// Evento para detectar el cambio de fuente /// y avisar a los oyentes. /// </summary> public event FuenteCambiadaEventHandler FuenteCambiada; public delegate void FuenteCambiadaEventHandler(Font fnt); /// <summary> /// Método privado para lanzar el evento /// </summary> protected void OnFuenteCambiada(Font fnt) { FuenteCambiada?.Invoke(fnt); //if (FuenteCambiada != null) // FuenteCambiada(fnt); } private void Form1_FontChanged(object sender, EventArgs e) { OnFuenteCambiada(this.Font); }
Como te dije, Current hará referencia al formulario Form1 que hayamos instanciado al iniciar la aplicación, el hacerlo compartido es para poder acceder usándolo de esta forma: Form1.Current. La asignación la tenemos que hacer en el constructor de la clase (Form1).
' ' El constructor del formulario ' Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. Form1.Current = Me
// // El constructor del formulario // public Form1() { // This call is required by the designer. InitializeComponent(); // Add any initialization after the InitializeComponent() call. Form1.Current = this; this.FontChanged += Form1_FontChanged;
En C# definimos el evento FontChanged del formulario, cosa que en Visual Basic no es necesario ya que añadiendo al final de la definición del método Handles Me.FontChanged es suficiente.
Sigamos con las explicaciones del código de los listados anteriores.
Definimos un evento llamado FuenteCambiada que define un argumento de tipo Font, en el método OnFuenteCambiada invocamos a dicho evento. En el código de C# te muestro las dos formas de hacerlo, la abreviada y la «clásica».
Ese evento lo lanzaremos cuando la fuente del formulario principal cambie y precisamente el evento FontChanged del formulario es el que nos avisa de que la fuente ha cambiado.
Y esto es todo lo que debes cambiar en el formulario principal.
Ahora vamos a crear un nuevo formulario llamado Form2.
En tiempo de diseño tendrá el siguiente aspecto (figura 1)
Ajusta los valores de Anchor tal como te indico a continuación:
La caja de texto es: Top, Left, Right, el botón: Top, Right y el RichTextBox es: Top, Bottom, Left, Right.
Salvo el RichTextBox, todos los controles tienen la fuente predeterminada, la caja de texto enriquecido lo he puesto con esta fuente: Segoe Script; 36pt; style=Bold.
Los nombres de los controles son: txtSaludo, RichTextBox1 y btnMostrar.
Y este es el código que tendrás que poner en ese formulario o en cualquier otro en el que quieras que se cambien las fuentes al cambiarlas en el formulario principal.
Te muestro primero el código de Visual Basic y después el de C#.
Public Class Form2 '-------------------------------------------------------------------------- ' Código a añadir a cada formulario ' en los que queramos que sincronicen la fuente ' ' Solo cambiar Form1 por el nombre del formulario ' que define el evento FuenteCambiada ' Private Sub FuenteCambiada(fnt As Font) Me.Font = fnt End Sub Private Sub FuenteCambiada_Load(sender As Object, e As EventArgs) Handles MyBase.Load Me.Font = Form1.Current.Font AddHandler Form1.Current.FuenteCambiada, AddressOf FuenteCambiada End Sub Private Sub FuenteCambiada_FormClosing(sender As Object, e As FormClosingEventArgs) Handles _ MyBase.FormClosing RemoveHandler Form1.Current.FuenteCambiada, AddressOf FuenteCambiada End Sub ' ' Fin del código para sincronizar la fuente '-------------------------------------------------------------------------- Private Sub btnMostrar_Click(sender As Object, e As EventArgs) Handles btnMostrar.Click RichTextBox1.Text = txtSaludo.Text End Sub
public partial class Form2 : Form { public Form2() { InitializeComponent(); //Form1.Current.FuenteCambiada += FuenteCambiada; this.Load += FuenteCambiada_Load; this.FormClosing += FuenteCambiada_FormClosing; } // -------------------------------------------------------------------- // Código a añadir a cada formulario // en los que queramos que sincronicen la fuente // // Solo cambiar Form1 por el nombre del formulario // que define el evento FuenteCambiada // private void FuenteCambiada(Font fnt) { this.Font = fnt; } private void FuenteCambiada_Load(object sender, EventArgs e) { this.Font = Form1.Current.Font; Form1.Current.FuenteCambiada += FuenteCambiada; } private void FuenteCambiada_FormClosing(object sender, FormClosingEventArgs e) { Form1.Current.FuenteCambiada -= FuenteCambiada; } // // Fin del código para sincronizar la fuente // -------------------------------------------------------------------- private void btnMostrar_Click(object sender, EventArgs e) { RichTextBox1.Text = txtSaludo.Text; }
¡Y esto es todo lo que hay que hacer!
Si ejecutas el programa (recuerda que puedes usar el código que te puse en el artículo anterior y solo añadir el nuevo formulario con el código que te acabo de mostrar).
En la figura 2 puedes ver cómo el segundo formulario (Form2) muestra el tamaño que hayamos indicado en el formulario principal (Form1).
Esto lo consigues cambiando el tamaño en el formulario principal.
Como ves el segundo formulario muestra las fuentes y tamaño de los controles según el porcentaje indicado en el formulario principal. El ojo ese que ves parcialmente es de la foto que tengo de fondo de escritorio 😉
Fíjate también que el RichTextBox solo se ha adaptado al nuevo tamaño del formulario, pero la fuente es del mismo tamaño que estaba definida en tiempo de diseño. Recuerda lo que te dije antes: sólo se cambian las fuente que tiene predeterminadas el diseñador de formularios de Visual Studio.
Solo me falta mostrarte cómo llamar al segundo formulario. Eso lo haremos desde el botón Mostrar Form2 que hay bajo Restablecer fuente. El código es el siguiente:
Private Sub btnForm2_Click(sender As Object, e As EventArgs) Handles btnForm2.Click Dim f2 As New Form2 f2.Font = Me.Font f2.Show() End Sub
private void btnForm2_Click(object sender, EventArgs e) { var f2 = new Form2(); f2.Font = this.Font; f2.Show(); }
Y ahora sí, esto es todo… después (o más tarde) te pongo el enlace con la solución para Visual Studio 2017 con la aplicación completa, tanto para Visual Basic como para C#.
Espero que te sea de utilidad, esa es siempre la intención 😉
Nos vemos.
Guillermo
El ZIP con el código completo (una solución de Visual Studio 2017 con los proyectos de Visual Basic y C#
El zip: Cambiar_tamano_controles_v2.zip (260 KB)
MD5 Checksum: D9C7439F1580CE64228383E7B66BDADA
Pues eso… que lo que publiqué anteayer sobre ajustar automáticamente la fuente de un formulario según la configuración del sistema (Cambiar automáticamente las fuentes de nuestro formulario a las de Windows), estaba muy bien, pero no era lo que yo andaba buscando… yo lo que quería era que los controles también cambiaran.
Y me puse a ello… como soy un poquillo torpe, ya sabes, no tengo estudios informáticos ni de otros tipos, pues… empecé a escribir el código para cambiar cada uno de los controles del formulario, y por supuesto también el del formulario, ahí si llego ;-). Sí, lo hice, escribí todo el código, con métodos recursivos, etc. En fin… :-/
La cuestión es que pensé que también estaría guay (cool) poder cambiar las fuentes, así todo estaría más grande… o más pequeño, ya que en el formulario de prueba (ver figura 1) puse la opción de ampliar o reducir.
Pero aquello era un caos, cuando ampliaba, lo hacía según el ancho y el alto y claro, si los controles no tienen todos los Anchor necesarios, pues… se te quedaba espacio vacío por aquí y por allá… no tengo capturas (¡gracias a Dios! si no, ya no vendrás más a visitarme 😛 )
Con decirte que hice hasta una clase para que manejase el tema este del cambio de los controles y así hacerlo más cool… con un evento que indicaba que se estaban haciendo los cambios, etc.; sí, lo que yo te diga, en fin…
Bueno, vale… otro día hago la captura y así ves cómo se puede uno embrollar con lo que después resultó ser algo muy simple de codificar.
Pues eso, la solución, para esto de que se cambien los controles de tamaño según indiquemos, es muy simple, tanto que… bueno… mejor me callo… ¡ay zeñó!
Y todo surgió porque los controles quedaban muy mal al cambiar el tamaño, y me dio por probar (esto aún podría estar en la batallita del Guille, pero bueno…) quitando el cambio del tamaño del formulario y de los controles, y dejar solo el cambio del tamaño de la fuente y… ¡voilà, todo va a la perfección!. No sabes la de cabezazos que me hubiera dado contra la pared… menos mal que ahora me he pelado al 2 y no era plan… 🙂
Te muestro un par de capturas con el 25% de ampliación y el 50% para que te hagas una idea de lo bien que funciona 🙂 y después te muestro el código tanto para Visual Basic como para C#.
Nota:
Según parece, la imagen 1 y 2 son iguales, pero si te fijas en la figura 3, en la que muestro el IDE de Visual Studio, comprobarás que el tamaño del formulario es significativamente más grande que el mostrado en el diseñador de VS.Pulsa en las imágenes para verlas en grande.
Comentarte que todo está en cambiar el tamaño de la fuente del formulario, por tanto debes tener asignado el valor Font a la propiedad AutoScaleMode del formulario (es el valor predeterminado al crear un nuevo formulario).
'-------------------------------------------------------------------------- ' Métodos para cambiar el tamaño del formulario ' y de sus controles '-------------------------------------------------------------------------- ''' <summary> ''' Cambia el tamaño del formulario (o control) indicado, ''' ampliando o reduciendo según un porcentaje. ''' Recomendado es solo cambiar el formulario. ''' </summary> Private Sub cambia(ctr As Control, ampliar As Boolean, por As Integer) If ampliar = False Then por = -por End If '---------------------------------------------------------------------- ' El ancho y alto es mejor no cambiarlo, ' las proporciones de los controles se verán bien ' pero si el Anchor no está "pensado" para todos ' los controles, quedarán espacios vacíos con ' respecto al diseño original. ' Al cambiar la fuente del formulario, ' el aspecto original no se pierde. '---------------------------------------------------------------------- '---------------------------------------------------------------------- ' Al cambiar el tamaño de la fuente del formulario ' los controles se adaptan al nuevo tamaño de fuente, ' sin perder el aspecto del diseño original. ' ' El formulario debe tener asignada la propiedad ' AutoScaleMode = Font '---------------------------------------------------------------------- ' ' Se debería poner un máximo y mínimo a las fuentes ' Dim fntSize = ctr.Font.Size fntSize = calculaPorcentaje(fntSize, por) ' No admitir valores menores de uno ' ni valores mayores de 3 veces la fuente If fntSize < 1 OrElse fntSize > ctr.Font.Size * 3 Then fntSize = ctr.Font.Size End If ctr.Font = New Font(ctr.Font.FontFamily, fntSize, ctr.Font.Style, ctr.Font.Unit, ctr.Font.GdiCharSet, ctr.Font.GdiVerticalFont) End Sub ''' <summary> ''' Calcula el porcentaje a partir de un valor Single, ''' para el tamaño de la fuente. ''' </summary> Private Function calculaPorcentaje(valor As Single, porcentaje As Integer) As Single Return valor + (valor * porcentaje / 100) End Function
// -------------------------------------------------------------------------- // Métodos para cambiar el tamaño del formulario // y de sus controles // -------------------------------------------------------------------------- /// <summary> /// Cambia el tamaño del formulario (o control) indicado, /// ampliando o reduciendo según un porcentaje. /// Recomendado es solo cambiar el formulario. /// </summary> private void cambia(Control ctr, bool ampliar, int por) { if (ampliar == false) por = -por; // ---------------------------------------------------------------------- // El ancho y alto es mejor no cambiarlo, // las proporciones de los controles se verán bien // pero si el Anchor no está "pensado" para todos // los controles, quedarán espacios vacíos con // respecto al diseño original. // Al cambiar la fuente del formulario, // el aspecto original no se pierde. // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- // Al cambiar el tamaño de la fuente del formulario // los controles se adaptan al nuevo tamaño de fuente, // sin perder el aspecto del diseño original. // // El formulario debe tener asignada la propiedad // AutoScaleMode = Font // ---------------------------------------------------------------------- // // Se debería poner un máximo y mínimo a las fuentes // var fntSize = ctr.Font.Size; fntSize = calculaPorcentaje(fntSize, por); // No admitir valores menores de uno // ni valores mayores de 3 veces la fuente if (fntSize < 1 || fntSize > ctr.Font.Size * 3) fntSize = ctr.Font.Size; ctr.Font = new Font(ctr.Font.FontFamily, fntSize, ctr.Font.Style, ctr.Font.Unit, ctr.Font.GdiCharSet, ctr.Font.GdiVerticalFont); } /// <summary> /// Calcula el porcentaje a partir de un valor Single, /// para el tamaño de la fuente. /// </summary> private float calculaPorcentaje(float valor, int porcentaje) { return valor + (valor * porcentaje / 100); }
Al método cambia le pasamos el control o formulario (recomendable el formulario) en el que queremos hacer el cambio de tamaño, el valor de ampliar es si queremos ampliar (true) o reducir (false) y por es el porcentaje, que es un valor entero y puede ser 0 (cero) para no hacer nada o dejarlo como estaba, o cualquier otro tamaño (no te recomiendo un valor mayor de 175 si no, pues… te faltará pantalla… 🙂
Como puedes comprobar, he puesto una comprobación para que el tamaño de la fuente no sea menor de 1 ni mayor de 3 veces el tamaño inicial.
Si quieres cambiarlo, hazlo, pero al menos deja la comprobación de que no sea menor de uno (o cero) ya que so es cero (0) te dará error.
Y ahora pasemos al código que he usado para cambiar el tamaño del formulario y sus controles.
En el ejemplo que he hecho (ver cualquiera de las figuras) he puesto la opción de Ampliar o Reducir, un combo con los porcentajes a usar y el botón Cambiar para aplicar el cambio.
También he puesto un botón para restaurar el tamaño al inicial (Restablecer fuente) pero en realidad no es necesario, ya que al indicar 0% se deja todo como estaba al inicio.
Lo que si hago es que ese porcentaje sea fijo, es decir, siempre que pulses en, por ejemplo, 50% se cambiará al 50% del valor inicial.
Te aclaro esto porque inicialmente (ya sabes: la torpeza del Guille) ese porcentaje lo aplicaba al valor que ya hubiese de antes, por tanto, si inicialmente lo habías ampliado al 10% y después seleccionabas el 50% este último se aplicaba sobre la ampliación (o reducción) anterior, y… bueno, que no quedaba nada bien.
Para conseguir esto, que siempre se aplique el porcentaje según la fuente inicial, he creado una variable llamada miWinFont, esa es la fuente que tiene el sistema, ya que he usado el mismo código que te mostré en el artículo anterior (Cambiar automáticamente las fuentes de nuestro formulario a las de Windows) para utilizar la fuente indicada en Windows.
La asignación de esa variable se hace al inicio del programa (en el constructor) y después se usa para reiniciar el tamaño de la fuente antes de hacer el cambio, ahora lo verás en el código.
Como tip te puedo decir que puedes usar otra forma de hacerlo, por ejemplo usando el valor de fntSize a partir del valor de miWinFont en vez de la fuente del formulario, pero no he cambiado el código porque eso se me acaba de ocurrir mientras escribo el artículo, y ya no es plan de cambiar el código 😉
Vamos a lo que vamos.
Empecemos con el código de Visual Basic, pero tanto en el de VB como en el de C# lo que hago es lo mismo: hacer el cambio usando el evento Click del botón Cambiar.
' ' Los métodos de evento para el cambio de la fuente ' Private Sub btnCambiarTamaño_Click(sender As Object, e As EventArgs) Handles _ btnCambiarTamaño.Click ' Cambiar solo el tamaño de la fuente del formulario txtInfo.Text = "Cambiando el tamaño de los controles..." Application.DoEvents() Me.Hide() ' restablecer a la fuente inicial Me.Font = miWinFont ' hacer el cambio de tamaño cambia(Me, optAmpliar.Checked, CInt(cboTamaños.SelectedItem)) Me.Show() txtInfo.Text = "Cambiado el tamaño de los controles." Application.DoEvents() End Sub Private Sub btnRestablecerFuente_Click(sender As Object, e As EventArgs) Handles _ btnRestablecerFuente.Click ' Restablecer siempre a la fuente del sistema ' si no queremos usar la del sistema, ' asignar al valor de miFuente (la original al diseñar) Me.Font = miWinFont End Sub
Ahora después te muestro dónde declaro miWinFont y dónde la asigno.
// // Los métodos de evento para el cambio de la fuente // private void btnCambiarTamaño_Click(object sender, EventArgs e) { // Cambiar solo el tamaño de la fuente del formulario txtInfo.Text = "Cambiando el tamaño de los controles..."; Application.DoEvents(); this.Hide(); // restablecer a la fuente inicial this.Font = miWinFont; // hacer el cambio de tamaño cambia(this, optAmpliar.Checked, Convert.ToInt32(cboTamaños.SelectedItem)); this.Show(); txtInfo.Text = "Cambiado el tamaño de los controles."; Application.DoEvents(); } private void btnRestablecerFuente_Click(object sender, EventArgs e) { // Restablecer siempre a la fuente del sistema // si no queremos usar la del sistema, // asignar al valor de miFuente (la original al diseñar) this.Font = miWinFont; }
Este es el código donde se define y se asigna miWinFont.
Adivina cuál es el de VB y cuál el de C# 😉
Public Class Form1 ''' <summary> ''' La fuente del sistema ''' </summary> Private miWinFont As Font [...] ' ' El constructor del formulario ' Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. miFont = Me.Font Me.Font = SystemFonts.IconTitleFont AddHandler SystemEvents.UserPreferenceChanged, AddressOf SystemEvents_UserPreferenceChanged miWinFont = Me.Font infoFuentes() End Sub ' ' Los métodos de evento para interactuar con la configuración de Windows ' Private Sub SystemEvents_UserPreferenceChanged(ByVal sender As Object, ByVal e As UserPreferenceChangedEventArgs) If e.Category = UserPreferenceCategory.Window Then Me.Font = SystemFonts.IconTitleFont miWinFont = Me.Font End If mostrarInfoForm() txtInfo.Text = String.Format("UserPreferenceChanged.Category = {0} ({1:HH:mm:ss}){2}{3}", e.Category.ToString(), Date.Now, Microsoft.VisualBasic.vbCrLf, txtInfo.Text) End Sub
public partial class Form1 : Form { /// <summary> /// La fuente del sistema /// </summary> private Font miWinFont; [...] // // El constructor del formulario // public Form1() { // This call is required by the designer. InitializeComponent(); // Add any initialization after the InitializeComponent() call. miFont = this.Font; this.Font = SystemFonts.IconTitleFont; SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; this.FormClosing += new FormClosingEventHandler(Form1_FormClosing); // Para que tenga dos m\f2étodos de evento el evento Load this.Load += new System.EventHandler(this.Form1_Load); miWinFont = this.Font; infoFuentes(); } // // Los métodos de evento para interactuar con la configuración de Windows // private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { if (e.Category == UserPreferenceCategory.Window) { this.Font = SystemFonts.IconTitleFont; miWinFont = this.Font; } mostrarInfoForm(); txtInfo.Text = string.Format("UserPreferenceChanged.Category = {0}"+ "({1:HH:mm:ss}){2}{3}", e.Category.ToString(), DateTime.Now, "\r\n", txtInfo.Text); }
Nota:
No te preocupes por la definición de los métodos infoFuentes ni mostrarInfoForm, ese código está en el ZIP que te pondré al final con el código completo.
Y esto es todo… ya ves qué simple… 🙂
Espero que te sea de utilidad, ¡’esa es siempre la idea!
Nos vemos.
Guillermo.
P.S.
Comentarte que en el código completo (sí, para VB y C#) la aplicación lo que hace es permitir que selecciones un fichero (archivo) y te muestre las propiedades, nombre completo, directorio, fecha de creación, etc.
El ZIP con el código completo (una solución de Visual Studio 2017 con los proyectos de Visual Basic y C#
ZIP: Cambiar tamaño controles.zip (254 KB)
MD5 Checksum: 0B5DDD8A525D61647F10070EFC198DCA
P.S. 2 (31/Dic/18)
Aquí tienes un ejemplo de cómo hacer que el resto de formularios de nuestra aplicación utilicen el tamaño asignado en el formulario principal:
Interceptar el cambio del tamaño de los controles de un formulario en otros formularios de la aplicación.
Pues eso… que como ahora «ando liado» con dos monitores, uno, el principal en el portátil (laptop) y el segundo uno de más resolución, pues… resulta que quiero que en el monitor se vean las fuentes más grandes y también el formulario y los controles, y antes de empezar a hacer «manualidades» (adaptar las cosas por la cuenta de la vieja, es decir, a mano) me he decidido a buscar en Internet a ver si había algo y resulta que sí, que lo hay (o casi), seguramente habrá más cosas pero lo que en principio me ha parecido una buena opción es lo que he encontrado en Cómo: Responder a los cambios de las combinaciones de fuentes en una aplicación de Windows Forms, concretamente en la sección: Para usar la fuente del escritorio y responder a los cambios de esquema de fuentes y concretamente el código mostrado en: Para cambiar manualmente la combinación de fuentes en Windows XP.
Por supuesto el código de ejemplo solo se muestra en C#, pero… no te preocupes aquí estoy yo para mostrarte cómo hacer eso que ahí dice en Visual Basic .NET (sí, y también en C# 😉 )
El código básicamente es como está en esa página que te he indicado, lo único que yo he añadido al ejemplo que te voy a poner es la opción de mostrar las fuentes originales (iniciales del formulario) o bien usar las que Windows te indique.
Para que te hagas una idea de lo que el código hace, te muestro dos capturas del formulario (Form1) en ejecución, la figura 1 es con las letras «normales» y la segunda captura (figura 2) es usando el código que hace que se adapte a las fuentes de Windows.
Nota:
Este código solo cambia el tamaño de las fuentes del formulario, no la de los controles.
Al cambiar la fuente del formulario, este cambia también de tamaño y «reubica los controles».
No te asustes porque haya muchos controles 😉
Es que ese formulario lo tengo para hacer unas pruebas para convertir de Windows Forms a WPF / XAML y tengo que probar con prácticamente todos los controles (al menos los que yo suelo usar), lo que importa es que veas que el formulario de la figura 2 ha cambiado de tamaño y también el contenido de algunos de los controles (salvo los menús y la barra de botones).
No te voy a explicar en detalle cómo funciona esto, ya que en la página que te indiqué ya lo hacen 🙂 pero si te voy a mostrar el código necesario para que funcione así.
Si estás usando Visual Basic tendrás que agregar el código del constructor (Sub New) (el IDE de Visual Studio agrega el contenido necesario de forma automática) y añadimos las siguientes líneas de código después de la llamada a InitializeComponent:
Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. Me.Font = SystemFonts.IconTitleFont AddHandler SystemEvents.UserPreferenceChanged, AddressOf SystemEvents_UserPreferenceChanged End Sub
public Form1() { InitializeComponent(); this.Font = SystemFonts.IconTitleFont; SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged); this.FormClosing += new FormClosingEventHandler(Form1_FormClosing); }
Si te fijas un poco, en el código de VB no he puesto el «manenejador de eventos» para el evento Form.Closing, ya que no es necesario, aunque se podría haber hecho como en C#, pero si Visual Basic facilita la creación de eventos, ¿por qué no usarla? (ya sabes, en la declaración del evento se añade la cláusula Handles y a continuación el nombre del evento a «capturar».
Te explico brevemente lo que hace ese código del constructor:
Asigna a la fuente del formulario la que hay definida en Windows, esto funciona desde Windows XP hasta el actual Windows 10 (actual en las fechas que estoy escribiendo esto, es decir el 27 de diciembre de 2018).
Después añade el manejador de eventos para la clase UserPreferenceChanged de SystemEvents, que es una clase definida en el espacio de nombres Microsoft.Win32, por tanto en el formulario hay que importar ese espacio de nombres, ya sabes:
Imports Microsoft.Win32 para Visual Basic o using Microsoft.Win32; para C#.
En C# también añade el controlador de eventos para el evento Form.Closing.
En realidad en el evento Form.Closing solo se desliga el manejador de eventos para UserPreferenceChanged.
Aquí tienes el código para VB y C#.
Private Sub SystemEvents_UserPreferenceChanged(ByVal sender As Object, ByVal e As UserPreferenceChangedEventArgs) If e.Category = UserPreferenceCategory.Window Then Me.Font = SystemFonts.IconTitleFont End If End Sub Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing RemoveHandler SystemEvents.UserPreferenceChanged, AddressOf SystemEvents_UserPreferenceChanged End Sub
void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { if (e.Category == UserPreferenceCategory.Window) { this.Font = SystemFonts.IconTitleFont; } } void Form1_FormClosing(object sender, FormClosingEventArgs e) { SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged); }
Y esto es todo amigo 🙂
Si me decido a poner algún ejemplo completo, actualizaré esta «entrada» (o post) poniendo el enlace para la descarga.
Espero que te sirva 😉
Nos vemos.
Guillermo
P.S.
Si quieres que los controles de tu formulario cambien
al tamaño que tu indiques (un porcentaje), mira esta entrada:
Cambiar el tamaño de los controles de un formulario automáticamente
Pues eso… lo prometido es deuda y aquí tienes el código completo de cómo mostrar todos los formularios de una aplicación (ensamblado) y cómo mostrar un formulario en el segundo monitor.
Al final de todo te pongo el enlace para descargar los proyectos tanto de Visual Basic como de C#.
¡Que te aproveche!
Estas son las capturas (en funcionamiento) de Visual Basic y C#:
Figura 1, la aplicación de Visual Basic
'------------------------------------------------------------------------------ ' Mostrar todos los formularios de una aplicación (ensamblado) (16/Dic/18) ' Estén abiertos o no ' Usando reflection ' ' Este código está basado en el artículo que publiqué el 13/Sep/2004 en: ' http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm ' ' ' (c) Guillermo (elGuille) Som, 2018 '------------------------------------------------------------------------------ Option Strict On Option Infer On Imports Microsoft.VisualBasic Imports vb = Microsoft.VisualBasic Imports System Imports System.Text Imports System.Collections.Generic Imports System.Windows.Forms Imports System.Drawing Imports System.Diagnostics Imports System.Linq Public Class Form1 ''' <summary> ''' Para recorrer todos los formularios de un ensamblado ''' estén abiertos o no ''' Basado en el código del elGuille.info: ''' http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm ''' </summary> Private ass As System.Reflection.Assembly ''' <summary> ''' Mostrar todos los formularios de la aplicación actual, ''' estén o no en memoria, usando reflection. ''' Además de los formularios, mostrará: ''' My.Application (solo en VB) ''' Y todos los nombres esmpiezan con el espacio de nombres ''' ESPACIO_DE_NOMBRES.NombreForm ''' </summary> Private Sub mostrarForms() ' llena una colección con los formularios de esta aplicación ' estén o no en memoria. ' Muestra el resultado en un listbox lbForms.Items.Clear() For Each t As Type In ass.GetTypes() Dim nombreTipo = t.BaseType.Name ' También tendrá My.Application: (solo en VB) ' <espacio de nombres>.My.MyApplication If nombreTipo.ToLower().Contains("form") Then lbForms.Items.Add(t.FullName) End If Next End Sub ''' <summary> ''' Muestra el formulario indicado en el argumento, ''' este debe ser con el espacio de nombres completo ''' ''' Si es el actual no lo muestra. ''' Si da error o no es un formulario se avisa. ''' </summary> Private Sub mostrarFormulario(s As String) ' creamos un tipo a partir del nombre Dim t As Type = ass.GetType(s) ' instanciamos un nuevo objeto en la memoria Dim o As Object ' por si hemos seleccionado algo que no es una clase Try o = Activator.CreateInstance(t) Catch ex As Exception MessageBox.Show(ex.Message, "Mostrar formularios") Exit Sub End Try ' si no es un formulario, mostramos un aviso y salimos If Not (TypeOf o Is Form) Then MessageBox.Show(s & ", no es un formulario", "Mostrar formularios") Exit Sub End If ' convertimos el objeto en un formulario ' como sabemos que si llega aquí es un formulario, ' usamos TryCast que hace menos trabajo que CType o DirectCast. Dim f As Form = TryCast(o, Form) If f Is Nothing Then MessageBox.Show(s & ", parece que no es un formulario", "Mostrar formularios") Exit Sub End If ' si el nombre es el de este formulario, ' lo cerramos y salimos. If f.Name = Me.Name Then ' no volver a crear este formulario 'f.Close() Me.BringToFront() MessageBox.Show(s & " es el formulario de inicio.", "Mostrar formularios") Return End If ' mostramos el formulario. f.Show() End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Mostrar esta ventana en el monitor secundario MostrarEnVentana2(Me) ' mostrar los formularios de esta aplicación ass = System.Reflection.Assembly.GetExecutingAssembly() mostrarForms() End Sub Private Sub btnAbrir_Click(sender As Object, e As EventArgs) Handles btnAbrir.Click ' Por si no hay un nombre indicado If String.IsNullOrWhiteSpace(txtForm.Text) Then Return ' Abrir el formulario indicado en el textbox mostrarFormulario(txtForm.Text) End Sub Private Sub btnSalir_Click(sender As Object, e As EventArgs) Handles btnSalir.Click Me.Close() End Sub Private Sub lbForms_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lbForms.SelectedIndexChanged txtForm.Text = lbForms.SelectedItem.ToString End Sub ''' <summary> ''' Mostrar el formulario indicado en la segunda pantalla ''' Código adaptado de la respuesta 42 de: ''' https://stackoverflow.com/questions/2561104/how-do-i-ensure-a-form-displays-on-the-additional-monitor-in-a-dual-monitor-sc ''' </summary> Public Shared Sub MostrarEnVentana2(frm As Form) Dim myScreen = Screen.PrimaryScreen Dim otherScreen = If(Screen.AllScreens.FirstOrDefault( Function(s) Not s.Equals(myScreen)), myScreen) ' Si queremos indicar dónde mostrarlo ' podemos cambiar los valores de Left y Top 'frm.Left = otherScreen.WorkingArea.Left + 12 'frm.Top = otherScreen.WorkingArea.Top + 12 End Sub Private Sub btnForm2_Click(sender As Object, e As EventArgs) Handles btnForm2.Click Dim f As New Form2 f.Show() End Sub Private Sub btnForm3_Click(sender As Object, e As EventArgs) Handles btnForm3.Click Dim f As New Form3 f.Show() End Sub End Class
// ---------------------------------------------------------------------------- // Mostrar todos los formularios de una aplicación (ensamblado) (16/Dic/18) // Estén abiertos o no // Usando reflection // // Este código está basado en el artículo que publiqué el 13/Sep/2004 en: // http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm // // // (c) Guillermo (elGuille) Som, 2018 // ---------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Drawing; namespace Mostrar_Nombres_Formularios_cs { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// Para recorrer todos los formularios de un ensamblado /// estén abiertos o no /// Basado en el código del elGuille.info: /// http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm /// </summary> private System.Reflection.Assembly ass; /// <summary> /// Mostrar todos los formularios de la aplicación actual, /// estén o no en memoria, usando reflection. /// Además de los formularios, mostrará: /// My.Application (solo en VB) /// Y todos los nombres esmpiezan con el espacio de nombres /// ESPACIO_DE_NOMBRES.NombreForm /// </summary> private void mostrarForms() { // llena una colección con los formularios de esta aplicación // estén o no en memoria. // Muestra el resultado en un listbox lbForms.Items.Clear(); foreach (Type t in ass.GetTypes()) { var nombreTipo = t.BaseType.Name; // También tendrá My.Application: (solo en VB) // <espacio de nombres>.My.MyApplication if (nombreTipo.ToLower().Contains("form")) lbForms.Items.Add(t.FullName); } } /// <summary> /// Muestra el formulario indicado en el argumento, /// este debe ser con el espacio de nombres completo /// /// Si es el actual no lo muestra. /// Si da error o no es un formulario se avisa. /// </summary> private void mostrarFormulario(string s) { // creamos un tipo a partir del nombre Type t = ass.GetType(s); // instanciamos un nuevo objeto en la memoria object o; // por si hemos seleccionado algo que no es una clase try { o = Activator.CreateInstance(t); } catch (Exception ex) { MessageBox.Show(ex.Message, "Mostrar formularios"); return; } // si no es un formulario, mostramos un aviso y salimos if (!(o is Form)) { MessageBox.Show(s + ", no es un formulario", "Mostrar formularios"); return; } // convertimos el objeto en un formulario Form f = o as Form; if (f == null) { MessageBox.Show(s + ", parece que no es un formulario", "Mostrar formularios"); return; } // si el nombre es el de este formulario, // lo cerramos y salimos. if (f.Name == this.Name) { // no volver a crear este formulario // f.Close() this.BringToFront(); MessageBox.Show(s + " es el formulario de inicio.", "Mostrar formularios"); return; } // mostramos el formulario. f.Show(); } private void Form1_Load(object sender, EventArgs e) { // Mostrar esta ventana en el monitor secundario MostrarEnVentana2(this); // mostrar los formularios de esta aplicación ass = System.Reflection.Assembly.GetExecutingAssembly(); mostrarForms(); } private void btnAbrir_Click(object sender, EventArgs e) { //' Por si no hay un nombre indicado if (String.IsNullOrWhiteSpace(txtForm.Text)) return; //' Abrir el formulario indicado en el textbox mostrarFormulario(txtForm.Text); } private void btnSalir_Click(object sender, EventArgs e) { this.Close(); } private void lbForms_SelectedIndexChanged(object sender, EventArgs e) { txtForm.Text = lbForms.SelectedItem.ToString(); } /// <summary> /// Mostrar el formulario indicado en la segunda pantalla /// Código adaptado de la respuesta 42 de: /// https://stackoverflow.com/questions/2561104/how-do-i-ensure-a-form-displays-on-the-additional-monitor-in-a-dual-monitor-sc /// </summary> public static void MostrarEnVentana2(Form frm) { var myScreen = Screen.PrimaryScreen; var otherScreen = Screen.AllScreens.FirstOrDefault(s => !s.Equals(myScreen)) ?? myScreen; //' Si queremos indicar dónde mostrarlo //' podemos cambiar los valores de Left y Top //'frm.Left = otherScreen.WorkingArea.Left + 12 //'frm.Top = otherScreen.WorkingArea.Top + 12 } } }
Archivo: Mostar_Nombres_Formularios_20181216_2346.zip (204 KB)
MD5 Checksum: DA004D15933BD82355684AC0F20680B1
Nos vemos.
Guillermo
Pues eso… que estoy haciendo una aplicación para convertir los Windows Forms en WPF (Xaml) y tengo que recorrer todos los formularios de la aplicación para ir convirtiéndolos y he buscado en Internet cómo hacer eso y… ¡no lo he encontrado!
Así que… me dije que yo tenía algún código haciendo Reflection para mostrar una clase y va y resulta que en ese ejemplo tenía la respuesta que buscaba… ¡lo que no esté en el sitio del Guille!
Concretamente en esta página:
Las clases de un ensamblado usando Reflection (por ejemplo para saber los formularios de nuestra aplicación)
Y para que no me vuelva a ocurrir lo mismo en un futuro (ese artículo lo escribí el 13 de septiembre de 2004… ¡casi ná! ) me he decidido a escribir este nuevo…
No te voy a explicar mucho (eso ya lo hice en ese artículo) solo te mostraré el código de ejemplo (y te pondré el proyecto para que lo puedas descargar).
Y como siempre el código tanto para Visual Basic como para C#.
Primero te muestro el código que recorre los formularios del ensamblado o aplicación (que es lo que realmente te puede interesar) y después te mostraré el resto.
''' <summary> ''' Para recorrer todos los formularios de un ensamblado ''' estén abiertos o no ''' Basado en el código del elGuille.info: ''' http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm ''' </summary> Private ass As System.Reflection.Assembly ''' <summary> ''' Mostrar todos los formularios de la aplicación actual, ''' estén o no en memoria, usando reflection. ''' Además de los formularios, mostrará: ''' My.Application (solo en VB) ''' Y todos los nombres esmpiezan con el espacio de nombres ''' ESPACIO_DE_NOMBRES.NombreForm ''' </summary> Private Sub mostrarForms() ' llena una colección con los formularios de esta aplicación ' estén o no en memoria. ' Muestra el resultado en un listbox lbForms.Items.Clear() For Each t As Type In ass.GetTypes() Dim nombreTipo = t.BaseType.Name ' También tendrá My.Application: (solo en VB) ' <espacio de nombres>.My.MyApplication If nombreTipo.ToLower().Contains("form") Then lbForms.Items.Add(t.FullName) End If Next End Sub
/// <summary> /// Para recorrer todos los formularios de un ensamblado /// estén abiertos o no /// Basado en el código del elGuille.info: /// http://www.elguille.info/NET/dotnet/reflectionTiposdeunensamblado.htm /// </summary> private System.Reflection.Assembly ass; /// <summary> /// Mostrar todos los formularios de la aplicación actual, /// estén o no en memoria, usando reflection. /// Además de los formularios, mostrará: /// My.Application (solo en VB) /// Y todos los nombres esmpiezan con el espacio de nombres /// ESPACIO_DE_NOMBRES.NombreForm /// </summary> private void mostrarForms() { // llena una colección con los formularios de esta aplicación // estén o no en memoria. // Muestra el resultado en un listbox lbForms.Items.Clear(); foreach (Type t in ass.GetTypes()) { var nombreTipo = t.BaseType.Name; // También tendrá My.Application: (solo en VB) // <espacio de nombres>.My.MyApplication if (nombreTipo.ToLower().Contains("form")) lbForms.Items.Add(t.FullName); } }
Solo comentarte que en el código de Visual Basic he añadido 2 botones extras para mostrar los formularios Form2 y Form3 respectivamente. Esto solo es para probar que se pueden mostrar de las dos formas
Pero en cualquier caso, siempre se muestra un nuevo formulario, esté o no abierto.
En la figura 1 puedes ver la aplicación en funcionamiento, mostrando una lista de los formularios de la aplicación.
Figura 1. La aplicación en ejecución mostrando los formularios del ensamblado (aplicación)
Si te fijas en la figura 1, se muestra también «My.MyApplication» que no es un formulario, pero el tipo «base» es Form, y por eso se muestra. Esto no ocurre en C#, ya que C# no tiene la clase My.
Nota:
El código de Visual Basic lo he convertido con una utilidad que hay en la web:
Telerik Code Converter
Esto ya lo publicaré otro día (realmente era lo que iba a publicar hoy junto con una explicación de los problemas de nuestro código de Windows Forms y Visual Studio y los monitores HDPI), y es que en el código incluyo cómo hacer que un formulario se muestre en el segundo monitor.
Actualmente estoy usando un portátil con la resolución 1920×1080 y usando el tamaño del texto (etc.) al 100% y un segundo monitor con 3840×2160 y el tamaño del texto, etc. al 150%.
Como te explicaré en otra ocasión, yo uso el Visual Studio abriéndolo desde la pantalla principal (la del portátil al 100%), pero después muevo el IDE de Visual Studio al segundo monitor, por tanto cuando ejecuto el código, lo que se muestra lo hace siempre en la pantalla del portátil, por eso busqué cómo hacer que se muestre un formulario en el segundo monitor, y ¡lo encontré!, eso sí, en código para C#, concretamente en una respuesta en el sitio de StackOverflow en un hilo con la siguiente pregunta: How do I ensure a form displays on the “additional” monitor in a dual monitor scenario?
La respuesta que me sirvió fue la 42, con este código:
var myScreen = Screen.FromControl(originalForm); var otherScreen = Screen.AllScreens.FirstOrDefault(s => !s.Equals(myScreen)) ?? myScreen; otherForm.Left = otherScreen.WorkingArea.Left + 120; otherForm.Top = otherScreen.WorkingArea.Top + 120;
Que traducido a Visual Basic sería algo así:
Dim myScreen = Screen.PrimaryScreen Dim otherScreen = If(Screen.AllScreens.FirstOrDefault( Function(s) Not s.Equals(myScreen)), myScreen)
Y ¡esto es todo amigo!
Espero , como siempre, que te sea de utilidad y si no… al menos a mí dentro de 14 años me puede ser útil…
Nos vemos.
Guillermo
P.S.
El código de ejemplo completo y el enlace para la descarga de los proyectos (hechos con Visual Studio 2017 y .NET 4.7.2, ninguno de estos requisitos son necesarios para el código) te los muestro en otra página.
El código de VB y C# de: Mostrar todos los formularios de una aplicación
Pues eso… aunque ya te expliqué cómo definir los decimales en un campo de una tabla de SQL Server (El número de decimales del tipo decimal de SQL Server) aquí te amplio ese mismo tema (o casi), ya que estuve trasteando con esto de los decimales, y… bueno, no presté mucha atención a la explicación de los argumentos indicados al declarar un tipo decimal o numeric de Transact-SQL (si la hubiese prestado no estaría escribiendo esto ).
Te copio/pego (buen invento este del Copy&Paste) lo que dice la página de la documentación de Transact-SQL sobre decimal y numeric:
Argumentos
decimal[ (p[ ,s] )] y numeric[ (p[ ,s] )]
Números de precisión y escala fijas. Cuando se utiliza la precisión máxima, los valores válidos se sitúan entre – 10^38 +1 y 10^38 – 1. Los sinónimos ISO para decimal son dec y dec(p, s). numeric equivale desde el punto de vista funcional a decimal.p (precisión)
El número total máximo de dígitos decimales que almacenará, tanto a la izquierda como a la derecha del separador decimal. La precisión debe ser un valor comprendido entre 1 y la precisión máxima de 38. La precisión predeterminada es 18.s (escala)
El número de dígitos decimales que se almacenará a la derecha del separador decimal. Este número se extrae de p para determinar el número máximo de dígitos a la izquierda del separador decimal. La escala debe ser un valor comprendido entre 0 y p. Solo es posible especificar la escala si se ha especificado la precisión. La escala predeterminada es 0; por tanto, 0 <= s <= p. Los tamaños de almacenamiento máximo varían según la precisión.
Si lees eso y lo asimila pues… no hace falta que sigas leyendo… pero ya que estás aquí… anda… sigue leyendo…
Pues yo… no me lo leí bien… y como iba a trabajar con campos para guardar el porcentaje de IVA, actualmente en España son: 4% (0,5% de RE), 10% (1,40% de RE) y 21% (5,20% de RE); así que pensé que declarando el tipo como decimal (4,4) me iba bien… ya que pensé que el primer 4 sería para las cifras a la izquierda del separador decimal y los otros 4 para lo que haya después del separador decimal), ¡craso error! (pero en este caso sería: ¡Guille error!
Bueno, el error fue el que me dio el SQL Server al querer guardar el valor del IVA del 4%, es decir, guardar un simple valor «cuatro» (con 4 decimales) .
El error fue:
El valor de parámetro ‘4,0000’ está fuera del intervalo.
Nota:
El error «El valor de parámetro ‘4,0000’ está fuera del intervalo.» me lo dio usando la inserción de datos usando Update en un DataAdapter.
Al usar ExecuteNonQuery o ExecuteScalar el error que da es: «Error de desbordamiento aritmético al convertir numeric al tipo de datos numeric.«
Estos dos casos de forma «programativa».
Si se añade desde Mangement Studio de SQL Server, el error que da es: «El nombre de columna o los valores especificados no corresponden a la definición de la tabla.«
Sí, estaba usando el objeto DataAdapter para crear o actualizar los datos.
El tema está en que si declaras un valor decimal de esta forma: decimal(4,4) esto lo que hace es definir el tipo decimal para 1ue acepte un máximo de 4 cifras (el primer argumento), incluidas las que van después del separador, y como está definido con 4 cifras decimales (el segundo valor) pues… ¡solo aceptará las 4 cifras decimales!
Por tanto… ¡error al canto! al menos si se asigna algún valor a la izquierda del separador decimal. Es decir, si le asignamos 4,0 dará error, pero si le asignas hasta un valor máximo de 0,9999 no lo dará. Otra cosa es que le asignes algo como: 0,9999876 en este caso si lo dará, ya que al redondear las cifras decimales a 4 se creará un valor mayor de cero, en este caso el valor que intentará guardar es: 1,0000.
Una solución sería definir el tipo para que acepte más cifras, por ejemplo:
decimal(6,4) permitirá 6 cifras en total, 4 de ellas para los decimales y 2 para la parte entera, de esta forma podremos guardar sin problemas valores hasta 99,9999.
Y esto sería todo, pero… te voy a mostrar código, que si no… no tienen gracia
Empezaré con un código Transact-SQL para crear dos tablas en una base de datos que previamente habrás creado con Management Studio de SQL Server llamada ErrorDecimal.
En la primera tabla (MiTabla1) definimos un tipo decimal(4,4):
use ErrorDecimal CREATE TABLE dbo.MiTabla1 ( Decimal_4_4 decimal(4,4) ); GO -- 21.5 dará error al asignar a decimal(4,4) ya que ese número tendría 6 cifras: el 21 y los 4 decimales -- incluso 4.0 también daría error, pero no 0.9999 INSERT INTO dbo.MiTabla1 VALUES (21,5); GO SELECT Decimal_4_4 FROM dbo.MiTabla1;
En la segunda tabla (MiTabla2) definimos dos columnas como decimal(6,4) y decimal(18,6):
use ErrorDecimal CREATE TABLE dbo.MiTabla2 ( Decimal_6_4 decimal(6,4) ,Decimal_18_6 decimal(18,6) ); GO -- 99.9991912 no dará error al asignar a decimal(6,4) ya que solo aceptará los 4 primeros dígitos decimales -- el valor que tendrá será: 99.9992 -- decimal(18,6) aceptará 18 cifras en total: 12 a la izquierda y 6 a la derecha -- 999,999,999,999.9999199 mostrará 999999999999.999920 INSERT INTO dbo.MiTabla2 VALUES (99.9991912, 999999999999.9999199); GO SELECT Decimal_6_4, Decimal_18_6 FROM dbo.MiTabla2;
Puedes ejecutar ese código y verás que te dará error al asignar el valor de MiTabla1.
En el código de MiTabla2 funcionará perfectamente (salvo que uses valores fuera de rango).
Lo siguiente que te voy a mostrar es el código para usar una aplicación de Windows Forms (al final del artículo te he puesto los enlaces para el código de Visual Basic así como para C#).
Para poder usar este código debes definir en la base de datos un usuario llamado UsuarioErrDec con la clave 123456.
Nota:
Si no sabes cómo crear el usuario y la contraseña… ¡jum!
Bueno, tendrás que esperar a otro artículo que tengo preparado sobre este tema… o casi…
Antes de poner el «tocho» de código (y las capturas del formulario en modo diseño) te diré las cosas que vas a encontrarte en el código, a ver si así te animas a seguir leyendo
Base de datos:
1- Crear la cadena de conexión usando SqlConnectionStringBuilder.
2- Añadir nuevos datos a las tablas usando SqlConnection y ExecuteNonQuery.
2a- Los métodos de añadir utilizan una tupla (Tuple) para devolver dos valores: un valor Boolean para saber si hubo o no error y un valor String con el error o un mensaje de que se han añadido correctamente los datos.
3- Leer los datos de las tablas usando ExecuteReader y devolviendo una cadena con los nombres de las columnas y los valores.
Código general: Detectar las pulsaciones en los TextBox numéricos usando una función (AceptarTeclas)
4- En las cajas de texto numéricas permitir solo números y el signo menos y el separador decimal que definamos (en este ejemplo se permite la coma como separador y no se acepta el punto.
5- Permitir en las teclas numéricas la pulsación de la tecla INTRO y BORRAR.
indicar cual será el separador decimal y solo permitir números (y el signo menos)
6- Te explico el «fallillo» de que el punto siempre se acepta, salvo que se intercepte la pulsación.
7- Si se pulsa la tecla INTRO pasar al siguiente control.
Código general: Interceptar a nivel de formulario la pulsación de las teclas de edición (Ctrl+C, Ctrl+V, Ctrl+X y Ctrl+Z) en los controles de texto y trabajar con el portapapeles ¡sin necesidad de usar el objeto ClipBoard!
8- Las pulsaciones de las teclas se comprueban en el evento KeyDown del formulario.
9- Se utiliza ActiveControl y se comprueba si es una caja de texto.
El formulario en tiempo de diseño.
Comentarte que para que funcione lo de las «tuplas» en Visual Basic debes usar el .NET 4.7.2, por tanto yo he usado esa versión del compilador con el Visual Studio 2017 versión 15.9.3.
He usado la versión Professional, pero también funciona con la Community (gratuita). Por curiosidad, también lo he probado con el Visual Studio Enterprise 2019 (versión 16.0.0 Preview 1.0) y todo funciona perfectamente.
Y esto es todo amigos. Espero que te sea de utilidad
Nos vemos.
Guillermo
P.S.
Este artículo está publicado originalmente en mi blog elguillemola.com pero también lo encontrarás en mi sitio elguille.info (realmente accede al post publicado en el blog).
Lo he puesto aparte para que sea más cómodo leerlo.
Estos son los enlaces en el blog, pero te recomiendo que lo veas en los publicados en elguille.info ya que el código se ve mejor :
El código de Visual Basic .NET (blog)
El código de C# (blog)
El código de Visual Basic (elguille.info)
El código de C# (elguille.info)
Nota:
Convertido con mi utilidad gsConvertirCodigo y modificado manualmente ya que aún no «entiende» bien todo esto de la inferencia de tipos y las tuplas
Por favor, cualquier comentario sobre el código por favor coméntalo en los posts correspondientes; aquí también puedes comentar, pero mejor en cada post para que el que vea ese post concreto sepa que se puede mejorar o lo que quieras que comentes, gracias.
El zip: SQL_Error_decimal.zip (en downloads.elguille.info)
Contiene una solución de Visual Studio 2017 con los proyectos para VB y C#
( MD5 Checksum: 2232781E047C1ED1A48DD00C7CA8E6F5 )
Aquí tienes el código para C# de la aplicación de ejemplo del artículo Error al guardar datos decimales: El valor del parámetro ‘xxx’ está fuera del intervalo.
Nota:
Si buscabas el código para Visual Basic está en este otro enlace:
Error al guardar datos decimales – El código para Visual Basic.
Cuando lo tenga publicado, te pondré el enlace para descargar la solución de Visual Studio 2017 tanto para VB como para C#.
//----------------------------------------------------------------------------- // Ejemplo para el error de asignar a decimal(4,4) (07/Dic/18) // // (c) Guillermo (elGuille) Som, 2018 //----------------------------------------------------------------------------- using System; using System.Data.SqlClient; using System.Text; using System.Windows.Forms; namespace SQL_Error_decimal_cs { public partial class Form1 : Form { //-------------------------------------------------------------------------- // Los campos para acceder a la base de datos //-------------------------------------------------------------------------- /// <summary> /// El usuario para acceder a la base de datos de SQL Server.<br /> /// Si es una cadena vacía se usará la seguridad integrada de Windows. /// </summary> private string userDb = "UsuarioErrDec"; /// <summary> /// El password del usuario que accede a la base de datos de SQL Server /// </summary> private string passwordDB = "123456"; /// <summary> /// El servidor donde está la base de datos.<br /> /// Normalmente será .\SQLEXPRESS o (local) /// </summary> private string serverName = @".\SQLEXPRESS"; /// <summary> /// El nombre de la base de datos de SQL Server /// </summary> private string databaseName = "ErrorDecimal"; /// <summary> /// Devuelve la cadena de conexión a la base de datos de SQL Server<br /> /// Si el usuario es una cadena vacía, se usará la seguridad integrada de Windows /// </summary> private string ConnectionString { get { var sb = new SqlConnectionStringBuilder(); sb.DataSource = serverName; sb.InitialCatalog = databaseName; if (String.IsNullOrWhiteSpace(userDb)) { sb.IntegratedSecurity = true; } else { sb.UserID = userDb; sb.Password = passwordDB; } return sb.ConnectionString; } } //-------------------------------------------------------------------------- // Añadir un valor a las tablas //-------------------------------------------------------------------------- private (bool hayError, string msg) AñadirMiTabla1(decimal valor) { var sel = "INSERT INTO MiTabla1 (Decimal_4_4) " + "VALUES (@Decimal_4_4)"; var retVal = (hayError: false, msg: ""); var sCon = ConnectionString; using (SqlConnection con = new SqlConnection(sCon)) { var cmd = new SqlCommand(sel, con); cmd.Parameters.AddWithValue("@Decimal_4_4", valor); con.Open(); try { var ret = Convert.ToInt32(cmd.ExecuteNonQuery()); retVal.hayError = (ret < 1); retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " + ret.ToString(); } catch (Exception ex) { retVal.msg = ex.Message; retVal.hayError = true; } con.Close(); } return retVal; } private (bool hayError, string msg) AñadirMiTabla2(decimal[] valores) { var sel = "INSERT INTO MiTabla2 (Decimal_6_4, Decimal_18_6) " + "VALUES (@Decimal_6_4, @Decimal_18_6)"; var retVal = (hayError:false, msg:""); var sCon = ConnectionString; using (SqlConnection con = new SqlConnection(sCon)) { SqlCommand cmd = new SqlCommand(sel, con); cmd.Parameters.AddWithValue("@Decimal_6_4", valores[0]); cmd.Parameters.AddWithValue("@Decimal_18_6", valores[1]); con.Open(); try { var ret = Convert.ToInt32(cmd.ExecuteNonQuery()); retVal.hayError = (ret < 1); retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " + ret.ToString(); } catch (Exception ex) { retVal.msg = ex.Message; retVal.hayError = true; } con.Close(); } return retVal; } private string leerMiTabla(string tabla) { var sel = "SELECT * FROM " + tabla; var retVal = ""; var sCon = ConnectionString; using (SqlConnection con = new SqlConnection(sCon)) { var cmd = new SqlCommand(sel, con); con.Open(); try { var ret = cmd.ExecuteReader(); StringBuilder sb = new StringBuilder(); while (ret.Read()) { sb.AppendLine(String.Format("{0} = {1}", ret.GetName(0), ret[0])); if (ret.FieldCount > 1) sb.AppendLine(String.Format("{0} = {1}", ret.GetName(1), ret[1])); }; retVal = sb.ToString(); } catch (Exception ex) { retVal = "ERROR: " + ex.Message; } con.Close(); }; return retVal; } //-------------------------------------------------------------------------- // Para aceptar la coma como decimal en las cajas numéricas //-------------------------------------------------------------------------- /// <summary> /// El separador de decimales para los campos numéricos /// </summary> const string SeparadorDecimal = ","; /// <summary> /// Para indicar qué tecla "decimal" no se debe admitir /// </summary> const string NoSeparadorDecimal = "."; /// <summary> /// Comprobar si se aceptan las teclas en una caja de texto. /// En la pulsación de los controles numéricos /// aceptar solo los caracteres numéricos, /// el valor negativo, el separador de decimales /// y las teclas Intro, Delete, Back (borrar hacia atrás) /// /// Es raro, si teclasAceptadas es: ",-1234567890" también acepta el punto /// </summary> private char AceptarTeclas(KeyPressEventArgs e, string teclasAceptadas) { char c = e.KeyChar; if (c == Convert.ToChar(Keys.Return)) { // con esto hacemos que se ignore la pulsación e.Handled = true; // se manda al siguiente control SendKeys.Send("{TAB}"); } else if (c == Convert.ToChar(NoSeparadorDecimal)) { e.KeyChar = Convert.ToChar(SeparadorDecimal); } else if (teclasAceptadas.Contains(c.ToString())) { // no hacer nada, se aceptan } else if (c == Convert.ToChar(Keys.Delete) || c == Convert.ToChar(Keys.Back)) { // no hacer nada, se aceptan } else { e.Handled = true; } return c; } //-------------------------------------------------------------------------- // Los métodos de evento del formulario //-------------------------------------------------------------------------- private void btnCerrar_Click(object sender, EventArgs e) { this.Close(); } private void btnAsignarTabla1_Click(object sender, EventArgs e) { var d = 0M; Decimal.TryParse(txtTabla1_campo1.Text, out d); var ret = AñadirMiTabla1(d); txtMensaje1.Text = ""; if (ret.hayError) txtMensaje1.Text = "ERROR\r\n"; txtMensaje1.Text += ret.msg; } private void btnAsignarTabla2_Click(object sender, EventArgs e) { var valores = new decimal[12]; var d = 0M; Decimal.TryParse(txtTabla2_campo1.Text, out d); valores[0] = d; d = 0M; Decimal.TryParse(txtTabla2_campo2.Text, out d); valores[1] = d; var ret = AñadirMiTabla2(valores); txtMensaje2.Text = ""; if (ret.hayError) txtMensaje2.Text = "ERROR\r\n"; txtMensaje2.Text += ret.msg; } private void btnMostrar1_Click(object sender, EventArgs e) { // mostrar los datos de MiTabla1 txtMensaje1.Text = leerMiTabla("MiTabla1"); } private void btnMostrar2_Click(object sender, EventArgs e) { // mostrar los datos de MiTabla2 txtMensaje2.Text = leerMiTabla("MiTabla2"); } private void txt_KeyPress(object sender, KeyPressEventArgs e) { AceptarTeclas(e, SeparadorDecimal + "-1234567890"); } public Form1() { InitializeComponent(); } < private void Form1_KeyDown(object sender, KeyEventArgs e) { // detecta la pulsación de las teclas en el formulario // antes de mandarla a los controles // En el diseñador de formularios tienes que // asignar un valor True a la propiedad KeyPreview if (e.Modifiers == Keys.Control) { if (e.KeyCode == Keys.C) { // copiar el texto if (ActiveControl is TextBox) { //Dim texto = ActiveControl.Text //Clipboard.SetText(texto) var txt = ActiveControl as TextBox; if (txt == null) return; txt.Copy(); e.Handled = true; } } else if (e.KeyCode == Keys.V) { // pegar el texto if (ActiveControl is TextBox) { var txt = ActiveControl as TextBox; if (txt == null) return; txt.Paste(); e.Handled = true; } } else if (e.KeyCode == Keys.X) { // cortar el texto if (ActiveControl is TextBox) { var txt = ActiveControl as TextBox; if (txt == null) return; txt.Cut(); e.Handled = true; } } else if (e.KeyCode == Keys.Z) { //deshacer if (ActiveControl is TextBox) { var txt = ActiveControl as TextBox; if (txt == null) return; if (txt.CanUndo) { txt.Undo(); } e.Handled = true; } } } } } }
Espero que te sea de utilidad.
Nos vemos
Guillermo
Aquí tienes el código para Visual Basic de la aplicación de ejemplo del artículo Error al guardar datos decimales: El valor del parámetro ‘xxx’ está fuera del intervalo.
Nota:
Si buscabas el código para C# está en este otro enlace:
Error al guardar datos decimales – El código para C#.
Cuando lo tenga publicado, te pondré el enlace para descargar la solución de Visual Studio 2017 tanto para VB como para C#.
'------------------------------------------------------------------------------ ' Ejemplo para el error de asignar a decimal(4,4) (07/Dic/18) ' ' (c) Guillermo (elGuille) Som, 2018 '------------------------------------------------------------------------------ Option Strict On Option Infer On Imports System 'Imports System.Data Imports System.Data.SqlClient Imports System.Text Public Class Form1 '-------------------------------------------------------------------------- ' Los campos para acceder a la base de datos '-------------------------------------------------------------------------- ''' <summary> ''' El usuario para acceder a la base de datos de SQL Server.<br /> ''' Si es una cadena vacía se usará la seguridad integrada de Windows. ''' </summary> Private userDb As String = "UsuarioErrDec" ''' <summary> ''' El password del usuario que accede a la base de datos de SQL Server ''' </summary> Private passwordDB As String = "123456" ''' <summary> ''' El servidor donde está la base de datos.<br /> ''' Normalmente será .\SQLEXPRESS o (local) ''' </summary> Private serverName As String = ".\SQLEXPRESS" ' "(local)" ''' <summary> ''' El nombre de la base de datos de SQL Server ''' </summary> Private databaseName As String = "ErrorDecimal" ''' <summary> ''' Devuelve la cadena de conexión a la base de datos de SQL Server<br /> ''' Si el usuario es una cadena vacía, se usará la seguridad integrada de Windows ''' </summary> Private ReadOnly Property ConnectionString As String Get With New SqlConnectionStringBuilder .DataSource = serverName .InitialCatalog = databaseName If String.IsNullOrWhiteSpace(userDb) Then .IntegratedSecurity = True Else .UserID = userDb .Password = passwordDB End If Return .ConnectionString End With End Get End Property '-------------------------------------------------------------------------- ' Añadir un valor a las tablas '-------------------------------------------------------------------------- Private Function AñadirMiTabla1(valor As Decimal) As (hayError As Boolean, msg As String) Dim sel = "INSERT INTO MiTabla1 (Decimal_4_4) VALUES (@Decimal_4_4)" Dim retVal = (hayError:=False, msg:="") Dim sCon = ConnectionString Using con As New SqlConnection(sCon) Dim cmd As New SqlCommand(sel, con) cmd.Parameters.AddWithValue("@Decimal_4_4", valor) con.Open() Try Dim ret = CInt(cmd.ExecuteNonQuery()) 'Dim ret = CInt(cmd.ExecuteScalar()) retVal.hayError = (ret < 1) retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " & ret.ToString Catch ex As Exception retVal.msg = ex.Message retVal.hayError = True End Try con.Close() End Using Return retVal End Function Private Function AñadirMiTabla2(valores() As Decimal) As (hayError As Boolean, msg As String) Dim sel = "INSERT INTO MiTabla2 (Decimal_6_4, Decimal_18_6) VALUES (@Decimal_6_4, @Decimal_18_6)" Dim retVal = (hayError:=False, msg:="") Dim sCon = ConnectionString Using con As New SqlConnection(sCon) Dim cmd As New SqlCommand(sel, con) cmd.Parameters.AddWithValue("@Decimal_6_4", valores(0)) cmd.Parameters.AddWithValue("@Decimal_18_6", valores(1)) con.Open() Try Dim ret = CInt(cmd.ExecuteNonQuery()) retVal.hayError = (ret < 1) retVal.msg = "Todo OK. cmd.ExecuteNonQuery() = " & ret.ToString Catch ex As Exception retVal.msg = ex.Message retVal.hayError = True End Try con.Close() End Using Return retVal End Function Private Function leerMiTabla(tabla As String) As String Dim sel = "SELECT * FROM " & tabla Dim retVal = "" Dim sCon = ConnectionString Using con As New SqlConnection(sCon) Dim cmd As New SqlCommand(sel, con) con.Open() Try Dim ret = cmd.ExecuteReader Dim sb As New StringBuilder While ret.Read() sb.AppendLine(String.Format("{0} = {1}", ret.GetName(0), ret(0))) If ret.FieldCount > 1 Then sb.AppendLine(String.Format("{0} = {1}", ret.GetName(1), ret(1))) End If End While retVal = sb.ToString Catch ex As Exception retVal = "ERROR: " & ex.Message End Try con.Close() End Using Return retVal End Function '-------------------------------------------------------------------------- ' Para aceptar la coma como decimal en las cajas numéricas '-------------------------------------------------------------------------- ''' <summary> ''' El separador de decimales para los campos numéricos ''' </summary> Private Const SeparadorDecimal As String = "," ''' <summary> ''' Para indicar qué tecla "decimal" no se debe admitir ''' </summary> Private Const NoSeparadorDecimal As String = "." ''' <summary> ''' Comprobar si se aceptan las teclas en una caja de texto. ''' En la pulsación de los controles numéricos ''' aceptar solo los caracteres numéricos, ''' el valor negativo, el separador de decimales ''' y las teclas Intro, Delete, Back (borrar hacia atrás) ''' ''' Es raro, si teclasAceptadas es: ",-1234567890" también acepta el punto ''' </summary> Private Function AceptarTeclas(e As KeyPressEventArgs, teclasAceptadas As String) As Char Dim c = e.KeyChar If c = Convert.ToChar(Keys.Return) Then ' con esto hacemos que se ignore la pulsación e.Handled = True ' se manda al siguiente control SendKeys.Send("{TAB}") ElseIf c = Convert.ToChar(NoSeparadorDecimal) Then e.KeyChar = Convert.ToChar(SeparadorDecimal) ElseIf teclasAceptadas.Contains(c) Then ' no hacer nada, se aceptan ElseIf c = Convert.ToChar(Keys.Delete) OrElse c = Convert.ToChar(Keys.Back) Then ' no hacer nada, se aceptan Else e.Handled = True End If Return c End Function '-------------------------------------------------------------------------- ' Los métodos de evento del formulario '-------------------------------------------------------------------------- Private Sub btnCerrar_Click(sender As Object, e As EventArgs) Handles btnCerrar.Click Me.Close() End Sub Private Sub btnAsignarTabla1_Click(sender As Object, e As EventArgs) Handles btnAsignarTabla1.Click Dim d = 0@ Decimal.TryParse(txtTabla1_campo1.Text, d) Dim ret = AñadirMiTabla1(d) txtMensaje1.Text = "" If ret.hayError Then txtMensaje1.Text = "ERROR" & vbCrLf End If txtMensaje1.Text &= ret.msg End Sub Private Sub btnAsignarTabla2_Click(sender As Object, e As EventArgs) Handles btnAsignarTabla2.Click Dim valores(1) As Decimal Dim d = 0@ Decimal.TryParse(txtTabla2_campo1.Text, d) valores(0) = d d = 0@ Decimal.TryParse(txtTabla2_campo2.Text, d) valores(1) = d Dim ret = AñadirMiTabla2(valores) txtMensaje2.Text = "" If ret.hayError Then txtMensaje2.Text = "ERROR" & vbCrLf End If txtMensaje2.Text &= ret.msg End Sub Private Sub txt_KeyPress(sender As Object, e As KeyPressEventArgs) Handles _ txtTabla2_campo1.KeyPress, txtTabla1_campo1.KeyPress,
txtTabla2_campo2.KeyPress AceptarTeclas(e, SeparadorDecimal & "-1234567890") End Sub Private Sub btnMostrar1_Click(sender As Object, e As EventArgs) Handles btnMostrar1.Click ' mostrar los datos de MiTabla1 txtMensaje1.Text = leerMiTabla("MiTabla1") End Sub Private Sub btnMostrar2_Click(sender As Object, e As EventArgs) Handles btnMostrar2.Click ' mostrar los datos de MiTabla2 txtMensaje2.Text = leerMiTabla("MiTabla2") End Sub Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles MyBase.KeyDown ' detecta la pulsación de las teclas en el formulario ' antes de mandarla a los controles ' En el diseñador de formularios tienes que ' asignar un valor True a la propiedad KeyPreview If e.Modifiers = Keys.Control Then If e.KeyCode = Keys.C Then ' copiar el texto If TypeOf ActiveControl Is TextBox Then 'Dim texto = ActiveControl.Text 'Clipboard.SetText(texto) Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return txt.Copy() e.Handled = True End If ElseIf e.KeyCode = Keys.V Then ' pegar el texto If TypeOf ActiveControl Is TextBox Then Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return txt.Paste() e.Handled = True End If ElseIf e.KeyCode = Keys.X Then ' cortar el texto If TypeOf ActiveControl Is TextBox Then Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return txt.Cut() e.Handled = True End If ElseIf e.KeyCode = Keys.Z Then 'deshacer If TypeOf ActiveControl Is TextBox Then Dim txt = TryCast(ActiveControl, TextBox) If txt Is Nothing Then Return If txt.CanUndo Then txt.Undo() End If e.Handled = True End If End If End If End Sub End Class
Espero que te sea de utilidad.
Nos vemos
Guillermo
Pues eso… aquí te voy a explicar (con ejemplo de código) cómo definir y usar las tuplas (Tuples) en Visual Basic .NET (y para los amantes de C#, también pondré algunos ejemplos en ese lenguaje puntoycomagudo )
En esta primera «entrega» te voy a dar 3 ejemplos (+ 1 extra) de cómo usar las tuplas con .NET Core 2.1 (si prefieres hacerlo con .NET normal tendrás que usar la versión 4.7.2 o superior) en una aplicación de Consola.
El contenido es el siguiente:
Ejemplo 1: definir las tuplas sin nombres
Ejemplo 2: definir las tuplas con nombres y asignación después de definir
Ejemplo 3: definir las tuplas con nombres y asignando al definir
Ejemplo 4: definir las tuplas con nombres y asignando al definir (al estilo de C#)
Ejemplo 5: definir un método que recibe un array tuplas
Mmmmm… te dije 3 + un extra y resulta que son 4 + un extra
Si no sabes qué son las tuplas, decirte que son como una estructura (Struct) pero definidas de forma «directa» sin necesidad de crear el tipo previamente.
Si quieres saber más sobre las tuplas, te recomiendo que leas la documentación de .NET:
Tipos de tupla en C#
Tuplas (Visual Basic)
Ejemplo 1: Ejemplo básico de tuplas sin definir los nombres de los miembros:
' Al definirla de esta forma ' tenemos una tupla con dos elementos del tipo cadena Dim t1 = ("Hola", "mundo") ' para acceder a los miembros usaremos Item1, Item2 Console.WriteLine("{0} {1}", t1.Item1, t1.Item2)
Ejemplo 2: Definimos una tupla con miembros con nombres, asignando los nombres individualmente. Para acceder a los miembros de la tupla usamos los nombres.
' definimos una tupla con miembros con nombres Dim t2 As (saludo As String, destino As String) ' para acceder a los miembros, usamos los nombres t2.saludo = "Hola" t2.destino = "Mundo" Console.WriteLine("{0} {1}", t2.saludo, t2.destino)
Ejemplo 3: Definimos una tupla con miembros con nombres, asignando los valores al definirla. Para acceder a los miembros, usamos los nombres.
' definimos una tupla con miembros con nombres Dim t2 As (saludo As String, destino As String) =
("Hola", "Mundo") ' para acceder a los miembros, usamos los nombres Console.WriteLine("{0} {1}", t2.saludo, t2.destino)
Ejemplo 4: Definimos una tupla con miembros con nombres, asignando los valores al definirla. Para acceder a los miembros, usamos los nombres.
' definimos una tupla con miembros con nombres Dim t2 = (saludo:="Hola", destino:="Mundo") ' para acceder a los miembros, usamos los nombres Console.WriteLine("{0} {1}", t2.saludo, t2.destino)
Ejemplo 5 (extra): Método que recibe un array de tipo «tupla».
Este código es un método llamado colorear que recibe un array del tipo (ConsoleColor, String), se procesa cada uno de los elementos del array y se usa el valor del color para indicar el color del valor ForegroundColor (color del texto) de la clase Consola, el contenido del valor String lo usamos para mostrarlo en la consola por medio de WriteLine.
Con ese código se consiguen salidas en la ventana de la consola como la siguiente:
El código del método colorear es el siguiente:
''' <summary> ''' Colorear la salida de los ejemplos, ''' también usando tuplas ;-) ''' Se pasa como argumento ''' un array del tipo (color As ConsoleColor, texto As String) ''' </summary> ''' <param name=items">un array del tipo (color As ConsoleColor, texto As String)</param>" Private Sub colorear(items As (color As ConsoleColor, texto As String)()) For Each it In items Console.ForegroundColor = it.color Console.WriteLine(it.texto) Next Console.ForegroundColor = co.Gray Console.WriteLine() End Sub
Para usarlo podemos hacer como en el código para el cuarto ejemplo:
Private Sub ejemplo4() colorear({(co.Green, "definimos una tupla con miembros con nombres"), (co.Green, "(asignando los valores al definirla)"), (co.Yellow, "Dim t2 = (saludo:=""Hola"", destino:=""Mundo"")"), (co.Green, "para acceder a los miembros, usamos los nombres"), (co.Yellow, "Console.WriteLine(""{0} {1}"", t2.saludo, t2.destino)")}) ' definimos una tupla con miembros con nombres Dim t2 = (saludo:="Hola", destino:="Mundo") ' para acceder a los miembros, usamos los nombres Console.WriteLine("{0} {1}", t2.saludo, t2.destino) Console.ReadLine() End Sub
Nota:
Es curioso, pero resulta que es más fácil definir los parámetros (de tipo array o matriz) directamente en la llamada del método colorear en Visual Basic que en C#.
En Visual Basic se indica el array del tipo tupla de la siguiente forma:colorear({
(co.Green, «definimos una tupla con miembros con nombres»),
(co.Green, «(asignando los valores al definirla)»), (co.Yellow, «Dim t2 = (saludo:=»»Hola»», destino:=»»Mundo»»)»),
(co.Green, «para acceder a los miembros, usamos los nombres»),
(co.Yellow, «Console.WriteLine(«»{0} {1}»», t2.saludo, t2.destino)»)})Mientras que en C# hay indicar que es un nuevo array:
colorear(new []{
(co.Green, «definimos una tupla con miembros con nombres»),
(co.Green, «(asignando los valores al definirla)»),
(co.Yellow, «var t2 = (saludo:\»Hola\», destino:\»Mundo\»);»),
(co.Green, «para acceder a los miembros, usamos los nombres»),
(co.Yellow, «Console.WriteLine(\»{0} {1}\», t2.saludo, t2.destino);»)});
Y esto es todo por hoy.
Bueno, no, más abajo te muestro el código de estos dos últimos códigos para C#.
Nota:
En otra ocasión te mostraré más código de ejemplo del uso de tuplas en Visual Basic y C#, seguramente usando funciones que devuelvan tuplas y cómo usar la «inferencia» de los argumentos con nombre… ¿se podrá hacer eso con Visual Basic? sí se puede… pero necesita más explicación, ya que el Visual Studio 2017 no lo compilará y hay que hacer «un tuquillo» para que lo compile. Con C# tampoco lo compila, y también hay que hacer un truco similar al de Visual Basic.
En realidad no te lo explico aquí porque no lo había probado, entre otras cosas porque no me ha hecho falta; lo de la función que devuelve una tupla si que lo he usado en una aplicación que estoy haciendo actualmente.
Espero que te sea de utilidad
Nos vemos.
Guillermo
P.S. 09/Dic/18:
No es un artículo dedicado a las tuplas, pero en el ejemplo que publiqué ayer sobre el Error al guardar datos decimales: El valor del parámetro ‘xxx’ está fuera del intervalo tienes un ejemplo de cómo usar una función que devuelve una tupla.
Private Function AñadirMiTabla1(valor As Decimal) As (hayError As Boolean, msg As String)
private (bool hayError, string msg) AñadirMiTabla1(decimal valor)
P.S.
Ejemplo 1: Ejemplo básico de tuplas sin definir los nombres de los miembros:
// Al definirla de esta forma // tenemos una tupla con dos elementos del tipo cadena var t1 = ("Hola", "mundo"); // para acceder a los miembros usaremos Item1, Item2 Console.WriteLine("{0} {1}", t1.Item1, t1.Item2);
Ejemplo 4: Definimos una tupla con miembros con nombres, asignando los valores al definirla. Para acceder a los miembros, usamos los nombres.
// definimos una tupla con miembros con nombres var t2 = (saludo: "Hola", destino: "Mundo"); // para acceder a los miembros, usamos los nombres Console.WriteLine("{0} {1}", t2.saludo, t2.destino);
Ejemplo 5 (extra): Método que recibe un array de tipo «tupla».
Este código es un método llamado colorear que recibe un array del tipo (ConsoleColor, string), se procesa cada uno de los elementos del array y se usa el valor del color para indicar el color del valor ForegroundColor (color del texto) de la clase Consola, el contenido del valor string lo usamos para mostrarlo en la consola por medio de WriteLine.
Con ese código se consiguen salidas en la ventana de la consola como la siguiente:
El código del método colorear es el siguiente:
/// <summary> /// Colorear la salida de los ejemplos, /// también usando tuplas ;-) /// Se pasa como argumento /// un array del tipo (ConsoleColor color, string texto) /// </summary> /// <param name=items">un array del tipo (ConsoleColor color, string texto)</param>" static private void colorear((ConsoleColor color, string texto)[] items) { foreach (var it in items) { Console.ForegroundColor = it.color; Console.WriteLine(it.texto); } Console.ForegroundColor = co.Gray; Console.WriteLine(); }
Para usarlo podemos hacer como en el código para el cuarto ejemplo:
/// <summary> /// Definimos una tupla con miembros con nombres /// (asignando los valores al definirla) /// Para acceder a los miembros, usamos los nombres /// </summary> static private void ejemplo4() { //new (co color,string texto)[] colorear(new []{ (co.Green, "definimos una tupla con miembros con nombres"), (co.Green, "(asignando los valores al definirla)"), (co.Yellow, "var t2 = (saludo:\"Hola\", destino:\"Mundo\");"), (co.Green, "para acceder a los miembros, usamos los nombres"), (co.Yellow, "Console.WriteLine(\"{0} {1}\", t2.saludo, t2.destino);")}); // definimos una tupla con miembros con nombres var t2 = (saludo: "Hola", destino: "Mundo"); // para acceder a los miembros, usamos los nombres Console.WriteLine("{0} {1}", t2.saludo, t2.destino); Console.ReadLine(); }
¡Hasta el siguiente post!
Si continuas usando este sitio, aceptas el uso de cookies. más información
The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.
Las cookies de este sitio (elguillemola.com) se usan para personalizar el contenido y los anuncios, para ofrecer funciones de medios sociales y para analizar el tráfico. Además, compartimos información sobre el uso que haga del sitio web con nuestros partners de medios sociales, de publicidad y de análisis web.
Más información sobre cómo usa Google los datos cuando utiliza las aplicaciones o los sitios web de nuestros partners