Microsoft MVP성태의 닷넷 이야기
Windows: 234. IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지 [링크 복사], [링크+제목 복사],
조회: 13815
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 21개 있습니다.)
Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법
; https://www.sysnet.pe.kr/2/0/13284

Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법
; https://www.sysnet.pe.kr/2/0/13285

Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
; https://www.sysnet.pe.kr/2/0/13286

Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법
; https://www.sysnet.pe.kr/2/0/13287

Windows: 230. Win32 - 대화창의 DLU 단위를 pixel로 변경하는 방법
; https://www.sysnet.pe.kr/2/0/13288

Windows: 231. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법
; https://www.sysnet.pe.kr/2/0/13289

Windows: 232. C/C++ - 일반 창에도 사용 가능한 IsDialogMessage
; https://www.sysnet.pe.kr/2/0/13292

Windows: 233.  Win32 - modeless 대화창을 modal처럼 동작하게 만드는 방법
; https://www.sysnet.pe.kr/2/0/13295

Windows: 234. IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지
; https://www.sysnet.pe.kr/2/0/13296

Windows: 235. Win32 - Code Modal과 UI Modal
; https://www.sysnet.pe.kr/2/0/13297

Windows: 237. Win32 - 모든 메시지 루프를 탈출하는 WM_QUIT 메시지
; https://www.sysnet.pe.kr/2/0/13299

Windows: 238. Win32 - Modal UI 창에 올바른 Owner(HWND)를 설정해야 하는 이유
; https://www.sysnet.pe.kr/2/0/13300

Windows: 242. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (쉬운 버전)
; https://www.sysnet.pe.kr/2/0/13305

Windows: 243. Win32 - 윈도우(cbWndExtra) 및 윈도우 클래스(cbClsExtra) 저장소 사용 방법
; https://www.sysnet.pe.kr/2/0/13306

Windows: 244. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (개선된 버전)
; https://www.sysnet.pe.kr/2/0/13312

Windows: 245. Win32 - 시간 만료를 갖는 컨텍스트 메뉴와 윈도우 메시지의 영역별 정의
; https://www.sysnet.pe.kr/2/0/13315

Windows: 246. Win32 C/C++ - 직접 띄운 대화창 템플릿을 위한 Modal 메시지 루프 생성
; https://www.sysnet.pe.kr/2/0/13329

Windows: 247. Win32 C/C++ - CS_GLOBALCLASS 설명
; https://www.sysnet.pe.kr/2/0/13330

Windows: 248. Win32 C/C++ - 대화창을 위한 메시지 루프 사용자 정의
; https://www.sysnet.pe.kr/2/0/13332

Windows: 249. Win32 C/C++ - 대화창 템플릿을 런타임에 코딩해서 사용
; https://www.sysnet.pe.kr/2/0/13333

Windows: 250. Win32 C/C++ - Modal 메시지 루프 내에서 SetWindowsHookEx를 이용한 Thread 메시지 처리 방법
; https://www.sysnet.pe.kr/2/0/13334




IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지

이전 글에서,

C/C++ - 일반 창에도 사용 가능한 IsDialogMessage
; https://www.sysnet.pe.kr/2/0/13292

IsDialogMessage API에 대한 설명을 했는데요, 사실 그 API가 메시지 루프에서 불리는 것만으로는 대화창의 부가 기능을 매끄럽게 구현할 수 없습니다. 왜냐하면, 대화창 내의 컨트롤들이 어떤 부가 기능을 가지고 있는지 알아야만 하기 때문입니다.

"C/C++ - 일반 창에도 사용 가능한 IsDialogMessage" 글에서도 설명했지만, 해당 API는 대화창에 TAB, ENTER/ESC, 화살표 키 등의 동작을 컨트롤과 연동합니다. 그뿐만이 아닙니다. 대화창은 Edit Control의 경우 입력 포커스를 받을 때마다 입력된 텍스트를 모두 선택하는 부가 기능이 있습니다. 예를 들어, 아래의 화면은 Edit 컨트롤에 "test"를 입력 후 TAB 키를 눌러 버튼으로 포커스를 옮겼다가, 다시 TAB 키를 눌러 Edit 컨트롤에 포커스가 간 경우를 보여줍니다.

