Archivo de la etiqueta: truco

Simular discard de C# en Visual Basic

Pues eso… haciendo pruebas con el conversor de C# a VB de Paul1956 comprobé que los discard de C# (asignación digamos que nula de una función o descartes según la traducción al español ) los generaba usando dos guiones bajo (undercore) y el «asistente» de VS indicó que creara una función para usar ese descarte. Y al ver que es válido usar el nombre __ (dos guiones bajos) como nombre de una función, pensé que también sería válido para una propiedad… de forma que se pudiese haccer ese tipo de asignación.

Por ejemplo, si tenemos este código de C# en el que se «descarta» el valor devuelto por la función MostrarAyuda:

class Program
{
    static void Main(string[] args)
    {
        _ = MostrarAyuda(true, false);

        //Console.WriteLine(_);

        Console.WriteLine();
        Console.WriteLine("Pulsa una tecla para finalizar.");
        Console.ReadKey();
    }

    private static string MostrarAyuda(bool value1, bool value2)
    {
        var s = $"Valores: value1: {value1}, value2: {value2}";
        Console.WriteLine(s);
        return s;
    }
}

La conversión podría quedar de esta forma:

Public Shared Sub Main(args As String())
    '__ = MostrarAyuda(True, False)

    __ = MostrarAyuda(True, False)

    Console.WriteLine(__)

    Console.WriteLine()
    Console.WriteLine("Pulsa una tecla para finalizar.")
    Console.ReadKey()
End Sub

Private Shared Property __ As Object

Private Shared Function MostrarAyuda(value1 As Boolean, value2 As Boolean) As String
    Dim s = $"Valores: value1: {value1}, value2: {value2}"
    Console.WriteLine(s)
    Return s
End Function

Es un apaño, pero puede valer para equiparar los dos lenguajes.

Nota:
Fíjate que en VB al ser una propiedad válida la podemos usar como cualquier otra variable para mostrar el contenido.

Y esto es todo… a ver si lo implementa y así no dará error cuando se convierta código de C# que tenga ese tipo de asignación (que la implementaron en C# 7.0)

Nos vemos.
Guillermo

Escribir código en C#. Guía para los developers de Visual Basic

Pues eso… Ahora que estoy empezando a escribir más programas en C# me doy cuenta de que esto de poner al final de cada sentencia un punto y coma es un poco engorroso. La idea sería que cuando se escribe una línea en C# al pulsar intro en el IDE de Visual Studio automáticamente le agregara el punto y coma al final. Esto lo he puesto como sugerencia en la Developer Community a ver si hacen caso, aunque no sé yo… si quieres ver esa sugerencia (Add automatically the semicolon (;) at the end when writing a new sentence in C# ), sigue el enlace.

Lo que si me he dado cuenta, o más bien, he confirmado, es que salvo excepciones y algunas características propias, escribir código en C# es casi como en Visual Basic. Por supuesto, si escribes una array debes usar corchetes [] en vez de paréntesis (), debes agregarle a todos los métodos sin parámetros los paréntesis al final, o la forma de escribir un simple if, tienes que acostumbrarte a poner entre paréntesis lo que vayas a comprobar con ese if.

