Paepoi

Paepoi » PyGObject Tips » Gtk(PyGObject) Tips | ダイアログを作る

Gtk(PyGObject) Tips | ダイアログを作る

# 最終更新日 2019.08.13

2019 年現在の仕様に追記と書き換え。
PyGtk 互換が非推奨になったようなので細かい書き直し。
及び文字列をシングルクォートに統一等を行いました。

GtkDialog
ダイアログといえば設定ダイアログのような補助的なものを思い浮かべますが単体でも利用できます。
メインループ処理は不要で run() で表示し destroy() で破棄という流れで使います。
Esc キーを押すと自動でキャンセル扱いになるという特徴もあります。

ちなみに PyGtk 互換のインスタンス化もまだ使える gtk.Dialog
ただし非推奨になったのか stderr を吐くようになったので使わないほうが懸命。
このページでも以前はこの方法を書いていましたが削除しました。
2012 年で開発の終わった PyGtk といつまで互換を続けるか解らないし。
# Compatibility PyGtk
d = Gtk.Dialog(buttons=('Click', Gtk.ResponseType.CLOSE)) #=> stderr

ということで PyGObject では C 同様に作ります。
ただ GtkDialog のバインディングは何故か new_with_buttons() 関数が利用できません。
そのためプロパティを使いたい所ですが何故か存在しません、サブクラスには色々あるのですが。
GtkDialog をそのまま使うには作成した後に関数を使ってボタンを追加する必要があります。
GtkWindow のサブクラスなので GtkWindow のプロパティはそのまま使えます。
#!/usr/bin/env python3

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

# 最小限のダイアログ

dlg = Gtk.Dialog() # 作成
dlg.add_button('Click', Gtk.ResponseType.CLOSE) # ボタンを追加
dlg.run() # 表示、メインループ不要
dlg.destroy() # 破棄

本来 GtkDialog には無い PyGtk と同様の vbox という属性がありそのままパッキングできます。
ダイアログはリサイズ可能にさせたくない場合が多い、その場合 set_resizable(False) を指定。
#!/usr/bin/env python3

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

class Dlg(Gtk.Dialog):
    def __init__(self):
        '''
            # PyGtk 互換の以下指定でも一応動きますが stderr 吐きまくりになります
            # 互換もいつまで続けるかわからないので定石どおりに作ったほうが懸命
            Gtk.Dialog.__init__(self, 'Titlebar', None, Gtk.DialogFlags.MODAL,
                (Gtk.STOCK_YES, Gtk.ResponseType.YES,Gtk.STOCK_NO, Gtk.ResponseType.NO))
        '''
        Gtk.Dialog.__init__(self, title='Titlebar')
        # ストックを使う場合
        self.add_button(Gtk.STOCK_YES, Gtk.ResponseType.YES)
        self.add_button(Gtk.STOCK_NO, Gtk.ResponseType.NO)
        # ラベル
        label = Gtk.Label(label='好きなバイクを選んで下さい')
        # パッキング
        #self.vbox = self.get_content_area() 不要
        self.vbox.pack_start(label, False, False, 0)
        for s in ['刀', 'GSX-R1000', '隼', 'V-ストローム1000XT']:
             b = Gtk.CheckButton(label=s)
             self.vbox.pack_start(b, False, False, 0)
        self.set_resizable(False)
        self.show_all()

dlg = Dlg()
dlg.run()
dlg.destroy()

GtkMessageDialog
GtkMessageDialog は GtkDialog のサブクラスで文字列の表示に特化した Widget です。
GtkDialog を使って同じことが可能ですが警告文の表示等に手間がかからず便利です。

こちらにはプロパティが沢山あるので使い易い、プロパティ名で何がドレなのか大体解ると思う。
ただ、いつからか忘れたけど GtkMessageType を指定しても指定アイコンは表示されなくなった。

GtkButtonsType は GtkDialog より簡素化されて ResponseType や複数ボタンもセットです。
NONE, OK, CLOSE, CANCEL, YES_NO, OK_CANCEL
#!/usr/bin/env python3

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

message = '「はい」を押すと終了'

while True:
    dlg = Gtk.MessageDialog(
        #transient_for=window   # 親ウインドウがある場合に利用、自動でモーダルになる
        #message_type=Gtk.MessageType.QUESTION, # 表示されなくなった
        buttons=Gtk.ButtonsType.YES_NO,
        text='スズキのバイクはかっこいい',
        secondary_text=message,
        secondary_use_markup=True )
    # 「はい」のほうにフォーカスを当てる
    dlg.set_default_response(Gtk.ResponseType.YES)
    # 開始
    r = dlg.run()
    dlg.destroy()
    # 判定
    if r == Gtk.ResponseType.YES:
        break
    elif r == Gtk.ResponseType.NO:
        message = 'それは<b>いいえ</b>でしょ!'
    elif r == Gtk.ResponseType.DELETE_EVENT:
        message = '<span bgcolor="red">Esc</span> 押さないで!'
    else:
        message = '不明な動作...'