dlgcode_1.png

보다시피, "test" 내용이 모두 선택돼 있습니다. 문제는, 그렇게 추가된 부가 기능을 때로는 사용자가 원하지 않을 수도 있다는 점입니다. 일례로, 위의 대화창에서처럼 Edit 컨트롤이 포커스를 받았을 때 텍스트가 선택되지 않게 만들고 싶다면 어떻게 해야 할까요?

바로 그런 기능을 위해 컨트롤이 구현하게 되는 코드가 WM_GETDLGCODE 메시지에 대한 반환값입니다. (WM_GETDLGCODE와 IsDialogMessage를 동일하게 Windows 2000 운영체제부터 지원하게 된 것은 우연이 아닙니다.) 기본적으로 Windows 2000 이후의 기본 컨트롤들은 WM_GETDLGCODE에 대한 반환값이 그들만의 Window Procedure에 이미 구현돼 있습니다.

그러니까, (대화창에 있는) Edit 컨트롤의 경우 입력 포커스를 받았을 때 텍스트를 선택하도록 DLGC_HASSETSEL 값이 설정돼 있는데요, 정말 그런지 테스트를 해볼까요? ^^ 지난번처럼 비주얼 스튜디오로 만든 C++ Windows Application 프로젝트의 기본 대화상자를 다음과 같이 변경한 후,

// resource.h
// #define IDC_EDIT1 110

IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About Project1"
FONT 8, "MS Shell Dlg"
BEGIN
    ICON            IDR_MAINFRAME,IDC_STATIC,14,14,21,20
    LTEXT           "Project1, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX
    EDITTEXT        IDC_EDIT1, 38, 24, 114, 15
    DEFPUSHBUTTON   "OK",IDOK,113,41,50,14,WS_GROUP
END

간단하게 SendMessage를 대상 컨트롤에 전달 후 반환값을 보면,

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        {
            int result = ::SendMessage(GetDlgItem(hDlg, IDC_EDIT1), WM_GETDLGCODE, 0, 0);
            if ((result & DLGC_HASSETSEL) == DLGC_HASSETSEL)
            {
                ::MessageBox(hDlg, L"DLGC_HASSETSEL", L"DLGC_HASSETSEL", MB_OK);
            }
        }

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

DLGC_HASSETSEL 값이 설정된 것을 볼 수 있습니다. 그렇다면 ^^ 이 기능을 제거하는 것도 가능할 텐데요, 이에 대해서는 다음의 글에서 자세하게 설명하고 있습니다.

Preventing edit control text from being autoselected in a dialog box
; https://devblogs.microsoft.com/oldnewthing/20031114-00/?p=41823

따라서 우리도 Edit 컨트롤의 Window Procdefure를 subclassing하는 코드를 다음과 같이 구현해 주면,

#include <CommCtrl.h>
#pragma comment(lib, "Comctl32.lib")

// ...[생략]...

LRESULT CALLBACK RemoveHasSetSelSubclassProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uiMsg) {
    
    case WM_NCDESTROY:
        RemoveWindowSubclass(hwnd, RemoveHasSetSelSubclassProc, uIdSubclass);
        break;
        
    case WM_GETDLGCODE:
        return DefSubclassProc(hwnd, uiMsg, wParam, lParam)
            & ~DLGC_HASSETSEL;
    }
    return DefSubclassProc(hwnd, uiMsg, wParam, lParam);
}

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        // How unique must the uIdSubclass parameter be when I call Set­Window­Subclass?
        SetWindowSubclass(GetDlgItem(hDlg, IDC_EDIT1), RemoveHasSetSelSubclassProc, 0, 0);
        return (INT_PTR)TRUE;

    // ...[생략]...
    }
    return (INT_PTR)FALSE;
}

