Paepoi

Paepoi » PyGObject Tips » Gtk(PyGObject) Tips | コンテナ

Gtk(PyGObject) Tips | コンテナ

# 最終更新日 2019.08.17

2019 年現在の仕様に追記と書き換え。
数が非常に多いので 3.10 より追加されたもの は別ページにしています。

GtkBox
複数の GtkWidget をレイアウトするにはコンテナを利用します。
GtkContainer のサブクラスはすべて add にて上に Widget を重ねることができます。
Object Hierarchy: GTK+ 3 Reference Manual で確認してください。
ただし一つしか重ねられない上にエリア一杯に広がるため部品を置く用途には使えない。

縦方向又は横方向に部品を積み重ねるには GtkBox を使います。
レイアウタの概念を使います、ディストロ毎に細かく差がある Linux では必須といえる。
縦又は横を作成時に指定して pack_start | pack_end で配置します。

下記にてアイテムを追加すると増えた Widget 分ウインドウが大きくなるのが解る。
多言語対応でも重なったりはみだしたりということが起こらないのがレイアウタの特徴です。
又、右から読み書きする言語にも柔軟に対応してくれるようです。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        縦: Gtk.Orientation.VERTICAL
        横: Gtk.Orientation.HORIZONTAL
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # 変数
        self.count = 0
        # 縦積みレイアウタ
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        # ボタン
        button = Gtk.Button(label='アイテムを追加する')
        # ハンドラ指定、ここでは user_data に vbox を指定
        button.connect('clicked', self.on_button_clicked, vbox)
        # パッキング
        vbox.pack_start(button, False, False, 0)
        self.add(vbox)
        #
        self.show_all()

    def on_button_clicked(self, button, vbox):
        '''
            ボタンを押すとアイテムがどんどん増える
            いくら追加してもアイテムがはみ出さないのを確認
        '''
        self.count += 1
        label = Gtk.Label(label=f'@ 追加アイテム {self.count:03d}', visible=True)
        vbox.pack_start(label, False, False, 0)

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtkbox.png
GtkScrolledWindow
コンテナに分類されていませんが必須なのでここで解説。
はみ出さないと困る場合もある、画像ビューアやテキストエディタ等の場合である。
その場合は GtkScrolledWindow を間に噛ませスクロールバーを表示させます。
コレが無いとウインドウがどんどん大きくなってしまいます。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        TextBuffer, TextView は別ページで解説
        スクリーンショットでは見えませんが
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # TextView
        buf = Gtk.TextBuffer(text='\n'.join([s for s in 'スズキのバイクはカッコイイ!']))
        textview = Gtk.TextView(buffer=buf)
        # ScrolledWindow
        scroll = Gtk.ScrolledWindow(child=textview)
        self.add(scroll)
        #
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtkscrolledwindow.png
GtkLayout
スクロールバーを出さずにはみ出させたい場合がある。
サイドバー等の大きくなりすぎるのも困る、という場合に有用。
GtkLayout は GtkDrawinArea のような空白ウイジェットですがコンテナです。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        無理矢理 Widget をはみ出しさせる例
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # GtkLayout
        layout = Gtk.Layout()
        # In Button
        button = Gtk.Button(label='スクロールできません', margin = 10)
        layout.add(button)
        self.add(layout)
        self.resize(100, 100)
        #
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtklayout.png
GtkGrid
その名のとおり Excel のような行と列にに配置していくコンテナです。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        grid.attach(child, left, top, width, height)
        or
        grid.attach_next_to(child, sibling, GtkPositionType, width, height)

        親のリサイズに追従するには子ウイジェットの 'expand' プロパティ
        マージンを指定するには子ウイジェットの 'margin' プロパティ
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py', resizable=False)
        # GtkGrid
        grid = Gtk.Grid()
        # 一列目
        grid.attach(Gtk.Label(label='ユーザー名', xalign=0, margin_left=5, margin_right=5), 0, 0, 1, 1)
        grid.attach(Gtk.Entry(expand=True), 1, 0, 1, 1)
        grid.attach(Gtk.Label(label='パスワード', margin_left=5, margin_right=5), 2, 0, 1, 1)
        grid.attach(Gtk.Entry(expand=True), 3, 0, 1, 1)
        # 二列目
        grid.attach(Gtk.Label(label='メールアドレス', xalign=0, margin_left=5, margin_right=5), 0, 1, 1, 1)
        grid.attach(Gtk.Entry(expand=True), 1, 1, 3, 1)
        # 三列目
        grid.attach(Gtk.CheckButton(label='上記で間違いありません'), 0, 2, 3, 1)
        grid.attach(Gtk.Button(label='送信'), 3, 2, 1, 1)
        self.add(grid)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)

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

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        各ページは GtkContainer なら何でもいい
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py', resizable=False)
        # GtkNotebook
        note = Gtk.Notebook()
        # 最初のページ
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        for s in ('バーグマン', 'アドレス', 'スウィッシュ'):
            button = Gtk.CheckButton.new_with_label(s)
            page.pack_start(button, False, False, 0)
        note.append_page(page, Gtk.Label(label='SUZUKI'))
        # 順次
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        for s in ('TMAX', 'XMAX', 'トリシティ'):
            button = Gtk.CheckButton.new_with_label(s)
            page.pack_start(button, False, False, 0)
        note.append_page(page, Gtk.Label(label='YAMAHA'))
        self.add(note)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtknotebook.png
