4) 가상 키보드 만들기

윈도우즈 폼 응용 프로젝트를 추가한 후에 키보드 이벤트와 마우스 이벤트 처리를 위한 WrapNative 클래스를 추가하세요. 이 부분은 앞에서 설명한 부분이라 별도의 설명은 생략할게요. 아래의 소스 코드를 참고하세요.

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;
namespace DemoKeyEvent
{
    [Flags]
    public enum KeyFlag{   KE_DOWN = 0, KE__EXTENDEDKEY=1, KE_UP = 2  } 
    static class WrapNative
    {
        static Mutex mu = new Mutex();
        const int IME_CMODE_NATIVE = 0x1;
        [DllImport("imm32.dll")]
        static extern int ImmGetContext(int hWnd);
        [DllImport("imm32.dll")]
        static extern int ImmReleaseContext(int hWnd, int hImc);
        [DllImport("imm32.dll")]
        static extern int ImmGetConversionStatus(int hImc, out int fdwConversion,
                                              out int fdwSentence);
        [DllImport("imm32.dll")]
        static extern bool ImmSetConversionStatus(IntPtr hIMC, int fdwConversion,
                                                int fdwSentence);
        public static bool IsNativeMode(int handle)
        {
            int hImc, dwConversion = 0, dwSentense = 0;
            hImc = ImmGetContext(handle);
            ImmGetConversionStatus(hImc, out dwConversion, out dwSentense);
            return (dwConversion & IME_CMODE_NATIVE) == 1;
        }
        [DllImport("user32.dll")]
        private static extern int GetKeyboardState(byte[] pbKeyState);
        [DllImport("User32.dll")]
        static extern void keybd_event(byte vk, byte scan, int flags, int extra);
        public static void KeyDown(Keys keycode)
        {
            mu.WaitOne();
            keybd_event((byte)keycode,0,(int)(KeyFlag.KE_DOWN|KeyFlag.KE__EXTENDEDKEY),0);            
            mu.ReleaseMutex();
        }
        public static void KeyUp(Keys keycode)
        {
            mu.WaitOne();
            keybd_event((byte)keycode, 0, (int)(KeyFlag.KE_UP|KeyFlag.KE__EXTENDEDKEY), 0);            
            mu.ReleaseMutex();
        }
        public static void KeyClick(Keys keycode,bool shift)
        {
            mu.WaitOne();
            if (shift)
            {
                keybd_event((byte)Keys.ShiftKey,0,
                            (int)(KeyFlag.KE_DOWN|KeyFlag.KE__EXTENDEDKEY),0);
                keybd_event((byte)keycode, 0, 
                            (int)(KeyFlag.KE_DOWN | KeyFlag.KE__EXTENDEDKEY), 0);
                keybd_event((byte)keycode, 0, 
                            (int)(KeyFlag.KE_UP | KeyFlag.KE__EXTENDEDKEY), 0);                
                keybd_event((byte)Keys.ShiftKey, 0, 
                            (int)(KeyFlag.KE_UP | KeyFlag.KE__EXTENDEDKEY), 0);                                                
            }
            else
            {
                keybd_event((byte)keycode, 0, 
                             (int)(KeyFlag.KE_DOWN | KeyFlag.KE__EXTENDEDKEY), 0);
                keybd_event((byte)keycode, 0, 
                             (int)(KeyFlag.KE_UP | KeyFlag.KE__EXTENDEDKEY), 0);
            }
            mu.ReleaseMutex();
        }
        public static bool IsPress(Keys keycode)
        {
            byte[] vks = new byte[256];
            GetKeyboardState(vks);
            return vks[(int)keycode] == 1;
        }
    }
}

[소스 8.11] WrapNative.cs

이번 작업에서는 논리적인 처리외에도 키보드 인터페이스 부분이 있어서 과도한 중복 작업이 존재합니다. 먼저 폼의 자식 컨트롤을 배치하세요.

[그림 8.07] 가상 키보드 폼의 자식 컨트롤 배치
[그림 8.07] 가상 키보드 폼의 자식 컨트롤 배치