이후부터는, Edit 컨트롤이 TAB 키에 의해 입력 포커스를 받아도 입력해 두었던 텍스트가 선택되는 현상은 발생하지 않습니다.




또 다른 글을 통해,

Other tricks with WM_GETDLGCODE
; https://devblogs.microsoft.com/oldnewthing/20031126-00/?p=41703

Microsoft KB 83302
; https://jeffpar.github.io/kbarchive/kb/083/Q83302/

DLGC_WANTMESSAGE 값에 대한 사용법을 볼 수 있습니다. 예를 들어, 대화창에서는 ESC 키를 누르면 대화창이 닫히게 되는데요, 만약 Edit 컨트롤에 입력 포커스가 있는 경우 그 동작을 막고 싶다면 다음과 같이 구현할 수 있습니다.

LRESULT CALLBACK PreventESCKeySubclassProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uiMsg) {

    case WM_NCDESTROY:
        RemoveWindowSubclass(hwnd, PreventESCKeySubclassProc, uIdSubclass);
        break;

    case WM_GETDLGCODE:
        {
            LRESULT lRet = DefSubclassProc(hwnd, uiMsg, wParam, lParam);
            if (lParam)
            {
                LPMSG lpmsg = (LPMSG)lParam;
                if (lpmsg->wParam == VK_ESCAPE)
                {
                    lRet |= DLGC_WANTMESSAGE;
                }
            }

            return lRet;
        }
    }
    return DefSubclassProc(hwnd, uiMsg, wParam, lParam);
}

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        {
            SetWindowSubclass(GetDlgItem(hDlg, IDC_EDIT1), PreventESCKeySubclassProc, 0, 0);
            return (INT_PTR)TRUE;
        }

    // ...[생략]...
    }
    return (INT_PTR)FALSE;
}


즉, 대화창의 부가 기능으로 제공되는 어떤 키도 필터링할 수 있는데요, 자주 쓸만한 키 같은 경우에는 저렇게 어렵게 구현하는 대신 DLGC_WANTCHARS, DLGC_WANTTAB, DLGC_WANTARROWS 플래그를 제공하고 있으니 사용하면 됩니다. (따지고 보면, 이러한 3가지 플래그들은 DLGC_WANTMESSAGE의 특수한 사례에 속합니다.)

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




그렇다면, 저러한 동작들이 C#으로 만든 Windows Forms에서는 어떨까요? ^^ 정답 먼저 말하면, 윈폼 환경에서는 WM_GETDLGCODE 메시지가 아무런 역할도 하지 않습니다. 그 이유가 뭘까요? ^^

왜냐하면, WM_GETDLGCODE 기능은 IsDialogMessage와의 협업으로 이뤄지는 것인데, Windows Forms의 경우에는 메시지 루프에서 IsDialogMessage를 호출하지 않고 독자적으로 편의 기능을 제공하고 있기 때문입니다.




DLGC_BUTTON 0x2000 Button.
DLGC_DEFPUSHBUTTON 0x0010 Default push button. 
DLGC_HASSETSEL 0x0008 EM_SETSEL messages.
DLGC_RADIOBUTTON 0x0040 Radio button.
DLGC_STATIC 0x0100 Static control.
DLGC_UNDEFPUSHBUTTON 0x0020 Non-default push button.
DLGC_WANTALLKEYS 0x0004 All keyboard input.
DLGC_WANTARROWS 0x0001 Direction keys.
DLGC_WANTCHARS 0x0080 WM_CHAR messages.
DLGC_WANTMESSAGE 0x0004 All keyboard input (the application passes this message in the MSG structure to the control).
DLGC_WANTTAB 0x0002 TAB key.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/26/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2023-04-26 09시33분
The WM_GETDLGCODE message is a query message and should not modify state
; https://devblogs.microsoft.com/oldnewthing/20230329-00/?p=107983
정성태