GtkPaned
ウィンドウを区切って二つのペーンに分割するコンテナです。
二つの枠を保持し各枠の中に Widget を一つずつ入れられます、add1() と add2() を使います。
ペーンをマウスで掴めば大きさを変更できます。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        各ページは GtkWidget なら何でもいい
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # Paned
        paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
        # 左サイド
        listbox = Gtk.ListBox()
        listbox.add(Gtk.Label(label='デフォルト', xalign=0))
        listbox.add(Gtk.Label(label='黄色', xalign=0))
        listbox.add(Gtk.Label(label='黄土色', xalign=0))
        paned.add1(listbox)
        # 右サイド
        paned.add2(Gtk.DrawingArea())
        # 指定しなければ左サイドの Widget サイズになる
        #self.resize(300, 100)
        #paned.set_position(100)
        self.add(paned)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtkpaned.png
GtkExpander
クリックすると下にヒョッコリと子 Widget がでてくるコンテナ。
GtkTreeView と違い直下に出てきます、作成は add するだけなので簡単。
階層にすることもできる、実際に動かしてみたほうが早い。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

# エッチな画像を指定してください
FILENAME = 'ero.png'

class Win(Gtk.ApplicationWindow):
    '''
        画像サイズにリサイズされますのでちいさめな画像でお願い
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # GtkExpander
        ex_root = Gtk.Expander(label='Click すると下にもう一つ Expander が出る')
        ex_child = Gtk.Expander(label='Click するとエッチな画像が見られるよ!')
        img = Gtk.Image()
        img.set_from_file(FILENAME)
        ex_child.add(img)
        ex_root.add(ex_child)
        self.add(ex_root)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtkexpander.png
GtkOverlay
その名のとおり上に重ねるウイジェットを指定できるコンテナ。
add_overlay(widget) されたウイジェットが上に被さりますので背景が透けるものを選ぶ。
被さった Widget はフォーカスを持てません、表示されるだけです。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    '''
        GtkEntry の上に GtkLabel を重ねた例
        この状態でも GtkEntry には書き込みできることを確認
        halign, valign を指定しない場合はセンターに表示される
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py', resizable=False)
        # GtkOverlay
        ol = Gtk.Overlay()
        entry = Gtk.Entry(text='GtkEntry の文字列')
        label = Gtk.Label(
            label='<span color="#f00" size="x-large">オーバーレイ</span>',
            use_markup=True,
            halign=Gtk.Align.END,
            valign=Gtk.Align.CENTER )
        ol.add(entry)
        ol.add_overlay(label)
        self.add(ol)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtkoverlay.png
GtkButtonBox
ボタンを右寄せや等間隔で並べるためのコンテナです。
GTK+ 3.12 より GTK_BUTTONBOX_EXPAND というスタイルが追加された。
タッチ操作向けに等幅で枠いっぱいに広がる最近よく見るアレです。
GNOME のデフォルトダイアログも全部コレになってますね。
どんな並び型になるかは下記のコードを動かして確認ください。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

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) )

class Win(Gtk.ApplicationWindow):
    '''
        レイアウトスタイルを全部まとめて
        指定しなければ EDGE になる
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        for key, value in STYLES:
            # GtkButtonBox
            box = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL, layout_style=value)
            box.add(Gtk.Button(label=Gtk.STOCK_YES))
            box.add(Gtk.Button(label=Gtk.STOCK_CANCEL))
            box.add(Gtk.Button(label=Gtk.STOCK_NO))
            frame = Gtk.Frame(label=key, shadow_type=Gtk.ShadowType.IN)
            frame.add(box)
            vbox.pack_start(frame, False, False, 5)
        self.add(vbox)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
container1/gtkbuttonbox.png
GtkFixed
Windows SDK, Cocoa 等と同様な絶対位置配置を行うための古臭いコンテナ。
多言語化どころかフォントやテーマの違いではみ出したり重なったりするのはお馴染。
Linux は Windows や macOS と違い日本語のみの環境でもフォントやテーマがバラバラです。
GNOME は絶対位置配置が必要なら Clutter を使って、というスタンスのようです。
#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

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

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
img/gtk_fixed.png

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

Copyright(C) sasakima-nao All rights reserved 2002 --- 2020.