Etiqueta: netcore

  • gsColorearCodigo

    Esta es la última versión de la utilidad gsColorearCodigo (a fecha del 27 de octubre de 2020) en la que he usado Visual Studio 2019 y .NET 4.8.

    Pulsa este enlace si quieres ver la versión original (creada para .NET 2.0).

     

    Descripción de gsColorearCodigo

    Nota del 27/Oct/2020
    La revisión actual a la versión 1.0.0.12  (y la instalación de ClickOnce) están ajustadas a la compilación de fecha de hoy martes 27 de octubre de 2020.
    También está actualizado el código en github con la «
    release» de los ejecutables (por si quieres descargar directamente los binarios y no instalarlos con ClickOnce.
    Por supuesto, en github está el código fuente de gsColorearCodigo y de la DLL gsColorearNET creada con .NET Standard 2.0.

     

    Nota del 26-oct-2020 20:59:
    Esta página estaba originalmente publicada en elguille.info y la he pasado al blog para poder editarla más fácilmente ya que lo que publico en elGuille.info lo hago con un editor de texto normal y corriente… y ¡es un rollo!
    Por supuesto, esta misma página aparecerá en elGuille.info, así que… no se notará mucho que no está realmente ubicada allí.

     

    NOTA del 24/Oct/2020
    Revisión de esta utilidad usando la nueva versión publicada hoy de gsColorearNET en NuGet.
    He actualizado también la instalación de ClickOnce.

     

    Nota del 12/Sep/2020
    Estoy probando la utilidad de colorear en .NET 5.0 Preview 8 y a duras penas ya está operativa…
    es que el editor de Visual Studio 2019 Preview está aún muy verde para las aplicaciones de Windows Forms para Visual Basic.

    El hacerlo con esa versión es para poder depurar el código de la DLL de colorear, ya que en un proyecto de .NET framework
    no se puede… o yo no sé cómo hacerlo… todo hay que decirlo.

    Cuando tenga tiempo publicaré en el blog las cosillas que recomiendo hacer hasta que mejoren el editor/diseñador de WinForms para Visual Basic.

    Nota del 11/Sep/2020:
    Esta nueva versión utiliza la librería de colorear código compilada para .NET Standard 2.0.
    Esa DLL la he instalado desde el paquete de NuGet que he creado para la DLL gsColorearNET.
    No he probado el instalador de ClickOnce en otro equipo, así que no sé si será totalmente operativo 😉
    Al menos lo es en mi propio equipo…
    De todas formas, actualizaré el código fuente de la nuevas versión, que aparte de usar esa DLL de colorear,
    también tiene otros cambios (mejoras) con respecto a la actualización anterior del 9 de septiembre pasado.

    El programa principal y la DLL de colorear están compilados con Visual Studio 2019,
    en el caso de la utilidad (gsColorearCodigo.exe) utilizando el .NET Framework 4.8,
    y la DLL (gsColorearNET.dll) está compilada para usar .NET Standard 2.0.

     

    Entre otras cosas, esta versión (aparte de las mejoras en gsColorearNET) incluye la opción de «Colorear desde RTF» (ver la Figura 1).
    Así como algunos cambios en la pestaña de configuración (ver figura 2) y en la ventana de mostrar RTF, que ahora permite
    cambiar el código RTF y al pulsar en el botón RTF se muestran los cambios realizados.

     

    Opción de colorear desde RTF
    Figura 1. Colorear desde RTF

    Es la opción que en un 99.99% de las veces utilizo para colorear el código ya que me permite copiar el texto de Visual Studio (es lo que se ve detrás de la utilidad) lo pego en la aplicación y al usar esa opción de Colorear desde RTF lo que hace es colorear para HTML usando el código de RTF, es decir, no comprueba las palabras clave, etc., para generar el código HTML a usar en una página WEB.

     

    Opciones de configuración
    Figura 2. Pestaña de opciones

     

    Instalar gsColorearCodigo (y el código fuente)

    Para instalar la utilidad puedes hacerlo de dos formas:

    • Usando el ZIP que hay más abajo, pero tendrás que compilarlo ya que el ejecutable no se incluye, solo el código fuente para Visual Basic.
    • Instalándolo por medio de ClickOnce (recomendado), ya que así podrás recibir notificaciones cuando haya alguna nueva versión e instalarla automáticamente.

    En cualquier caso, lo instales directamente o por medio de ClickOnce, desde la ventana de Acerca de puedes comprobar si hay nuevas versiones del programa o también mirando esta página, que al fin y al cabo es la que usa el programa para saber si hay nuevas versiones (o actualizaciones).

    Nota:
    En realidad la página que mira para ver si hay nuevas actualizaciones es la anterior (la original creada con .NET 2.0) pero he puesto allí la misma versión que en esta… y cuando actualice nuevamente la utilidad, cambiaré el enlace para que mire esta página.
    Esto es así porque esta página la he publicado después de compilar y crear el instalador. Smile

     

    Más abajo tienes los enlaces con el código completo para Visual Basic usando una solución de Visual Studio 2019 con .NET 4.8 en el que se hace referencia a la DLL gsColorearNET instalada con el package de NuGet.

    Espero que te sea de utilidad Smile

    Nos vemos.
    Guillermo

     

     

    El ZIP con el código completo

    El código tanto de la aplicación gsColorearCodigo para .NET Framework 4.8 como de la DLL gsColorearNET para .NET Standard 2.0 lo puedes descargar desde GitHub

     

    Estos son los enlaces a github para el código fuente:
    El código fuente de gsColorearCodigo (la aplicación) para .NET Framework 4.8
    El código fuente de gsColorearNET (la DLL) para .NET Standard 2.0

     

    Pulsa aquí si la quieres instalar con ClickOnce.

     

  • gsColorearNET

    Nota del 26-oct-2020 20:45:
    Esta página estaba originalmente publicada en elguille.info y la he pasado al blog para poder editarla más fácilmente ya que lo que publico en elGuille.info lo hago con un editor de texto normal y corriente… y ¡es un rollo!
    Por supuesto, esta misma página aparecerá en elGuille.info, así que… no se notará mucho que no está realmente ubicada allí.

     

    Esta es una biblioteca (DLL) compilada para .NET Standard 2.0 y así poder usarla en cualquier plataforma que lo acepte, incluidos los proyectos de .NET Framework 4.6.1 a 4.8 y de .NET Core 2.0 a .NET 5.0, entre otros…
    Si quieres ver dónde se pueden usar las DLL compiladas con .NET Standard 2.0 mira este enlace: .NET Standard

    Últimas actualizaciones

    Nota del 24/Oct/2020 (v1.0.0.14)
    He actualizado tanto el paquete de NuGet como el código en GitHub.
    También la utilidad de gsColorearCodigo para que use esta nueva versión.

    Los cambios principales son para no eliminar las líneas en blanco que haya en el código.
    Esto era porque la función Split original de Visual Basic no elimina las líneas en blanco.
    Además de usar vbCr en lugar de vbCrLf al examinar cada línea y crear nuevas separaciones, ya que al añadir vbCrLf añadía una línea extra en blanco.

    Aunque últimamente se me ha dado el problema que no todos los ficheros tienen el mismo tipo de retorno de carro.
    Por tanto he tenido que comprobar antes de hacer el Split qué tipo de retorno de carro tiene: vbCrLf o vbCr o vbLf.

    Nota del 16/Sep/2020 (v1.0.0.7)
    He actualizado tanto el paquete de NuGet como el código en GitHub.
    También la utilidad de gsColorearCodigo para que use esta nueva versión.

    Los cambios principales son para no eliminar las líneas en blanco que haya en el código.
    Esto era porque la función Split original de Visual Basic no elimina las líneas en blanco.
    Además de usar vbCr en lugar de vbCrLf al examinar cada línea y crear nuevas separaciones, ya que al añadir vbCrLf añadía una línea extra en blanco.

    Nota del 13/Sep/2020 (v1.0.0.6)
    He actualizado tanto el paquete de NuGet como el código en GitHub.
    También la utilidad de gsColorearCodigo para que use esta nueva versión.

    Con fecha del 12 de septiembre de 2020 he creado un repositorio en gitHub para el código de gsColorearNET.

    De ahí puedes descargar el proyecto completo a excepción del fichero de nombre seguro (.snk)

    Este es el enlace: gsColorearNET en gitHub.

    Y este es el enlace a los paquetes de NuGet: gsColorearNET en NuGet.

    Estoy probando la utilidad de colorear en .NET 5.0 Preview 8 y a duras penas ya está operativa… es que el editor de Visual Studio 2019 Preview está aún muy verde para las aplicaciones de Windows Forms para Visual Basic (y para C# tampoco está muy fino).

    El hacerlo con esa versión es para poder depurar el código de la DLL de colorear, ya que en un proyecto de .NET framework no se puede… o yo no sé cómo hacerlo… todo hay que decirlo.

    Cuando tenga tiempo publicaré en el blog las cosillas que recomiendo hacer hasta que mejoren el editor/diseñador de WinForms para Visual Basic.

    NOTA:
    Aún la estoy probando desde NuGet ya que usándola como proyecto compila y funciona bien, pero al publicar el paquete en NuGet (aquí tienes el enlace: gsColorearNET en NuGet), no me encuentra los lenguajes de las palabras clave.
    ¡Esto último ya está solucionado con el paquete 1.0.0.1!
    Smile

    He publicado la utilidad de colorear código (gsColorearCodigo) para instalar con ClickOnce que utiliza el paquete de NuGet 1.0.0.1 y… ¡funciona a la perfección!

    Por ahora no te voy a poner el código fuente, en realidad ha cambiado poco desde lo que publiqué anteayer, y como lo que quiero añadir o corregir aún lo tengo que hacer… pues… habrá que esperar unos días a que lo tenga terminado y así ya la publico de forma, que salvo que haya algunos fallos, no tenga que modificarla en algún tiempo.

    Lo que sí haré es actualizar el paquete de NuGet con las correcciones que le vaya haciendo.

    Sobre el uso de un proyecto DLL de .NET Standard con un proyecto de .NET Framework

    Probando el código de gsColorearCodigo usando el .NET 4.8 en Visual Studio 2019 con el proyecto de gsColorearNET usando .NET Standard 2.0, al producirse un error (o sin que se produzca) y querer depurar en el código de la DLL, el VS no me dejaba…
    Así que… para comprobar porqué fallaba el penúltimo cambio que le hice, tuve que
    abrir un proyecto de .NET 5.0 (Preview 8) usando el Visual Studio Preview, y en ese entorno si pude depurar y averiguar cuál era el fallo… que después resultó ser algo trivial, pero…

    Así qué… ya sabes… si mezclas… no debugues… 😉

    Usar código de Visual Basic en .NET Standard

    Otra de las cosillas que me he encontrado a la hora de poder compilar el código del proyecto gsColorear para .NET Framework (ya hay que ir haciendo las aclaraciones de que no todo es simplemtente .NET, porque tenemos .NET Framework, .NET Core, .NET Standard y… casi ya… también simplemente .NET refiriéndose a .NET Core) es la definición de algunas de las funciones del ensamblado Microsoft.VisualBasic en concreto de la clase Strings que no están definidas en .NET Standard 2.0, tal como:
    Len, Left, Mid, Right, Split, InStr y Trim.

    Para no tener que cambiar todo el código que usaba esas funciones (recuerda que el código de colorear lo hice sobre el año 2001, sí, con Visual Basic 6.0, después en diciembre del 2002 lo pasé a la beta de Visual Studio .NET, la aplicación de aquél entonces era HTMLColorCode, aunque debo tener otra aplicación llamada gsEditor… el tiempo no perdona… por la memoria…) así que… me he creado una clase en la que
    he definido esas funciones, que (seguramente) pueden mejorarse, pero… así las he programado / codificado / como prefieras decirlo.

    Y te pego aquí el código por si te puede ser de ayuda.

    '------------------------------------------------------------------------------
    ' Clase definida en la biblioteca para .NET Standard 2.0            (10/Sep/20)
    ' Basada en gsColorear y gsColorearCore
    '
    ' VBCompat                                                          (10/Sep/20)
    ' Clase con instrucciones para compatibilidad con .NET Standard 2.0
    '
    ' Declaro algunas funciones de Microsoft.VisualBasic.Strings
    ' que no están en .NET Standard 2.0
    '
    ' (c) Guillermo (elGuille) Som, 2020
    '------------------------------------------------------------------------------
    Option Strict On
    Option Infer On
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    'Imports System.Data
    Public Class VBCompat
        ''' <summary>
        ''' Devuelve los caracteres desde la posición (en base 1)
        ''' hasta el final.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="pos"></param>
        Public Shared Function Mid(str As String, pos As Integer) As String
            Return str.Substring(pos - 1)
        End Function
        ''' <summary>
        ''' Devuelve la cadena desde la posición indicada con len caracteres.
        ''' La posición del primer carácter es el 1.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="pos"></param>
        ''' <param name="len"></param>
        ''' <remarks>10/Sep/20/20</remarks>
        Public Shared Function Mid(str As String, pos As Integer, len As Integer) As String
            Return str.Substring(pos - 1, len)
        End Function
        ''' <summary>
        ''' Devuelve el número de caracteres.
        ''' Si es cadena vacía o nulo devuelve 0.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <returns></returns>
        Public Shared Function Len(str As String) As Integer
            If String.IsNullOrEmpty(str) Then Return 0
            Return str.Length
        End Function
        ''' <summary>
        ''' Devuelve los primeros caracteres de la cadena.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="len"></param>
        ''' <returns></returns>
        Public Shared Function Left(str As String, len As Integer) As String
            Return str.Substring(0, If(len > str.Length, str.Length, len))
        End Function
        ''' <summary>
        ''' Devuelve los caracteres indicados desde la derecha.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <param name="len"></param>
        ''' <returns></returns>
        Public Shared Function Right(str As String, len As Integer) As String
            Dim iPos = str.Length - len
            Return str.Substring(iPos, len)
        End Function
        ''' <summary>
        ''' Devuelve la posición (en base 1) de la segunda cadena en la primera
        ''' </summary>
        ''' <param name="str1"></param>
        ''' <param name="str2"></param>
        ''' <returns></returns>
        Public Shared Function InStr(str1 As String, str2 As String) As Integer
            Return str1.IndexOf(str2) + 1
        End Function
        ''' <summary>
        ''' Devuelve la posición (en base 1) de la segunda cadena en la primera 
        ''' empezando en la posición indicada.
        ''' </summary>
        ''' <param name="startPos"></param>
        ''' <param name="str1"></param>
        ''' <param name="str2"></param>
        ''' <returns></returns>
        Public Shared Function InStr(startPos As Integer, str1 As String, str2 As String) As Integer
            Return str1.IndexOf(str2, startPos - 1) + 1
        End Function
        ''' <summary>
        ''' Devuelve una cadena después de haber quitado 
        ''' los espacios delante y detrás.
        ''' </summary>
        ''' <param name="str"></param>
        ''' <returns></returns>
        Public Shared Function Trim(str As String) As String
            Return str.Trim
        End Function
        ''' <summary>
        ''' Divide una cadena en elementos de un array.
        ''' Usando el delimitador indicado.
        ''' </summary>
        ''' <param name="Expression"></param>
        ''' <param name="Delimiter"></param>
        ''' <returns></returns>
        Public Shared Function Split(Expression As String, Optional Delimiter As String = " ") As String()
            Return Expression.Split(Delimiter.ToCharArray, StringSplitOptions.RemoveEmptyEntries)
        End Function
        ''
        '' El código IL de Prueba1 es más corto (y parece que eficiente) que el de Prueba2
        ''
        'Public Shared Function Prueba1(str As String, len As Integer) As String
        '    Return str.Substring(0, If(len > str.Length, str.Length, len))
        'End Function
        'Public Shared Function Prueba2(str As String, len As Integer) As String
        '    If len > str.Length Then
        '        len = str.Length
        '    End If
        '    Return str.Substring(0, len)
        'End Function
    End Class
    

    Y esto es todo por ahora…

    Espero que te sea de utilidad

    Nos vemos.
    Guillermo

     

     
    El código fuente completo

    El código para Visual Basic con el proyecto para Visual Studio 2019 está publicado en gitHub.

     

    Este es el enlace: gsColorearNET en gitHub.

     

    Y este es el enlace a los paquetes de NuGet: gsColorearNET en NuGet.

     

     

  • 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