다음은 자식 컨트롤에 배치한 컨트롤 형식과 컨트롤 이름입니다. 그림에는 보이지 않지만 좌측 상단에 정보를 표시할 레이블(Label lb_text)을 하나 배치하세요.

Button btn_toggle_ime; 한/영 변환
Button btn_esc; Esc
Button btn_f1; F1
Button btn_f2; F2
Button btn_f3; F3
Button btn_f4; F4
Button btn_f5; F5
Button btn_f6; F6
Button btn_f7; F7
Button btn_f8; F8
Button btn_f9; F9
Button btn_f10; F10
Button btn_f11; F11
Button btn_f12; F12
Button btn_equal; =
Button btn_dash; -
Button btn_0; 0
Button btn_9; 9
Button btn_8; 8
Button btn_7; 7
Button btn_6; 6
Button btn_5; 5
Button btn_4; 4
Button btn_3; 3
Button btn_2; 2
Button btn_1; 1
Button btn_fpu; ` (ESC 밑)
Button btn_bs; \
Button btn_barrow; <-
Button btn_enter; Enter
Button btn_postb; ]
Button btn_preb; [
Button btn_p; p
Button btn_o; o
Button btn_i; i
Button btn_u; u
Button btn_y; y
Button btn_t; t
Button btn_r; r
Button btn_e; e
Button btn_w; w
Button btn_q; q
Button btn_tab; Tab
Button btn_upper; '(Enter 옆)
Button btn_semi; ;
Button btn_l; l
Button btn_k; k
Button btn_j; j
Button btn_h; h
Button btn_g; g
Button btn_f; f
Button btn_d; d
Button btn_s; s
Button btn_a; a
Button btn_caps; Caps
Button btn_slush; /
Button btn_dot; .
Button btn_comma; ,
Button btn_m; m
Button btn_n; n
Button btn_b; b
Button btn_v; v
Button btn_c; c
Button btn_x; x
Button btn_z; z
Button btn_shift; Shift
Button btn_space; Space
Button btn_alt; Alt
Button btn_ctrl; Ctrl
ListBox lbox_event; 
CheckBox cb_cabs;
CheckBox cb_with_shift;
Label lb_text; //(좌측 상단에 레이블을 배치하세요.)

이제 폼의 코드를 작성합시다. 먼저 현재 쉬프트 키를 눌렀는지 확인할 수 있는 멤버 필드를 선언하세요.

bool is_press_shift;

한글 모드인지 확인할 수 있는 멤버 필드를 선언하세요.

bool check;

자동화 요소의 초점 변경 이벤트 개체를 기억할 멤버를 선언하세요.

AutomationFocusChangedEventHandler afceh;

현재 초점을 소유한 자동화 요소 개체와 값 패턴의 개체를 기억할 멤버를 선언합니다.

AutomationElement target_ae;
ValuePattern target;

한글 개체를 기억할 멤버도 선업합니다.

Hangul hangul;

폼의 Load 이벤트 핸들러를 추가하세요.

private void MainForm_Load(object sender, EventArgs e)
{

초점 변경 이벤트 핸들러를 생성하여 등록합니다.

    afceh = new AutomationFocusChangedEventHandler(FocusChanged);
    Automation.AddAutomationFocusChangedEventHandler(afceh);
}
private void FocusChanged(object sender, AutomationFocusChangedEventArgs e)
{

초점이 바뀌었을 때 AutomationElement 클래스의 정적 멤버인 FocusedElement를 이용하여 초점을 소유한 자동화 요소 개체를 참조합니다.

    AutomationElement ae = AutomationElement.FocusedElement;

만약 멤버 필드로 기억하고 있는 타겟 요소와 같으면 이벤트 처리기를 끝냅니다.

    if (ae == target_ae){    return;    }

만약 초점을 소유한 개체의 프로세스 ID가 현재 프로세스 ID와 같을 때도 이벤트 처리기를 끝냅니다.

    if (ae.Current.ProcessId == Process.GetCurrentProcess().Id){    return;    }

자동화 요소 개체가 활성화 상태인지 확인합니다.

    if (ae.Current.IsEnabled)
    {

활성화 상태이면 Value 패턴인지 확인합니다.

        object obj;
        if (ae.TryGetCurrentPattern(ValuePattern.Pattern, out obj))
        {

Value 패턴이면 타겟을 설정합니다. 이 부분은 크로스 스레드 문제가 발생할 수 있으므로 별도의 메서드를 만들게요.

            SetTarget(ae, obj);
        }

Value 패턴이 아니면 타겟 자동화 요소와 타겟 Value 패턴을 null로 설정합니다.

        else{    target_ae = null;    target = null;    }
    }

활성화 상태가 아니면 타겟 자동화 요소와 타겟 Value 패턴을 null로 설정합니다.

     else{    target_ae = null;    target = null;    }
}

SetTarget 메서드를 정의합시다. 이 부분에서는 크로스 스레드 문제가 발생할 수 있어서 이를 처리하기 위한 대리자를 정의합니다.

delegate void SetTargetDele(AutomationElement ae,object obj);
private void SetTarget(AutomationElement ae,object obj)
{

수행하고 있는 스레드가 폼을 소유하는 스레드인지 확인합니다.

    if(this.InvokeRequired)
    {

만약 폼을 소유하는 스레드가 아니면 Invoke 메서드를 호출하여 .NET 프레임워크를 통해 폼을 소유한 스레드가 SetTarget 메서드를 호출할 수 있게 합니다.

        this.Invoke(new SetTargetDele(SetTarget),new object[]{ae,obj});
    }
    else
    {

전달받은 타겟을 Value 패턴으로 참조 연산합니다.

        target = obj as ValuePattern;

타겟 자동화 개체도 설정합니다.

        target_ae = ae;

새로운 타겟을 설정하였으니 해당 개체에 한글 처리를 위한 한글 개체를 생성합니다.

        hangul = new Hangul();

타겟의 값을 가져와 한글 개체의 Text 속성에 설정합니다.

        hangul.Text = target.Current.Value;

정보를 표시할 레이블의 Text 속성도 설정합니다.

        lb_text.Text = hangul.Text;

래핑한 API 정적 클래스 WrapNative의 IsPress 메서드를 호출하여 CapsLock 버튼을 누른 상태인지 얻어와서 cb_cabs의 Checked 속성을 설정합니다.

        cb_cabs.Checked = WrapNative.IsPress(Keys.CapsLock);

이에 따라 키보드 UI 부분을 설정합니다. 이 부분은 ButtonSet 메서드로 구현합시다.

        ButtonSet();

이벤트 정보를 표시할 리스트 박스에 항목을 추가합니다.

        lbox_event.Items.Add(ae.Current.Name);
    }
}

키보드 버튼 상태에 따라 키보드 UI를 설정하는 ButtonSet 메서드를 정의합시다. 이 부분은 특별한 연산은 없지만 많은 키의 Text 속성을 설정하므로 작업량이 많습니다.

private void ButtonSet()
{
    if (is_press_shift)
    {

쉬프트 키를 눌렀을 때 버튼의 Text 속성을 설정합시다.

        btn_fpu.Text = "~";  btn_1.Text = "!";       btn_2.Text = "@";
        btn_3.Text = "#";     btn_4.Text = "$";      btn_5.Text = "%";
        btn_6.Text = "^";     btn_7.Text = "&&";   btn_8.Text = "*";
        btn_9.Text = "(";      btn_0.Text = ")";       btn_dash.Text = "_";
        btn_equal.Text = "+";  btn_bs.Text = "|";    btn_preb.Text = "{";
        btn_postb.Text = "}"; btn_semi.Text = ":"; btn_upper.Text = "\"";
        btn_comma.Text = "<"; btn_dot.Text = ">"; btn_slush.Text = "?";
    }
    else
    {

쉬프트 키를 누르지 않을 때의 버튼 Text 속성을 설정합시다.

        btn_fpu.Text = "`"; btn_1.Text = "1"; btn_2.Text = "2";
        btn_3.Text = "3";  btn_4.Text = "4";  btn_5.Text = "5";
        btn_6.Text = "6";  btn_7.Text = "7";  btn_8.Text = "8";
        btn_9.Text = "9";  btn_0.Text = "0";  btn_dash.Text = "-";
        btn_equal.Text = "=";   btn_bs.Text = "\\";
        btn_preb.Text = "[";  btn_postb.Text = "]";
        btn_semi.Text = ";";  btn_upper.Text = "'";
        btn_comma.Text = ","; btn_dot.Text = ".";
        btn_slush.Text = "/";
    }

