* 3년 전에 썼던 글을 내용을 보충하여 리메이크 한 것이다.
Windows 운영체제에서 생성하는 윈도우들은 그 본질이 크게 overlapped, popup, 그리고 child 이렇게 셋으로 나뉜다. 이해를 돕기 위해 아래의 Windows 1.0 사진을 한번 살펴보도록 하자. 그때는 이 세 종류의 구분이 지금보다 훨씬 더 명확했기 때문이다.
1. overlapped
1985년에 발표된 Windows 1.0 첫 버전은 기술적인 한계 때문..은 아니고, 애플 사와의 이상한 특허 분쟁에 얽히는 바람에 응용 프로그램 창들이 서로 겹치지를 못하고 타일 형태의 배치만 가능한 정말 괴상한 형태로 개발되었던 걸로 유명하다.
그러다 Windows 2.0에서는 타일 제약 봉인이 풀렸기 때문에 이 윈도우들은 겹쳐지는 게 가능해졌으며 Z-order라는 개념도 생겼다. 그게 워낙 뜻깊은 일이었던지라 명칭에까지 OVERLAPPED가 붙은 것이다.
그리고 저렇게, 타일 형태의 배치가 가능한 응용 프로그램의 최상단 껍데기 윈도우가 바로 오늘날의 개념으로 치면 overlapped 윈도우이다. 캡션이라고 불리는 제목 표시줄이 달려 있고 크기가 언제든지 유동적으로 바뀔 수 있으며, CreateWindow(Ex) 함수에다 위치와 크기를 지정할 때 CW_USEDEFAULT(대충 적당히 알아서)를 줄 수 있는 유일한 타입의 윈도우이다.
사실, WS_OVERLAPPED의 값은 그냥 0이다. popup이나 child 같은 속성이 따로 지정되지 않은 윈도우는 기본적으로 overlapped 속성이 지정된다. 여기에다가 최소화/최대화(WS_M??MIZEBOX)/닫기(시스템 메뉴 WS_SYSMENU) 버튼, 크기 조절 가능한 굵은 껍데기(WS_THICKBORDER) 비트들이 합쳐진 것이 바로 WS_OVERLAPPEDWINDOW 스타일이다.
2. popup
그럼 popup은 무엇이냐 하면 저 위의 About 대화상자처럼, overlapped window의 위에 겹쳐져서 배치될 수 있는 윈도우이다.
그런데 당장 Windows 2.0부터 오버랩은 말 그대로 overlapped window에서도 다 가능해졌으니, 둘의 실질적인 차이가 없어졌다고 볼 수도 있다. 하지만 둘은 여전히 완전히 동일하지는 않다.
popup 윈도우는 기본적으로 캡션이 없는 형태이며, WS_CAPTION 같은 별도의 옵션을 줘야만 캡션이 달린다. 그러나 overlapped 윈도우는 옵션을 주지 않아도 캡션이 무조건 달려 나온다. Windows 2~3 시절까지만 해도 응용 프로그램에서 캡션이 없고 제목이 없는 대화상자는 지금보다 훨씬 더 흔하게 볼 수 있었다.
지금은 대화상자들도 다 캡션이 달려 있으며 일반적인 응용 프로그램처럼 아이콘에다 최소· 최대화 버튼과 두꺼운 프레임까지 별도로 스타일로 주고 나면.. popup 형태의 대화상자 프로그램과, overlapped 형태의 일반 프로그램 창과 외형상의 구분은 사실상 다 사라지는 건 사실이다.
그럼에도 불구하고 popup과 overlapped의 구분이 원래 저런 데서 시작되었다는 것을 알면 되겠다. 다른 창의 내부에 종속되지 않고 독자적으로 화면에 떠 있으면서 캡션 같은 외형이 없거나 취사선택 가능한 모든 custom 윈도우라면, 묻지도 따지지도 말고 그냥 WS_POPUP을 주면 된다.
대화상자 리소스 편집기에서도 이 대화상자의 초기 스타일을 지정해 줄 수 있다. 프로퍼티 페이지처럼 다른 대화상자의 내부에 들어가는 대화상자이면 WS_CHILD를 주면 되고, 나머지 경우에는 WS_OVERLAPPED는 신경 쓸 필요 없고 그냥 WS_POPUP을 지정하면 된다.
여담이지만, 인터넷을 하면서 수시로 튀어나오는 웹브라우저 팝업창은 명칭과는 달리 사실은 overlapped 윈도우라고 생각하면 된다. 팝업창에도 웹브라우저 창 고유의 캡션과 프레임은 그대로 남아 있기 때문에 overlapped 윈도우의 정의에 훨씬 더 부합하는 걸 알 수 있다.
3. child
끝으로, WS_CHILD는 동작 방식이 위의 둘과는 굉장히 다르니 이해하기 쉽다.
자기의 위상이 독자적이지 않고 외형상 부모 윈도우의 내부에 종속된 모든 윈도우들은 child 윈도우이다. 대화상자의 내부 컨트롤들이 대표적인 예임.
얘는 컨트롤 ID라는 정보도 갖는다. HWND는 운영체제가 창들을 식별하기 위해 부여하는 가변적인 번호인 반면, ID는 창을 생성하는(= 운영체제에다 생성을 요청하는) 주체 측에서 고정붙박이로 부여하는 번호라는 차이가 있다. GetDlgItem은 이름처럼 굳이 대화상자의 자식 컨트롤뿐만 아니라 부모-자식 관계를 갖는 아무 윈도우에서나 ID값으로부터 자식 창을 얻을 때 사용 가능하다.
popup이나 overlapped 윈도우에는 저런 ID라는 개념이 존재하지 않으며, 그 대신 메뉴를 표시하는 기능이 있다.
뭐, child 윈도우도 비록 메뉴는 태생적으로 없을지언정 마치 overlapped 윈도우처럼 캡션과 프레임, 그리고 시스템 메뉴를 갖는 건 불가능하지 않다. 그 대표적인 예는 MDI 프레임 윈도우이긴 한데.. 그래도 그걸 빼면 캡션과 프레임을 갖춘 child 윈도우는 매우 드물다. 캡션과 프레임 자체가 최상위 윈도우의 상징과도 같으니 말이다.
이렇게 보면 overlapped와 popup이 한 묶음이고, 성격이 다른 child가 혼자 좀 따로 노는 것처럼 보인다. 하지만 동일한 클래스의 윈도우가 상황에 따라서 popup과 child 속성을 취사선택해서 동작하는 경우도 의외로 있다. 콤보 박스에서 내부적으로 쓰이는 ComboLBox라는 리스트 박스가 대표적인 예이다.
콤보 박스의 타입이 Simple이어서(대표적인 예는 글꼴 선택 대화상자) 리스트가 언제나 표시되어 보일 때는 얘는 콤보 박스에 딸려 있는 child 윈도우이다.
그러나 콤보 박스를 클릭하거나 F4를 눌렀을 때만 리스트가 표시되는 drop list 상태일 때는 그 리스트는 대화상자의 위에 별도로 표시되는 popup 윈도우 형태로 생성된다. 이해가 되시겠는가?
차일드 윈도우의 표시 위치는 자기 부모 윈도우의 클라이언트 위치를 기준으로 상대적으로 산정된다. 그런데 자기가 현재 부모 윈도우의 클라이언트 위치 기준으로 어디에 있는지를 한 번에 얻는 게 은근히 힘들다. 대화상자 크기에 따라 차일드 컨트롤들을 적절하게 재배치하는 코드를 작성해 보았다면 이 말이 무슨 뜻인지 잘 알 것이다.
이 경우 GetWindowRect를 한 후에 부모 윈도우를 기준으로 ScreenToClient를 하여 화면 좌표를 한번 거쳐야 하거나, 아니면 번거로운 구조체 초기화를 해야 하는 GetWindowPlacement 함수를 호출해야 한다. 후자 함수의 경우, 최대화된 윈도우라도 원래 있던 위치와 크기까지.. 그 윈도우의 위치와 관련된 모든 정보를 되돌려 주기 때문에 유용하다. 응용 프로그램이 종료 후 나중에 재실행될 때 원래 위치를 100% 그대로 실행되기를 원할 때 이 구조체 값을 백업해 두면 된다.
4. 윈도우 간의 부모/자식 관계
child 윈도우야 그 정의상 태생적으로 부모 자식 관계가 명백하게 존재할 수밖에 없다. 하지만 popup 윈도우도 비록 child처럼 표시되는 위치와 영역이 부모 윈도우 내부로 한정되는 급까지는 아니더라도, 부모 자식 관계 비스무리한 개념이 물론 존재한다.
popup 윈도우는 Z-order상으로 자기 부모 윈도우를 가리고 언제나 더 앞에 출력되며, 부모 윈도우가 소멸될 때 자기도 같이 없어진다. 요렇게 child가 아닌 popup 윈도우의 부모 역할을 하는 윈도우를 개념상으로 owner 윈도우라고 따로 부르기도 한다.
그럼 popup 말고 overlapped 윈도우는? 지금까지 살펴보았듯이 쟤는 애초에 주 용도가 응용 프로그램의 최상단 프레임 껍데기이다. 그러니 태생적으로 부모 윈도우 같은 걸 지정하지 않고 생성되며 부모 자식 관계를 따지는 건 딱히 의미가 없다고 봐야 할 것이다.
그런데, 여기서 유의해야 할 점이 있다. EnumChildWindow나 GetWindow(GW_CHILD) 함수에서 찾아 주는 건 순수하게 child 윈도우들뿐이다. Spy++를 실행하면 계층 구조로 표시된 윈도우 트리를 볼 수 있는데, 이것도 child 윈도우들의 관계만 볼 수 있다.
쉽게 말해 어떤 대화상자 내부의 대화상자(프로퍼티 페이지)라든가 각종 컨트롤들은 계층 구조로 표시되지만, 대화상자에서 얘를 owner로 삼아서 또 다른 modal 대화상자를 꺼내 놓은것을 계층 구조로 보여주지는 않는다는 뜻이다.
자신을 부모(정확히는 owner)로 갖는 서열상 하위의 popup 윈도우들을 한번에 찾아 주는 API는 의외로 존재하지 않는다. 난 이게 당연히 있을 줄 알았는데 없는 걸 발견하고는 개인적으로 굉장히 놀랐다.
일단 top-level 윈도우들을 다 enumerate 한 뒤, 얘들의 owner가 일치하는 놈을 일일이 뒤져 봐야 한다. 그래서 Spy++가 표시해 준 윈도우 리스트가 생각보다 직관적이지 않고 top-level 윈도우가 많은 것이었구나.
이상이다. Windows 프로그래밍을 15년 가까이나 판 본인도 몇 년 전까지만 해도 child는 그렇다 치더라도 popup과 overlapped는 도대체 왜 존재하는 구분인지를 잘 몰랐다. 그리고 parent 윈도우와 owner 윈도우의 관계도 정확하게 모르고 있었고 owned 윈도우는 child 윈도우 조회하듯이 곧장 조회가 가능하지 않다는 것도 미처 생각을 못 하고 있었다. 그러다가 요 근래에야 어렴풋이 이해하게 된 것들을 이렇게 정리해 보았다.
Posted by 사무엘