1. disabled 스타일 개론

소프트웨어 GUI에서 대화상자나 메뉴 같은 구성요소를 보면, 상태를 나타내는 속성 중에 논리값으로 enabled 여부라는 게 존재한다.
이게 false여서 disable된 물건은 비록 화면에 보이긴 하지만 흐리게 표시되며 완전히 없는 물건으로 취급된다. 사용자의 키보드나 마우스 조작에 반응을 하지 않으며 선택할 수도 없다.

Windows 운영체제에서는 윈도우에서 WS_DISABLED라는 스타일 비트가 이런 역할을 한다. 기본 스타일에 이 비트가 지정되어 있는 윈도우는 키보드 포커스를 받을 수 없으며, 거기에다 대고 마우스 포인터를 움직여도 통상적인 WM_MOUSEMOVE, WM_?BUTTONDOWN 같은 메시지가 오지 않는다.

즉, availability는 어느 정도 운영체제가 직접 관리를 해 주는 예약된 속성이다.
어떤 윈도우가 enabled인지 여부를 알려면 IsWindowEnabled 함수를 호출하면 된다.
IsWindowEnabled(hWnd)는 !(GetWindowLongPtr(hWnd, GWL_STYLE)&WS_DISABLED)와 동치라고 생각하면 된다.

enabled 여부를 설정하는 함수는 EnableWindow이다. 이때 대상 윈도우는 WM_ENABLE라는 메시지를 받음으로써 자신의 availability 속성이 바뀌었다는 통지를 받는다.
Get과 마찬가지로 SetWindowLongPtr를 통해 스타일을 수동으로 바꿔 줘도 거의 같은 효과를 낼 수 있다.
단, 이 방법은 간단히 전용 함수를 호출하는 것보다 번거로우며, 이렇게 속성이 바뀌면 대상 윈도우는 WM_STYLECHANGED만을 받지 WS_DISABLED 비트에 차이가 있더라도 WM_ENABLE 메시지가 오지는 않는다.

2. 화면에 그리기

대화상자 컨트롤이라면 WM_ENABLE 메시지가 왔을 때 자신을 화면에 다시 그리는 처리를 한다.
가령, 평소에는 COLOR_WINDOWTEXT라는 시스템 색상으로 글자를 찍은 반면, disable된 뒤부터는 COLOR_GRAYTEXT 색상으로 글자를 다시 찍는다.

지금이야 Windows 8 때부터 고전 테마라는 게 사라져서 점차 과거의 유물이 돼 가지만..
옛날에 Windows UI를 보면, 메뉴나 도구모음줄에서 사용할 수 없는 항목은 글자가 단순히 회색이 아니라 흰색 위에 회색이 깔려서 뭔가 음각 엠보싱처럼 그려지곤 했다.

사용자 삽입 이미지

그거 처리를 해서 disable 상태를 화면에 표현하는 건 DrawState라는 함수 호출 한 방이면 바로 된다. 이건 딱 회색 3D 대화상자 스타일이 도입된 Windows 95와 NT4에서 첫 추가된 함수이다. 게다가 텍스트와 비트맵(아이콘) 모두 그렇게 그리는 걸 지원한다.

비트맵의 경우, 마스크 비트맵을 바탕으로 엠보싱을 만들며, 이 테크닉은 지금도 그대로 쓰인다. 그렇기 때문에 요즘은 32비트 비트맵 내부의 알파 채널이 투명색을 대신하는 지경이 되었음에도 불구하고 DrawState 함수로 disable 상태의 엠보싱 아이콘을 그리려면 비트맵에 모노크롬 흑백 배경 마스크 비트맵도 넣어 줘야 한다.
뭐, 궁극적으로는 트루컬러 아이콘이라면 구시대스러운 비트 연산이 아니라, 투명도를 높이고 채도를 낮춰서 그림을 더 엷고 탁하게 만드는 '현대적인' 방식으로 disable 상태를 그려야 하겠지만 말이다.

3. disabled 윈도우의 또 다른 용도