한글 모드인지 여부에 따라서 키보드 UI를 설정하는 부분도 필요합니다.

    if (check)
    {
        if (is_press_shift)
        {

한글 모드에서 쉬프트 키를 눌렀을 때의 버튼 Text 속성을 설정하세요.

            btn_q.Text = "ㅃ"; btn_w.Text = "ㅉ"; btn_e.Text = "ㄸ";
            btn_r.Text = "ㄲ";  btn_t.Text = "ㅆ";  btn_o.Text = "ㅒ";
            btn_p.Text = "ㅖ";
        }
        else
        {

한글 모드에서 쉬프트 키를 누르지 않았을 때의 버튼 Text 속성을 설정하세요.

            btn_q.Text = "ㅂ"; btn_w.Text = "ㅈ"; btn_e.Text = "ㄷ";
            btn_r.Text = "ㄱ";  btn_t.Text = "ㅅ";  btn_o.Text = "ㅐ";
            btn_p.Text = "ㅔ";
        }

한글 모드에서 쉬프트 키에 관계없는 버튼의 Text 속성을 설정하세요.

        btn_y.Text = "ㅛ"; btn_u.Text = "ㅕ"; btn_i.Text = "ㅑ";
        btn_a.Text = "ㅁ"; btn_s.Text = "ㄴ"; btn_d.Text = "ㅇ";
        btn_f.Text = "ㄹ"; btn_g.Text = "ㅎ"; btn_h.Text = "ㅗ";
        btn_j.Text = "ㅓ"; btn_k.Text = "ㅏ"; btn_l.Text = "ㅣ";
        btn_z.Text = "ㅋ"; btn_x.Text = "ㅌ"; btn_c.Text = "ㅊ";
        btn_v.Text = "ㅍ"; btn_b.Text = "ㅠ"; btn_n.Text = "ㅜ";
        btn_m.Text = "ㅡ";
    }
    else
    {

한글 모드가 아닐 때의 처리입니다.

        if (is_press_shift ^ cb_cabs.Checked)
        {

쉬프트 키를 눌렀거나 CapsLock이 눌렀거나 둘 중에 하나가 참일 때의 처리입니다.

            btn_q.Text = "Q"; btn_w.Text = "W"; btn_e.Text = "E";
            btn_r.Text = "R";  btn_t.Text = "T";   btn_y.Text = "Y";
            btn_u.Text = "U"; btn_i.Text = "I";    btn_o.Text = "O";
            btn_p.Text = "P"; btn_a.Text = "A";  btn_s.Text = "S";
            btn_d.Text = "D"; btn_f.Text = "F";   btn_g.Text = "G";
            btn_h.Text = "H"; btn_j.Text = "J";    btn_k.Text = "K";
            btn_l.Text = "L";   btn_z.Text = "Z";  btn_x.Text = "X";
            btn_c.Text = "C"; btn_v.Text = "V";  btn_b.Text = "B";
            btn_n.Text = "N"; btn_m.Text = "M";
        }
        else
        {

쉬프트 키를 누르고 CapsLock도 눌러진 상태이거나 둘 다 누른 상태가 아닐 때의 처리입니다.

            btn_q.Text = "q"; btn_w.Text = "w"; btn_e.Text = "e";
            btn_r.Text = "r"; btn_t.Text = "t"; btn_y.Text = "y";
            btn_u.Text = "u"; btn_i.Text = "i"; btn_o.Text = "o";
            btn_p.Text = "p"; btn_a.Text = "a"; btn_s.Text = "s";
            btn_d.Text = "d"; btn_f.Text = "f"; btn_g.Text = "g";
            btn_h.Text = "h"; btn_j.Text = "j"; btn_k.Text = "k";
            btn_l.Text = "l"; btn_z.Text = "z"; btn_x.Text = "x";
            btn_c.Text = "c"; btn_v.Text = "v"; btn_b.Text = "b";
            btn_n.Text = "n"; btn_m.Text = "m";
        }
    }
}

