Archivo de la etiqueta: c-sharp

Utilidad para invertir las asignaciones de controles a objeto y viceversa (ReordenarAsignaciones)

Pues eso, hay ocasiones en las que en el código asigno los datos de una tabla a los controles del «formulario» (quien dice formulario dice página XAML, etc.), sí, sé que se puede hacer con DataBinding, pero… casi siempre prefiero usar el modo manual ya que así parece que tengo más control con lo que hago y, de hecho, en algunas ocasiones el DataBinding no va como debería, al menos eso me ha pasado en varias páginas de Xamarin.Forms. No sé si tú lo harás así, pero yo suelo hacerlo con bastante frecuencia, será por la forma de trabajar que tengo. 😊

Te explico:
Digamos que tengo una tabla de una base de datos, que he convertido con la utilidad Generar las clases (de VB o C#) de una tabla de SQL Server o Access (mdb) en la que tengo asignada en una variable (por ejemplo, LaFactura) el contenido de la tabla Facturas y quiero asignar el contenido de ese objeto a los diferentes controles, haría algo como esto para asignar los valores de LaFactura a los controles:

/// <summary>
/// Mostrar la factura en los campos.
/// </summary>
private void MostrarLaFactura()
{
    chkFacActiva.IsChecked = LaFactura.Activa;
    txtFacActividad.Text = LaFactura.Actividad;
    txtFacAdultos.Text = LaFactura.Adultos.ToString();
    txtFacAgente.Text = LaFactura.Agente;
    txtFacCuantasReservas.Text = LaFactura.CuantasReservas.ToString();
    txtFacCuantosPagos.Text = LaFactura.CuantosPagos.ToString();
    txtFacDescuento.Text = LaFactura.Descuento.ToString("0.##");
    txtFacFechaFactura.Text = LaFactura.FechaFactura.ToString("dd/MM/yyyy HH:mm");
    txtFacHoraActividad.Text = LaFactura.HoraActividad.ToString("hh\\:mm");
}

Y tengo este otro método para hacer el caso contrario (asignar al objeto LaFactura el valor de los controles):

/// <summary>
/// Asignar los campos a la factura para guardar.
/// </summary>
/// <returns>True si algo no va bien.</returns>
private bool AsignarLaFactura()
{
    LaFactura.Activa = chkFacActiva.IsChecked;
    LaFactura.Actividad = txtFacActividad.Text;
    LaFactura.Adultos = txtFacAdultos.Text.AsInteger();
    LaFactura.Agente = txtFacAgente.Text;
    LaFactura.CuantasReservas = txtFacCuantasReservas.Text.AsInteger();
    LaFactura.CuantosPagos = txtFacCuantosPagos.Text.AsInteger();
    LaFactura.Descuento = txtFacDescuento.Text.AsDecimal();
    LaFactura.FechaFactura = txtFacFechaFactura.Text.AsDateTime();
    LaFactura.HoraActividad = txtFacHoraActividad.Text.AsTimeSpan();

    return false;
}

En el método MostrarLaFactura las asignaciones las hago de la forma habitual, es decir, asigno a los controles los datos que quiero mostrar, por ejemplo, si es fecha le asigno de la forma dd/MM/yyyy, etc.

El caso especial es cuando lo hago al revés: AsignarLaFactura, que debo convertir el contenido de los controles al formato adecuado, en este caso utilizo para las fechas, horas y cifras con decimales unos métodos de extensión que tengo definidos para hacer esa tarea (que empiezan con As y tienen el formato AsTIPODATOS), esto último no es parte de la utilidad que me he fabricado, pero… la tiene en cuenta 😉

Hacer esto manualmente, que es como lo hacía hasta hace 2 días, es tedioso y, por supuesto se pueden producir errores, que modificas tras el aviso de Visual Studio, pero… es, ¿cómo decirlo?, un peñazo por no decir co**zo. 😊

Y me puse a fabricarme una utilidad (sí, en C#) para hacer eso, por ahora es muy simple y en las asignaciones no contempla que haya comentarios, pero… todo se andará.

Las asignaciones que convertir se indican como argumentos en la línea de comandos, yo lo que hago es tener el proyecto abierto en Visual Studio y en la parte de DEBUG le asigno lo que se indicará en la línea de comandos (ver la captura 1), al hacerlo de esta forma (indicando los valores a convertir como argumentos de la aplicación), hay que tener en cuenta que los valores asignados al array args del método Main se hacen de esta forma:
ladoIzquierdo = ladoDerecho, de forma que si pones: chkFacActiva.IsChecked = LaFactura.Activa; esta será las asignaciones al array args:
args[0] = "chkFacActiva.IsChecked"
args[1] = "="
args[2] = "LaFactura.Activa;"

Y esto es lo que tengo en cuenta, por eso aún no contemplo los comentarios, ya que analizo el contenido del array args de tres en tres valores:

for (int i = 0; i < args.Length - 2; i += 3)

Pero eso ya lo arreglaré en la próxima revisión.

Figura 1. Los argumentos de la línea de comandos del proyecto

 

Dicho esto, no me queda mucho más que decir 😊

Solo ponerte el enlace al proyecto publicado en GitHub: ReordenarAsignaciones, el nombre en realidad tendría que ser InvertirAsignaciones, pero… así es como lo creé inicialmente y… seguramente se quedará así 😉

Nota:
En el repositorio de GitHub incluyo las clases con las extensiones que te he comentado y muchas más.
El código de Extensiones está tanto para Visual Basic (el original) como para C# (el convertido).

También he creado un paquete de NuGet que he definido como Tool de .NET 6 de forma que lo puedas instalar si tienes instalado el SDK de .NET 6 o superior.
De esa forma lo podrás tener como una utilidad de .NET y no preocuparte de instalarlo (extraer el código ejecutable en una carpeta) y asignar el path a ese directorio para poder usarla desde cualquier línea de comandos.

El paquete de NuGet se llama igual: ReordenarAsignaciones y este enlace te llevará a la página de NuGet donde tengo ese «paquete».
Notar que no es necesario instalarlo en un proyecto de Visual Studio, simplemente instalarlo usando la línea de comandos:
dotnet tool install --global ReordenarAsignaciones --version 1.2.0
Esto te instalará la versión que tengo a la hora de escribir esto, pero en la página de NuGet te muestra siempre qué tienes que hacer para instalarlo.

Para actualizar el «paquete» siempre es el mismo código independientemente de la versión:
dotnet tool update --global ReordenarAsignaciones

 

Espero que te sea de utilidad, esa es la intención 😉

Y si te parece bien, acuérdate de invitarme a un cafelito virtual mediante un donativo/donación con PayPal. Usando este enlace o este código QR:

Gracias 🙏🏻

Nos vemos.
Guillermo

Evaluar expresiones (código en Java y C#)

Pues eso… Sí, has leído bien el título: en Java. Y no en JavaScript (aparte de en C#).

Y es que hace unos días, empecé a estudiar esto de Java a raíz que mi hijo David está haciendo un curso de Java (back-end) y era por si le echaba una mano en algunos conceptos, ya que lo que él estudio en su día fue COBOL, algo de Pascal y algo de Visual Basic 6, y claro… los conceptos de POO y esas cosas, como que no las tiene claras.

Y para poder ayudarle (si es que así me lo hace saber), me puse a «trastear» en Java, y de camino he aprendido entre otras cosas, que hay diferencias, algunos grandes, en comparación con C# que es lo más parecido, (con Visual Basic ni te cuento).

Algunas de esas diferencias son a favor de Java, y la mayoría, a favor de C# o si lo prefieres a favor de .NET, seguramente porque lo conozco más.

Pero sea como sea, y peleándome un poco con el código y el IDE, que por cierto estoy usando uno que me parece muy bueno y es gratis: IntelliJ IDEA (Community Edition) de JetBrains, he conseguido hacer algo más o menos operativo y que me está enseñando algunas de esas cosas que ni siquiera sabía que C# las tenía y otras que al parecer C# tiene porque Java las tenía.

Lo que más he echado en falta son las «tuplas», aunque en este código de evaluar expresiones aritméticas he solventado con records, más que nada porque en algunas funciones necesitaba devolver dos valores (seguramente en Java habrá otra forma de hacerlo, pero no la he encontrado). Otra cosa que he echado en falta en Java es las estructuras (tipos de datos definibles, pero por valor).

 

No te voy a explicar «paso a paso» el código ni nada de eso (al menos por ahora), simplemente te voy a decir lo que este evaluador sabe hacer (creo que lo hace bien) y el código lo podrás ver en el repositorio de GitHub.

Quiero comentarte que primero hice una versión usando records para hacer las operaciones de suma, restas, multiplicación y división (basándome en un ejemplo de la documentación de Java), y el último es usando tipos double que además de esas cuatro operaciones hace un par de ellas más: módulo y factorial.

El enlace de GitHub o, mejor dicho, los enlaces son para el código basado en el tipo double, en esos repositorios tienes los enlaces al código fuente que usa los records, tanto para Java como para C#.

 

Lo que hace el evaluador de expresiones

  • Evalúa expresiones entre paréntesis (con varios niveles de anidación).
  • Evalúa primero los operadores multiplicativos (* y x para multiplicar, / y : para dividir y % para el módulo) y después los operadores aditivos (+ para sumar, – para restar).
  • La expresión puede tener espacios, pero al evaluarla se quitan, por tanto: «1 5 * 2» se convierte en «15*2».
  • Si hay un paréntesis de apertura precedido por un dígito o de un paréntesis de cierre, se considera una multiplicación y se pone el signo *.
  • Si hay un paréntesis de cierre seguido de un dígito o de un paréntesis de apertura, se considera una multiplicación y se pone el signo *.
  • Evalúa factoriales (usando el carácter «!»), tanto de números enteros positivos (naturales) como números negativos con parte decimal.
  • El valor de la factorial para números no naturales se calcula usando la función gamma basada en un código adaptado de un ejemplo de StackOverflow.

 

El código de Java y C# en GitHub

Estos son los enlaces del código fuente que tengo publicado en GitHub:

EvaluarExpresiones-java y EvaluarExpresiones-csharp.

Espero que te sea de utilidad.

Nos vemos.
Guillermo

Serializar/deserializar con Json.Serialization (ejemplo para C#)

Pues eso… ahora esto de los ficheros con la extensión .json es lo que está en la «onda» y… pues habrá que aprovecharlo que incluso es fácil usarlo en las aplicaciones de .NET.

Espero que no sirva de precedente, pero en este post no hay ejemplo para Visual Basic, solo para C#. No es porque yo abandone mis END IFs y me pase definitivamente a las llaves y puntos y comas, es porque acabo de terminar una clase «serializable» y la he escrito en C# y por vagancia, no he querido crear un ejemplo en Visual Basic. Eso sí, esta clase la utilizo desde código de Visual Basic, ya que en C# solo he hecho la clase, y como he comprobado que los ejemplos que he consultado no hacían bien el trabajo, me he decidido a escribir esta entra en el blog (post).

La clase que serializo es muy simple, es para «recordar» los tamaños de las columnas de, en mi caso, un DataGridView. Esa cuadrícula utiliza distintos tipos de nombres y anchos de columnas, por tanto, el objeto que contiene los valores está formado por un diccionario en el que la clave es una cadena (string), para saber el tipo de datos mostrados, y los valores es otro diccionario de tipo entero en la clave y el valor que guardará serán el índice de la columna y el ancho de la misma.

Esa propiedad está definida de la siguiente forma:

/// <summary>
/// Diccionario para el tipo de listado y los valores de cada columna 
/// (por índice) y el ancho de la columna.
/// </summary>
[JsonPropertyName("anchos")]
public Dictionary<string, Dictionary<int, int>> Anchos { get; set; } = new();

El atributo JsonPropertyName es el que le indica al compilador que esa propiedad es serializable y que está enlazada con el valor anchos del fichero .json.

Esta es la parte fácil.

Ahora hay que leer y guardar los datos en el fichero de texto con la extensión que queramos, pero que en este ejemplo utilizo el valor estándar: .json.

Y estos son los métodos principales para guardar (Save) y leer (Load) el contenido de la clase en el fichero. Save guarda el contenido de la clase en el fichero (lo serializa) y Load lee el contenido del fichero (lo de-serializa) y lo asigna a un objeto del tipo de la clase del tipo donde está definida esa propiedad. No te líes. Es más sencillo el código que explicar lo que hace… 😉

/// <summary>
/// Carga los valores del fichero indicado.
/// </summary>
/// <param name="fileName">El path del fichero a leer y devolver el contenido.</param>
/// <returns>El objeto leído del fichero indicado.</returns>
private static AnchoColumnas Load(string fileName)
{
    // Abrir el fichero para leer, compartido para lectura y escritura.
    using var stream = new FileStream(fileName, 
                                      FileMode.OpenOrCreate, 
                                      FileAccess.Read, 
                                      FileShare.ReadWrite);
    // Si tiene contenido, deserializarlo, si no, devolver un valor nulo.
    if (stream.Length > 0)
        return JsonSerializer.Deserialize<AnchoColumnas>(stream);
    else
        return null;
}

/// <summary>
/// Guarda los datos de tipo AnchoColumnas indicado.
/// </summary>
/// <param name="anchosColumnas"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private static void Save(AnchoColumnas anchosColumnas, string fileName)
{
    // Abrir el fichero para escribir, compartido para lectura y escritura.
    using var stream = new FileStream(fileName, 
                                      FileMode.OpenOrCreate, 
                                      FileAccess.Write, 
                                      FileShare.ReadWrite);
    // Que se indente el contenido.
    var options = new JsonSerializerOptions { WriteIndented = true };
    // Guardar (serializar) el contenido de la clase.
    JsonSerializer.Serialize<AnchoColumnas>(stream, anchosColumnas, options);
}

En mi caso, he creado otros dos métodos llamados Guardar y Leer en el que asigno el nombre del fichero que contendrá los datos serializados de la clase.
Ese fichero está en el path del ejecutable.

Este es el código:

/// <summary>
/// Guardar los datos de los anchos de los listados.
/// </summary>
/// <param name="anchosColumnas"></param>
public static void Guardar(AnchoColumnas anchosColumnas)
{
    // El fichero está en la carpeta del ejecutable.
    var fic = Path.Combine(Application.StartupPath, "AnchosColumnas.json");
    Save(anchosColumnas, fic);
}

/// <summary>
/// Leer los anchos de las columnas.
/// </summary>
/// <returns></returns>
public static AnchoColumnas Leer()
{
    // El fichero está en la carpeta del ejecutable.
    var fic = Path.Combine(Application.StartupPath, "AnchosColumnas.json");
    return Load(fic);
}

Como puedes comprobar, el método Guardar recibe como parámetro la clase a serializar y el método Leer devuelve un objeto con la clase deserializada (o un valor nulo en caso de que aún no tenga contenido el fichero).

La parte interesante está en los métodos Save y Load, ya que utilizo código «seguro» a la hora de leer o escribir en un fichero que no exista.
Y el truco está en crear el objeto de tipo Stream usando FileStream en lugar de usar los métodos OpenRead u OpenWrite de la clase File, ya que, para usar esos dos métodos habría que hacer comprobaciones si existen, si están compartidos, etc., etc., etc.

Por último, en esa misma clase tengo un método (también compartido o estático) para acceder a la información. Es una propiedad de solo lectura, que se encarga de asignar/leer el objeto si debe hacerlo (cuando inicialmente no está asignado).

Este es el código:

private static AnchoColumnas _AnchosColumnas = null;
/// <summary>
/// Los anchos de las columnas de los listados.
/// </summary>
public static AnchoColumnas AnchosColumnas
{
    get
    {
        if (_AnchosColumnas == null)
        {
            _AnchosColumnas = Leer();
            if (_AnchosColumnas == null)
            {
                _AnchosColumnas = new();
            }
        }
        return _AnchosColumnas;
    }

Y finalmente te muestro cómo uso esa clase desde el código que tengo en Visual Basic:

Primero para asignar las columnas al objeto DataGridView (en el código indicado por lvDatos) y después cuándo guardar los nuevos valores, cosa que hago en el evento ColumnWidthChanged.

La asignación de los anchos guardados lo hago en un método (con más código del mostrado) en el que asigno los valores de las columnas, tanto el texto a mostrar como el ancho, y por supuesto el número de las mismas.

Este es el código para leer los valores guardados y asignarlos a las columnas del objeto lvDatos:

' Asignar los anchos que estén guardados.           (22/abr/22 20.34)
If AnchoColumnas.AnchosColumnas.Anchos.ContainsKey(value.ToString()) = False Then
    AnchoColumnas.AnchosColumnas.Anchos.Add(value.ToString(),
                                            New Dictionary(Of Integer, Integer))
End If
Dim cols = AnchoColumnas.AnchosColumnas.Anchos(value.ToString())
If cols.Keys.Count = 0 Then
    For i = 0 To lvDatos.Columns.Count - 1
        cols.Add(i, lvDatos.Columns(i).Width)
    Next
Else
    For i = 0 To cols.Keys.Count - 1
        lvDatos.Columns(i).Width = cols(i)
    Next

d End If

El valor de la variable «value» es el nombre de la enumeración que utilizo y que es un valor asignado como parámetro de este método (recuerda que esto es solo un extracto en el que se asignan los valores leídos, si es que existe el fichero con esos valores ya guardados).

Y para finalizar, el código del evento ColumnWidthChanged, en el que, tengo puesto una comprobación de si se está inicializando (cuando se carga el formulario) con idea de que no se guarden los valores iniciales que tenga.

Private Sub lvDatos_ColumnWidthChanged(sender As Object, e As DataGridViewColumnEventArgs) Handles lvDatos.ColumnWidthChanged
    ' Cambiar el ancho de las columnas de los totales       (10/mar/22 04.28)
    ' al cambiar el del principal.
    If inicializando Then Return

    ' Asignar y guardar los valores.                        (22/abr/22 20.39)
    Dim value = TipoListado.ToString()
    With lvDatos
        If AnchoColumnas.AnchosColumnas.Anchos.ContainsKey(value) = False Then
            AnchoColumnas.AnchosColumnas.Anchos.Add(value, New Dictionary(Of Integer, Integer))
        End If
        ' Asegurarse que se asignan correctamente.
        Dim cols = AnchoColumnas.AnchosColumnas.Anchos(value)
        cols.Clear()
        For i = 0 To .Columns.Count - 1
            cols.Add(i, .Columns(i).Width)
        Next
        AnchoColumnas.Guardar(AnchoColumnas.AnchosColumnas)
    End With
End Sub

Y esto es todo… otro día pondré el código para Visual Basic de la clase y un ejemplo de cómo usarla escrito en C#, pero eso será en otra ocasión 😉

Nos vemos.
Guillermo