Tutorial 22: SuperClassing
이 번장에서는 슈퍼 클래싱이 무엇이며 그 사용법에 대해서 설명한다. 또한 탭 콘트롤에 대해서도 설명한다.
소스 | 실행 결과 |
|
오랜시간 프로그램을 작성하다 보면, 자신이 필요한 용도의 컨트롤이 필요하게 된다는 것을 느끼게 될 것이다. 예를 들면, 숫자만 입력할 수 있는 10개의 에디트 컨트롤이 필요한 경우등이다, 이럴경우 해결책으로는 몇가지가 있다.
- 자신만의 윈도우 클래스를 생성해서, 컨트롤을 인스턴스화한다.
- 에디트 컨트롤을 생성하고, 그들을 모두 서브 클래싱해서 사용한다.
- 에디트 컨트롤을 슈퍼 클래싱한다
첫번째 방법은 너무나 번거로운 방법이다. 프로그래머가 모든 기능을 구현해야 하기 때문이다.두번째 방법은, 편하기는 하지만, 그래도 여전히 번거롭다. 서브 클래싱해야 하는 컨트롤의 수가 적다면, 이것이 해결책이 되지만 여러개의 컨트롤을 서브클래싱하는 것은 효율적이지 않다. 이와 같은 경우는 슈퍼 클래싱이 아주 유용할 것이다.
슈퍼 클래싱은 특정의 윈도우 클래스를 「변경한다」는 방법이다. 「변경한다」 의미는, 어떤 목적을 위해서 윈도우 클래스의 속성을 변경해서, 컨트롤을 생성한다는 뜻이다.
슈퍼 클래싱의 순서는 다음과 같다.
- GetClassInfoEx 함수를 호출해서 슈퍼 클래싱하고 싶은 윈도우의 클래스정보를 얻는다. GetClassInfoEx 함수가 성공적으로 수행되면, 해당 윈도우 클래스에 관한 정보를 설정한 WNDCLASSEX 구조체의 포인터를 반환한다.
- WNDCLASSEX 멤버를 변경한다. 하지만, 변경하는 것은 2개의 멤버 뿐이다.
- hInstance
프로그램의 인스턴스 핸들을 지정한다- lpszClassName
새로운 클래스명의 포인터를 지정한다
lpfnWndProc 멤버는 변경하지 않지만, 대부분, 변경할 필요가 있다. 단지, CallWindowProc 함수로 원래의 윈도우 프로시저를 호출할려면, 변경 전의 원래의 lpfnWndProc의 값을 저장해두어야만 한다.- WNDCLASSEX 구조체를 등록하고, 새로운 윈도우 클래스를 얻는다.
- 새로운 윈도우 클래스로부터 윈도우를 생성한다.
슈퍼 클래싱은 같은 속성의 컨트롤을 여러개 생성해야 할 경우 서브클래싱보다 편리한 방법이다. 곧바로 예제를 살펴보면 알 수 있을 것이다.
|
|
|
이 프로그램은 작업영역에 6개의 「사용자 정의된」에디트 컨트롤을 포함한 단순한 형태의 윈도우를 생성한다. 에디트 컨트롤은 16진수 문자만을 입력 받는 기능을 한다. 실제로, 서브 클래싱의 예제를 슈퍼 클래싱용으로 변경한 것이다. 일반적인 프로그램과 같지만, 흥미로운 부분은, 메인 윈도우가 생성되는 곳이다.
.if uMsg==WM_CREATE mov wc.cbSize, sizeof WNDCLASSEX invoke GetClassInfoEx, NULL, addr EditClass, addr wc먼저, 슈퍼 클래싱하고 싶은 클래스의 데이터로 WNDCLASSEX 구조체를 설정해야 한다. 예제는, EDIT 클래스가 된다. GetClassInfoEx 함수를 호출하기 전에 WNDCLASSEX의 멤버변수 cbSize를 설정하는것을 잊지 말기 바란다. 그렇지 않으면, WNDCLASSEX 구조체의 데이터를 설정할 수 없다. GetClassInfoEx 함수로돌아와서, 변수 wc에는 새로운 윈도우 클래스를 생성하는데 필요한 정보가 설정되어 있다.
push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName, OFFSET OurClasswc의 멤버중에서 변경해야 하는 것이 있다. 가장 먼저 윈도우 프로시저의 포인터다. 원래의 윈도우 프로시저가 필요 하기때문에, 원래의 윈도우 프로시저의 포인터를 저장해 두고,, CallWindowProc 함수를 사용해서 원래의 윈도우 프로시저를 호출할 수 있다. 이 테크닉은 SetWindowLong 함수를 호출하지 않고도 직접 WNDCLASSEX 구조체를 수정한다는 것을 제외하면, 서브 클래싱때에 사용한 테크닉과 같은 것이다.
다음으로 2개의 멤버 hInstance 와 lpsClassName는 반드시 변경해야 한다.변경하지 않게되면 새로운 윈도우 클래스를 등록할 수 없게된다. 그렇기 때문에 자신의 프로그램의 hInstance와 새로운 클래스명을 변경한다.
invoke RegisterClassEx, addr wc이제 준비가 되었으므로, 새로운 윈도우 클래스를 등록하고, 새로운 클래스를 얻는다.
xor ebx, ebx mov edi, 20 .while ebx<6 invoke CreateWindowEx, WS_EX_CLIENTEDGE, ADDR OurClass, NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER, 20,\ edi, 300,25, hWnd, ebx,\ hInstance, NULL mov dword ptr [hwndEdit+4*ebx], eax add edi, 25 inc ebx .endw invoke SetFocus, hwndEdit클래스를 등록하고, 그 클래스를 기본으로해서 윈도우를 생성한다. 위의 코드는, ebx 레지스터를 생성하는 윈도우의 숫자를 세기 위한 용도로 사용하고 있다. edi 레지스터는 윈도우의 y좌표로서 사용되고 있고, 윈도우가 생성될때, 핸들은 배열에 저장된다. 모든 윈도우가 생성되면, 가장 처음의 윈도우에 포커스를 설정 한다.
이 시점에서, 16 진수 문자만을 받아들이는 에디트 컨트롤을 6개 생성하고, 윈도우 프로시저에서는 유저의 입력을 필터링 하고 있다. 실제로, 서브 클래싱의 예제와 같은 윈도우 프로시저로서, 서브 클래싱하는 것보다 더 많은 코드를 기록해야하는 일은 없다.
예제에서는, 좀더 변화를 주기위해, 탭컨트롤을 사용하고 있다. 보통, 다이얼로그 박스에 컨트롤을 배치할 때, 다이얼로그 박스 매니저는 유저가 탭키를 누르면, 다음 컨트롤로 포커스를 이동시키고, 쉬프트+탭키를 누르면, 이전의 컨트롤로 포커스를 옮겨 준다. 하지만 이런 기능은 표준 윈도우에 있는 컨트롤에서만 이용할 수 있는 것이다. 그렇기 때문에, 서브 클래싱을 했을 경우 이와 같은 처리를 직접해주어야 한다.
.elseif al==VK_TAB invoke GetKeyState, VK_SHIFT test eax, 80000000 .if ZERO? invoke GetWindow, hEdit, GW_HWNDNEXT .if eax==NULL invoke GetWindow, hEdit, GW_HWNDFIRST .endif .else invoke GetWindow, hEdit, GW_HWNDPREV .if eax==NULL invoke GetWindow, hEdit, GW_HWNDLAST .endif .endif invoke SetFocus, eax xor eax, eax ret위의 코드는, EditWndProc 프로시저로부터, 유저가 탭 키를 눌렀는지를 체크하고, 눌렀다면 GetKeyState 함수로 쉬프트 키가 눌려진지도 체크하고있다. GetKeyState 함수는 지정된 키가 눌려져 있는지를 검사해서 결과를 eax 레지스터에 반환한다. 만약 키가 눌렸다면, eax 레지스터의 상위비트가 설정 되고,만약 눌려지지 않았다면, 상위 비트가 클리어되어있게 된다. 그렇기 때문에 test명령어를 사용해서, 80000000 h와 비교하고 있다. 만약 상위 비트가 설정되면, 쉬프트+탭키가 눌려진 경우이고, 이경우는 다른 처리가 필요하게 된다.(이전 컨트롤로 이동)
유저가 탭 키만 눌렀을 경우, GetWindow 함수로 다음 컨트롤의 핸들을 얻는다.이때, 현재 에디트 컨트롤 hEdit 로부터 다음의 윈도우를 얻을수 있도록 하기위해, GW_HWNDNEXT 플래그를 사용해서 GetWindow 함수를 호출한다. 이 함수가 NULL을 반환하면, 현재 컨트롤이 가장 마지막 컨트롤이란 의미이고, GetWindow 함수를 GW_HWNDFIRST 플래그를 지정해서 호출함으로써 처음 컨트롤을 얻는다. 쉬프트+탭의 경우는, 탭 키가 눌렸을때의 반대로 작동하므로, 같은 것이다.