Paepoi

Paepoi » GTK4(Python) Tips » GTK4(Python) Tips | GtkApplicationWindow

GTK4(Python) Tips | GtkApplicationWindow

# 最終更新日 2022.04.24

GtkApplicationWindow
GTK4 になりメインループを開始する gtk_main 関数が廃止されました。
GMainLoop でも一応動くようですがどこにも解説されておらず非推奨のようです。
GUI アプリを作る場合は必ず GtkApplication を使う必要があります。

ウインドウは GtkApplicationWindow という GtkWindow のサブクラスを使います。
application プロパティは GtkWindow 側にありますがコチラを使えということみたいです。
GTK4 では GtkWindow 自体は継承して使う Interface として提供されている感じです。

ウインドウ側にて close-request シグナルで破棄を行う処理も必要もありません。
閉じるボタンを押す(close-request 発生)で GtkApplication と切り離され自動的に破棄されます。

関数も GTK3 の時といくつか変わっているので以下に基本的サンプルを。
#!/usr/bin/env python3
 
import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gio

class Win(Gtk.ApplicationWindow):
    '''
        GTK4 でウインドウを作るサンプル
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, title='Title', application=app)
        self.button = Gtk.Button(label='ハローワールド')
        self.button.connect('clicked', self.on_button_connect)
        # add は set_child に変わりました
        self.set_child(self.button)
        # resize は set_default_size に変わりました
        self.set_default_size(320, 240)
        # show_all は無くなり widget はデフォルトで表示に変わりました
        # delete-event は close-request に変わりましたが処理不要です

    def on_button_connect(self, button):
        '''
            ボタンを押す毎にハローワールドが増える
        '''
        button.props.label += '\nハローワールド'

class App(Gtk.Application):
    '''
        GTK4 では gtk_main は使えません
    '''
    def __init__(self):
        '''
            初期化、引数を処理しないならコレだけでいい
        '''
        Gtk.Application.__init__(self)

    def do_startup(self):
        '''
            最初のウインドウは基本的にココで作ります
        '''
        Gtk.Application.do_startup(self) # startup をオーバーライドする場合必須
        Win(self)

    def do_activate(self):
        '''
            アプリケーションがアクティブになったらココにきます
            HANDLES_COMMAND_LINE 指定の場合は無視されるので注意
        '''
        self.props.active_window.present() # ウインドウのアクティブ化

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

パッキング
GtkBox を使ったパッキングは GTK4 で完全に変更されました。
親コンテナ側で指定していたパッキング情報は子 Widget のプロパティに移管されています。
pack_start(child, expand, fill, padding)
# 上記パッキング情報は子 Widget の以下プロパティで指定
expand:  hexpand|vexpand
fill:    halign|valign
padding: margin-top|margin-botom|margin-start|margin-end
何をどう指定すればどういう動作になるのかは御自身で色々試してみてください。
ウインドウサイズを変更しないアプリなら単純になったとも言えます。
#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        縦: Gtk.Orientation.VERTICAL
        横: Gtk.Orientation.HORIZONTAL
    '''
    def __init__(self, app):
        '''
            リサイズするとボタンのほうだけ大きくなるサンプル
        '''
        Gtk.ApplicationWindow.__init__(self, title='Title', application=app)
        # 縦型コンテナの作成
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        # ボタン
        button = Gtk.Button(label='ボタン', vexpand=True)
        button.connect('clicked', self.on_button_clicked)
        # 一行エディットは後で参照するので self にくっつける
        self.entry = Gtk.Entry(editable=False, text='ボタンを押せ')
        # pack_start pack_end は append に変わりました
        vbox.append(button)
        vbox.append(self.entry)
        self.set_child(vbox)

    def on_button_clicked(self, widget):
        self.entry.set_text('Hello World')

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)

枠無しウインドウ
枠無しウインドウは GTK3 同様に decorated プロパティを使います。
ただしそれだけではなく CSS にて透過させる処理が必要になりました。
デスクトップマスコット等の背景透過画像を表示させるには以下のようにします。
又タイトルバーが無い状態でもマウスで動かせるようにする処理も入れています。
#!/usr/bin/env python3
 
import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GdkPixbuf, Gdk, Gio

# Picture File
PNGFILE = 'test.png'

# CSS
APP_CSS = 'window { background-color: rgba(255, 255, 255, 0); }'.encode('utf-8')

class Win(Gtk.ApplicationWindow):
    '''
        GTK4: No Decorated Window
    '''
    def __init__(self, app):
        try:
            Gtk.ApplicationWindow.__init__(self, application=app, decorated=False)
            # Transparent
            provider = Gtk.CssProvider()
            provider.load_from_data(APP_CSS)
            context = self.get_style_context()
            context.add_provider_for_display(
                self.get_display(),
                provider,
                Gtk.STYLE_PROVIDER_PRIORITY_USER)
            # Mouse Move Signal
            click = Gtk.GestureClick()
            click.connect('pressed', self.on_gesture_click_pressed)
            self.add_controller(click)
            # Draw
            self.pixbuf = GdkPixbuf.Pixbuf.new_from_file(PNGFILE)
            da = Gtk.DrawingArea()
            da.set_draw_func(self.da_draw_func)
            self.set_child(da)
            # Resize
            self.set_default_size(self.pixbuf.get_width(), self.pixbuf.get_height())
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()

    def da_draw_func(self, da, cr, width, height):
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
        cr.paint()

    def on_gesture_click_pressed(self, click, n_press, x, y):
        '''
            GTK4: gtk_window_begin_move_drag
        '''
        button = click.get_button()
        toplevel = self.get_surface() # GdkToplevel
        display = self.get_display()
        seat = display.get_default_seat()
        device = seat.get_pointer()
        s, win_x, win_y = device.get_surface_at_position()
        #print(f'{win_x}, {win_y}')
        time = device.get_timestamp()
        toplevel.begin_move(device, button, win_x, win_y, time)

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # set Ctrl+Q
        self.set_accels_for_action('app.quit_action', ['<Control>Q'])
        quit_action = Gio.SimpleAction(name='quit_action')
        self.add_action(quit_action)
        quit_action.connect('activate', lambda a, p: self.quit())
        #
        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 --- 2023.