L'Isola di Niente
L'Isola di Niente » PyGObject Tips » コンテナ

コンテナ

複数の GtkWidget をレイアウトするにはコンテナを利用します。

レイアウタの概念を使います、HTML, Swing, WPF 等でお馴染みです。
多言語対応アプリがハミ出さずに文字列を表示できているのはレイアウタのおかげです。

GtkBox

基本コンテナ、GtkVBox, GtkHBox は非推奨になり統合されました。
これに Widget を縦、横方向に積み重ねていきます。
#!/usr/bin/env python3

from gi.repository import Gtk, Gdk

class BaseWin(Gtk.Window):
    """
        縦: Gtk.Orientation.VERTICAL
        横: Gtk.Orientation.HORIZONTAL
    """
    def __init__(self):
        """
            拡縮でクライアントエリアだけ連動するサンプル
            pack_start で第二と第三引数を True
            ステータスバーは pack_end で下に合体
        """
        Gtk.Window.__init__(self)
        self.set_title("GtkBox")
        self.connect("delete-event", Gtk.main_quit)
        # Vertical Box
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        self.add(vbox)
        # Client Area
        client_area = Gtk.DrawingArea()
        vbox.pack_start(client_area, True, True, 0)
        # Horizontal Box (Fake Statusbar)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        vbox.pack_end(hbox, False, False, 0)
        # Statusbar Item
        button = Gtk.Button.new_with_label("アイテム追加")
        button.connect("clicked", self.on_clicked, hbox)
        hbox.pack_start(button, False, False, 0)
        #
        self.resize(200, 200)
        self.show_all()

    def on_clicked(self, widget, hbox):
        """
            ボタンを押すとアイテムが増える
            いくら追加してもアイテムがはみ出さないのを確認
        """
        label = Gtk.Label("[追加アイテム]")
        label.show()
        hbox.pack_start(label, False, False, 0)

BaseWin()
Gtk.main()
img2/box.png
アイテムを追加すると増えた Widget 分ウインドウが大きくなるのが解る。
積み重ねなので Widget 同士が重なることも絶対にありえません。

又、多言語で右から読み書きする言語にも柔軟に対応してくれるようです。
Windows で海外アプリを使うとよく重なっていますよね、GTK+ ならありえない。

GtkScrolledWindow

はみ出さないと困る場合もある、画像ビューアやテキストエディタ等の場合である。
その場合は GtkScrolledWindow を間に噛ませスクロールバーを表示させます。
Windows の WPF 等は Widget 側にその機能がありますが GTK+ では別体です。
#!/usr/bin/env python3

from gi.repository import Gtk, Gdk

class ScrolledWin(Gtk.Window):
    """
        文字列の長さや改行で Widget が大きくなると
        全自動でスクロールバーが出るサンプル
        GtkScrolledWindow を噛ませるだけ
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Scrolled")
        self.connect("delete-event", Gtk.main_quit)
        # Vertical Box
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        self.add(vbox)
        # Client Area
        client_area = Gtk.ScrolledWindow()
        vbox.pack_start(client_area, True, True, 0)
        # TextView
        view = Gtk.TextView()
        client_area.add(view)
        # Fake Statusbar
        statusbar = Gtk.Label("Statusbar")
        vbox.pack_end(statusbar, False, False, 0)
        self.show_all()

ScrolledWin()
Gtk.main()
img2/scrolled.png

コレが無いとウインドウがどんどん大きくなってしまいます。

GtkLayout

スクロールバーを出さずにはみ出させたい場合がある、かもしれない...
GtkLayout は GtkDrawinArea のような空白ウイジェットですがコンテナです。
使い道がいまいち解らないけどこんなことができます。
#!/usr/bin/env python3

from gi.repository import Gtk, Gdk

class LayoutWin(Gtk.Window):
    """
        スクロールバーが出ないサンプル
        ウインドウを大きくするとボタンが出てくる
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Layout")
        self.connect("delete-event", Gtk.main_quit)
        # Vertical Box
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        self.add(vbox)
        # GtkLayout
        layout = Gtk.Layout.new(None, None)
        vbox.pack_start(layout, True, True, 0)
        # In Button
        button = Gtk.Button.new_with_label("実はボタンが隠れている")
        button.props.margin = 10
        layout.add(button)
        # Fake Statusbar
        statusbar = Gtk.Label("Statusbar")
        vbox.pack_end(statusbar, False, False, 0)
        #
        self.show_all()

LayoutWin()
Gtk.main()
img2/layout.png

GtkGrid

その名のとおり Excel のような行と列にに配置していくコンテナです。

#!/usr/bin/env python3

from gi.repository import Gtk