... 121  122  123  124  125  [126]  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10806정성태6/16/201542354.NET Framework: 515. OpenAuth.VerifyAuthentication 호출 시 The remote server returned an error: (400) Bad Request
10805정성태6/15/201524173Java: 17. 자바의 재미있는 상수 처리 방식
10804정성태6/10/201524179.NET Framework: 514. .NET CLR2 보안 모델에서의 APTCA 역할 (2)파일 다운로드1
10803정성태6/2/201526123.NET Framework: 513. UWP(Universal Windows Platform) 응용 프로그램의 새로운 라이브러리 버전 관리 해법 [2]
10802정성태6/2/201525406개발 환경 구성: 269. 마이크로소프트 온라인 강좌 소개 - Azure VPN 구성 방법 [1]
10801정성태5/31/201530538.NET Framework: 512. async/await 사용 시 hang 문제가 발생하는 경우 - 두 번째 이야기 [3]
10800정성태5/29/201526021개발 환경 구성: 268. 소개 - 프로세싱(https://processing.org/)
10799정성태5/29/201523554사물인터넷: 3. 책 소개 - 라즈베리 파이로 구현하는 사물 인터넷 프로젝트 [1]
10798정성태5/26/201523600기타: 53. 2015년 6월 10일 밤 10시 온라인 세미나 - 새로운 Windows 10 App을 개발하는 방법
10797정성태5/23/201523249VC++: 91. 자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드파일 다운로드1
10796정성태5/23/201534045오류 유형: 293. SQL Server Management Studio 실행 시 "Cannot find one or more components" 오류
10795정성태5/23/201532206오류 유형: 292. InstallUtil로 .NET 서비스 등록 시 오류 - Operation is not supported. (Exception from HRESULT: 0x80131515). [3]
10794정성태5/22/201527153개발 환경 구성: 267. (무료) 마이크로소프트 온라인 강좌 소개 - 네트워킹 기초 [1]
2925정성태5/14/201526448디버깅 기술: 73. PDB 기호 파일의 경로 구성 방식파일 다운로드1
2924정성태5/14/201529898VS.NET IDE: 100. 비주얼 스튜디오 원격 디버깅 시 'Unknown function' 콜스택이 나온다면?
2923정성태5/12/201589317기타: 52. 도서: 시작하세요! C# 6.0 프로그래밍: 기본 문법부터 실전 예제까지 [17]
2922정성태5/12/201525619오류 유형: 291. ssindex.cmd 실행 시 '...[tfs_collection_url]...' not found in srcsrv.ini 오류 발생
2921정성태5/9/201532352개발 환경 구성: 266. 인텔에서 구현한 최대 절전 모드 기능 - Intel® Rapid Start Technology
2920정성태5/9/201523318오류 유형: 290. 디스크 관리자의 파티션 축소 시, There is not enough space available on the disk(s) to complete this operation.
2919정성태5/9/201523286오류 유형: 289. Error: this template attempted to load component assembly 'NuGet.VisualStudio.Interop, ...'
2918정성태5/9/201541795Windows: 111. 복구(Recovery) 파티션 삭제하는 방법 [3]
2917정성태5/9/201532430오류 유형: 288. .NET Framework 4.6이 설치된 경우 "Intel® Rapid Storage Technology (Intel® RST) RAID Driver"가 설치 안 되는 문제 [5]
2916정성태5/9/201533427오류 유형: 287. 레지스트리 권한 오류 - Cannot edit [Registry key name]: Error writing the value's new contents.
2915정성태5/9/201532897개발 환경 구성: 265. TrustedInstaller 권한으로 프로그램 실행시키는 방법 [11]
2914정성태5/9/201529925DDK: 7. 정식 인증서가 있는 경우 Device Driver 서명하는 방법 [2]
2913정성태4/30/201527610.NET Framework: 511. Build 2015 행사에서 소개된 (맥/리눅스/윈도우 용 무료) Visual Studio Code 개발 도구 [8]
... 121  122  123  124  125  [126]  127  128  129  130  131  132  133  134  135  ...