Archivo de la etiqueta: github

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

gsNotasNET.Android release v2.0.0.32

gsNotasNET.Android v2.0.0.32

Utilidad para dispositivos móviles Android para tomar notas y guardarlas localmente o en una base de datos externa.

En esta versión (v2.0.0.32) la utilidad/aplicación hace lo siguiente:

Permite crear notas y marcarlas con estos atributos/propiedades:

  • Indicar a qué grupo (tag/etiqueta) pertenece.

  • Se pueden crear tantos grupos como se deseen.

  • El programa recordará el último nombre de grupo utilizado al guardar una nota.

  • Se pueden ver los grupos que hay creados, mostrando la información de las notas que contiene.

  • Al mostrar los grupos se muestra la información de las notas que contiene.

  • Al seleccionar un grupo (clic o tap) te muestra la información de tallada de las notas que contiene, así como la lista de esas notas pertenecientes a ese grupo. Desde esa lista puedes editar las notas.

Atributos /Propiedades de cada nota:

  • Favorita, al mostrar las notas (de la base de datos externa) las notas favoritas se muestran al principio.

  • Notificar, las notas marcadas para notificar se pueden usar para que al hacer login se muestren automáticamente, ahí puedes anotar los recordatorios que necesites ver cada vez que inicias sesión.

  • Archivada, esto hará que la nota no se muestre al mostrar las notas activas.

  • Eliminada, es otra forma de ocultar las notas, ya que nunca se eliminan físicamente de la base de datos (tanto local como externa).

  • Sincronizada, esta propiedad indica si la nota está sincronizada entre las bases local y externa. Las notas siempre se sincronizan entre las dos bases de datos.

Para mostrar las notas hay varios apartados en el menú o pantalla principal, pudiendo mostrar las notas según el atributo o propiedad seleccionado.

  • En la lista de notas se muestra la siguiente información:

  • El título de la nota que consiste en los primeros 50 caracteres o si hay cambios de líneas en la nota, la primera línea.

  • El nombre del grupo, la fecha de modificación y una letra cn el nombre del atributo y si está marcado (True) o no lo está (False).

  • Las abreviaturas de los atributos son:

    Favorita, Notificar, Archivada y Eliminada.

  • En una nueva actualización añadiré Sincronizada, aunque se supone que siempre deben estar sincronizadas.

  • Seguramente también añadiré una página (opción) para mostrar las notas sincronizadas y poder asegurarte que si no está sincronizada, lo haga.

En la configuración puedes indicar:

  • Recordar el usuario con el que se ha hecho Login.
  • Recordar el password usado.
  • Iniciar la aplicación con el último usuario.
  • Mantener las notas sincronizadas (siempre está activada esta opción).
  • Mostrar las notas a Notificar al iniciar el programa (o hacer Login).
  • Usar las notas locales (cuando se activa) o usar las notas de la base externa.

En la información del Perfil te muestra los datos de la cuenta con la que has hecho Login.

  • Ahí puedes cambiar la cuenta de correo y el password.

  • Si cambias la cuenta de correo, se enviarán 2 mensajes, uno a cada cuenta pidiendo confirmación.

  • Te muestra la inforamción de la fecha de alta, último acceso, cuántas notas de la base externa puedes escribir:

  • Para los primeros 90 usuarios que se registren tienen una cuota de 1.000 notas, a partir del usuario 100 (los otros 10 los tengo reservados para mí) tendrán 100 notas como máximo de forma totalmente gratuita.

  • En esa cantidad, se cuentan todas las notas, estén o no eliminadas (ya que nunca se eliminan las notas).

  • El importe por cada 1.000 notas será de una donación anual de 12$ USD (unos 10€).

  • En esa cuota no se cuentan las notas ofrecidas gratuitamente.

  • El importe indicado en Pagos será el importe que hayas ido pagando.

  • Ya te digo que solamente debes pagar si quieres más cantidad de notas. La aplicación es totalmente gratuita. Salvo que prefieras hacer un donativo voluntario.

  • El donativo lo puedes hacer mediante este enlace: Donativo para elGuille.

  • Todos los donativos serán siempre bienvenidos 🙂

Puedes realizar búsquedas en las notas (tanto en la base local como en la externa).

  • La búsqueda se realiza sin tener en cuenta el case (no diferencia entre mayúsculas y minúsculas).

  • Por ahora solo hace la búsqueda en el Texto de las notas.

  • En una nueva actualización incluiré que se pueda hacer tanbién (o solo) en los nombres de los grupos).

  • En el resultado de la búsqueda puedes pulsar (hacer clic o tap) en la nota para editarla.

  • Aunque estés usando las notas de la base de datos remota (de SQL Server) puedes mostrar las notas de la base de datos loca, en este caso las notas no son editables, solo se muestran en la lista y haciendo tap o clic en ellas no se muestran. ??? Si quieres editar una nota local, debes cambiarlo en la configuración seleccionando la opción Usar las notas locales.

En la pantalla principal además te encontrarás con estas opciones:

  • Validar Email. Esto tendrás que usarlo cuando te registres con un nuevo usuario. O al crear un nuevo usuario.
  • Sincronizar. Esta opción te sirve para comprobar si las notas están sincronizadas (deberían estarlo), pero en caso de que no lo estuvieran, puedes sincronizar las locales al romoto o al revés.
  • Comentarios. Por si quieres enviarme alguna sugerencia, bug o cualquier cosa que me quieras decir. Esta opción te permite indicar un comentario y al pulsar en el botón de ENVIAR… abrirá una cuenta de email que tengas configurada en tu dispositivo para poder enviar el mensaje. El texto que hayas escritor aparecerá en el cuerpo del mensaje.
  • Acerca de Abre una ventana con un poco de ayuda sobre la aplicación. Seguramente añadiré un enlace a esta página descriptiva, ya que tanto texto no se puede poner en ese tipo de páginas… o al menos no se vería bien.
  • Cambiar de usuario. Desde ahí puedes hacer login, si no lo has hecho ya, o bien poder registrar un nuevo usuario.

En todas (o casi todas) existe un botón POLÍTICA DE PRIVACIDAD que al pulsarlo te muestra (en el navegador) la política de privacidad, es decir, qué datos personales se recogen.

Ahora muestra el contenido de elguillemola.com: Política de privacidad en elguillemola.com pero seguramente lo cambiaré a una que sea específica de esta aplicación.

De todas formas, comentarte que con los datos que facilitas o las notas que escribe yo no hago nada, ni cambairé nada, salvo que tú me lo pidas expresamente, bien porque no tengas acceso o cualquier otra circunstancia que te impida acceder a las notas o tu cuenta de usuario.

Bueno y creo que esto es todo…

He escrito tanto con idea de crear una página en elguillemola.com y así poder usarla como ayuda de la aplicación.

También crearé un video explicativo con las cosas que debes saber sobre la aplicación.

Si has llegado leyendo hasta aquí… ¡muchas gracias! 🙂

Y como estamos en las fechas que estamos (30 de diciembre de 2020) te deseo ¡FELICES FIESTAS!

 

Galería de fotos / capturas de la versión 2.0.0.32
(al menos en las que hay cambios, el resto es de la v2.0.0.31)

 

Puedes descargar el "paquete" de instalación desde GitHub:

Este enlace es para la versión v2.0.0.31.

Este otro es para la versión v2.0.0.32 (si no te funciona el enlace es que lo has pulsado antes de que yo lo haya publicado 😉 )

 

NOTA

Hay una nueva vaersión: v2.0.0.33

Usa este enlace para no perderte lo último: gsNotasNET.Android v2.0.0.33

 

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