EDITOR_LABELS = ("Name", "Key", "URL", "Query")
EDITOR_CHECK = "Is POST"
ENTER_BUTTON = "_Edit"

class GridWin(Gtk.Window):
    """
        grid.attach(child, left, top, width, height)
        or
        grid.attach_next_to(child, sibling, GtkPositionType, width, height)

        親のリサイズに追従するには子ウイジェットの 'expand' プロパティ
        マージンを指定するには子ウイジェットの 'margin' プロパティ
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Grid")
        self.connect("delete-event", Gtk.main_quit)
        # GtkGrid
        grid = Gtk.Grid.new()
        # Edit Widget
        self.edit_name = Gtk.Entry()
        # 親のリサイズに追従
        self.edit_name.props.expand = True
        grid.attach(self.edit_name, 1, 0, 1, 1)
        self.edit_key = Gtk.Entry()
        grid.attach(self.edit_key, 3, 0, 1, 1)
        self.edit_url = Gtk.Entry()
        self.edit_url.props.expand = True
        grid.attach(self.edit_url, 1, 1, 5, 1)
        self.edit_query = Gtk.Entry()
        self.edit_query.props.expand = True
        grid.attach(self.edit_query, 1, 2, 3, 1)
        self.check_post = Gtk.CheckButton.new_with_label(EDITOR_CHECK)
        grid.attach(self.check_post, 4, 2, 1, 1)
        # Label
        labels = []
        for label in EDITOR_LABELS:
            labels.append(Gtk.Label(label))
        grid.attach(labels[0], 0, 0, 1, 1)
        grid.attach(labels[1], 2, 0, 1, 1)
        grid.attach(labels[2], 0, 1, 1, 1)
        grid.attach(labels[3], 0, 2, 1, 1)
        # マージンを指定
        labels[1].props.margin_left = 10
        labels[1].props.margin_right = 10
        # Button
        self.button = Gtk.Button.new_with_mnemonic(ENTER_BUTTON)
        grid.attach(self.button, 4, 0, 1, 1)
        self.add(grid)
        self.show_all()

GridWin()
Gtk.main()

img2/grid.png

GtkNotebook

日本ではよくタブと呼ばれている複数ページを切り替えるコンテナです。
タブに表示する GtkLabel と GtkBox を作成し append_page して作成します。
GTK+ 3.12 より幅一杯に大きくなるようになりました。
#!/usr/bin/env python3

from gi.repository import Gtk

class NotebookWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Notebook")
        self.connect("delete-event", Gtk.main_quit)
        # GtkNotebook
        note = Gtk.Notebook()
        for tab in ("表示", "エディタ", "プラグイン"):
            # タブのラベル
            label = Gtk.Label(tab)
            # ページ(クライアントエリア)
            page = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
            # 挿入
            note.append_page(page, label)
        # 1ページ目の中身
        page1 = note.get_nth_page(0)
        for s in ("GtkNotebook", "の", "サンプル"):
            button = Gtk.CheckButton.new_with_label(s)
            page1.pack_start(button, False, False, 0)
        self.add(note)
        self.show_all()

NotebookWin()
Gtk.main()
img2/notebook.png

GtkPaned

ウィンドウを区切って二つのペーンに分割するコンテナです。
コレも GtkVPaned, GtkHPaned は非推奨となり統合されました。
二つの枠を保持し各枠の中に Widget を一つずつ入れられます、add1() と add2() を使います。
#!/usr/bin/env python3

from gi.repository import Gtk

class PanedWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Paned")
        self.connect("delete-event", Gtk.main_quit)
        # Left: Layout and Label
        layout = Gtk.Layout.new(None, None)
        label = Gtk.Label("ツール")
        layout.add(label)
        # Right: ScrolledWindow and TextView
        sw = Gtk.ScrolledWindow()
        tv = Gtk.TextView()
        sw.add(tv)
        # Paned
        paned = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        paned.add1(layout)
        paned.add2(sw)
        self.add(paned)
        # Paned Position
        self.resize(300, 100)
        paned.set_position(100)
        #
        self.show_all()

PanedWin()
Gtk.main()
img2/paned.png
ペーンをマウスで掴めば大きさを変更できます。
GtkScrolledWindow を噛ませるのを忘れずに。

GtkExpander

クリックすると下にヒョッコリと子 Widget がでてくるコンテナ。
階層にすることもできる、実際に動かしてみたほうが早い。
#!/usr/bin/env python3

from gi.repository import Gtk

class ExpanderWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Expander")
        self.connect("delete-event", Gtk.main_quit)
        # GtkExpander
        ex_root = Gtk.Expander.new("Click すると下にもう一つ Expander が出る")
        ex_child = Gtk.Expander.new("Click すると下にラベルが出る")
        ex_root.add(ex_child)
        label = Gtk.Label.new("Label")
        ex_child.add(label)
        self.add(ex_root)
        self.show_all()

ExpanderWin()
Gtk.main()
img2/expander.png

GtkOverlay

その名のとおり上に重ねるウイジェットを指定できるコンテナ。
add_overlay(widget) されたウイジェットが被さります、通常 DrawingArea を使うと思う。
halign, valign を指定しない場合はセンターに表示されるようです。
#!/usr/bin/env python3

from gi.repository import Gtk

class OverlayWin(Gtk.Window):
    def __init__(self):
        """
            GtkEntry の上に GtkLabel を重ねた例
        """
        Gtk.Window.__init__(self)
        self.set_title("Overlay")
        self.connect("delete-event", Gtk.main_quit)
        # GtkOverlay
        ol = Gtk.Overlay.new()
        entry = Gtk.Entry()
        label = Gtk.Label('<span color="#77aadd">邪魔してやる!</span>')
        label.set_use_markup(True)
        label.set_halign(Gtk.Align.END)
        label.set_valign(Gtk.Align.CENTER)
        ol.add(entry)
        ol.add_overlay(label)
        self.add(ol)
        self.show_all()

OverlayWin()
Gtk.main()

img2/overlay.png

GtkButtonBox

ボタンを右寄せや等間隔で並べるためのコンテナです。
コレも GtkVButtonBox, GtkHButtonBox は非推奨となり統合されました。

GTK+ 3.12 より GTK_BUTTONBOX_EXPAND というスタイルが追加された。
タッチ操作向けに等幅で枠いっぱいに広がる最近よく見るアレです。

GtkButtonBoxStyle の指定により並べ方を変更できます。
どんな並び型になるかは下記のコードを動かして確認ください。
#!/usr/bin/env python3

from gi.repository import Gtk

window = Gtk.Window()
window.set_position(Gtk.WindowPosition.CENTER)
window.set_size_request(450, -1)
window.connect('destroy', Gtk.main_quit)

vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)

styles = (
    ('Start', Gtk.ButtonBoxStyle.START),
    ('End', Gtk.ButtonBoxStyle.END),
    ('Center', Gtk.ButtonBoxStyle.CENTER),
    ('Spread', Gtk.ButtonBoxStyle.SPREAD),
    ('Edge(Default)', Gtk.ButtonBoxStyle.EDGE),
    ('Expand', Gtk.ButtonBoxStyle.EXPAND) )
  
for key, value in styles:
    box = Gtk.ButtonBox.new(Gtk.Orientation.HORIZONTAL)
    box.set_layout(value)
    box.add(Gtk.Button.new_from_stock(Gtk.STOCK_YES))
    box.add(Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL))
    box.add(Gtk.Button.new_from_stock(Gtk.STOCK_NO))
    frame = Gtk.Frame.new(key)
    frame.set_shadow_type(Gtk.ShadowType.IN)
    frame.add(box)
    vbox.pack_start(frame, False, False, 5)

window.add(vbox)
window.show_all()
Gtk.main()
img2/buttonbox.png

GtkFixed

Windows SDK 等と同様な絶対位置配置を行うためのコンテナ。
多言語化どころかフォントやテーマの違いではみ出したり重なったりするのはお馴染。
なのでどうしても絶対値配置が必要な場合のみ利用する。
Linux は Windows とは違いたとえ日本語のみの環境でもフォントやテーマがバラバラです。
#!/usr/bin/env python3

from gi.repository import Gtk

class FixedWin(Gtk.Window):
    """
        コレは極端な例にしましたけど
        Ubuntu で作っているのに Ubuntu デフォルトテーマで重なった
        という信じられないアプリを本当に見たことがある、初心者恐るべし
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("Fixed")
        self.connect("delete-event", Gtk.main_quit)
        # GtkFixed
        fixed = Gtk.Fixed.new()
        button1 = Gtk.Button.new_with_label("Lubuntu 12.10 では")
        button2 = Gtk.Button.new_with_label("OK だけど")
        button3 = Gtk.Button.new_with_label("fedora 18 では重なる例")
        fixed.put(button1, 0, 0)
        fixed.put(button2, 136, 0)
        fixed.put(button3, 0, 26)
        #
        self.add(fixed)
        self.show_all()

FixedWin()
Gtk.main()
img/gtk_fixed.png

Glade で配置するとわりと簡単なので初心者丸出しな人がよく使っている。
英語が読めるなら devhelp に「使うな」と書いてあるのが解るはずなんですが...
とにかく確実に黒歴史となるので使わないほうがいい。
Copyright(C) sasakima-nao All rights reserved 2002 --- 2017.