Índice de la sección dedicada a .NET (en el Guille) Cómo... en .NET

Deshabilitar Alt+Tab en Windows XP

También en NT/2000/Vista además de las teclas: Ctrl+Esc y Alt+Esc

Código para Visual Basic.NET (VB.NET)

Código para C Sharp (C#)

 
Publicado el 15/Oct/2004
Actualizado el 24/Mar/2007
Autor: Guillermo 'guille' Som

Deshabilitar las teclas Alt+Tab, Ctrl+Esc y Alt+Esc en Windows XP (también en 2000 y Vista) usando .NET (ejemplos para Visual Studio 2003 y Visual Studio 2005, tanto para Visual Basic como para Visual C#).

 

Introducción:
Este ejemplo está basado en el código de VB6 publicado hace más de un año aquí en mi sitio.
El hacer la versión para Visual Basic .NET y C# ha sido a raíz de una llamada desesperada de Nathaly en los grupos de noticias públicos de Microsoft (microsoft.public.es.csharp). Lo de desesperada es porque se ha tomado al pie de la letra lo de no hacerme consultas personales y hacerla en los grupos públicos (a ver si la gente toma ejemplo), y viendo que no le resolvían el problema, pues hizo una llamada al Guille, que como resulta que de vez en cuando también se da una vuelta por el grupo de C#, pues... en fin... pero no lo toméis como algo a hacer... que eso de pedir auxilio personalizado en un sitio en el que somos muchos los que contestamos a las dudas, pues no queda bien.

 

Nota del 24/Mar/2007:

A raíz de un mensaje en mis foros en el que comentaban que el código de este artículo no funcionaba (después aclararon que era con la versión 2005 de C#), he optado por incluir el código tanto de Visual Basic 2005 como de Visual C# 2005 para que puedas comprobar que SI funciona.

Al menos funciona con Visual Studio 2005 SP1 y en Windows Vista Ultimate.

Las pruebas que he hecho han sido con el ejecutable NO desde el IDE de Visual Studio, no porque no funcione, que a lo mejor funciona desde el propio IDE, sino porque yo NUNCA pruebo (o al menos lo intento) desde el IDE los programas que alteran de alguna forma el funcionamiento normal del sistema operativo, en particular estas cosas que utilizan "ganchos" (hook), ya que si se produce algún error, el sistema se puede quedar inestable. La verdad es que no se si eso sigue ocurriendo con Visual Studio 2005 ni con Windows Vista, pero como se que desde Visual Basic 5.0 esas cosas eran "peligrosas", pues me aplico el parche e intento no hacerlo.
La razón es que si se produce algún error mientras el gancho está modificado, es posible que por causa del error ese gancho no se vuelva a restaurar, y... bueno... que puede que todo el sistema se quede esperando que ocurra algo que no ocurrirá. Por tanto, mejor no intentarlo, para que no se produzcan efectos no deseados.

A lo que iba, las pruebas las he hecho desde el ejecutable (.EXE) no desde el IDE. En esos casos, la versión de Visual Basic 2005 SIEMPRE ha funcionado, tanto si está compilado en modo DEBUG o en modo RELEASE.
Sin embargo, usando el ejecutable creado con el proyecto de C#, cuando lo he probado en modo RELEASE me ha fallado al usar la combinación Alt+Tab. En modo DEBUG siempre me ha funcionado bien.

Resumiendo:
Con esto puedes comprobar que no todo el código que a mi me funciona, a otros les funciona. Y la razón por la que puede que no funcione puede ser variada, en este caso, es posible que el que probó el código lo hiciera con C# y usando la compilación RELEASE. En otras ocasiones, es posible que la configuración del propio sistema operativo, el tipo de usuario que en ese momento esté activo en el sistema operativo o porque la Luna está en fase menguante (por decir algo) es posible que un código que a uno le funciona a otros no les funcione.
Esto mismo es extensible a tu propio código... porque... a ver... ¿a cuantos les ha pasado que un código que funciona perfectamente en tu equipo, no funciona correctamente (o de la misma forma) en el equipo de otra persona? A lo mejor a ti no te ha pasado, pero te puedo asegurar que a muchos otros si que les ha pasado.

Pues eso... que te recomiendo siempre que pruebes todo el código que vayas a usar... y si no te funciona pues... intenta buscar la causa... e incluso, prueba a preguntarlo en los foros o en otros sitios que hay en Internet que seguro que alguien te responde... aunque sea con tanto "rollo" como acabo de hacerlo yo ahora... je, je.

 

Bueno, vamos al tema, te voy a explicar un poco cómo he convertido el código para que te sirva para futuras conversiones de código de VB6 que usa API de Windows a código .NET.

Explicaciones para Visual Basic

Primero convertí el proyecto de VB6 usando el asistente de Visual Studio .NET, ya se que yo no lo recomiendo, pero al menos te evitas un poco el tener que crear el formulario y cuatro cosillas más. De todas formas, del código generado sólo ha quedado lo "justo", ya que he tenido que cambiar algunas cosillas.

En Visual Basic .NET, las declaraciones de las funciones del API son parecidas a VB6, salvo por el tipo de datos ya que en VB6 un Long corresponder a un Integer de .NET, por tanto aquí no voy a entrar en detalles, sin embargo en C# se usa de otra forma, la cual también podemos usar en VB.NET, pero, realmente no es necesario.
Los únicos detalles son que cuando a una función del API se va a pasar un puntero a una función (las funciones CallBack son realmente punteros) en .NET debemos usar delegados, ya que el CLR no nos permite usar punteros, y aunque te resulte extraño, en VB se usan punteros, pero por medio de AddressOf. Otro de los detalles es que no se usa la función CopyMemory para copiar un "puntero" a una estructura en una estructura:

CopyMemory pkbhs, ByVal lParam, Len(pkbhs)

Ya que en lugar de esto he usado PtrToStructure de la clase System.Runtime.InteropServices.Marshal, que en .NET está para eso, así que, mejor usar código de .NET cuando sea posible:

pkbhs = CType(Marshal.PtrToStructure(New IntPtr(lParam), pkbhs.GetType), tagKBDLLHOOKSTRUCT)

 

Como te he comentado, lo siguiente que había que cambiar era la forma de llamar a la función callback, para la que hay que usar un delegado, en este caso para recibir la notificación de las teclas pulsadas.
La función que usará el API para mandarnos esa información de las teclas es la que hemos definido como LLKeyBoardProc y la función del API que la tiene que llamar es: SetWindowsHookEx.
La declaración del delegado, de la función API y del método de nuestra clase que será llamado quedaría así:

' El delegado para usar con AddressOf
Private Delegate Function LLKeyBoardProcDelegate(ByVal nCode As Integer, _
        ByVal wParam As Integer, ByVal lParam As Integer) As Integer

' para asignar un gancho (hook)
Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" _
        (ByVal idHook As Integer, ByVal lpfn As LLKeyBoardProcDelegate, _
        ByVal hMod As Integer, ByVal dwThreadId As Integer) As Integer

' La función a usar para el gancho del teclado
Private Function LLKeyBoardProc(ByVal nCode As Integer, _
        ByVal wParam As Integer, ByVal lParam As Integer) As Integer

 

Realmente estos son los puntos a destacar en el código de Visual Basic, aunque también queda por explicar cómo se usa ese delegado para que se cree el gancho a nuestro método, el código sería este:

mHook = SetWindowsHookEx(WH_KEYBOARD_LL, _
        New LLKeyBoardProcDelegate(AddressOf LLKeyBoardProc), _
        hMod, 0)

 

Pues esto es todo sobre el código de Visual Basic .NET.

Explicaciones para C#

Como te comentaba, en C# las declaraciones del API se hacen al estilo .NET, que también se puede hacer de esta forma en VB.
En el ejemplo también vas a ver cómo declarar el delegado, el método que se usa como función gancho y la forma de usar el delegado:

// El delegado para usar como gancho
private delegate int LLKeyBoardProcDelegate(int nCode, int wParam, int lParam);

// para asignar un gancho (hook)
[DllImport("user32.dll")]
private static extern int SetWindowsHookEx(int idHook, LLKeyBoardProcDelegate lpfn, 
        int hMod, int threadId);

// La función a usar para el gancho del teclado
private static int LLKeyBoardProc(int nCode, int wParam, int lParam)

 

Otra cosa que se diferencia del código de VB es la forma de usar el "módulo" en el que se han declarado las funciones del API, ya que en C# no existe el concepto "módulo" como el de VB, pero como seguramente sabrás, el equivalente de un Module de VB es una clase estática en C#, en el que los miembros también están declarados como estáticos (static), y que para usarla debemos usar el nombre de la clase y el método a usar, como por ejemplo en el código que llama a la función del API:

mHook = SetWindowsHookEx(WH_KEYBOARD_LL, new LLKeyBoardProcDelegate(LLKeyBoardProc), hMod, 0);

 

Ahora sí que sí, ya hemos terminado.
Un poco más abajo tienes el código tanto para Visual Basic .NET como para C#, así como el link para que te puedas bajar el código fuente tanto para VB como para C#.

Y si te ha parecido interesante, acuérdate de votar en la cajita de PanoramaBox.
Gracias.

Nos vemos.
Guillermo

 


Código para Visual Basic.NET (VB.NET)El código para VB .NET

El código del módulo con las declaraciones del API y demás métodos:

 

'------------------------------------------------------------------------------
' Para bloquear algunas teclas en Windows NT/2000/XP                (08/Mar/03)
' Para NT debe tener el SP3 como mínimo
'
' ¡¡¡ NO FUNCIONA para Ctrl+Alt+Supr !!!
'
' En este ejemplo se bloquean las siguientes teclas:
'   Ctrl+Esc, Alt+Tab y Alt+Esc
'
' Convertido a Visual Basic .NET 2003                               (15/Oct/04)
' Para una petición de Nathaly en el grupo public.es.csharp
'
' ©Guillermo 'guille' Som, 2003-2004
'------------------------------------------------------------------------------
Option Strict On
Option Explicit On 

Imports System
Imports Microsoft.VisualBasic
Imports System.Runtime.InteropServices

Module MBloquearTeclas
    ' para guardar el gancho creado con SetWindowsHookEx
    Private mHook As Integer
    '
    ' para indicar a SetWindowsHookEx que tipo de gancho queremos instalar
    Private Const WH_KEYBOARD_LL As Integer = 13
    ' este es para el ratón
    'Private Const WH_MOUSE_LL As Long = 14&
    '
    Private Structure tagKBDLLHOOKSTRUCT
        Dim vkCode As Integer
        Dim scanCode As Integer
        Dim flags As Integer
        Dim time As Integer
        Dim dwExtraInfo As Integer
    End Structure
    '
    Private Const VK_TAB As Integer = &H9
    Private Const VK_CONTROL As Integer = &H11 ' tecla Ctrl
    'Private Const VK_MENU As Long = &H12        ' tecla Alt
    Private Const VK_ESCAPE As Integer = &H1B
    'Private Const VK_DELETE As Integer = &H2E      ' tecla Supr (Del)
    '
    Private Const LLKHF_ALTDOWN As Integer = &H20
    '
    ' códigos para los ganchos (la acción a tomar en el gancho del teclado)
    Private Const HC_ACTION As Integer = 0
    '
    ' Funciones del API de Windows
    '-----------------------------
    ' para asignar un gancho (hook)
    Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" _
            (ByVal idHook As Integer, ByVal lpfn As LLKeyBoardProcDelegate, _
            ByVal hMod As Integer, ByVal dwThreadId As Integer) As Integer
    ' para quitar el gancho creado con SetWindowsHookEx
    Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Integer) As Integer
    ' para llamar al siguiente gancho
    Private Declare Function CallNextHookEx Lib "user32" _
            (ByVal hHook As Integer, ByVal nCode As Integer, _
            ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    ' para saber si se ha pulsado en una tecla
    Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Integer) As Short
    '
    ' El delegado para usar con AddressOf
    Private Delegate Function LLKeyBoardProcDelegate(ByVal nCode As Integer, _
            ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    '
    ' La función a usar para el gancho del teclado
    Private Function LLKeyBoardProc(ByVal nCode As Integer, _
            ByVal wParam As Integer, ByVal lParam As Integer) As Integer
        Dim pkbhs As tagKBDLLHOOKSTRUCT
        Dim ret As Integer = 0
        '
        ' copiar el parámetro en la estructura
        pkbhs = CType(Marshal.PtrToStructure(New IntPtr(lParam), pkbhs.GetType), tagKBDLLHOOKSTRUCT)
        '
        If nCode = HC_ACTION Then
            '
            ' si se pulsa Ctrl+Esc
            If pkbhs.vkCode = VK_ESCAPE Then
                If (GetAsyncKeyState(VK_CONTROL) And &H8000S) <> 0 Then
                    ret = 1
                End If
            End If
            '
            ' si se pulsa Alt+Tab
            If pkbhs.vkCode = VK_TAB Then
                If (pkbhs.flags And LLKHF_ALTDOWN) <> 0 Then
                    ret = 1
                End If
            End If
            '
            ' si se pulsa Alt+Esc
            If pkbhs.vkCode = VK_ESCAPE Then
                If (pkbhs.flags And LLKHF_ALTDOWN) <> 0 Then
                    ret = 1
                End If
            End If
            '
            '' si se pulsa Alt+Supr
            '' (esto no funciona con Ctrl)
            'If pkbhs.vkCode = VK_DELETE Then
            '    If (pkbhs.flags And LLKHF_ALTDOWN) <> 0 Then
            '        ret = 1
            '    End If
            'End If
        End If
        '
        If ret = 0 Then
            ret = CallNextHookEx(mHook, nCode, wParam, lParam)
        End If
        '
        Return ret
        '
    End Function
    '
    Public Sub HookKeyB(ByVal hMod As Integer)
        ' instalar el gancho para el teclado
        ' hMod será el valor de App.hInstance de la aplicación
        mHook = SetWindowsHookEx(WH_KEYBOARD_LL, New LLKeyBoardProcDelegate(AddressOf LLKeyBoardProc), hMod, 0)
    End Sub
    '
    Public Sub UnHookKeyB()
        ' desinstalar el gancho para el teclado
        ' Es importante hacerlo antes de finalizar la aplicación,
        ' normalmente en el evento Unload o QueryUnload
        If mHook <> 0 Then
            UnhookWindowsHookEx(mHook)
        End If
    End Sub
End Module

 

El código del formulario:

 

'------------------------------------------------------------------------------
' Formulario de prueba para desactivar algunas teclas especiales    (08/Mar/03)
' en Windows NT/2000/XP
'
' Convertido a Visual Basic .NET 2003                               (15/Oct/04)
' Para una petición de Nathaly en el grupo public.es.csharp
'
' ©Guillermo 'guille' Som, 2003-2004
'------------------------------------------------------------------------------
Option Strict On
Option Explicit On 

Imports System
Imports Microsoft.VisualBasic
Imports System.Runtime.InteropServices

Friend Class fBloquearTeclas
    Inherits System.Windows.Forms.Form
    '
#Region "Código generado por el Diseñador de Windows Forms "
    '
#End Region
    '
    Private Sub cmdCerrar_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdCerrar.Click
        Me.Close()
    End Sub
    '
    Private Sub cmdHook_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdHook.Click
        ' iniciar el gancho para el teclado
        ' (el valor a pasar es el hInstance de la aplicación)
        HookKeyB((Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()(0))).ToInt32)
    End Sub
    '
    Private Sub cmdUnHook_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdUnHook.Click
        ' quitar el gancho del teclado
        UnHookKeyB()
    End Sub
    '
    Private Sub fBloquearTeclas_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        ' al cerrar el formulario, quitar el gancho del teclado
        UnHookKeyB()
    End Sub
End Class

 


Código para C Sharp (C#)El código para C#

El código del módulo con las declaraciones del API y demás métodos:

 

/*-----------------------------------------------------------------------------
' Para bloquear algunas teclas en Windows NT/2000/XP                (08/Mar/03)
' Para NT debe tener el SP3 como mínimo
'
' ¡¡¡ NO FUNCIONA para Ctrl+Alt+Supr !!!
'
' En este ejemplo se bloquean las siguientes teclas:
'   Ctrl+Esc, Alt+Tab y Alt+Esc
'
' Convertido a C#                                                    (15/Oct/04)
' Para una petición de Nathaly en el grupo public.es.csharp
'
' ©Guillermo 'guille' Som, 2003-2004
'------------------------------------------------------------------------------
*/
using System;
using Microsoft.VisualBasic;
using System.Runtime.InteropServices;

class MBloquearTeclas
{
    // para guardar el gancho creado con SetWindowsHookEx
    private static int mHook;
    //
    // para indicar a SetWindowsHookEx que tipo de gancho queremos instalar
    const int WH_KEYBOARD_LL = 13;
    // este es para el ratón
    //Private Const WH_MOUSE_LL As Long = 14&
    //
    private struct tagKBDLLHOOKSTRUCT
    {
        internal int vkCode;
        internal int scanCode;
        internal int flags;
        internal int time;
        internal int dwExtraInfo;
    }  
    //
    const int VK_TAB = 0x9;
    const int VK_CONTROL = 0x11; // tecla Ctrl
    //Private Const VK_MENU As Long = &H12        ' tecla Alt
    const int VK_ESCAPE = 0x1B;
    //Private Const VK_DELETE As Integer = &H2E      ' tecla Supr (Del)
    //
    const int LLKHF_ALTDOWN = 0x20;
    //
    // códigos para los ganchos (la acción a tomar en el gancho del teclado)
    const int HC_ACTION = 0;
    //
    // Funciones del API de Windows
    //-----------------------------
    // para asignar un gancho (hook)
    [DllImport("user32.dll")]
    private static extern int SetWindowsHookEx(int idHook, LLKeyBoardProcDelegate lpfn, 
            int hMod, int threadId);
    // para quitar el gancho creado con SetWindowsHookEx
    [DllImport("user32.dll")]
    private static extern bool UnhookWindowsHookEx(int idHook);
    // para llamar al siguiente gancho
    [DllImport("user32.dll")]
    private static extern int CallNextHookEx(int idHook, int nCode, int wParam, int lParam);   
    // para saber si se ha pulsado en una tecla
    [DllImport("user32.dll")]
    private static extern short GetAsyncKeyState(int vKey);   
    //
    // El delegado para usar como gancho
    private delegate int LLKeyBoardProcDelegate(int nCode, int wParam, int lParam);
    //
    // La función a usar para el gancho del teclado
    private static int LLKeyBoardProc(int nCode, int wParam, int lParam) 
    {
        tagKBDLLHOOKSTRUCT pkbhs;
        int ret = 0;
        //
        // copiar el parámetro en la estructura
        pkbhs = ((tagKBDLLHOOKSTRUCT)Marshal.PtrToStructure(new IntPtr(lParam), typeof(tagKBDLLHOOKSTRUCT)));
        //
        if( nCode == HC_ACTION )
        {
            //
            // si se pulsa Ctrl+Esc
            if( pkbhs.vkCode == VK_ESCAPE )
            {
                if( (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0 )
                {
                    ret = 1;
                }
            }
            //
            // si se pulsa Alt+Tab
            if( pkbhs.vkCode == VK_TAB )
            {
                if( (pkbhs.flags & LLKHF_ALTDOWN) != 0 )
                {
                    ret = 1;
                }
            }
            //
            // si se pulsa Alt+Esc
            if( pkbhs.vkCode == VK_ESCAPE )
            {
                if( (pkbhs.flags & LLKHF_ALTDOWN) != 0 )
                {
                    ret = 1;
                }
            }
            //
        }
        //
        if( ret == 0 )
        {
            ret = CallNextHookEx(mHook, nCode, wParam, lParam);
        }
        //
        return ret;
        //
    }  
    //
    public static void HookKeyB(int hMod) 
    {
        // instalar el gancho para el teclado
        // hMod será el valor de App.hInstance de la aplicación
        mHook = SetWindowsHookEx(WH_KEYBOARD_LL, new LLKeyBoardProcDelegate(LLKeyBoardProc), hMod, 0);
    }  
    //
    public static void UnHookKeyB() 
    {
        // desinstalar el gancho para el teclado
        // Es importante hacerlo antes de finalizar la aplicación,
        // normalmente en el evento Unload o QueryUnload
        if( mHook != 0 )
        {
            UnhookWindowsHookEx(mHook);
        }
    }  
}

 

 

El código del formulario:

 

/*-----------------------------------------------------------------------------
' Formulario de prueba para desactivar algunas teclas especiales    (08/Mar/03)
' en Windows NT/2000/XP
'
' Convertido a C#                                                    (15/Oct/04)
' Para una petición de Nathaly en el grupo public.es.csharp
'
' ©Guillermo 'guille' Som, 2003-2004
'------------------------------------------------------------------------------
*/
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class Form1 : System.Windows.Forms.Form
{
    public System.Windows.Forms.Button cmdCerrar;
    public System.Windows.Forms.Button cmdUnHook;
    public System.Windows.Forms.Button cmdHook;
    public System.Windows.Forms.Label Label1;
    /// <summary>
    /// Variable del diseñador requerida.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public Form1()
    {
        //
        // Necesario para admitir el Diseñador de Windows Forms
        //
        InitializeComponent();

        //
        // TODO: agregar código de constructor después de llamar a InitializeComponent
        //
    }

    /// <summary>
    /// Limpiar los recursos que se estén utilizando.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    #region Código generado por el Diseñador de Windows Forms
    //
    #endregion
    //
    /// <summary>
    /// Punto de entrada principal de la aplicación.
    /// </summary>
    [STAThread]
    static void Main() 
    {
        Application.Run(new Form1());
    }
    //
    //---------------------------------------------------------
    //TODO: Usar esta asignación en el constructor de la clase:
    //
    //cmdCerrar.Click += new System.EventHandler(cmdCerrar_Click);
    //---------------------------------------------------------
    private void cmdCerrar_Click(object sender, System.EventArgs e) 
    {
        this.Close();
    }  
    //
    private void cmdHook_Click(object sender, System.EventArgs e) 
    {
        // iniciar el gancho para el teclado
        // (el valor a pasar es el hInstance de la aplicación)
        MBloquearTeclas.HookKeyB((Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0])).ToInt32());
    }  
    //
    private void cmdUnHook_Click(object sender, System.EventArgs e) 
    {
        // quitar el gancho del teclado
        MBloquearTeclas.UnHookKeyB();
    }  
    //
    private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) 
    {
        // al cerrar el formulario, quitar el gancho del teclado
        MBloquearTeclas.UnHookKeyB();
    }
}

 

 


la Luna del Guille o... el Guille que está en la Luna... tanto monta...