GtkAboutDialog
GtkAboutDialog は掟に従って作成するだけです。
全部プロパティだけで設定できるようになりました。

普通は親ウインドウがある状態で呼び出すものなのでコメントアウト部分のように。
self にアイコン指定済みなら上記関数でアイコンを利用できる。
GTK2 時と違い set_url_hook しなくても URL は開ける。
#!/usr/bin/env python3

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

w = Gtk.AboutDialog(
    #transient_for=self, # 親ウインドウ
    #logo=self.get_icon(), # アイコン
    program_name='SUZUKI',
    version='1.0.0',
    comments='スズキは車もカッコイイ!',
    website='http://localhost/',
    website_label='localhost', # 指定しなければ「ホームページ」と表示
    copyright='Copyright(C) 2019 sasakima-nao',
    # 以下ライセンスボタン、指定しなければボタンは出ない
    license='GPL',
    # 以下クレジットボタン、指定しなければボタンは出ない、配列に注意
    authors=['sasakima-nao'],     # 開発担当
    artists=['sasakima-nao'],     # アートワーク担当
    documenters=['sasakima-nao'], # ドキュメント担当
    translator_credits='sasakima-nao' # 翻訳担当、ここは str
)
w.run()
w.destroy()

GtkAssistant
GtkAssistant は Windows でいうウィザードです。
iPhone の初期設定のようなもの、と書いたほうが今の若い人には解りやすいかも。
対話形式で質問に答えていくことで複雑な処理を簡易に行う一つの手段である。

set_page_type() でページの種類を指定
set_page_complete() で次に進むボタンの ON/OFF
が行えます、特化しすぎているためか何も以前と変わっていない稀有な例。
#!/usr/bin/env python3

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

class AssistantWin(Gtk.Assistant):
    def __init__(self):
        '''
            4 ページあるサンプル
        '''
        Gtk.Assistant.__init__(self)
        self.connect('cancel', self.on_close)
        self.connect('close', self.on_close)
        self.connect('apply', self.on_apply)
        # Pages
        for title in ['最初に', 'コンテンツ', '進展状況', '完了']:
            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            self.append_page(vbox)
            self.set_page_title(vbox, title)
        # 最初に
        label0 = Gtk.Label(label='導入ページ - 進んでください')
        page = self.get_nth_page(0)
        page.pack_start(label0, False, False, 0)
        self.set_page_type(page, Gtk.AssistantPageType.INTRO)
        self.set_page_complete(page, True)
        # コンテンツ
        label1 = Gtk.Label(label='コンテンツページ - チェックすると次に進めます')
        check = Gtk.CheckButton(label='スズキはスクーターもかっこいい')
        check.connect('toggled', self.on_checkbutton_toggled)
        page = self.get_nth_page(1)
        page.pack_start(label1, False, False, 0)
        page.pack_start(check, False, False, 0)
        self.set_page_type(page, Gtk.AssistantPageType.CONTENT)
        # 進展状況
        label2 = Gtk.Label(label='進度ページ - 本当ですか?')
        r1 = Gtk.RadioButton(label='いいえ')
        r2 = Gtk.RadioButton(label='はい', group=r1)
        r2.connect('toggled', self.on_radiobutton_toggled)
        page = self.get_nth_page(2)
        page.pack_start(label2, False, False, 0)
        page.pack_start(r1, False, False, 0)
        page.pack_start(r2, False, False, 0)
        self.set_page_type(page, Gtk.AssistantPageType.CONTENT)
        self.set_page_complete(page, True)
        # 完了
        self.last_label = Gtk.Label(label='そうですか...')
        page = self.get_nth_page(3)
        page.pack_start(self.last_label, False, False, 0)
        self.set_page_type(page, Gtk.AssistantPageType.SUMMARY)
        self.set_page_complete(page, True)
        #
        self.show_all()

    def on_close(self, assistant):
        '''
            delete-event もココにくる
        '''
        Gtk.main_quit()

    def on_apply(self, assistant):
        '''
            コンプリート(CONFIRM)でココにくる
            close シグナルも発生するので main_quit 処理は不要
        '''
        dlg = Gtk.MessageDialog(
            transient_for=self,
            buttons=Gtk.ButtonsType.OK,
            text='やっぱりスズキが一番!')
        dlg.run()
        dlg.destroy()

    def on_checkbutton_toggled(self, widget):
        '''
            次に進むボタンの ON/OFF
        '''
        self.set_page_complete(self.get_nth_page(1), widget.get_active())

    def on_radiobutton_toggled(self, widget):
        '''
            CONFIRM, SUMMARY の振り分け
        '''
        if widget.get_active():
            self.last_label.set_text('適用してください')
            self.set_page_type(self.get_nth_page(3), Gtk.AssistantPageType.CONFIRM)
        else:
            self.last_label.set_text('そうですか...')
            self.set_page_type(self.get_nth_page(3), Gtk.AssistantPageType.SUMMARY)

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