Paepoi » PyGObject Tips » Gtk(PyGObject) Tips | コンテナ(3.10 以降)
Gtk(PyGObject) Tips | コンテナ(3.10 以降)
# 最終更新日 2019.08.17
2019 年現在の仕様に追記と書き換え。
数が非常に多いので 3.10 以前からあるもの は別ページにしています。
小さな画面でもクライアントエリアのサイズを最大限に大きくできるメリットがある
add したコンテナやウイジェットを切り替えして一画面として利用します。
切り替えのエフェクトを GtkStackTransitionType で指定することも可能。
GtkStackSwitcher 又は GtkStackSidebar と合わせて使います。
サンプルコードはそちらで。
諸々の指定は GtkStack 側にしてこちらは切り替えだけを行なう感じになります。
GNOME の設定画面で使われています。
親ウインドウの show_all() に左右されず視覚効果も指定できて便利。
また選択状態になった、クリックされた等のシグナルも受け取れます。
Windows でいうところの LISTBOX とは全然違い上記が可能なコンテナです。
Android, iOS のスマートフォン設定画面を思い浮かべると理解しやすい。
GtkBox にダミーを入れて振り分ける必要もう無い(実際に筆者はやっていた)
最上部は GtkHeaderBar があるので最下部で振り分けたい時に便利。
メニューのような単一用途ではなくどんな Widget でも入れることができる。
常に表示は不要だけど即呼び出したい補助機能に利用すると便利。
ポップアップコンテンツをメニューに特化しサブメニュー機能を追加したものです。
メニューアイテムは GtkMenuItem ではなく GtkModelButton を使います。
この GtkModelButton の action-name プロパティに GAction を指定しハンドラにします。
アプリケーションで利用の場合 [app] のプリフィクスが必要。
ウインドウで利用の場合 [win] のプリフィクスが必要。
下記でテキストビューアを作ってみましたのでこんな感じに。
CSS3 の FlexBox みたいなのを期待したけど少し違います。
どうやら最小横幅を基準にコンテンツの高さを決定するようです。
GtkIconView のセルと違いコチラはどんな Widget でも入れられる。
それと GtkListBox 同様にコンテンツを選択することもできる。
2019 年現在の仕様に追記と書き換え。
数が非常に多いので 3.10 以前からあるもの は別ページにしています。
GtkHeaderBar
タイトルバーをコンテナ化して Widget を置くことができるようにしたもの小さな画面でもクライアントエリアのサイズを最大限に大きくできるメリットがある
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Win(Gtk.ApplicationWindow):
'''
set_custom_title で中心
pack_start|end で左右にパッキング
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# 進む戻るボタン
back = Gtk.Button.new_from_icon_name('go-previous-symbolic', Gtk.IconSize.MENU)
forw = Gtk.Button.new_from_icon_name('go-next-symbolic', Gtk.IconSize.MENU)
#back.set_valign(Gtk.Align.CENTER) 不要になった
#forw.set_valign(Gtk.Align.CENTER)
#
# Nautilus の進む戻るボタンの同様に合体させる
bfbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
bfbox.get_style_context().add_class('linked')
bfbox.pack_start(back, False, False, 0)
bfbox.pack_start(forw, False, False, 0)
# メニューボタンが必要になったので
menu = Gtk.Button.new_from_icon_name('open-menu-symbolic', Gtk.IconSize.MENU)
# GtkHeaderBar
hbar = Gtk.HeaderBar(show_close_button=True)
hbar.pack_start(bfbox)
hbar.set_custom_title(Gtk.Label(label='タイトル'))
hbar.pack_end(menu)
# self
self.set_titlebar(hbar)
self.resize(300, 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)
GtkStack
GtkStack はコンテナです、 GtkNotebook のように単独ページを切り替えできます。add したコンテナやウイジェットを切り替えして一画面として利用します。
切り替えのエフェクトを GtkStackTransitionType で指定することも可能。
GtkStackSwitcher 又は GtkStackSidebar と合わせて使います。
サンプルコードはそちらで。
GtkStackSwitcher
GtkStackSwitcher は GtkStack の各ページを切り替えするものです。諸々の指定は GtkStack 側にしてこちらは切り替えだけを行なう感じになります。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Win(Gtk.ApplicationWindow):
'''
スイッチャーをあまり増やせないという欠点がある
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# GtkStack
stack = Gtk.Stack(transition_type=Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
# GtkStackSwitcher
sw = Gtk.StackSwitcher(stack=stack)
# page1
label1 = Gtk.Label(label='かっこいい')
stack.add_titled(label1, 'suzuki', 'スズキ')
# page2
label2 = Gtk.Label(label='平凡')
stack.add_titled(label2, 'yamaha', 'ヤマハ')
# page2
label3 = Gtk.Label(label='カワサキか...')
stack.add_titled(label3, 'kawasaki', 'カワサキ')
# GtkHeaderBar
hbar = Gtk.HeaderBar(show_close_button=True)
hbar.set_custom_title(sw)
# self
self.set_titlebar(hbar)
self.add(stack)
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)
GtkStackSidebar
3.16 で追加された GtkStackSwitcher の派生、サイドバーバージョンです。GNOME の設定画面で使われています。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Win(Gtk.ApplicationWindow):
'''
ちょっとサイドバーの高さが大きいような
CROSSFADE にすると GNOME 設定と同じになる
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# GtkStack
stack = Gtk.Stack(transition_type=Gtk.StackTransitionType.CROSSFADE)
# GtkStackSwitcher
ss = Gtk.StackSidebar(stack=stack)
# page1
label1 = Gtk.Label(label='かっこいい')
stack.add_titled(label1, 'suzuki', 'スズキ')
# page2
label2 = Gtk.Label(label='平凡')
stack.add_titled(label2, 'yamaha', 'ヤマハ')
# page2
label3 = Gtk.Label(label='カワサキか...')
stack.add_titled(label3, 'kawasaki', 'カワサキ')
# Paned
paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
paned.add1(ss)
paned.add2(stack)
# self
self.add(paned)
self.resize(300, 200)
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)
GtkRevealer
GtkRevealer は Widget の表示非表示をアニメーションするコンテナです。親ウインドウの show_all() に左右されず視覚効果も指定できて便利。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
TYPES = dict(
CrossFade= Gtk.RevealerTransitionType.CROSSFADE,
SlideRight=Gtk.RevealerTransitionType.SLIDE_RIGHT,
SlideLeft= Gtk.RevealerTransitionType.SLIDE_LEFT,
SlideUp= Gtk.RevealerTransitionType.SLIDE_UP,
SlideDown= Gtk.RevealerTransitionType.SLIDE_DOWN )
class Win(Gtk.ApplicationWindow):
'''
valign, halign を調節しないと上手く動かない
デフォルトはどちらも GTK_ALIGN_FILL
このサンプルでは一旦 CROSSFADE に変更するとたまに動かなくなる場合あり
通常は途中でアニメーション効果を変えることはないので問題無いけど
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py', resizable=False)
# button
button = Gtk.Button(label='Click')
button.connect('clicked', self.on_button_clicked)
# GtkReveal
self.revealer = Gtk.Revealer(
transition_duration=2000,
transition_type=Gtk.RevealerTransitionType.CROSSFADE,
halign = Gtk.Align.START,
valign = Gtk.Align.START)
# このラベルをアニメーションさせる
label = Gtk.Label(label='<span bgcolor="#0ff" size="x-large">Hello\nWorld</span>', use_markup=True)
self.revealer.add(label)
# パッキング
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
root = None
for key, value in TYPES.items():
r = Gtk.RadioButton(label=key, group=root)
if not root:
root = r
r.connect('toggled', self.on_radio_toggled, value)
hbox.pack_start(r, False, False, 0)
hbox.pack_start(button, False, False, 0)
hbox.pack_start(self.revealer, False, False, 0)
self.add(hbox)
self.show_all()
def on_radio_toggled(self, button, user_data):
# align の調節
if user_data == Gtk.RevealerTransitionType.SLIDE_UP:
self.revealer.props.valign = Gtk.Align.END
elif user_data == Gtk.RevealerTransitionType.SLIDE_LEFT:
self.revealer.props.halign = Gtk.Align.END
else:
self.revealer.props.valign = Gtk.Align.START
self.revealer.props.halign = Gtk.Align.START
# 反映
self.revealer.set_transition_type(user_data)
def on_button_clicked(self, widget):
# アニメーションさせる
self.revealer.set_reveal_child(not self.revealer.props.reveal_child)
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)
GtkListBox
GtkListBox は GtkBox をリストビューのように選択可能にするコンテナです。また選択状態になった、クリックされた等のシグナルも受け取れます。
Windows でいうところの LISTBOX とは全然違い上記が可能なコンテナです。
Android, iOS のスマートフォン設定画面を思い浮かべると理解しやすい。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
DATA = ['YAMAHA', 'HONDA', 'KAWASAKI', 'SUZUKI']
class Win(Gtk.ApplicationWindow):
'''
どう動くかだけのサンプルコード
以前は GtkCssProvider の例も書いていたけど繁栄されなくなったので削除
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py', resizable=False)
# ListBox
listbox = Gtk.ListBox()
listbox.connect('row-activated', self.on_listbox_row_activated)
for i, data in enumerate(DATA):
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
hbox.pack_start(Gtk.Label(label=data), False, False, 0)
hbox.pack_end(Gtk.Switch(), False, False, 0)
listbox.insert(hbox, i)
# Selection
combobox = Gtk.ComboBoxText()
combobox.append_text('Gtk.SelectionMode.NONE')
combobox.append_text('Gtk.SelectionMode.SINGLE')
combobox.set_active(listbox.get_selection_mode())
combobox.connect('changed', self.on_combobox_changed, listbox)
# CheckButton
checkbutton = Gtk.CheckButton(label='Single Click Mode')
checkbutton.set_active(listbox.get_activate_on_single_click())
checkbutton.connect('clicked', self.on_checkbutton_clicked, listbox)
# Pack
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
vbox.pack_start(listbox, False, False, 0)
vbox.pack_start(combobox, False, False, 0)
vbox.pack_start(checkbutton, False, False, 0)
self.add(vbox)
self.set_border_width(10)
self.show_all()
def on_listbox_row_activated(self, listbox, row):
s = DATA[row.get_index()]
self.props.title = f'{s} を選択'
def on_combobox_changed(self, combo, listbox):
b = combo.get_active() == 1
listbox.set_selection_mode(b)
def on_checkbutton_clicked(self, button, listbox):
b = button.get_active()
listbox.set_activate_on_single_click(b)
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)
GtkActionBar
GtkActionBar は Widget 配置を左右及び中心に綺麗に振り分けるコンテナ。GtkBox にダミーを入れて振り分ける必要もう無い(実際に筆者はやっていた)
最上部は GtkHeaderBar があるので最下部で振り分けたい時に便利。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Win(Gtk.ApplicationWindow):
'''
関数は GtkHeaderBar と同じ
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# クライアントエリア(ダミー)
area = Gtk.DrawingArea()
# 中心
c1 = Gtk.Button.new_from_icon_name('go-previous-symbolic', Gtk.IconSize.MENU)
c2 = Gtk.Button.new_from_icon_name('camera-photo-symbolic', Gtk.IconSize.MENU)
c3 = Gtk.Button.new_from_icon_name('go-next-symbolic', Gtk.IconSize.MENU)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
hbox.pack_start(c1, False, False, 0)
hbox.pack_start(c2, False, False, 0)
hbox.pack_start(c3, False, False, 0)
hbox.get_style_context().add_class('linked')
# 左右
left = Gtk.Button.new_from_icon_name('user-trash-symbolic', Gtk.IconSize.MENU)
right = Gtk.Button.new_from_icon_name('open-menu-symbolic', Gtk.IconSize.MENU)
# GtkActionBar
bar = Gtk.ActionBar()
bar.pack_start(left)
bar.set_center_widget(hbox)
bar.pack_end(right)
# pack
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
vbox.pack_start(area, True, True, 0)
vbox.pack_start(bar, False, False, 0)
# self
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)
GtkPopover
GtkPopover はコンテンツをポップアップする Widget です。メニューのような単一用途ではなくどんな Widget でも入れることができる。
常に表示は不要だけど即呼び出したい補助機能に利用すると便利。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Win(Gtk.ApplicationWindow):
'''
関数は GtkHeaderBar と同じ
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# クライアントエリア(ダミー)
area = Gtk.DrawingArea()
# このボタンでポップアップさせる
button = Gtk.Button(label='アイテム')
button.connect('clicked', self.on_option_button_clicked)
# GtkActionBar
bar = Gtk.ActionBar()
bar.pack_end(button)
# ポップアップコンテンツ
c1 = Gtk.CheckButton(label='ムチを使う')
c2 = Gtk.CheckButton(label='ロウソクを使う')
c3 = Gtk.CheckButton(label='三角木馬を使う')
popbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
popbox.pack_start(c1, False, False, 0)
popbox.pack_start(c2, False, False, 0)
popbox.pack_start(c3, False, False, 0)
# GtkPopover
self.pop = Gtk.Popover(relative_to=button, child=popbox)
# パッキング
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
vbox.pack_start(area, True, True, 0)
vbox.pack_start(bar, False, False, 0)
self.add(vbox)
self.show_all()
def on_option_button_clicked(self, widget):
'''
コンテンツ Widget は非表示になっている
一括表示でいいなら show_all でいい
'''
self.pop.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)
GtkPopoverMenu
GtkPopoverMenu は GtkPopover のサブクラス。ポップアップコンテンツをメニューに特化しサブメニュー機能を追加したものです。
メニューアイテムは GtkMenuItem ではなく GtkModelButton を使います。
この GtkModelButton の action-name プロパティに GAction を指定しハンドラにします。
アプリケーションで利用の場合 [app] のプリフィクスが必要。
ウインドウで利用の場合 [win] のプリフィクスが必要。
下記でテキストビューアを作ってみましたのでこんな感じに。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class Win(Gtk.ApplicationWindow):
'''
サブメニュー不要なら GtkPopover でよかったりする
サブメニューについては別ページを後で作る予定
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# このボタンでポップアップさせる
button = Gtk.Button.new_from_icon_name('open-menu-symbolic', Gtk.IconSize.MENU)
button.connect('clicked', self.on_option_button_clicked)
# GtkModelButton
menu_open = Gtk.ModelButton(active=True, action_name='app.new_file_action', text='開く(_O)', use_markup=True)
menu_new = Gtk.ModelButton(active=True, action_name='app.new_window_action', text='新しいウインドウ(_N)', use_markup=True)
menu_quit = Gtk.ModelButton(active=True, action_name='app.quit_action', text='終了(_Q)', use_markup=True)
# ポップアップコンテンツ(メニュー)
vbox = Gtk.Box(visible=True, margin=10, orientation=Gtk.Orientation.VERTICAL)
vbox.pack_start(menu_open, False, False, 0)
vbox.pack_start(menu_new, False, False, 0)
vbox.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
vbox.pack_start(menu_quit, False, False, 0)
# GtkPopoverMenu
self.popovermenu = Gtk.PopoverMenu(relative_to=button, child=vbox)
# GtkHeaderBar
hbar = Gtk.HeaderBar(show_close_button=True)
hbar.pack_end(button)
# GtkTextView and GtkScrolledWindow
self.view = Gtk.TextView()
sw = Gtk.ScrolledWindow(child=self.view)
# self
self.set_titlebar(hbar)
self.add(sw)
self.show_all()
def on_option_button_clicked(self, widget):
self.popovermenu.show_all()
def open_dialog(self):
dlg = Gtk.FileChooserNative(
title='Open',
transient_for=self,
action=Gtk.FileChooserAction.OPEN)
if dlg.run() == Gtk.ResponseType.ACCEPT:
#self.set_uri(dlg.get_uri())
with open(dlg.get_filename()) as f:
s = f.read()
self.view.get_buffer().set_text(s)
dlg.destroy()
class App(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(self)
def do_startup(self):
Gtk.Application.do_startup(self)
# GAction 作成
new_window_action = Gio.SimpleAction(name='new_window_action')
new_file_action = Gio.SimpleAction(name='new_file_action')
quit_action = Gio.SimpleAction(name='quit_action')
# 追加
self.add_action(new_window_action)
self.add_action(new_file_action)
self.add_action(quit_action)
# アクセラレーターの指定
self.set_accels_for_action('app.new_window_action', ['<Control>N'])
self.set_accels_for_action('app.new_file_action', ['<Control>O'])
self.set_accels_for_action('app.quit_action', ['<Control>Q'])
# シグナル
new_window_action.connect('activate', self.on_new_window_action)
new_file_action.connect('activate', self.on_new_file_action)
quit_action.connect('activate', self.on_quit_action)
# Window を作る
Win(self)
def on_new_file_action(self, action, parameter):
self.props.active_window.open_dialog()
def on_new_window_action(self, action, parameter):
Win(self)
def on_quit_action(self, action, parameter):
self.quit()
def do_activate(self):
self.props.active_window.present()
app = App()
app.run(sys.argv)
GtkFlowBox
GtkFlowBox は Widget を幅に合わせて自動的に改行するコンテナ。CSS3 の FlexBox みたいなのを期待したけど少し違います。
どうやら最小横幅を基準にコンテンツの高さを決定するようです。
GtkIconView のセルと違いコチラはどんな Widget でも入れられる。
それと GtkListBox 同様にコンテンツを選択することもできる。
#!/usr/bin/env python3
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, GdkPixbuf
# 画像が沢山あるディレクトリに書き換えしてください
PICTURES = '/home/sasakima-nao/www/tips/pygobject/container1'
class Win(Gtk.ApplicationWindow):
'''
サムネイルされた後にウインドウのサイズを変更すると動作が解る
valign の指定は必須だったけど不要になったみたい
'''
def __init__(self, app):
Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
# GtkFlowBox
flowbox = Gtk.FlowBox()#valign=Gtk.Align.START)
# 指定ディレクトリのファイルを探す
d = Gio.file_new_for_path(PICTURES)
enum = d.enumerate_children(Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0)
for info in enum:
content_type = info.get_content_type()
if content_type == 'image/jpeg' or content_type == 'image/png':
fullpath = f'{PICTURES}/{info.get_name()}'
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(fullpath, 100, 100, True)
image = Gtk.Image(pixbuf=pixbuf)
flowbox.add(image)
scroll = Gtk.ScrolledWindow(child=flowbox)
# self
self.add(scroll)
self.resize(400, 300)
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)
Copyright(C) sasakima-nao All rights reserved 2002 --- 2025.