Después hay cosas que son más raras (C# y otros lenguajes de la misma familia son expertos en esas rarezas que la gente de Visual Basic vemos como ganas de complicarse la vida, jejeje, pero bueno, es cuestión de ir acostumbrándose) por ejemplo el operador ternario de C# que en Visual Basic lo escribimos como un If(comprobación, valor si true, valor si false):

Dim s = If( valor, "Es True", "Es False")
dim s = if(valor,  "Es True" , "Es False")

en C# hay que hacerlo así:

var s = valor ? "Es True" : "Es False";
var s = valor ? "Es True" : "Es False";

Esto asigna a la variable s lo que corresponda, dependiendo de que la variable valor contenga un valor verdadero o falso.

Viendo el código de VB que te acabo de poner, parece que me contradigo con lo que he comentado de los ifs de C# (que hay que ponerlo entre paréntesis) pero es que este If que he usado en VB es un If especial, y sirve para lo que acabo de explicarte.

Si queremos usar un If normal podríamos hacerlo de la siguiente forma:

Dim s = ""
If valor Then
    s = "Es True"
Else
    s = "Es False"
End If
Dim s = ""
If valor Then
    s = "Es True"
Else
    s = "Es False"
End If

En C# sería algo así (se puede escribir con o sin llaves {}:

var s = "";
if( valor )
    s = "Es True";
else
    s = "Es False";
var s = "";
if( valor )
    s = "Es True";
else
    s = "Es False";

De esta forma, es para cuando después del if o del else solo hay una instrucción, si queremos poner más de una instrucción, debemos usar las llaves:

var s = "";
if( valor )
{
    s = "Es True";
}
else
{
    s = "Es False";
}
var s = "";
if (valor)
{
    s = "Es True";
}
else
{
    s = "Es False";
}

Y así… más cosas… como los bucles for, etc.

No voy a seguir porque no es plan de hacer un curso de cómo hacer las cosas en los dos lenguajes, de todas formas, en mi sitio (www.elguille.info) y en este blog los ejemplos de código que pongo los tengo en los dos lenguajes.

Y si te interesa saber algunas de las equivalencias entre Visual Basic .NET y C# las puedes encontrar en esta página (en realidad son 3 páginas de equivalencias):

Equivalencias entre VB.NET y C# (1) (elguille.info)

Equivalencias entre Visual Basic para .NET y C# (2) (elguille.info)

Equivalencias entre Visual Basic para .NET y C# (3) (elguille.info)

En otro momento le echaré un vistazo al contenido completo y veré si añado nuevas cosas, que las hay… ya que la última página la publiqué el 6 de agosto de 2006 y desde entonces (en estos más de 14 años) han salido cosas nuevas que hay que equiparar 😉 

Y esto es todo por ahora… mañana más (u otro día más).

Nos vemos.
Guillermo

#evolveVB #evolucionarVB

P.S.
Escrito en documentos de Google, intentando usar el dictado por voz, pero se ve que no me entiende tan bien o al menos no es mejor de lo que me esperaba… ya que parte de ese texto lo escribí usando Keep y dictándolo, pero como no había forma de añadir los puntos, coma, etc., he intentado con los documentos en la web usando Chrome.
Seguiré intentándolo… como con C#, a base de probar y probar al final se consigue lo que uno quiere 😉

P.S.2
El código en Github:
elGuille-info/escribir-codigo-en-csharp: Escribir código en C#. Guía para los developers de Visual Basic (github.com)

Generar clave SHA1 con el nombre y password del usuario

Pues eso… en este post te explico cómo generar una clave (usando la clase SHA1CryptoServiceProvider) formada a partir de dos cadenas, normalmente el nombre del usuario y el password (o contraseña), de esta forma se genera una cadena única (de 40 caracteres) de forma que si alguien accede a ella no sabrá nunca cuales fueron las dos cadenas que la formaron (o eso es lo que espero que ocurra, jejeje). Además te mostraré también una función para evitar que el usuario introduzca caracteres no válidos y que pueden se usados para acceder maliciosamente a una base de datos.
Por supuesto, te mostraré el código tanto para Visual Basic como para C#.

El código que te mostraré (al menos el de generar la clave SHA1) está basado en este código publicado en mi sitio (usando .NET 1.1):
El ejemplo de Visual Basic .NET:
comprobar_usuario_usando_base_datos_vb2003
El ejemplo de C#:
comprobar_usuario_usando_base_datos_cs2003

Generar una clave SHA1 usando SHA1CryptoServiceProvider

Para acceder a esta clase necesitas una importación del espacio de nombres System.Security.Cryptography y como en el código usaremos un objeto StringBuilder y UTF8Encoding, también habrá que importar System.Text.

En el código de ejemplo para usar los métodos definidos en la clase UtilSHA1 (que será en la que defino los dos métodos usados) se hará una comprobación de si tanto en el nombre como en el password usado hay caracteres no válidos, los caracteres que compruebo en el método ValidarTextoClave son los caracteres: ?*%’_ y

De esa forma intentamos asegurarnos que no se pueda hacer un SQL injection, es decir, intentar acceder maliciosamente a la base de datos a la que presumiblemente se quiere acceder.

Veamos primero el código que usa los dos métodos, el de comprobar la validez del texto introducido (ValidarTextoClave) y el de generar la clave SHA1 (GenerarClaveSHA1).

Método Main con el código de prueba para usar los métodos de la clase

Como te dije antes, se pide el nombre del usuario y el password a usar para generar la cadena con la clave SHA1 (que será de 40 caracteres convertidos a mayúsculas).

Este sería el código del método Main para Visual Basic, con las importaciones de los espacios de nombres necesarios en todo el código de ejemplo:

Option Strict On
Option Infer On

Imports System

Imports System.Text
Imports System.Security.Cryptography

Module Program
    Sub Main(args As String())
        
        dim valido As Boolean
        dim usuario As String
        dim passw as String

        Do
            Console.Write("Escribe el nombre del usuario: ")
            usuario = Console.ReadLine()
            ' si el nombre del usuario tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(usuario)
            if not valido
                Console.WriteLine("Nombre de usuario NO VÁLIDO.")
            end if
        Loop While Not valido

        Do
            Console.Write("Escribe la clave: ")
            passw = Console.ReadLine()
            ' si la clave tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(passw)
            if not valido
                Console.WriteLine("La clave NO ES VÁLIDA.")
            end if
        Loop While Not valido

        ' generar la clave SHA1 y mostrarla
        dim claveSHA1 = UtilSHA1.GenerarClaveSHA1(usuario, passw)
        Console.WriteLine($"La clave SHA1 es: '{claveSHA1}'.")
            
    End Sub
End Module

 

Este sería el código del método Main para C#, con las importaciones (using) de los espacios de nombres usados en el código:

using System;

using System.Text;
using System.Security.Cryptography;

class Program
{
    static void Main(string[] args)
    {
        bool valido;
        string usuario;
        string passw;

        do
        {
            Console.Write("Escribe el nombre del usuario: ");
            usuario = Console.ReadLine();
            // si el nombre del usuario tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(usuario);
            if (!valido)
                Console.WriteLine("Nombre de usuario NO VÁLIDO.");
        }
        while (!valido);

        do
        {
            Console.Write("Escribe la clave: ");
            passw = Console.ReadLine();
            // si la clave tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(passw);
            if (!valido)
                Console.WriteLine("La clave NO ES VÁLIDA.");
        }
        while (!valido);

        // generar la clave SHA1 y mostrarla
        var claveSHA1 = UtilSHA1.GenerarClaveSHA1(usuario, passw);
        Console.WriteLine($"La clave SHA1 es: '{claveSHA1}'.");
    }
}

 

La clase UtilSHA1 con los métodos para comprobar la validez del texto y generar la clave

A continuación te muestro el código del método para validar el texto del nombre del usuario y el password o contraseña para que no contenga caracteres no deseados.

Para Visual Basic:

''' <summary>
''' Validar caracteres en la clave.
''' No se aceptan ?*%'_ ni --
''' </summary>
Public Shared Function ValidarTextoClave(laClave As String) As Boolean
    Dim sNoVale As String = "?*%'_"

    laClave = laClave.Trim()

    If laClave.IndexOf("--") > -1 Then
        Return False
    End If
    If laClave.IndexOfAny(sNoVale.ToCharArray) > -1 Then
        Return False
    End If

    Return True
End Function

 

Para C#:

/// <summary>
/// Validar caracteres en la clave.
/// No se aceptan ?*%'_ ni --
/// </summary>
public static bool ValidarTextoClave(string laClave)
{
    string sNoVale = "?*%'_";

    laClave = laClave.Trim();

    if (laClave.IndexOf("--") > -1)
        return false;
    if (laClave.IndexOfAny(sNoVale.ToCharArray()) > -1)
        return false;

    return true;
}

 

Y ahora el código con la definición del método GenerarClaveSHA1 en el que indicaremos dos cadenas: el nombre del usuario y el password y a partir de la concatenación de ambas generar el valor SHA1 producido por el método ComputeHash que en realidad devuelve un array de tipo Byte, el cual convertimos en valores hexadecimales (con dos cifras por valor) con idea de que se genere la cadena deseada de 40 caracteres en total, que finalmente convertimos en mayúsculas, pero que bien puedes dejarlo en minúsculas si así te parece mejor.

El código para Visual Basic:

''' <summary>
''' Generar una clave SHA1 para guardarla en lugar del password,
''' de esa forma no se podrá saber la clave.
''' La longitud es de 40 caracteres.
''' </summary>
''' <remarks>
''' Crear una clave SHA1 como la generada por:
''' FormsAuthentication.HashPasswordForStoringInConfigFile
''' Basado en el ejemplo de mi sitio:
''' http://www.elguille.info/NET/dotnet/comprobar_usuario_usando_base_datos_vb2003.htm
''' </remarks>
Public Shared Function GenerarClaveSHA1(nick As String, clave As String) As String
    ' Crear una clave SHA1 como la generada por 
    ' FormsAuthentication.HashPasswordForStoringInConfigFile
    ' Adaptada del ejemplo de la ayuda en la descripción de SHA1 (Clase)
    Dim enc As New UTF8Encoding
    ' Por si el usuario (nick) es nulo
    If String.IsNullOrWhiteSpace(nick) Then
        nick = ""
    Else
        nick = nick.ToLower
    End If
    Dim data() As Byte = enc.GetBytes(nick & clave)
    Dim result() As Byte

    Dim sha As New SHA1CryptoServiceProvider
    ' This is one implementation of the abstract class SHA1.
    result = sha.ComputeHash(data)

    ' Convertir los valores en hexadecimal
    ' cuando tiene una cifra hay que rellenarlo con cero
    ' para que siempre ocupen dos dígitos.
    Dim sb As New StringBuilder
    For i As Integer = 0 To result.Length - 1
        If result(i) < 16 Then
            sb.Append("0")
        End If
        sb.Append(result(i).ToString("x"))
    Next

    Return sb.ToString.ToUpper
End Function

 

El código para C#:

/// <summary>
/// Generar una clave SHA1 para guardarla en lugar del password,
/// de esa forma no se podrá saber la clave.
/// La longitud es de 40 caracteres.
/// </summary>
/// <remarks>
/// Crear una clave SHA1 como la generada por:
/// FormsAuthentication.HashPasswordForStoringInConfigFile
/// Basado en el ejemplo de mi sitio:
/// http://www.elguille.info/NET/dotnet/comprobar_usuario_usando_base_datos_cs2003.htm
/// </remarks>
public static string GenerarClaveSHA1(string nick, string clave)
{
    // Crear una clave SHA1 como la generada por 
    // FormsAuthentication.HashPasswordForStoringInConfigFile
    // Adaptada del ejemplo de la ayuda en la descripción de SHA1 (Clase)
    UTF8Encoding enc = new UTF8Encoding();
    // Por si el usuario (nick) es nulo
    if (string.IsNullOrWhiteSpace(nick))
        nick = "";
    else
        nick = nick.ToLower();
    byte[] data = enc.GetBytes(nick + clave);
    byte[] result;

    SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
    // This is one implementation of the abstract class SHA1.
    result = sha.ComputeHash(data);

    // Convertir los valores en hexadecimal
    // cuando tiene una cifra hay que rellenarlo con cero
    // para que siempre ocupen dos dígitos.
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < result.Length; i++)
    {
        if (result[i] < 16)
            sb.Append("0");
        sb.Append(result[i].ToString("x"));
    }

    return sb.ToString().ToUpper();
}

 

Nota:
El código final del método GenerarClaveSHA1 se puede simplificar para que use dos caracteres hexadecimales sin necesidad de la comparación de si el valor es menor de 16.
El código te lo muestro en el repositorio de github por si quieres intentarlo por tu cuenta 😉

 

Nota importante:
Comentarte que la generación de la clave SHA1 distingue entre mayúsculas y minúsculas, es decir, si al generar la clave SHA1 usaste Guillermo como usuario (o nick) si vuelves a generarla con el nombre en minúsculas (guillermo) el valor generado será diferente.
Esto mismo es aplicable a la contraseña o password.

 

El código completo con los proyectos para usar con .NET 5.0 (tanto con dotnet como con Visual Studio Code o con Visual Studio 2019 v16.8) está publicado en github:
Generar clave SHA1 con el nombre y password del usuario.

 

Y esto es todo… espero que te sea de utilidad… ya sabes que esa es la idea… y si te parece bien (y puedes) no estaría de más que dejaras una propina usando el enlace de PayPal 😉
Gracias.

 

Nos vemos.
Guillermo

Truco por si asignas la posición del formulario a una guardada y cambias de tipo de pantalla

Pues eso… que hace poco mandé al servicio técnico de LG la pantalla externa que conecto a mi ordenador portátil y estoy teniendo problemas cuando abro las aplicaciones que he estado usando con ese monitor, al menos las que tengo programadas para que al cambiar la posición o el tamaño se guardan en un fichero de configuración para cuando cargue de nuevo esa aplicación se muestre donde estuvo la vez anterior.

Una de las opciones por la que opté fue poner que siempre se mostrasen en el centro de la pantalla, pero con el tamaño indicado en el diseño del formulario.

La idea que se me ha ocurrido esta mañana es la de comprobar dónde está la posición Left y Top guardadas y si estarían fuera del rango de esos mismos valores de las propiedades de el WorkingArea de PrimaryScreen.

Los valores de alto y ancho (Height y Width) no los toco, ya que el usuario podrá cambiar esos tamaños a su antojo, ya que ahora sí que podrá ver la ventana de la aplicación.

Este es el código para Visual Basic con el que hago la comprobación que te he mencionado:

' Asignar el tamaño y última posición
' Comprobar que esté en la parte visible                    (24/Oct/20)
Dim l = cfg.GetValue("Ventana", "Left", Me.Left)
Dim t = cfg.GetValue("Ventana", "Top", Me.Top)
If Screen.PrimaryScreen.WorkingArea.Left < l Then
    Me.Left = cfg.GetValue("Ventana", "Left", Me.Left)
Else
    Me.Left = 0
End If
If Screen.PrimaryScreen.WorkingArea.Top < t Then
    Me.Top = cfg.GetValue("Ventana", "Top", Me.Top)
Else
    Me.Top = 0
End If
Me.Height = cfg.GetValue("Ventana", "Height", Me.Height)
Me.Width = cfg.GetValue("Ventana", "Width", Me.Width)

Los valores de la posición y tamaño del formulario los obtengo de un fichero de configuración y solo asigno el valor Left si la posición Left de WorkingArea es menor, ya que cuando está en el monitor externo (al menos en mi caso) el valor de Left suele ser negativo.
Y con el valor Top hacemos lo mismo, solo asignarlo si no es menor que el valor Top del área de trabajo de la pantalla principal.

 

Y aquí tienes el mismo código para C#:

// Asignar el tamaño y última posición
// Comprobar que esté en la parte visible                    (24/Oct/20)
var l = cfg.GetValue("Ventana", "Left", this.Left);
var t = cfg.GetValue("Ventana", "Top", this.Top);
if (Screen.PrimaryScreen.WorkingArea.Left < l)
    this.Left = cfg.GetValue("Ventana", "Left", this.Left);
else
    this.Left = 0;

if (Screen.PrimaryScreen.WorkingArea.Top < t)
    this.Top = cfg.GetValue("Ventana", "Top", this.Top);
else
    this.Top = 0;

this.Height = cfg.GetValue("Ventana", "Height", this.Height);
this.Width = cfg.GetValue("Ventana", "Width", this.Width);

 

Y esto es todo, espero que te haya sido de utilidad.

 

Nos vemos.
Guillermo

Detectar varias pulsaciones de teclas en aplicación de Windows Forms (código para C# y Visual Basic)

Pues eso… necesitaba saber cómo detectar varias pulsaciones de teclas al estilo de Ctrl+K, Ctrl+C y similares, es decir, se pulsa la tecla Control seguida de la K y se pulsa la tecla Control seguida de la C (como la combinación de Visual Studio para poner comentarios en la selección que haya en el código). Así que… busqué en internet, pero… había ejemplos muy enrevesados… con temporizadores y demás monadas… así que… basándome en algunos ejemplos (seguramente del mismo autor o copiados unos de otros) he hecho algo que puede servir… al menos a mí me sirve, aunque se puede mejorar, como todo.

De la forma que lo he hecho da igual si se pulsa primero Ctrl+K que Ctrl+C, ya que lo que he intentado es que se sepa cuando se han hecho esas pulsaciones, y si entre cualquiera de las dos pulsaciones se ha pulsado otra tecla, no se tiene en cuenta esa combinación. Es decir si quieres detectar Ctrl+K seguida de Ctrl+C (que para el caso del código que te mostraré es lo mismo que si pulsas Ctrl+C seguida de Ctrl+K) pulsas otra tecla o combinación de teclas, no se dará como detectada esa doble pulsación.

Nota:
Precisamente con esas teclas: Ctrl+C y otras automatizadas de edición: Ctrl+V, Ctrl+X, Ctrl+P, etc., habría que tener cuidado o hacerle un seguimiento distinto al que ahora hago para que no la detecte y, por ejemplo pegue el texto si es Ctrl+P).

¿Dónde se hará la comprobación de la tecla pulsada?

Las comprobaciones de qué tecla se está pulsando (o se ha pulsado) la hago en el evento KeyDown del formulario. Y como de forma predeterminada el formulario no intercepta las pulsaciones de las teclas, habrá que hacer una asignación de un valor verdadero (true) a la propiedad KeyPreview del formulario. Eso lo he puesto en el evento Load, con idea de que esté activado si por casualidad cambio el valor en el diseñador (esas cosas suelen ocurrir, y es complicado de saber porqué antes funcionaba y después no).

En la figura 1 tienes una captura del código de ejemplo en funcionamiento (en ese caso la aplicación de C# creada con .NET Framework 4.8).

Figura 1. La aplicación de ejemplo en funcionamiento.

¿Cómo saber si hay varias combinaciones de teclas?

Lo que yo he hecho es crear unas variables para asignarles un valor si la combinación que se quiere detectar se cumple. Esas variables (o campos) definidas a nivel de la clase, las he declarado de tipo entero, (en los ejemplos que vi en la web eran de tipo Boolean, bool en C#), ya que lo que me interesa es saber si algunas de las combinaciones se ha hecho más de una vez, ese es el caso de Ctrl+K, Ctrl+K, es decir: pulsar dos veces la tecla Control y la tecla K.

Si no quieres comprobar si hay una combinación que se haga más de una vez, lo mismo puedes usar variables de tipo Bolean (bool en C#), eso ya a tu discreción (o preferencia).

Aquí te muestro el código con la definición de esas variables (tanto para VB como para C#):

' Para doble pulsación de teclas
Private CtrlK As Integer
Private CtrlC As Integer
Private CtrlU As Integer
Private CtrlL As Integer
Private ShiftAltL As Integer
Private ShiftAltS As Integer
// Para doble pulsación de teclas
private int CtrlK;
private int CtrlC;
private int CtrlU;
private int CtrlL;
private int ShiftAltL;
private int ShiftAltS;

Esas variables las usaremos en el evento KeyDown del formulario, incrementando el valor cuando se cumpla que se han pulsado las teclas indicadas, por ejemplo si queremos detectar la pulsación de Ctrl+K, tendremos que incrementar el valor de la variable CtrlK, ídem con el resto.

Como en el ejemplo hay varias combinaciones de teclas que detectar, puede ser un poco largo de ver, pero prefiero que lo veas completo para que no te líes demasiado.
Ahí se comprueban las tres posibles teclas de «control«, es decir, Control, Shift y Alt. También hago comprobaciones para que, por ejemplo, si queremos detectar Ctrl+Shift se haga en un bloque de código diferente para cuando se detecta, por ejemplo Ctrl+Alt o Shift+Alt.
Creo que el código está bastante claro y no tendrás complicaciones de ver el proceso que se hace.

Aquí tienes el código de VB y C#.

Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
        ' Comprobaciones para Ctrl+Shift

        ' esta de forma simple
        If e.Control AndAlso e.Shift Then
            If e.KeyCode = Keys.V Then
                e.Handled = True

                'MostrarRecortes();
                txtPulsadas.Text = "Capturada: Ctrl+Shift+V" & vbCrLf & txtPulsadas.Text

            End If

            ' Estas son con varias combinaciones

            ' Comprobaciones para Shift+Alt
        ElseIf e.Shift AndAlso e.Alt Then
            ' si se ha pulsado Shift+Alt+L
            If e.KeyCode = Keys.L Then
                e.Handled = True

                ShiftAltL += 1
                ' si se ha pulsado Shift+Alt+S
            ElseIf e.KeyCode = Keys.S Then
                e.Handled = True

                ShiftAltS += 1
            End If
            ' Si se ha pulsado Shitf+Alt+S, Shift+Alt+L
            ' (en cualquier orden)
            If ShiftAltL = 1 AndAlso ShiftAltS = 1 Then
                e.Handled = True

                'ClasificarSeleccion();
                txtPulsadas.Text = "Capturada: Shift+Alt+L, Shift+Alt+S" & vbCrLf & txtPulsadas.Text

            End If

            ' Comprobaciones para solo la tecla Ctrl (sin Shift ni Alt)
        ElseIf e.Control AndAlso Not e.Shift AndAlso Not e.Alt Then
            ' Solo se ha pulsado la tecla Ctrl
            ' comprobar el resto de combinaciones
            ' Forma simple si se ha pulsado Ctrl+B
            If e.KeyCode = Keys.B Then
                e.Handled = True

                ' Esta solo es para detectar
                ' la combinación 'simple' de Ctrl+B
                ' No es necesario llevar la cuenta de las pulsaciones
                txtPulsadas.Text = "Capturada: Ctrl+B" & vbCrLf & txtPulsadas.Text


            ElseIf e.KeyCode = Keys.K Then
                e.Handled = True

                CtrlK += 1
                txtPulsadas.Text = "Ctrl+K - " & txtPulsadas.Text

            ElseIf e.KeyCode = Keys.C Then
                e.Handled = True

                CtrlC += 1
                txtPulsadas.Text = "Ctrl+C - " & txtPulsadas.Text

            ElseIf e.KeyCode = Keys.U Then
                e.Handled = True

                CtrlU += 1
                txtPulsadas.Text = "Ctrl+U - " & txtPulsadas.Text

            ElseIf e.KeyCode = Keys.L Then
                e.Handled = True

                CtrlL += 1
                txtPulsadas.Text = "Ctrl+L - " & txtPulsadas.Text

            End If

            ' Si se ha pulsado Ctrl+K, CtrlC
            If CtrlK = 1 AndAlso CtrlC = 1 Then
                e.Handled = True

                ' Ctrl+K, Ctrl+C
                CtrlK = 0
                CtrlC = 0
                'PonerComentarios(richTextBoxCodigo);
                txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+C" & vbCrLf & txtPulsadas.Text


                ' Si se ha pulsado Ctrl+K, Ctrl+U
            ElseIf CtrlK = 1 AndAlso CtrlU = 1 Then
                e.Handled = True

                ' Ctrl+K, Ctrl+U
                CtrlK = 0
                CtrlU = 0
                'QuitarComentarios(richTextBoxCodigo);
                txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+U" & vbCrLf & txtPulsadas.Text


                ' Si se ha pulsado Ctrl+K, Ctrl+L
            ElseIf CtrlK = 1 AndAlso CtrlL = 1 Then
                e.Handled = True

                ' Ctrl+K, Ctrl+L
                CtrlK = 0
                CtrlL = 0
                ' preguntar
                'buttonEditorMarcadorQuitarTodos.PerformClick();
                txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+L" & vbCrLf & txtPulsadas.Text


                ' Si se ha pulsado Ctrl+K, Ctrl+K
            ElseIf CtrlK = 2 Then
                e.Handled = True

                ' Ctrl+K, Ctrl+K
                CtrlK = 0
                'MarcadorPonerQuitar();

                txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+K" & vbCrLf & txtPulsadas.Text

            End If
        Else
            txtPulsadas.Text = $"{vbCrLf}No es una de las teclas comprobadas: {e.KeyCode} +{vbCrLf}" &
                               $"    Ctrl: {e.Control}, Shift: {e.Shift}, Alt: {e.Alt}{vbCrLf}" & txtPulsadas.Text

            CtrlK = 0
            CtrlC = 0
            CtrlU = 0
            ShiftAltL = 0
            ShiftAltS = 0

            ' Otras pulsaciones
            ' No están detectadas explícitamente

        End If
    End Sub
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    // Comprobaciones para Ctrl+Shift

    // esta de forma simple
    if (e.Control && e.Shift)
    {
        if (e.KeyCode == Keys.V)
        {
            e.Handled = true;

            //MostrarRecortes();
            txtPulsadas.Text = "Capturada: Ctrl+Shift+V\r\n" + txtPulsadas.Text;

        }
    }
            
    // Estas son con varias combinaciones

    // Comprobaciones para Shift+Alt
    else if (e.Shift && e.Alt)
    {
        // si se ha pulsado Shift+Alt+L
        if (e.KeyCode == Keys.L)
        {
            e.Handled = true;

            ShiftAltL += 1;
        }
        // si se ha pulsado Shift+Alt+S
        else if (e.KeyCode == Keys.S)
        {
            e.Handled = true;

            ShiftAltS += 1;
        }
        // Si se ha pulsado Shitf+Alt+S, Shift+Alt+L
        // (en cualquier orden)
        if (ShiftAltL == 1 && ShiftAltS == 1)
        {
            e.Handled = true;

            //ClasificarSeleccion();
            txtPulsadas.Text = "Capturada: Shift+Alt+L, Shift+Alt+S\r\n" + txtPulsadas.Text;

        }
    }

    // Comprobaciones para solo la tecla Ctrl (sin Shift ni Alt)
    else if (e.Control && !e.Shift && !e.Alt)
    {
        // Solo se ha pulsado la tecla Ctrl
        // comprobar el resto de combinaciones

        // Forma simple si se ha pulsado Ctrl+B
        if (e.KeyCode == Keys.B)
        {
            e.Handled = true;

            // Esta solo es para detectar
            // la combinación 'simple' de Ctrl+B
            // No es necesario llevar la cuenta de las pulsaciones
            txtPulsadas.Text = "Capturada: Ctrl+B\r\n" + txtPulsadas.Text;

        }

        else if (e.KeyCode == Keys.K)
        {
            e.Handled = true;

            CtrlK += 1;
            txtPulsadas.Text = "Ctrl+K - " + txtPulsadas.Text;

        }
        else if (e.KeyCode == Keys.C)
        {
            e.Handled = true;

            CtrlC += 1;
            txtPulsadas.Text = "Ctrl+C - " + txtPulsadas.Text;

        }
        else if (e.KeyCode == Keys.U)
        {
            e.Handled = true;

            CtrlU += 1;
            txtPulsadas.Text = "Ctrl+U - " + txtPulsadas.Text;

        }
        else if (e.KeyCode == Keys.L)
        {
            e.Handled = true;

            CtrlL += 1;
            txtPulsadas.Text = "Ctrl+L - " + txtPulsadas.Text;

        }

        // Si se ha pulsado Ctrl+K, CtrlC
        if (CtrlK == 1 && CtrlC == 1)
        {
            e.Handled = true;

            // Ctrl+K, Ctrl+C
            CtrlK = 0;
            CtrlC = 0;
            //PonerComentarios(richTextBoxCodigo);
            txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+C\r\n" + txtPulsadas.Text;

        }

        // Si se ha pulsado Ctrl+K, Ctrl+U
        else if (CtrlK == 1 && CtrlU == 1)
        {
            e.Handled = true;

            // Ctrl+K, Ctrl+U
            CtrlK = 0;
            CtrlU = 0;
            //QuitarComentarios(richTextBoxCodigo);
            txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+U\r\n" + txtPulsadas.Text;

        }

        // Si se ha pulsado Ctrl+K, Ctrl+L
        else if (CtrlK == 1 && CtrlL == 1)
        {
            e.Handled = true;

            // Ctrl+K, Ctrl+L
            CtrlK = 0;
            CtrlL = 0;
            // preguntar
            //buttonEditorMarcadorQuitarTodos.PerformClick();
            txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+L\r\n" + txtPulsadas.Text;

        }

        // Si se ha pulsado Ctrl+K, Ctrl+K
        else if (CtrlK == 2)
        {
            e.Handled = true;

            // Ctrl+K, Ctrl+K
            CtrlK = 0;
            //MarcadorPonerQuitar();

            txtPulsadas.Text = "Capturada: Ctrl+K, Ctrl+K\r\n" + txtPulsadas.Text;

        }
    }
    else
    {
        txtPulsadas.Text = $"\r\nNo es una de las teclas comprobadas: {e.KeyCode} +\r\n"+
                           $"    Ctrl: {e.Control}, Shift: {e.Shift}, Alt: {e.Alt}\r\n" + txtPulsadas.Text;
        CtrlK = 0;
        CtrlC = 0;
        CtrlU = 0;
        ShiftAltL = 0;
        ShiftAltS = 0;

        // Otras pulsaciones
        // No están detectadas explícitamente
    }
}

Nota:
En el código están comentadas las funciones / métodos a los que en el programa que uso esta forma de controlar las pulsaciones (múltiples) de teclas llama cuando se produce una. Así sabrás cuándo tienes que actuar cuando se produzca la pulsación esperada.

Una aclaración sobre la diferencia entre KeyCode y KeyValue

Hay gente que no se aclara entre los valores de esas dos propiedades del argumento KeyEventArgs del evento KeyDown (o KeyUp).

KeyCode contiene el código de la tecla pulsada y el del tipo Keys (enumeración).
KeyValue contiene el código de la tecla pulsada y el de tipo Integer (int en C#).

En Visual Basic se puede usar indistintamente sin hacer nada especial, es decir, par saber si se ha pulsado la tecla B puedes hacerlo de estas dos formas:
If e.KeyCode = Keys.B Then o If e.KeyValue = Keys.B

Pero en C# no te permite hacer la comparación del valor int con un valor de la enumeración Keys. Si así lo quieres hacer, tendrías que hacer un cast al tipo entero:
if (e.KeyValue == (int)Keys.B)

Por tanto, es mejor usar e.KeyCode si la intención es compararla con un valor de Keys.

Ya solo me queda ponerte el código completo de esta aplicación de prueba, pero como últimamente estoy haciendo (creo que solo lo he hecho una vez) ese código (tanto el de Visual Basic como el de C#) está en mis repositorios en GitHub, concretamente en varias-pulsaciones-de-teclas.

Dicho código está creado en un proyecto para Visual Studio 2019 usando .NET Framework 4.8, pero también es válido para aplicaciones (de WinForms) creadas para .NET Core, al menos yo lo estoy usando en .NET 5.0 RC1.

Y esto es todo… espero que te sea de utilidad.

Nos vemos.
Guillermo

Indicar a Visual Studio que al copiar texto lo haga con colores más enriquecidos (el RTF)

Pues eso… Si usas el texto copiado de Visual Studio para después mostrarlo coloreado en una publicación en la WEB (un blog, etc.) este truco que te explico aquí te será de gran ayuda, ya que le indica que a demás de colorear el código «normal» (instrucciones, comentarios y cadenas entrecomilladas) también copie el color de los tipos.

Esto lo utilizo con mi utilidad de colorear el código (gsColorearCodigo), concretamente usando la opción Colorear desde RTF del menú contextual de la caja de textos donde se pega el código a colorear.

Y ( lo que decía ese párrafo anterior 🙂 ) antes, al copiar texto del editor de Visual Studio, éste me contenía los colores de los tipos de datos (verde azulado), pero últimamente no lo hacía, así que… esta mañana me puse a ver las opciones del editor de textos (realmente buscando otra cosa, todo hay que decirlo) y me topé con una opción que no era muy aclaratorio de que podía ser eso lo que lo solucionara: Usar clasificación precisa. Pero al estar justo debajo de Copiar texto enriquecido al copiar o cortar, pues… ¡tuve que probarlo! 😉

Y eso es… aquí te dejo la nota «adicional» que puse en el post anterior en el que explico sobre la nueva versión de la utilidad de colorear código.

 

Nota adicional sobre el coloreado de Visual Studio

Comenté en el párrafo anterior que Visual Studio no pasa los colores de las clases (verde azulado) y antes lo hacía. ¿Lo habrán quitado?

No, no lo han quitado.

Y es que el que lo haga o no depende de una opción en la configuración del editor de textos de Visual Studio.

Esa opción en español es: Usar clasificación precisa.
En inglés es: Use accurate classification.

Y con esos nombres… ¿Quién lo iba a saber?

Para poder cambiar (activar) esa opción lo tienes que hacer en: (ver la captura)
Herramientas>Opciones>Editor de textos>Avanzadas
En inglés sería:
Tools>Options>Text editor>Advanced

Opción para que al copiar el código en Visual Studio coloree también las definiciones de los tipos

Fíjate que también tiene que estar seleccionada la opción Copiar texto enriquecido al copiar o cortar (Copy rich text on copy/cut).

Al pegar el texto coloreado, quedaría así:

''' <summary>
''' Devuelve la versión de la DLL.
''' Si completa es True, se devuelve también el nombre de la DLL:
''' gsColorear v 1.0.7.0 (para .NET Framework 4.7.2 revisión del dd/MMM/yyyy)
''' </summary>
Public Shared Function Version(Optional completa As Boolean = False) As String
    Dim res = ""
    Dim ensamblado = System.Reflection.Assembly.GetExecutingAssembly
    'Dim m_fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(ensamblado.Location)
    'res = $"v {m_fvi.FileVersion}"

    Dim versionAttr = ensamblado.GetCustomAttributes(GetType(System.Reflection.AssemblyVersionAttribute), False)
    'DirectCast(DirectCast(versionAttr, System.Reflection.AssemblyVersionAttribute())(0), System.Reflection.AssemblyVersionAt
    Dim vers = If(versionAttr.Length > 0, (TryCast(versionAttr(0), System.Reflection.AssemblyVersionAttribute)).Version,
                                      "1.0.7.0")
    Dim fileVerAttr = ensamblado.GetCustomAttributes(GetType(System.Reflection.AssemblyFileVersionAttribute), False)
    Dim versF = If(fileVerAttr.Length > 0, (TryCast(fileVerAttr(0), System.Reflection.AssemblyFileVersionAttribute)).Version,
                                      "1.0.7.1")

    res = $"v {vers} ({versF})"

    If completa Then
        Dim prodAttr = ensamblado.GetCustomAttributes(GetType(System.Reflection.AssemblyProductAttribute), False)
        Dim producto = If(prodAttr.Length > 0, (TryCast(prodAttr(0), System.Reflection.AssemblyProductAttribute)).Product,
                                        "gsColorear")

        res = $"{producto} {res} (para .NET Framework 4.7.2 revisión del 09/Sep/2020)"
    End If
    Return res
End Function

 

Nos vemos.
Guillermo

Compilar y ejecutar versión para .NET Core (.NET 5.0)

Nota del 26-oct-2020:
Esta utilidad (seguramente) no tendrá más actualizaciones.
En su lugar he publicado otra más completa en github: gsEvaluarColorearCodigoNET que incluye evaluación del código, compilar, compilar y ejecutar (por ahora para un solo fichero), colorear y colorear en HTML y tiene cosas interesantes para la creación de un editor de múltiples ficheros con algunas utilidades más.
Cuando tenga la página publicada en el blog, te dejaré el enlace.

 

Pues eso… siguiendo las pruebas de crear proyectos de .NET Core (con .NET 5.0) para Visual Basic, aquí traigo el de Compilar y Ejecutar que está creado con WPF / XAML y que además usa la biblioteca de clases para colorear (gsColorearCore) que también he convertido para .NET Core, en esta ocasión para la versión 3.1.

La he convertido para usar con .NET 5.0 (.NET Core), más que nada para probar, pero visto lo visto (los problemas) prefiero quedarme con la versión de .NET Framework.

Nota:
Abajo tienes una actualización del 16/Sep/2020

 

¿Por qué es preferible usar .NET Framework para este tipo de aplicación?

Por la sencilla razón de que salvo cosas puntuales, no tiene mucho sentido hacerla para .NET Core. Ya que este tipo de aplicaciones (las creadas para WPF o Windows Forms) solo se ejecutarán o funcionarán en equipos que utilicen Windows como sistema operativo.

Que tú prefieres usar el .NET Core porque «DICEN» que es más rápido, ocupa menos, se puede «embeber» con la aplicación y otras monerías… pues muy bien… lo mismo yo utilizo esas cosas «puntuales» para hacer algo con .NET 5.0, o hacer mucho cuando ya sea .NET 6.0, el tiempo lo dirá… No hay que descartar nada ni decir de este agua no beberé 😉

Cosas a tener en cuenta en la migración de .NET Framework a .NET Core

Aparte de que hay ciertas cosas que ya no existen, al menos de la forma a la que estamos habituados en las aplicaciones para .NET Framework, el resto no cambia nada o no cambia mucho.

Configuración (My.Settings)

Por ejemplo, me he encontrado con problemas a la hora de usar My.Settings. Me estuvo funcionando y de buenas a primeras dejó de hacerlo, así que… corté por lo sano y me deshice de My.Settings y las configuración la uso por mi cuenta, concretamente usando la clase Config que tengo definida en la DLL de gsColorear.

No quiero decir con esto que no se puedan usar… solo digo que a mí me estuvo funcionando bien hasta que de buenas a primeras dejó de funcionar.

Propiedades del proyecto

A las propiedades del proyecto ya no accede desde el nodo MyProject.
Ahora puedes hacerlo pulsando en el nombre del proyecto con el botón secundario del ratón (normalmente el derecho) y del menú mostrado pulsar en Propiedades (ver la figura 1).

Figura 1. Acceder a las propiedades del proyecto

NOTA:
Si simplemente haces clic en el nombre del proyecto (y la ventana de propiedades no estaba abierta anteriormente y ocasionalmente si también está abierta) se mostrará el fichero .vbproj con las opciones de configuración en formato XML no con tantas «cosas» como el de una aplicación para .NET Framework (ver la figura 2).
Ahí, entre otras cosas, se indica la versión del .NET Core que estás usando (en este caso .NET 5.0 para Windows), qué tipo de aplicación es, el espacio de nombres, etc.

Figura 2. Propiedades del proyecto en XML

Este es el aspecto (simplificado) de la ventana de propiedades del proyecto (ver figura 3).

Figura 3. Ventana de propiedades del proyecto

Si pulsas en Paquete (en el panel izquierdo de la ventana de Propiedades) tendrás algo parecido a la información del ensamblado de las aplicaciones para .NET Framework.

Referencias

Las referencias a DLL externas (u otros proyectos), se pueden hacer desde la ventana de propiedades, pero en la ventana del explorador de soluciones ya no se muestra como el nodo Referencias. Ahora está en Dependencias.

Si pulsas en Referencias de la ventana de propiedades, verás que está vacía, pero puedes agregar las referencias que necesites, supongo que, a diferencia de una aplicación de .NET Framework, aquí solo tienes que añadir las referencias externas, es decir, las que no se encuentren ya definidas en el propio .NET Core.

En realidad en Dependencias añadirás las mismas cosas que antes (con .NET Framework) añadías en tu proyecto: paquetes NuGet, referencias a otros proyectos, etc.

En el caso de las referencias a otros proyecto, están en un nodo diferenciado (Proyectos), tal como puedes comprobar en la figura 4.

Figura 4. Nodo de proyectos usados en el proyecto actual

Y básicamente estos es lo diferente… y eso que no me he puesto a añadir configuraciones ni recursos desde la ventana de propiedades… pero eso lo dejo estar… al menos por ahora 😉

 

Todo esto lo estoy escribiendo el 5 de septiembre de 2020 y estoy usando Visual Studio Community 2019 Preview Versión 16.8.0 Preview 2.1 con el .NET 5.0 versión 5.0.100-preview.8.20417.9.

Para estas pruebas he copiado el proyecto para WPF Compilar y ejecutar versión 1.0.0.21 del 31 de agosto de 2020 con la librería gsColorear2008 versión 1.0.6.3 del 8 de enero de 2019.

Nota:
Me apunto actualizar la página de gsColorear en mi sitio para que tenga la última versión tanto de la DLL como de la aplicación.

En la librería de colorear el código, también tuve que quitar los ficheros de recursos (con las palabras clave de los lenguajes) y guardarlos (y abrirlos al usarlos) de forma manual en vez de como si fuese un recurso. No fue complicado, pero… es otra cosa que rompe la compatibilidad entre proyectos. Y esto es independiente de la versión de .NET Core que tenga asignada en el proyecto, ya que lo probé inicialmente con el .NET 5.0 y después con el .NET Core 3.1.
Al final lo he dejado con el .NET Core 3.1, ya que al ser una biblioteca de clases, .NET Core 3.1 si da soporte a ese tipo de proyectos de Visual Basic.

 

Y esto es todo por hoy… otro día más…

Espero que te haya sido de utilidad.

 

Actualización del 16/Sep/2020

He actualizado el código, tanto de la utilidad de compilar y ejecutar como de la DLL de compilar, esta última ahora usa código de Visual Basic en lugar de C#, y también he actualizado la DLL de colorear el código.

Todos esos cambios están en el repositorio de gitHub de gsCompilarEjecutarNET.

Además, he convertido el código de la utilidad a C# y también está publicado en gitHub.

 

Aquí te dejo el enlace original al código que puse cuando publiqué este artículo originalmente el 5 de septiembre.

El enlace para descargar los proyectos Compilar y ejecutar y gsColorearCore para .NET Core

ZIP: Compilar_ejecutar_NetCore_20200905_1710.zip (70.8 KB)
MD5 checksum: ACDC9EF7E2C0F4469719F06D88F8F812

 

Nos vemos.
Guillermo

Tips para crear proyectos de .NET Core (.NET 5.0) en Visual Studio 2019 Preview

Pues eso… leyendo el otro día el artículo Visual Basic support planned for .NET 5.0 (soporte planeado en .NET 5.0 para Visual Basic) indicaba una serie de tipos de proyectos, entre ellos de Windows Forms y WPF, pero no solo para Windows y usando el .NET Framework (que es lo que dicen por ahí que será lo que nos quede en un futuro a los que preferimos usar Visual Basic en lugar de C# (u otro lenguaje de .NET).

Así que… abrí el Visual Studio 2019 (Community) Preview (v16.8.0 Preview 2) y me puse a mirar los tipos de proyectos que había… para C# había de esos dos tipos de aplicaciones tanto para .NET Core como para .NET Framework, pero para Visual Basic solo era para este último marco de trabajo.

Mirando la configuración de los ficheros del proyecto de C# pude crear un proyecto de Windows Forms para Visual Basic que usa el .NET 5.0 basándome en uno de consola (de esos si que hay para el .NET Core o el .NET 5.0).

¡Y funciona!

El problema, es que al agregar los controles al formulario y crear los métodos de evento (haciendo doble-pulsación en el control), los métodos de evento se creaban, pero no estaban conectados con los controles. Y eso es porque al crear los controles (añadiéndolos al formulario) no se definían con WithEvents.

La solución es fácil, se abre el fichero Form1.Designer.vb, se modifica la declaración de los controles (que suelen estar al final de ese fichero) y asunto arreglado… bueno, si le añades el típico Handles después de la declaración del método, por ejemplo:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Label1.Text = "¡Hola Mundo!"
End Sub

Esto último es lo menos engorroso de hacer… lo complicado (o tedioso) es convertir ese proyecto de consola a uno de Windows Forms.
¡Y no te voy a explicar cómo hacerlo! 🙂
No, ya que no es necesario hacer nada si sigues el consejo que te daré a continuación.

Crear proyectos de Visual Basic para .NET Core (o .NET 5.0)

Si no quieres complicarte mucho la vida, haz lo siguiente (tal como se muestra en la figura 1):

Selecciona el menú de Herramientas>Opciones y se muestra la ventana de configuración, selecciona Entorno>Características en versión preliminar y ahí marca la casilla Mostrar todas las plantillas de .NET Core en el cuadro de diálogo nuevo proyecto y pulsa Aceptar. Tendrás que cerrar y abrir nuevamente el Visual Studio y ya tendrás todas las plantillas que hay actualmente disponibles.

 

Usar los nuevos tipos de proyectos

Si ahora creas un nuevo proyecto (o agregas uno a una solución existente) verás que ya están los proyectos de Visual Basic para todas las plataformas (usando Windows Forms y WPF entre otros).

Una vez que seleccionas uno de los proyectos para «todas las plataformas» te dará la opción de elegir la plataforma de destino (el «framework» que usarás). (ver la figura 3)

Si eliges una aplicación de WPF te mostrará las 3 opciones de la figura anterior, si eliges una aplicación de Windows Forms, solo mostrará .NET Core 3.1 y .NET 5.0.

 

NOTA:
Como sabrás .NET 5.0 es una especie de remix entre el .NET Framework y el .NET Core o lo que es lo mismo, es la continuidad de .NET Core, pero unificado con el .NET Framework.
Y la primera versión definitiva está planeada para noviembre de este año de 2020.

 

¿Se soluciona algo al crear así las aplicaciones de Windows Forms?

Pues no… o casi… Me refiero a que al añadir un control al formulario y hacer doble-clic en él se genere correctamente el método de evento.

Ni usando el .NET Core 3.1 ni usando el .NET 5.0 (al menos en Visual Basic) se generan correctamente esos métodos de evento.

Si usas .NET Core 3.1 como plataforma de destino, al menos se definirán los controles con WithEvents, pero tendrás que añadirle el Handles o enlazar el evento y el método con AddHandler.

Si usas como plataforma de destino el .NET 5.0, no se definen los controles con WithEvents y, por tanto, tampoco se crean los métodos con Handles ya que es un requisito el que las variables (controles en este caso) estén definidos con WithEvents para permitir definir los eventos con Handles.

 

A esperar toca…

Esperemos que en futuras revisiones del .NET 5.0 o de Visual Studio esté solucionado, si no… lo tenemos complicado.
No sé quién será el encargado de arreglar esto, supongo que los de VS, y esperemos que sea así, ya que el .NET 5.0 ha entrado en lo que llaman la fase “feature complete” (función completa) en la Preview 8 de hace una semana y ya solo nos quedan las release candidate en las que solo arreglan bugs, no añaden nuevas características.

Habrá que reportarlo como BUG para ver si hacen algo y lo solucionan… porque si no lo solucionan, es que realmente no quieren que los desarrolladores de Visual Basic sigamos usándolo… y… optemos por cambiar a C#… en fin…

 

Nota:
Aunque al crear los proyectos de Windows Forms y WPF aparentemente sean para todas las plataformas, en realidad solo están soportadas en Windows.
O al menos eso quiere decir (o es lo que yo entiendo que significa) esto en la configuración de la aplicación:

<TargetFramework>net5.0-windows</TargetFramework>

 

Espero que te haya servido para algo todo lo aquí comentado… ya sabes que esa es la idea 😉

Nos vemos.
Guillermo

Métodos de extensión (ejemplos para Visual Basic y C#)

Pues eso… y por si no lo sabes, esto de los métodos de extensión (Extension Methods en inglés) sirve para ampliar la funcionalidad de clases existentes, ya sean definidas en el propio .NET (String, Integer, TextBox, etc.) como en otras librerías (bibliotecas) que tengamos referenciadas en nuestro proyecto.

NOTA:
Aquí no te voy a explicar en detalle de qué va esto de los métodos de extensión, solo decirte cómo crear los tuyos propios, con código de Visual Basic y de C#, para ello te pondré unos ejemplos.

NOTA2:
¡Vaya despiste!
Pues eso… que se ve que en septiembre del año pasado (5-sep-2019) ya publiqué algo sobre los métodos de extensión… y es que lo tenía apuntado (en mi cabeza) y se ve que después de publicarlo no lo borré… jajaja… en fin… Más vale 2 que nada… 😉

En este artículo voy a ponerte un par de ejemplos para añadir una nueva función a la clase String y algunos métodos de conversión a la clase TextBox, de forma que puedas usarlos para convertir el contenido de la propiedad Text en un valor entero, un valor decimal, etc.

Crear métodos de extensión en Visual Basic

En Visual Basic necesitamos una importación a System.Runtime.CompilerServices y crear los métodos en un módulo (no se pueden crear en clases).
La definición del método empezará con el atributo <Extension> y después seguirá la definición de la función ( o método), pero en el primer parámetro indicaremos la clase que queremos ampliar (o extender) y después, opcionalmente, podemos usar más parámetros.

Crear método de extensión en C#

Para crear métodos de extensión en C# necesitamos una clase definida como static y definir los métodos de extensión también como compartidos (static), pero no es necesario usar la importación de CompilerServices ni añadir el atributo Extension, ya que el compilador sabe que es un método de extensión si el primer argumento de la función (o método) empieza con this y el tipo que queremos extender.

Un par de ejemplos de métodos de extensión

En el siguiente código tenemos un par de ejemplos de métodos de extensión de la clase String, el primero como función y el segundo como un método que no devuelve nada.
Primero el código de Visual Basic y después el de C#.

Imports System.Runtime.CompilerServices

Module Extensiones
    <Extension>
    Public Function Prueba(str As String) As String
        Return "PRUEBA " & str
    End Function

    <Extension>
    Public Sub Imprimir(str As String)
        Console.WriteLine(str)
    End Sub
End Module
static class Extensiones
{
    //[Extension]
    public static string Prueba(this string str)
    {
        return "PRUEBA " + str;
    }

    public static void Imprimir(this string str)
    {
        Console.WriteLine(str);
    }

Algunos métodos prácticos para extender String y TextBox

Aquí te dejo el código para extender la clase String con un método que quita las tildes (acentos) a las vocales, con idea de que podamos hacer comparaciones sin que se tenga en cuenta si la vocal está o no acentuada (con la tilde).

En el caso del control TextBox te dejo los métodos de extensión para convertir el contenido en un valor de tipo Integer, Double, Decimal, Date y TimeSpan.
Esto lo empecé a usar en el último programa que he hecho, hasta ahora, cuando quería convertir el contenido de una cadena en un valor numérico, etc., lo que hacía era usar funciones para esas conversiones.
Esas conversiones podría haberlas puesto en la clase String, pero al hacerla en el TextBox me ahorraba tener que indicar la propiedad Text cada vez que quería hacer la conversión 😉
En el código de ejemplo te pongo también la función que convierte en número entero para la clase String, así lo tendrás fácil si quieres extender dicha clase en lugar del TextBox.

El código para Visual Basic

Primero los métodos de extensión y después cómo usarlos.

Imports System

Imports System.Runtime.CompilerServices
Imports System.Windows.Forms

Module Extensiones

    ''' <summary>
    ''' Devuelve un valor Integer de la propiedad Text del TextBox indicado.
    ''' </summary>
    ''' <param name="txt">El TextBox a extender</param>
    <Extension>
    Public Function AsInteger(txt As TextBox) As Integer
        Dim i As Integer = 0

        Integer.TryParse(txt.Text, i)

        Return i
    End Function

    ''' <summary>
    ''' Devuelve un valor Double de la propiedad Text del TextBox indicado.
    ''' </summary>
    <Extension>
    Public Function AsDouble(txt As TextBox) As Double
        Dim d As Double = 0

        Double.TryParse(txt.Text, d)

        Return d
    End Function

    ''' <summary>
    ''' Devuelve un valor Decimal de la propiedad Text del TextBox indicado.
    ''' </summary>
    <Extension>
    Public Function AsDecimal(txt As TextBox) As Decimal
        Dim d As Decimal = 0

        Decimal.TryParse(txt.Text, d)

        Return d
    End Function

    ''' <summary>
    ''' Devuelve un valor TimeSpan de la propiedad Text del TextBox indicado.
    ''' </summary>
    <Extension>
    Public Function AsTimeSpan(txt As TextBox) As TimeSpan
        Dim c As New TimeSpan(0, 0, 0)

        TimeSpan.TryParse(txt.Text, c)

        Return c
    End Function

    ''' <summary>
    ''' Devuelve un valor Date de la propiedad Text del TextBox indicado.
    ''' Si es un valor nulo o vacío, devuelve el 1 de enero de 1900.
    ''' </summary>
    <Extension>
    Public Function AsDate(txt As TextBox) As Date
        Dim d As DateTime = New DateTime(1900, 1, 1)

        If Not (String.IsNullOrWhiteSpace(txt.Text) OrElse txt.Text.Equals(DBNull.Value)) Then
            DateTime.TryParse(txt.Text, d)
            If d.Year < 1900 Then
                d = New DateTime(1900, 1, 1, 0, 0, 0)
            End If
        Else
            ' asignar el 01/01/1900 si es un valor en blanco
            d = New DateTime(1900, 1, 1, 0, 0, 0)
        End If

        Return d.Date
    End Function


    ''' <summary>
    ''' Quitar las tildes de una cadena.
    ''' </summary>
    ''' <param name="str">La cadena a extender</param>
    <Extension>
    Public Function QuitarTildes(ByVal str As String) As String
        Dim tildes1 = "ÁÉÍÓÚáéíóú"
        Dim tildes0 = "AEIOUaeiou"
        Dim res As New System.Text.StringBuilder

        For i = 0 To str.Length - 1
            Dim j = tildes1.IndexOf(str(i))
            If j > -1 Then
                res.Append(tildes0.Substring(j, 1))
            Else
                res.Append(str(i))
            End If
        Next
        Return res.ToString
    End Function

    ''' <summary>
    ''' Devuelve un valor Integer de la cadena a la que se aplica este método.
    ''' </summary>
    <Extension>
    Public Function AsInteger(str As String) As Integer
        Dim i As Integer = 0

        Integer.TryParse(str, i)

        Return i
    End Function



    <Extension>
    Public Function Prueba(str As String) As String
        Return "PRUEBA " & str
    End Function

    <Extension>
    Public Sub Imprimir(str As String)
        Console.WriteLine(str)
    End Sub
End Module
Imports System

Imports System.Windows.Forms

Module Module1

    Sub Main()
        Dim s = "Hola"
        Console.WriteLine(s.Prueba)

        s.Imprimir()

        s = "Código de ejemplos prácticos"
        Console.WriteLine(s.QuitarTildes())

        Dim txt As New TextBox

        txt.Text = "125"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.Text.AsInteger+10 : {0}", txt.Text.AsInteger + 10)
        Console.WriteLine("txt.AsInteger+10 : {0}", txt.AsInteger + 10)

        txt.Text = "125,38"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.AsDecimal*2 : {0}", txt.AsDecimal * 2)
        Console.WriteLine("txt.AsDouble*2 : {0}", txt.AsDouble * 2)

        txt.Text = "10:30"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.AsTimeSpan : {0}", txt.AsTimeSpan)

        txt.Text = "24/08/2020"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.AsDate : {0}", txt.AsDate)
        Console.WriteLine("txt.AsDate.AddDays(2) : {0}", txt.AsDate.AddDays(2))


        Console.ReadKey()
    End Sub

End Module

El código para C#

Primero los métodos de extensión y después cómo usarlos.

using System;
using System.Windows.Forms;

static class Extensiones
{
    //[Extension]
    public static string Prueba(this string str)
    {
        return "PRUEBA " + str;
    }

    public static void Imprimir(this string str)
    {
        Console.WriteLine(str);
    }

    /// <summary>
    /// Devuelve un valor Integer de la propiedad Text del TextBox indicado.
    /// </summary>
    /// <param name="txt">El TextBox a extender</param>
    public static int AsInteger(this TextBox txt)
    {
        int i = 0;

        int.TryParse(txt.Text, out i);

        return i;
    }

    /// <summary>
    /// Devuelve un valor Double de la propiedad Text del TextBox indicado.
    /// </summary>
    public static double AsDouble(this TextBox txt)
    {
        double d = 0;

        double.TryParse(txt.Text, out d);

        return d;
    }

    /// <summary>
    /// Devuelve un valor Decimal de la propiedad Text del TextBox indicado.
    /// </summary>
    public static decimal AsDecimal(this TextBox txt)
    {
        decimal d = 0;

        decimal.TryParse(txt.Text, out d);

        return d;
    }

    /// <summary>
    /// Devuelve un valor TimeSpan de la propiedad Text del TextBox indicado.
    /// </summary>
    public static TimeSpan AsTimeSpan(this TextBox txt)
    {
        TimeSpan c = new TimeSpan(0, 0, 0);

        TimeSpan.TryParse(txt.Text, out c);

        return c;
    }

    /// <summary>
    /// Devuelve un valor Date de la propiedad Text del TextBox indicado.
    /// Si es un valor nulo o vacío, devuelve el 1 de enero de 1900.
    /// </summary>
    public static DateTime AsDate(this TextBox txt)
    {
        DateTime d = new DateTime(1900, 1, 1);

        if (!(string.IsNullOrWhiteSpace(txt.Text) || txt.Text.Equals(DBNull.Value)))
        {
            DateTime.TryParse(txt.Text, out d);
            if (d.Year < 1900)
                d = new DateTime(1900, 1, 1, 0, 0, 0);
        }
        else
            // asignar el 01/01/1900 si es un valor en blanco
            d = new DateTime(1900, 1, 1, 0, 0, 0);

        return d.Date;
    }


    /// <summary>
    /// Quitar las tildes de una cadena.
    /// </summary>
    /// <param name="str">La cadena a extender</param>
    public static string QuitarTildes(this string str)
    {
        var tildes1 = "ÁÉÍÓÚáéíóú";
        var tildes0 = "AEIOUaeiou";
        System.Text.StringBuilder res = new System.Text.StringBuilder();

        for (var i = 0; i <= str.Length - 1; i++)
        {
            var j = tildes1.IndexOf(str[i]);
            if (j > -1)
                res.Append(tildes0.Substring(j, 1));
            else
                res.Append(str[i]);
        }
        return res.ToString();
    }

    /// <summary>
    /// Devuelve un valor Integer de la cadena a la que se aplica este método.
    /// </summary>
    public static int AsInteger(this string str)
    {
        int i = 0;

        int.TryParse(str, out i);

        return i;
    }
}
using System;

using System.Windows.Forms;

class Program
{
    static void Main(string[] args)
    {
        var s = "Hola";
        Console.WriteLine(s.Prueba());
        //Console.WriteLine(s.Prueba2());

        s.Imprimir();

        s = "Código de ejemplos prácticos";
        Console.WriteLine(s.QuitarTildes());


        TextBox txt = new TextBox();

        txt.Text = "125";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.Text.AsInteger+10 : {0}", txt.Text.AsInteger() + 10);
        Console.WriteLine("txt.AsInteger+10 : {0}", txt.AsInteger() + 10);

        txt.Text = "125,38";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.AsDecimal*2 : {0}", txt.AsDecimal() * 2);
        Console.WriteLine("txt.AsDouble*2 : {0}", txt.AsDouble() * 2);

        txt.Text = "10:30";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.AsTimeSpan : {0}", txt.AsTimeSpan());

        txt.Text = "24/08/2020";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.AsDate : {0}", txt.AsDate());
        Console.WriteLine("txt.AsDate.AddDays(2) : {0}", txt.AsDate().AddDays(2));


        Console.ReadKey();
    }

    //Error CS1106: Un método de extensión debe definirse en una clase estática no genérica

    //public static string Prueba2(this string str)
    //{
    //    return "PRUEBA2 " + str;
    //}

}

Y esto es todo… espero que te haya sido de utilidad… ya sabes que esa es la idea 😉

Nos vemos.
Guillermo

Cuidadín con los For Each si se modifica el objeto del bucle

Pues eso… aunque esto ya es algo viejo, y se supone que sabido, el otro día no lo recordé y revisando el código de un procedimiento que, a pesar de tratar con muchos registros de una base de datos de SQL Server, veía que tardaba demasiado… y mientras estaba trabajando el método me entretuve en ver la ventana esa de diagnóstico en la que te muestra todo lo que iba haciendo el código (ver la figura 1), y me percaté que repetía fechas…

Ventana de las herramientas de diagnóstico de Visual Studio
Figura 1. La ventana de las Herramientas de diagnósticos de Visual Studio.

Al principio no caí en porqué lo hacía, así que… me puse a revisar el código con las llamadas a las actualizaciones a la base de datos… y al cabo de un rato caí en que estaba usando un bucle For Each y que la variable de ese bucle era un objeto de la clase que se encarga de actualizar los datos (e incluso de crearlos si no existen en la base de datos) y… ¡se me encendió la bombillita esa Light bulb de… idea!

Fue cambiarlo a un bucle normal y tardar muchísimo menos Eye rolling smile, creo que una de las veces me llegó a tardar más de media hora y al hacer el cambio se quedó en 4 minutos o menos.
Y el cambio que hice fue, por ejemplo (en VB), de este:

For Each p In Productos

a este otro:

For i = 0 To Productos.Count - 1
Dim p = Productos(i)

Nota:
El problema de los bucles for each no es solo de Visual Basic, también lo es de C# aunque se escriba de otra forma.

Y lo cuento aquí para que cuando busque «problemas» me de la solución.

Es que, por si no lo sabes, cuando no recuerdo cómo hacer algo en VB o C#, busco en Google, pero le añado al final elguille para que salgan los resultados que tengo publicados y, salvo excepciones, encuentro siempre lo que busco Flirt male, así que… ya sabes… Winking smile

Nos vemos.
Guillermo