TreeView는 ListStore 또는 TreeStore의 내용을 보여주는 창입니다. ListStore는 스프레드 시트와 같습니다. 2차원 평면 자료구조이며 행과 열로 나뉩니다. 반면에 TreeStore는 트리로 할 수 있듯이 자료 묶음 방향을 가지로 쪼갤 수 있습니다. 이 예제에서는 TreeView로 가상의 이름과 전화번호가 들어간 ListStore의 내용을 보여주고, 누른 항목의 이름이 무엇인지 자세한 정보를 보여주도록 창 하단의 Label에 설정하겠습니다.
TreeView는 단일 위젯이 아니지만 여러 작은 부분을 담고 있습니다:
TreeViewColumn 위젯은 ListStore의 각(수직) 열 정보를 보여줍니다. 각각의 TreeViewWidget은 스크린샷에서 보시는 바와 같이 열 상단에 나타낼 제목을 지니고 있습니다.
CellRenderer 위젯은 각 TreeViewColumn에 "패킹" 처리하고, 각 "셀" 또는 ListStore의 항목을 표시하는 방식의 지시 사항을 담습니다. 여기서 사용하는 CellRendererText 그리고 그림("픽셀 버퍼")를 나타내는 CellRendererPixBuf 같은 다양한 형식이 있습니다.
마지막으로 ListStore에서 (가로 방향) 행을 가리키는 보이지 않는 커서로 위젯이 아닌 TreeIter 객체를 활용하겠습니다. 예를 들어 전화번호부에 이름을 누르면, 선택한 줄을 가리키는 TreeIter를 만드는데, TreeIter는 레이블에 더 많은 정보를 보여줄 항목이 무엇인지 ListStore에 지시할 때 활용합니다.
TreeView는 아마도 Gtk 위젯 중에 제일 복잡한 gtk 위젯이 아닐까 하는데, 위젯이 지닌 부분의 양과 함께 동작하는 방식 때문입니다. 동작 방식과 실험을 배울 시간을 확보하시든지, 하다가 문제 생기면 쉬운 부분 부터 먼저 진행하십시오.
#!/usr/bin/gjs
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Pango = imports.gi.Pango;
이 프로그램을 실행할 때 가져올 라이브러리입니다. 시작 부분에 항상 gjs가 필요함을 알리는 줄을 작성해야 함을 기억하십시오.
const TreeViewExample = new Lang.Class({
Name: 'TreeView Example with Simple ListStore',
// Create the application itself
_init: function() {
this.application = new Gtk.Application({
application_id: 'org.example.jstreeviewsimpleliststore'
});
// Connect 'activate' and 'startup' signals to the callback functions
this.application.connect('activate', Lang.bind(this, this._onActivate));
this.application.connect('startup', Lang.bind(this, this._onStartup));
},
// Callback function for 'activate' signal presents window when active
_onActivate: function() {
this._window.present();
},
// Callback function for 'startup' signal builds the UI
_onStartup: function() {
this._buildUI ();
},
이 예제의 모든 코드는 TreeViewExample 클래스에 들어갑니다. 위 코드는 위젯과 창이 들어갈 Gtk.Application을 만듭니다.
// Build the application's UI
_buildUI: function() {
// Create the application window
this._window = new Gtk.ApplicationWindow({
application: this.application,
window_position: Gtk.WindowPosition.CENTER,
default_height: 250,
default_width: 100,
border_width: 20,
title: "My Phone Book"});
_buildUI 함수는 프로그램 사용자 인터페이스를 만드는 모든 코드를 넣는 곳입니다. 첫 단계에서는 모든 위젯을 우겨넣을 새 Gtk.ApplicationWindow를 만듭니다.
// Create the underlying liststore for the phonebook
this._listStore = new Gtk.ListStore ();
this._listStore.set_column_types ([
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_STRING]);
읽
우선 어떤 위젯도 다룰 수 있도록 ListStore를 만들겠습니다. 그 다음 set_column_types 메서드를 호출하여 GObject 데이터 형식의 배열에 전달하겠습니다(한 줄에 모든 형식을 넣을 수 있지만, 보기 쉽게 하려는 목적으로 쪼갰습니다).
여러분이 사용할 수 있는 GObject 데이터 형식은 다음과 같습니다:
이 경우, 각 칸마다 문자열 값이 들어가는 4칸을 지닌 ListStore를 만듭니다.
GObject 형식을 사용할 수 있게 하려면, 우리가 예제에서 했던 것처럼 프로그램 코드 시작 부분에
// Data to go in the phonebook
this.phonebook =
let phonebook =
[{ name: "Jurg", surname: "Billeter", phone: "555-0123",
description: "A friendly person."},
{ name: "Johannes", surname: "Schmid", phone: "555-1234",
description: "Easy phone number to remember."},
{ name: "Julita", surname: "Inca", phone: "555-2345",
description: "Another friendly person."},
{ name: "Javier", surname: "Jardon", phone: "555-3456",
description: "Bring fish for his penguins."},
{ name: "Jason", surname: "Clinton", phone: "555-4567",
description: "His cake's not a lie."},
{ name: "Random J.", surname: "Hacker", phone: "555-5678",
description: "Very random!"}];
ListStore에 정보를 넣었습니다. 전화번호부의 단일 항목에 해당하는 객체 배열입니다.
참고로 스크린샷의 TreeView는 실제로 "description" 속성의 데이터를 보여주지 않습니다. 대신 레이블 아래 여러분이 클릭해서 나타난 정보를 보여줍니다. 왜냐면 TreeView와 ListStore는 별개의 존재고, TreeView는 ListStore의 모든 부분을 보여줄 수 있으며, 무엇이 들어있는지 보여주는 방식이 다르기 때문입니다. 우리 예제의 레이블 내지는 두번째 TreeView 처럼 동일한 ListStore의 내용을 여러 위젯으로 보여줄 수 있습니다.
for (i = 0; i < phonebook.length; i++ ) {
let contact = phonebook [i];
this._listStore.set (this._listStore.append(), [0, 1, 2, 3],
[contact.name, contact.surname, contact.phone, contact.description]);
}
ListStore의
// Create the treeview
this._treeView = new Gtk.TreeView ({
expand: true,
model: this._listStore });
여기서는 기본적인 TreeView 위젯을 만들어, 가로 세로 방향으로 필요한 만큼 충분한 공간을 늘릴수 있게 하겠습니다. 우리가 만든 ListStore를 "모델"로 사용하거나, 어딘가로부터 가져온 내용을 활용하게 하겠습니다.
// Create the columns for the address book
let firstName = new Gtk.TreeViewColumn ({ title: "First Name" });
let lastName = new Gtk.TreeViewColumn ({ title: "Last Name" });
let phone = new Gtk.TreeViewColumn ({ title: "Phone Number" });
이제 TreeView에서 볼 각각의 TreeViewColumn을 만들겠습니다. 각 TreeViewColumn의 제목은, 스크린샷에서 보시는 대로 상단에 올라갑니다.
// Create a cell renderer for when bold text is needed
let bold = new Gtk.CellRendererText ({
weight: Pango.Weight.BOLD });
// Create a cell renderer for normal text
let normal = new Gtk.CellRendererText ();
// Pack the cell renderers into the columns
firstName.pack_start (bold, true);
lastName.pack_start (normal, true);
phone.pack_start (normal, true);
여기서 ListStore의 텍스트를 나타내는데 활용할 CellRenderer를 만들었고 TreeViewColumns에 감쌌습니다. 각 CellRendererText는 칸에 들어간 모든 항목에 사용합니다. 굵은 글씨에는 더 굵은 텍스트를 사용하지만, 일반적인 CellRendererText는 그냥 텍스트를 만듭니다. 일단 이름 열에 넣고, 다른 두 부분은 일반 CellRendererText로 넣으라고 하겠습니다.
여러분이 활용할 수 있는 다른 텍스트 속성 목록 입니다. 팡고 상수를 활용하려면, 우리가 했던 바와 같이
firstName.add_attribute (bold, "text", 0);
lastName.add_attribute (normal, "text", 1);
phone.add_attribute (normal, "text", 2);
// Insert the columns into the treeview
this._treeView.insert_column (firstName, 0);
this._treeView.insert_column (lastName, 1);
this._treeView.insert_column (phone, 2);
이제 CellRenderer를 TreeViewColumns에 넣었습니다.
첫번째 매개변수는 우리가 렌더러에 데이터를 가져다 넣을때 활용할 CellRenderer입니다.
두번째 매개변수는 가져다젛을 정보의 종류입니다. 여기서는 텍스트를 렌더링하라고 알려줍니다.
세번째 매개변수는 우리가 가져올 정보가 들어간 ListStore의 열 위치를 말합니다.
설정하고 나면 TreeView의
보통 TreeView를 초기화하려고 루프를 사용하고 싶을지도 모릅니다만, 이 예제에서는 이해를 쉽게 하기 위한 단계별로 하나하나 설명하겠습니다.
// Create the label that shows details for the name you select
this._label = new Gtk.Label ({ label: "" });
// Get which item is selected
this.selection = this._treeView.get_selection();
// When something new is selected, call _on_changed
this.selection.connect ('changed', Lang.bind (this, this._onSelectionChanged));
TreeView의
TreeSelection을 TreeView에서 가져오고 난 후, TreeSelection에 가리키는 행이 어디인지 요청할 수 있습니다.
// Create a grid to organize everything in
this._grid = new Gtk.Grid;
// Attach the treeview and label to the grid
this._grid.attach (this._treeView, 0, 0, 1, 1);
this._grid.attach (this._label, 0, 1, 1, 1);
// Add the grid to the window
this._window.add (this._grid);
// Show the window and all child widgets
this._window.show_all();
},
위 과정을 끝내고 나면, 모든 위젯을 넣을 Grid를 만들고, Grid를 창에 추가하여 창과 창에 있는 내용을 표시하도록 합니다.
_onSelectionChanged: function () {
// Grab a treeiter pointing to the current selection
let [ isSelected, model, iter ] = this.selection.get_selected();
// Set the label to read off the values stored in the current selection
this._label.set_label ("\n" +
this._listStore.get_value (iter, 0) + " " +
this._listStore.get_value (iter, 1) + " " +
this._listStore.get_value (iter, 2) + "\n" +
this._listStore.get_value (iter, 3));
}
});
let 문 코드 줄은 조금 복잡하긴 하지만, 그래도 TreeSelection에서 동일한 줄을 가리키는 TreeIter를 가져오는 최상의 수단입니다. 다른 수많은 객체 참조를 들고 있긴 하지만,
이 과정이 끝나면 레이블의
TreeView의 일부가 아닌 "hidden"을 네 열의 데이터와 가져오려합니다. 여기서 TreeView에 맞추긴 너무 긴 문자열을 Label에 표시하여 굳이 살펴볼 필요가 없게 하겠습니다.
// Run the application
let app = new TreeViewExample ();
app.application.run (ARGV);
마지막으로 TreeViewExample 클래스의 새 인스턴스를 만들고 프로그램 실행을 설정하겠습니다.
#!/usr/bin/gjs
imports.gi.versions.Gtk = '3.0';
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango;
class TreeViewExample {
// Create the application itself
constructor() {
this.application = new Gtk.Application({
application_id: 'org.example.jstreeviewsimpleliststore'
});
// Connect 'activate' and 'startup' signals to the callback functions
this.application.connect('activate', this._onActivate.bind(this));
this.application.connect('startup', this._onStartup.bind(this));
}
// Callback function for 'activate' signal presents window when active
_onActivate() {
this._window.present();
}
// Callback function for 'startup' signal builds the UI
_onStartup() {
this._buildUI();
}
// Build the application's UI
_buildUI() {
// Create the application window
this._window = new Gtk.ApplicationWindow({
application: this.application,
window_position: Gtk.WindowPosition.CENTER,
default_height: 250,
default_width: 100,
border_width: 20,
title: "My Phone Book"});
// Create the underlying liststore for the phonebook
this._listStore = new Gtk.ListStore ();
this._listStore.set_column_types ([
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_STRING]);
// Data to go in the phonebook
let phonebook =
[{ name: "Jurg", surname: "Billeter", phone: "555-0123",
description: "A friendly person."},
{ name: "Johannes", surname: "Schmid", phone: "555-1234",
description: "Easy phone number to remember."},
{ name: "Julita", surname: "Inca", phone: "555-2345",
description: "Another friendly person."},
{ name: "Javier", surname: "Jardon", phone: "555-3456",
description: "Bring fish for his penguins."},
{ name: "Jason", surname: "Clinton", phone: "555-4567",
description: "His cake's not a lie."},
{ name: "Random J.", surname: "Hacker", phone: "555-5678",
description: "Very random!"}];
// Put the data in the phonebook
for (let i = 0; i < phonebook.length; i++ ) {
let contact = phonebook [i];
this._listStore.set (this._listStore.append(), [0, 1, 2, 3],
[contact.name, contact.surname, contact.phone, contact.description]);
}
// Create the treeview
this._treeView = new Gtk.TreeView ({
expand: true,
model: this._listStore });
// Create the columns for the address book
let firstName = new Gtk.TreeViewColumn ({ title: "First Name" });
let lastName = new Gtk.TreeViewColumn ({ title: "Last Name" });
let phone = new Gtk.TreeViewColumn ({ title: "Phone Number" });
// Create a cell renderer for when bold text is needed
let bold = new Gtk.CellRendererText ({
weight: Pango.Weight.BOLD });
// Create a cell renderer for normal text
let normal = new Gtk.CellRendererText ();
// Pack the cell renderers into the columns
firstName.pack_start (bold, true);
lastName.pack_start (normal, true);
phone.pack_start (normal, true);
// Set each column to pull text from the TreeView's model
firstName.add_attribute (bold, "text", 0);
lastName.add_attribute (normal, "text", 1);
phone.add_attribute (normal, "text", 2);
// Insert the columns into the treeview
this._treeView.insert_column (firstName, 0);
this._treeView.insert_column (lastName, 1);
this._treeView.insert_column (phone, 2);
// Create the label that shows details for the name you select
this._label = new Gtk.Label ({ label: "" });
// Get which item is selected
this.selection = this._treeView.get_selection();
// When something new is selected, call _on_changed
this.selection.connect ('changed', this._onSelectionChanged.bind(this));
// Create a grid to organize everything in
this._grid = new Gtk.Grid;
// Attach the treeview and label to the grid
this._grid.attach (this._treeView, 0, 0, 1, 1);
this._grid.attach (this._label, 0, 1, 1, 1);
// Add the grid to the window
this._window.add (this._grid);
// Show the window and all child widgets
this._window.show_all();
}
_onSelectionChanged() {
// Grab a treeiter pointing to the current selection
let [ isSelected, model, iter ] = this.selection.get_selected();
// Set the label to read off the values stored in the current selection
this._label.set_label ("\n" +
this._listStore.get_value (iter, 0) + " " +
this._listStore.get_value (iter, 1) + " " +
this._listStore.get_value (iter, 2) + "\n" +
this._listStore.get_value (iter, 3)
);
}
};
// Run the application
let app = new TreeViewExample ();
app.application.run (ARGV);
이 예제는 다음 참고자료가 필요합니다:
Gtk.Application
Gtk.ApplicationWindow
Gtk.CellRendererText
Gtk.ListStore
Gtk.TreeIter
Gtk.TreeSelection
Gtk.TreeView
Gtk.TreeViewColumn