그러면 이런 disabled 속성은 오로지 대화상자 내부의 컨트롤 같은 WS_CHILD급 윈도우에서만 쓰이는가 하면 그렇지 않다. 타 윈도우의 자식이 아니라 top-level이 될 수 있는 WS_POPUP급 윈도우도 WS_DISABLED 속성을 줘서 생성할 수 있다. 이 윈도우는 화면이 달랑 떠 있기만 하지 사용자가 포커스를 줘서 키보드 입력을 할 수가 없게 된다.

사실, 한 프로세스 안에서 child 윈도우는 클릭했다고 해서 딱히 포커스가 자동으로 거기로 옮겨지지는 않는다. 포커스가 가는 건 해당 윈도우가 WM_LBUTTONDOWN이 왔을 때 SetFocus를 자체적으로 호출했기 때문이다.
그러나 소속된 프로세스가 다른 top-level 윈도우의 경우, 클릭하면 일단 그 창으로 WM_ACTIVATE 메시지가 가고 컨텍스트 전환이 발생한다. 이것은 프로그램의 의사와 무관하게 운영체제가 일방적으로 하는 일이었는데, disabled 윈도우는 그걸 막을 수 있다.

물론 disabled 윈도우는 앞서 말했듯이 정상적인 마우스 메시지가 오지 않는데, 그래도 이것도 받는 방법이 있다.
disabled라고 해도 top-level 윈도우에는 기본적으로 마우스 포인터 설정을 위해서 WM_SETCURSOR 메시지 정도는 온다. 이 메시지의 lParam에는 이 윈도우에 원래 오려고 한 메시지가 담겨 있기 때문에 이를 토대로 비록 disable 상태이지만 마우스 동작에 반응을 하도록 프로그래밍이 얼마든지 가능하다. child 윈도우가 아닌 top-level 윈도우이기 때문에 이런 메시지가 온다.

disabled 편법을 쓰지 않고 '클릭해도 반응만 하지 포커스가 바뀌지는 않는 간단한 윈도우'라는 개념은 비교적 늦게 등장했다. Windows 2000부터 WS_EX_NOACTIVATE라는 확장 스타일이 정식으로 도입된 것이다. 이런 윈도우는 ShowWindow에다가도 단순히 SW_SHOW가 아니라 SW_SHOWNOACTIVATE를 줘야 한다.

4. 상태 변경과 관련된 연계 작업들

대화상자에서 컨트롤을 enable/disable시키는 상황은 크게 선천적인 것과 후천적인 것으로 나뉜다. 전자는 WM_INITDIALOG에서 단 한 번 enable 여부가 결정되고 그 뒤에 대화상자가 닫힐 때까지 그 상태가 바뀔 일이 없는 경우를 말한다.
후자는 한 컨트롤의 조작 여부나 값에 따라 다른 컨트롤의 enable 상태가 인터랙티브하게 수시로 바뀌는 경우를 가리킨다. 예를 들어 라디오 버튼이 특정 항목으로 맞춰져 있을 때만 조건부로 사용 가능해지는 하부 조건 체크박스 같은 것. UI 프로그래밍을 해 본 분이라면 이 분류가 수긍이 갈 것이다.

그런데 이렇게 컨트롤들의 상태를 바꾸는 건, 단순히 한 윈도우에 대해 EnableWindow를 호출하는 것 이상으로 이와 결합된 반복 패턴이 여럿 존재한다.

(1) 첫째, 대상 컨트롤의 이전에 있는 label 컨트롤을 같이 enable/disable시키는 경우이다. 대상 컨트롤이 버튼이라면--push, check, radio 모두 포함-- 그 자체가 &로 시작하는 Alt 액셀러레이터 글쇠를 갖는다. 그러나 나머지 edit, list, combo 박스 같은 것들은 자신의 액셀러레이터가 없으며 그 이전의 static 라벨로부터 액셀러레이터를 넘겨받는 형태이다.

따라서 그런 컨트롤이 disable됐다면 자기의 앞의 컨트롤도 같이 disable되는 게 이치에 맞다. 앞의 단순 label은 보통 독자적인 컨트롤 ID가 없이 그냥 IDC_STATIC인 경우도 많으므로 핸들값을 GetWindow(hCtrl, GW_HWNDPREV)로 얻어 오는 수밖에 없다.