cb_cabs 체크 박스의 체크 상태 변경 이벤트 핸들러를 등록하세요.

private void cb_cabs_CheckedChanged(object sender, EventArgs e)
{

체크 상태에 따라 체크 박스의 배경 색을 다르게 지정하여 쉽게 알 수 있게 합시다.

    if (cb_cabs.Checked){    cb_cabs.BackColor = Color.YellowGreen;    }
    else{    cb_cabs.BackColor = SystemColors.Control;    }
}

cb_with_shift 체크 박스의 체크 상태 변경 이벤트 핸들러를 등록하세요.

private void cb_with_shift_CheckedChanged(object sender, EventArgs e)
{

is_press_shift에 현재 체크 상태로 설정하고 키보드 버튼의 UI 정보를 설정합니다.

    is_press_shift = cb_with_shift.Checked;
    ButtonSet();
}

폼의 FormClosed 이벤트 핸들러를 등록합니다.

private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{

폼 Load할 때 등록했던 초점 변경 이벤트 핸들러를 해제합니다.

    Automation.RemoveAutomationFocusChangedEventHandler(afceh);
}

모든 버튼의 클릭 이벤트는 Button_Click으로 추가하세요.

private void Button_Click(object sender, EventArgs e)
{

먼저 어느 버튼인지 sender를 Button 형식으로 참조합니다.

    Button btn = sender as Button;

만약 값 패턴 개체인 target이 존재하는지 확인합니다.

    if (target != null)
    {

target이 존재하면 타겟에 초점을 설정합니다.

        target_ae.SetFocus();

내부 처리 속도로 SetFocus를 호출해도 바로 초점을 잡지 않아 약간의 시간을 지연합니다.

        Thread.Sleep(10);
    }

이제 버튼에 따라 처리를 합니다.

    switch (btn.Text)

    {

다음은 래핑한 API 정적 클래스인 WrapNative의 KeyClick 메서드를 호출하여 프로그램 방식으로 키 이벤트를 발생할 키에 관한 처리입니다.

        case "Esc": WrapNative.KeyClick(Keys.Escape, is_press_shift); break;
        case "F1": WrapNative.KeyClick(Keys.F1, is_press_shift); break;
        case "F2": WrapNative.KeyClick(Keys.F2, is_press_shift); break;
        case "F3": WrapNative.KeyClick(Keys.F3, is_press_shift); break;
        case "F4": WrapNative.KeyClick(Keys.F4, is_press_shift); break;
        case "F5": WrapNative.KeyClick(Keys.F5, is_press_shift); break;
        case "F6": WrapNative.KeyClick(Keys.F6, is_press_shift); break;
        case "F7": WrapNative.KeyClick(Keys.F7, is_press_shift); break;
        case "F8": WrapNative.KeyClick(Keys.F8, is_press_shift); break;
        case "F9": WrapNative.KeyClick(Keys.F9, is_press_shift); break;
        case "F10": WrapNative.KeyClick(Keys.F10, is_press_shift); break;
        case "F11": WrapNative.KeyClick(Keys.F11, is_press_shift); break;
        case "F12": WrapNative.KeyClick(Keys.F12, is_press_shift); break;
        case "Tab": WrapNative.KeyClick(Keys.Tab, is_press_shift); break;
        case "Shift": WrapNative.KeyClick(Keys.ShiftKey, is_press_shift); break;
        case "Enter": WrapNative.KeyClick(Keys.Enter, is_press_shift); break;
        case "Caps": WrapNative.KeyClick(Keys.CapsLock, is_press_shift);

캡션 키를 눌렀을 때는 체크 박스의 체크 상태를 토글합니다.

            cb_cabs.Checked ^= true;

캡션에 따라 키보드 UI 인터페이스의 내용이 바뀌어야 하므로 ButtonSet 메서드를 호출합니다.

            ButtonSet();    break;
        case "Ctrl": WrapNative.KeyClick(Keys.ControlKey, is_press_shift); break;
        case "Alt": WrapNative.KeyClick(Keys.Alt, is_press_shift); break;
        case "한/영 변환":

한/영 변환 버튼을 눌렀을 때도 한글 모드를 기억하는 check 멤버를 토클합니다.

            check ^= true;

그리고 키보드 UI 인터페이스의 내용을 바꾸어야 하므로 ButtonSet 메서드를 호출합니다.

            ButtonSet();     break;

Space 버튼을 누렀을 때는 별도의 메서드를 통해 문자를 누른 것처럼 처리할 PressKey 메서드를 이용하여 처리할게요.

         case "Space": PressKey(' '); break;

<- 버튼을 눌렀을 때도 PressKey 메서드를 호출합시다.

         case "<-": PressKey('\b'); break;

나머지 키는 버튼의 Text 속성의 첫번째 문자를 인자로 PressKey 메서드를 호출합니다.

        default: PressKey(btn.Text[0]); break;

    }

키보드 이벤트를 보여줄 리스트 박스에 항목을 추가합니다.

    lbox_event.Items.Add(btn.Text);

그리고 추가한 항목을 화면에 보일 수 있게 선택 항목으로 설정합니다.

    lbox_event.SelectedIndex = lbox_event.Items.Count - 1;

래핑한 API 정적 클래스 WrapNative의 KeyClick 메서드를 이용해 End 키를 누른 이벤트를 발생합니다. 이를 통해 캐럿이 맨 뒤로 이동합니다.

    WrapNative.KeyClick(Keys.End,is_press_shift);
}
private void PressKey(char ch)
{

만약 타겟 개체가 없으면 메서드를 종료합니다.

    if (target == null){    return;    }

만약 한글 모드이면 hangul 개체의 Input 메서드를 호출합니다.

    if (check){    hangul.Input(ch);    }

그렇지 않다면 한글 개체의 InputNoKorea 메서드를 호출합니다.

    else{    hangul.InputNoKorea(ch);    }

정보를 표시할 레이블의 Text 속성을 한글의 Text 속성으로 설정합니다.

    lb_text.Text = hangul.Text;

Value 패턴인 타겟의 SetValue 메서드를 이용하여 한글의 Text 속성으로 설정합니다.

    target.SetValue(hangul.Text);
}
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using EHHangulLibrary;
using System.Windows.Automation;
using System.Collections.Generic;
using System.Diagnostics;

