GTK+ 화면 배치

1. GUI 프로그램의 시작

GUI 프로그램의 시작은 화면을 구성하는 것으로 부터 시작한다. Window를 생성하고 Window내에 Label과 Button등 필요한 Widget들을 각각 생성하여 원하는 형태로 배치하는것이다. GtkWindow와 GtkButton을 만들고 GtkWindow내에 GtkButton이 포함되도록 하여 프로그램을 띄우면 Window에 버튼이 들어가 있는 형태로 보이는 그런것을 말하는 것이다.

화면의 구성은 정보의 구성

GTK에서 화면을 구성한다는 것을 내부적으로 보았을때 정보를 구성하는것이다. 화면을 표시할때 사용할 화면 구성 정보를 미리 셋팅해 놓고, 그 정보를 이용하여 화면에 그려내는것이다. GtkWindow에 GtkButton을 넣어둔것처럼 표시한다고 하면, GtkWindow와 GtkButton은 각각 생성해 놓고 화면을 표시함에 있어 GtkWindow가 GtkButton을 포함하도로 내부의 값을 변경하여 정보를 구성해 두는것이다. 화면을 표시할때 그 값들에 맞게 화면에 그려낸다.

GtkContainer

GtkContainer는 Widget이 다른 Widget을 포함 할 수 있도록 하는 근간이 되는 기본 클래스이다. 다른 Widget을 하나만 포함할 수 있는 GtkBin, 여러개의 다른 Widget을 포함하는 GtkTable, GtkFixed등도 모두 GtkContainer를 상속받는다. GtkContainer는 무언가 할 수 있도록 구체적으로 구현된것은 아니다. Widget이 다른 Widget을 포함할 수 있도록 하는 추상적인 Class이다.

하나의 다른 Widget을 포함하는 GtkBin

다른 Widget을 포함할 수 있도록 하는 것중 가장 기본적인 것이 GtkBin이다 GtkBin도 GtkContainer를 상속받아 Widget이 하나의 다른 Widget만을 포함할 수 있도록 하는 추상적인 Class이다. 여기서 중요한것은 오직 하나만 포함한다는것이다. GtkWindow가 GtkBin을 상속받아 다른 Widget을 포함하는 대표적인 예이다. GtkWindow에는 gtk_container_add 함수를 호출하여 오직 하나의 다른 Widget만을 포함하게 할 수 있다.

여러개의 다른 Widget을 포함하는 GtkTable과 GtkFixed

여러개의 Widget을 배치하여 다채로운 화면을 구성할 수 있는 것들로는 GtkBox와 GtkTable, GtkFixed등이 있다. 본인의 강좌에서는 GtkBox는 가급적 다루지 않을것이다. 귀찮기도 하고 GtkTable만으로도 충분히 모든것을 해낼 수 있고 gnome공식문서에서도 GtkTable을 권장한다고 한다. (이는 본인이 확인하지 못한 내용이라 확실치는 않지만 전에 다니던 회사의 울트라영어인이였던 실장님께서 문서를 보고 해주신 말이다 ~.~)

GtkTable과 GtkFixed

GtkTable은 각종 Word 프로그램의 테이블을 생각하면 된다. 표와 같은 형태이며 Col과 Row가 있고, 각 Cell마다 Widget하나를 배치할 수 있다. 또 Colspan, Rowspan과 같이 연속되는 여러개의 Cell이나 Row를 병합할 수 있다. GtkTable은 양식을 가지고 배치하는 반면 GtkFixed는 좀더 자유롭다. 그냥 GtkFixed라는 하나의 틀 안에서 X, Y 좌표 그리고 크기를 지정하여 별다른 제약없이 원하는 위치 원하는 크기로 배치가 가능하다. 물론 GtkFixed밖으로 벗어나는 부분은 화면에 보이지 않는다.

여기까지만 알아도 된다. 아래의 내용은 이해는 못하더라도 알아두기를 바라지만, 본인이 설명을 쉽게 하지 못한듯 싶다. 한번쯤 읽어본뒤 쉽사리 이해가 안된다면 Signal에 대한 기본적인 내용과 gtkcontainer.c, gtkbin.c, gtktable.c gtkfixed.c 소스파일을 함께 보길 바란다. 사실 본인또한 한참 GTK로 개발하던 시절에도 상세히 알지 못했으나 개발하는데는 큰 지장은 없었던 내용이다. 하지만 계속 말해왔듯 알고 모르고의 차이는 나중에 나타난다. 당장 프로그램을 만들어 띄우는데는 문제가 없을지 모르나 나중에 응용을 해야하는 상황이 왔을때는 답답함을 금할 수 없을것이다. 이왕이면 알아두자. 그다지 어려운 내용은 아니지만 이내용을 이해하기 위해 기본적으로 지녀야할 지식이 몇가지 필요하기 때문에 어려울 수가 있다. 사실 본인이 설명을 지지리도 못한것이 가장 큰 요인이긴 하다 ㅡ.ㅡ 참으로 죄송하게 생각한다.