(2) 둘째, 대상 컨트롤을 화면에서 감출 때에도 ShowWindow(hCtrl, SW_HIDE)만 할 게 아니라 disable을 시켜 줘야 한다. 왜냐 하면 enable 상태인 컨트롤은 비록 화면에 없더라도 Alt 액셀러레이터에는 반응을 해서 사용자가 여전히 기능 접근이 가능하기 때문이다.
본인은 개인적으로는 이게 바람직한 설계가 아니라고 생각하며, Windows가 왜 그렇게 동작하는지 알지 못한다. 하지만 어쨌든 Windows 95부터 8.1에 이르기까지, 조건부로 컨트롤들을 보였다가 숨겼다가 하는 UI의 경우, 감춰지는 컨트롤은 disable도 시켜 줘야 한다.

(3) 셋째, 지금 키보드 포커스를 받고 있는 컨트롤이 disable되는 경우, 포커스를 자신의 다음 컨트롤로 옮기는 일을 해당 프로그램이 수동으로 해 줘야 한다. 이걸 안 해 주면 그 컨트롤이 disable된 뒤부터 키보드 상태가 꼬여 버린다.
이 단서의 주된 적용 대상은 push-버튼이다. 대표적인 예로는 프로퍼티 페이지에 있는 '적용' 버튼. 이 버튼을 누른 순간 이 버튼은 사용 불가 상태가 되며, 사용자가 다른 설정을 또 건드려 줘야 다시 사용 가능해지니 말이다.

본인의 개인적인 생각은 이것도 역시 운영체제가 자동으로 처리해 줘야 하는 게 아닌가 싶지만, 현실은 시궁창이다. 생각보다 자비심이 없다..;;
대화상자에서 특정 컨트롤에다 포커스를 주는 건 SetFocus를 해서는 안 되며, 대화상자 부모 윈도우에다가 WM_NEXTDLGCTL 메시지를 보내는 방식으로 해야 한다. 그렇게 해야 대화상자의 default 버튼에 굵은 테두리가 그려지는 처리가 올바르게 된다.

그래서 본인은 위의 세 시나리오를 모두 감안하여 대화상자 컨트롤을 enable/disable시키는 함수를 별도로 만들어서 사용하고 있다.
그림을 좀 더 곁들이면 전반적으로 설명하기가 더 편하겠다는 생각이 드는데.. 귀찮아서 생략한다. ㄱ-

Posted by 사무엘

2014/12/08 08:29 2014/12/08 08:29
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1037

Trackback URL : http://moogi.new21.org/tc/trackback/1037

Comments List

  1. Lyn 2014/12/08 15:37 # M/D Reply Permalink

    쓰실 말이 많으나 여백이 부족했군요!

    1. 사무엘 2014/12/08 18:30 # M/D Permalink

      여백=_=도 여백이지만 문득 생각해 보니, 요즘 세상에 이렇게 Windows UI 골동품 프로그래밍을 하는 사람이 얼마나 될까 싶습니다. ㅋㅋㅋ

  2. 천세진 2014/12/12 09:43 # M/D Reply Permalink

    사실 다이얼로그에서 임의로 포커스를 옮길 때 WM_NEXTDLGCTL를 사용해야 한다는 사실을 아는 Windows 개발자가 많지 않다고 생각됩니다.
    저도 Windows 프로그래밍을 하고 나서 꽤 후에 알게 된..;;;
    기술적으로 깊이있는 글 감사합니다.

    1. 사무엘 2014/12/12 12:22 # M/D Permalink

      반갑습니다~! ^^
      저도 그 사실을 나중에 알고서 제 프로그램의 기존 코드에다가도 뒤늦게 수정을 했답니다.
      alt+액셀러레이터나 탭 등이 제대로 동작하려면, 대화상자는 메시지나 윈도 프로시저 호출 같은 걸 일반 윈도우 취급하듯이 취급해서는 안 되는 경우가 종종 있지요.

Leave a comment
« Previous : 1 : ... 1296 : 1297 : 1298 : 1299 : 1300 : 1301 : 1302 : 1303 : 1304 : ... 2204 : Next »

블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/11   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Site Stats

Total hits:
2984727
Today:
280
Yesterday:
2184