가. 키보드 이벤트 예광탄

이번에는 가상 키보드를 제작할 때 필요한 키보드 이벤트를 발생하는 예광탄을 만듭시다.

키보드 이벤트 예광탄 실행화면 및 컨트롤 배치
[그림 8.02] 실행화면 및 컨트롤 배치

 윈도우즈 응용 프로그램 프로젝트를 생성한 후에 컨트롤 배치를 먼저 하세요.

번호컨트롤 타입컨트롤 명설명
1ListBoxlbox_key키 목록
2TextBoxtbox_demo키보드 이벤트 발생할 타겟

[표 8.1] Form1의 컨트롤 배치

키보드 이벤트 예광탄 응용에서는 키 목록을 표시한 리스트 박스의 항목을 선택하면 프로그램 방식으로 초점을 텍스트 박스로 옮긴 후에 약간의 지연을 두고 키보드 이벤트를 발생시킬 것입니다. 이를 통해 프로그램 방식으로 키보드 이벤트를 발생하는 방법을 익히고자 합니다.

키보드 이벤트나 마우스 이벤트를 강제로 발생하기 위해서는 Windows API에서 제공하는 시스템 함수를 호출해야 합니다. 다음은 프로그램 방식으로 키보드 이벤트를 발생시키는 keybd_event 함수의 원형입니다.

WINUSERAPI VOID WINAPI keybd_event(
   __in BYTE bVk, __in BYTE bScan,__in DWORD dwFlags,__in ULONG_PTR dwExtraInfo);

C#에서 Native 에서 제공하는 것을 사용하려면 System.Runtime.InteropServices 네임스페이스의 DllImport 특성을 이용합니다.

DllImport 특성을 이용할 때 사용할 함수를 정의하고 있는 dll 파일의 정보를 지정해야 하는데 WINUSERAPI 매크로 상수가 함수 원형 앞에 명시되어 있으면 User32.dll 파일을 지정하세요.

Native dll에 정의되어 있는 함수를 마이그레이션 할 때는 extern 키워드를 포함하여 같은 이름의 함수를 선언합니다. 그리고 입력 인자 형식이 포인터일 때는 ref 혹은 out으로 바꾸시고 리턴 형식이 포인터일 때는 IntPtr로 하면 적당합니다. 자세한 내용은 MSDN의 상호 운용성 부분을 살펴보시기 바랍니다.

프로젝트에 Windows API를 래핑하여 사용할 WrapNative 클래스를 추가하세요.

먼저 발생할 키보드 이벤트의 종류를 열거형으로 정의합시다. 발생할 키보드 이벤트 종류는 키 누름, 확장 키, 키 뗌이 있고 이들의 조합을 허용하기 위해 Flags 특성을 지정합니다.

[Flags]
public enum KeyFlag{    KE_DOWN = 0, KE__EXTENDEDKEY = 1, KE_UP = 2    }
public static class WrapNative
{

User32.dll에 있는 keybd_event 시스템 함수를 선언합니다.

    [DllImport("User32.dll")]
    static extern void keybd_event(byte vk, byte scan, int flags, int extra);

이제 프로그램 방식으로 키 누름 이벤트를 발생할 메서드를 정의합니다.

    public static void KeyDown(int keycode)
    {
        keybd_event((byte)keycode, 0, (int)(KeyFlag.KE_DOWN), 0);
    }

키 뗌 이벤트를 발생할 함수도 제공합시다.

    public static void KeyUp(int keycode)
    {
        keybd_event((byte)keycode, 0, (int)(KeyFlag.KE_UP), 0);
    }

키를 눌렀다가 떼는 클릭 함수도 제공합시다.

    public static void KeyClick(int keycode)
    {
 먼저 프로그램 방식으로 키를 누르는 이벤트를 발생하는 KeyDown 메서드를 호출합니다.
        KeyDown(keycode);
 그리고 프로그램 방식으로 키를 떼는 이벤트를 발생하는 KeyUp 메서드를 호출합니다.
        KeyUp(keycode);
    }
}

이번 예광탄에서는 확장 키에 관한 부분은 다루지 않았습니다. 이 책에서는 UI 자동화 기술에 초점을 맞추고 있으며 실습에 필요한 부분은 UI 자동화 기술이 아니어도 다루고 있습니다. 하지만 UI 자동화 기술이 아닌 부분까지 상세히 다루면 전체의 초점이 흐트러질 수 있어서 지금처럼 간략하게 소개하고 사용하는 수준에 머물 것입니다. 여러분께서는 실제 프로그래밍에 필요한 부분이 있으면 별도의 학습을 진행하세요.

using System;
using System.Runtime.InteropServices;
namespace 키보드_이벤트_예광탄
{
    [Flags]
    public enum KeyFlag{ KE_DOWN = 0, KE__EXTENDEDKEY = 1, KE_UP = 2 }
    public static class WrapNative
    {
        [DllImport("User32.dll")]
        static extern void keybd_event(byte vk, byte scan, int flags, int extra);
        public static void KeyDown(int keycode)
        {
            keybd_event((byte)keycode, 0, (int)(KeyFlag.KE_DOWN), 0);
        }
        public static void KeyUp(int keycode)
        {
            keybd_event((byte)keycode, 0, (int)(KeyFlag.KE_UP), 0);
        }
        public static void KeyClick(int keycode)
        {
            KeyDown(keycode);
            KeyUp(keycode);
        }
    }
}

[소스 8.1] WrapNative.cs

이제 Form1 부분을 구현합시다. 먼저 Form1의 Load 이벤트 핸들러를 등록하세요.

private void Form1_Load(object sender, EventArgs e)
{
 키 항목을 0에서 255까지 리스트 박스 항목에 추가합시다.
 여기에서는 프로그램 방식으로 키보드 이벤트를 발생하는 방법을 다루기 위한 것이라 모든 키를 추가할 필요가 없습니다.
    for (int i = 0; i < 256; i++)
    {
        lbox_key.Items.Add((Keys)i);
    }
}

키 항목을 보관한 리스트 박스의 선택 항목 변경 이벤트 핸들러를 등록하세요.

private void lbox_key_SelectedIndexChanged(object sender, EventArgs e)
{
 만약 선택 항목이 없으면 메서드를 종료합니다.
    if (lbox_key.SelectedIndex == -1){    return;    }
 프로그램 방식으로 데모 텍스트 박스에 초점을 소유하게 합니다.
    tbox_demo.Focus();
 의도적으로 0.1 초의 지연시킵니다.
    Thread.Sleep(100);
 리스트 박스의 선택 항목을 Keys 형식으로 명식적 형 변환하여 WrapNative의 KeyClick을 호출합니다.
    Keys key = (Keys)lbox_key.SelectedItem;
    WrapNative.KeyClick((int)key);
}
using System;
using System.Windows.Forms;
using System.Threading;

namespace 키보드_이벤트_예광탄
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            for (int i = 0; i < 256; i++)
            {
                lbox_key.Items.Add((Keys)i);
            }
        }
        private void lbox_key_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lbox_key.SelectedIndex == -1)
            {
                return;
            }
            tbox_demo.Focus();
            Thread.Sleep(100);
            Keys key = (Keys)lbox_key.SelectedItem;
            WrapNative.KeyClick((int)key);
        }
    }
}

[소스 8.2] Form1.cs