2. GtkContainer

다른 widget을 포함하는 widget을 위한 기본 클래스라고 API에 적혀있다. 거기에 추상적인 Class이다. 내부적으로 필요한 멤버변수와 default handler를 지정할 수 있도록 준비되어져 있다. 하지만 그 자체로서 뭔가를 하기엔 무리가 있다. 기본적인 구조만 준비되어 있을뿐 구체적인 정보의 관리는 GtkContainer를 상속받아 사용하는 쪽에서 구현해야한다.

GtkContainer의 멤버중 'child'는 화면을 표시할때 자신이 포함하는 widget의 포인터를 가지고 있다. 이처럼 중요한 역할을 하지만 GtkContainer자체는 이것을 변경시키지 않는다. gtk_container_add 함수가 다른 widget을 포함하게 하는 함수인데 GtkContainer에서는 "add" Signal을 발생시키는것 외엔 다른 일을 하지 않는다. 실제적인 구현은 GtkBin에서한다. GtkBin에 "add" Signal에 대한 default handler가 구현되어있다. 이 default handler에서 자신의 'child'의 값을, 포함되어질 widget의 'parent'값을 저장해준다.

default handler는 Signal이 발생했을때 기본적으로 호출할 handler이다. handler라고 너무 어렵게 생각하지 말고 GTK에서 handler는 함수의 포인터를 지정해두는 정도이다.

+----GtkWidget

+----GtkContainer

+----GtkBin

+----GtkWindow

+----GtkButton

GtkWindow와 GtkButton으로 예를 들어보자. GtkWindow의 부모는 GtkBin이고 또 이 GtkBin의 부모는 GtkContainer이다. gtk_container_add(GTK_CONTAINER(window), button)과 같이 호출하면 window는 "add" Signal을 발생시키고(GtkContainer에 구현, GtkWindow는 GtkContainer를 상속받음), GtkBin 에서 구현되어있는 "add"의 default handler가 호출된다.("add"의 default handler를 저장하는 멤버는 GtkContainer에 있으며, GtkBin은 GtkContainer를 상속받음). 그러면 GtkBin에 구현된 default handler의 함수내에서는 window의 'child'(GtkContainer)의 값을 button의 포인터로 저장하고, button의 parent(GtkWidget)는 window의 포인터로 저장된다. 화면 표시할때 이정보들을 이용하여 window에 button이 들어가있는것처럼 그려낸다.

1. gtk_container_add(GTK_CONTAINER(window), button) 호출

2. window는 "add" Signal 발생(GtkContainer)

3. window "add" Signal의 default handler(GtkBin, default handler 저장 멤버는 GtkContainer) 호출

4. window의 'child'(GtkContainer)는 button의 포인터, button의 'parent'(GtkWidget)는 window의 포인터

5. 화면 표시할때 위의 정보들을 바탕을 window에 button이 들어가 있는것처럼 그려냄

정말 너무 어려운 얘기다. ㅡ.ㅡ 쉬운내용임에도 본인이 설명을 잘 못한것 같다. 핵심은 다음과 같다.

1. GtkContainer에 'child' 멤버와 "add" Signal의 default handler를 저장하는 멤버변수가 있다.

2. gtk_container_add 함수를 실행하면 GtkContainer는 "add" Signal을 발생시킨다.

3. GtkBin은 GtkContainer를 상속받으므로 'child'와 "add" Signal의 default handler 멤버 변수를 사용 할 수 있다.

4. GtkBin은 "add" Signal의 default handler가 구현되어 있으며, 'child'와 포함되어질 widget의 'parent'값을 저장한다.

5. GtkWindow는 GtkBin을 상속받으므로 위와같은 일을 모두 수행한다.

어찌보면 GtkContainer를 상속받으면 이와같은 방식들을 다 따라야할것만 같지만 GtkContainer를 상속받는다고 해서 다 이러한 방식을 사용하는것은 아니다. GtkBin이 이러한 방식을 사용하며, GtkTable과 GtkFixed는 각각 개별적으로 구현되어 있다. GtkTable과 GtkFixed는 아무래도 여러개의 widget을 포함하다보니 내부에 list 형태로 포함할 widget들의 정보를 저장하고 있다.

2. 정리해보자

    • GTK에서 화면을 구성하는것은 정보를 구성하는 것이다.
    • GtkContainer는 widget이 다른 widget을 포함할 수 있도록 하는 추상적인 기본 class이다.
    • GtkBin은 GtkContainer를 상속받았으며, 단 하나만의 다른 widget을 포함할 수 있다.
    • GtkTable, GtkFixed는 GtkContainer를 상속받았지만 각각 개별적인 방법으로 구현되어 있다.
    • 대부분의 Widget은 GtkContainer를 상속 받았으며, GTK_CONTAINER() 매크로로 타입캐스팅(업캐스팅)이 가능하다.