namespace DemoKeyEvent
{
    public partial class Form1 : Form
    {
        bool is_press_shift;
        bool check;

        AutomationFocusChangedEventHandler afceh;
        AutomationElement target_ae;
        ValuePattern target;
        Hangul hangul;
        
        public Form1()
        {
            InitializeComponent();
        }
        private void MainForm_Load(object sender, EventArgs e)
        {
            afceh = new AutomationFocusChangedEventHandler(FocusChanged);
            Automation.AddAutomationFocusChangedEventHandler(afceh);
        }
        private void FocusChanged(object sender, AutomationFocusChangedEventArgs e)
        {
            AutomationElement ae = AutomationElement.FocusedElement;
            if (ae == target_ae){    return;    }
            if (ae.Current.ProcessId == Process.GetCurrentProcess().Id)
            {
                return;
            }
            if (ae.Current.IsEnabled)
            {
                object obj;
                if (ae.TryGetCurrentPattern(ValuePattern.Pattern, out obj))
                {
                    SetTarget(ae, obj);
                }
                else
                {
                    target_ae = null;
                    target = null;
                }
            }
            else
            {
                target_ae = null;
                target = null;
            }
        }
        delegate void SetTargetDele(AutomationElement ae, object obj);
        private void SetTarget(AutomationElement ae, object obj)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new SetTargetDele(SetTarget), new object[] { ae, obj });
            }
            else
            {
                target = obj as ValuePattern;
                target_ae = ae;
                hangul = new Hangul();
                hangul.Text = target.Current.Value;
                lb_text.Text = hangul.Text;
                cb_cabs.Checked = WrapNative.IsPress(Keys.CapsLock);
                ButtonSet();
                lbox_event.Items.Add(ae.Current.Name);
            }
        }
        private void ButtonSet()
        {
            if (is_press_shift)
            {
                btn_fpu.Text = "~"; btn_1.Text = "!"; btn_2.Text = "@";
                btn_3.Text = "#"; btn_4.Text = "$"; btn_5.Text = "%";
                btn_6.Text = "^"; btn_7.Text = "&&"; btn_8.Text = "*";
                btn_9.Text = "("; btn_0.Text = ")"; btn_dash.Text = "_";
                btn_equal.Text = "+"; btn_bs.Text = "|"; btn_preb.Text = "{";
                btn_postb.Text = "}"; btn_semi.Text = ":"; btn_upper.Text = "\"";
                btn_comma.Text = "<"; btn_dot.Text = ">"; btn_slush.Text = "?";
            }
            else
            {
                btn_fpu.Text = "`"; btn_1.Text = "1"; btn_2.Text = "2";
                btn_3.Text = "3"; btn_4.Text = "4"; btn_5.Text = "5";
                btn_6.Text = "6"; btn_7.Text = "7"; btn_8.Text = "8";
                btn_9.Text = "9"; btn_0.Text = "0"; btn_dash.Text = "-";
                btn_equal.Text = "="; btn_bs.Text = "\\";
                btn_preb.Text = "["; btn_postb.Text = "]";
                btn_semi.Text = ";"; btn_upper.Text = "'";
                btn_comma.Text = ","; btn_dot.Text = ".";
                btn_slush.Text = "/";
            }
            if (check)
            {
                if (is_press_shift)
                {
                    btn_q.Text = "ㅃ"; btn_w.Text = "ㅉ"; btn_e.Text = "ㄸ";
                    btn_r.Text = "ㄲ"; btn_t.Text = "ㅆ"; btn_o.Text = "ㅒ";
                    btn_p.Text = "ㅖ";
                }
                else
                {
                    btn_q.Text = "ㅂ"; btn_w.Text = "ㅈ"; btn_e.Text = "ㄷ";
                    btn_r.Text = "ㄱ"; btn_t.Text = "ㅅ"; btn_o.Text = "ㅐ";
                    btn_p.Text = "ㅔ";
                }
                btn_y.Text = "ㅛ"; btn_u.Text = "ㅕ"; btn_i.Text = "ㅑ";
                btn_a.Text = "ㅁ"; btn_s.Text = "ㄴ"; btn_d.Text = "ㅇ";
                btn_f.Text = "ㄹ"; btn_g.Text = "ㅎ"; btn_h.Text = "ㅗ";
                btn_j.Text = "ㅓ"; btn_k.Text = "ㅏ"; btn_l.Text = "ㅣ";
                btn_z.Text = "ㅋ"; btn_x.Text = "ㅌ"; btn_c.Text = "ㅊ";
                btn_v.Text = "ㅍ"; btn_b.Text = "ㅠ"; btn_n.Text = "ㅜ";
                btn_m.Text = "ㅡ";
            }
            else
            {
                if (is_press_shift ^ cb_cabs.Checked)
                {
                    btn_q.Text = "Q"; btn_w.Text = "W"; btn_e.Text = "E";
                    btn_r.Text = "R"; btn_t.Text = "T"; btn_y.Text = "Y";
                    btn_u.Text = "U"; btn_i.Text = "I"; btn_o.Text = "O";
                    btn_p.Text = "P"; btn_a.Text = "A"; btn_s.Text = "S";
                    btn_d.Text = "D"; btn_f.Text = "F"; btn_g.Text = "G";
                    btn_h.Text = "H"; btn_j.Text = "J"; btn_k.Text = "K";
                    btn_l.Text = "L"; btn_z.Text = "Z"; btn_x.Text = "X";
                    btn_c.Text = "C"; btn_v.Text = "V"; btn_b.Text = "B";
                    btn_n.Text = "N"; btn_m.Text = "M";
                }
                else
                {
                    btn_q.Text = "q"; btn_w.Text = "w"; btn_e.Text = "e";
                    btn_r.Text = "r"; btn_t.Text = "t"; btn_y.Text = "y";
                    btn_u.Text = "u"; btn_i.Text = "i"; btn_o.Text = "o";
                    btn_p.Text = "p"; btn_a.Text = "a"; btn_s.Text = "s";
                    btn_d.Text = "d"; btn_f.Text = "f"; btn_g.Text = "g";
                    btn_h.Text = "h"; btn_j.Text = "j"; btn_k.Text = "k";
                    btn_l.Text = "l"; btn_z.Text = "z"; btn_x.Text = "x";
                    btn_c.Text = "c"; btn_v.Text = "v"; btn_b.Text = "b";
                    btn_n.Text = "n"; btn_m.Text = "m";
                }
            }
        }


        private void cb_cabs_CheckedChanged(object sender, EventArgs e)
        {
            if (cb_cabs.Checked)
            {
                cb_cabs.BackColor = Color.YellowGreen;
            }
            else
            {
                cb_cabs.BackColor = SystemColors.Control;
            }
        }
        private void cb_with_shift_CheckedChanged(object sender, EventArgs e)
        {
            is_press_shift = cb_with_shift.Checked;
            ButtonSet();
        }
        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            Automation.RemoveAutomationFocusChangedEventHandler(afceh);
        }
        private void Button_Click(object sender, EventArgs e)
        {            
            Button btn = sender as Button;
            if (target != null)
            {
                target_ae.SetFocus();
                Thread.Sleep(10);
            }
            switch (btn.Text)
            {
                case "Esc": WrapNative.KeyClick(Keys.Escape, is_press_shift); break;
                case "F1": WrapNative.KeyClick(Keys.F1, is_press_shift); break;
                case "F2": WrapNative.KeyClick(Keys.F2, is_press_shift); break;
                case "F3": WrapNative.KeyClick(Keys.F3, is_press_shift); break;
                case "F4": WrapNative.KeyClick(Keys.F4, is_press_shift); break;
                case "F5": WrapNative.KeyClick(Keys.F5, is_press_shift); break;
                case "F6": WrapNative.KeyClick(Keys.F6, is_press_shift); break;
                case "F7": WrapNative.KeyClick(Keys.F7, is_press_shift); break;
                case "F8": WrapNative.KeyClick(Keys.F8, is_press_shift); break;
                case "F9": WrapNative.KeyClick(Keys.F9, is_press_shift); break;
                case "F10": WrapNative.KeyClick(Keys.F10, is_press_shift); break;
                case "F11": WrapNative.KeyClick(Keys.F11, is_press_shift); break;
                case "F12": WrapNative.KeyClick(Keys.F12, is_press_shift); break;                
                case "Tab": WrapNative.KeyClick(Keys.Tab, is_press_shift); break;                
                case "Shift": WrapNative.KeyClick(Keys.ShiftKey, is_press_shift); break;
                case "Enter": WrapNative.KeyClick(Keys.Enter, is_press_shift); break;
                case "Caps": WrapNative.KeyClick(Keys.CapsLock, is_press_shift);
                    cb_cabs.Checked ^= true;
                    ButtonSet();    break;
                case "Ctrl": WrapNative.KeyClick(Keys.ControlKey, is_press_shift); break;
                case "Alt": WrapNative.KeyClick(Keys.Alt, is_press_shift); break;                
                case "한/영 변환":  check ^= true;    ButtonSet();        break;                
                case "Space": PressKey(' '); break;
                case "<-": PressKey('\b'); break;
                default: PressKey(btn.Text[0]); break;
            }
            lbox_event.Items.Add(btn.Text);
            lbox_event.SelectedIndex = lbox_event.Items.Count - 1;
            WrapNative.KeyClick(Keys.End,is_press_shift);
        }
        private void PressKey(char ch)
        {
            if (target == null){    return;    }
            if (check){    hangul.Input(ch);    }
            else
            {
                hangul.InputNoKorea(ch);
            }
            lb_text.Text = hangul.Text;
            target.SetValue(hangul.Text);            
        }
    }
}

[소스 8.12] Form1.cs