最近在学习 PyGTK,在学习 GTK+3 参考手册(翻译版),在学习填充窗口中遇到了一个 python 绑定中 GtkBuilder template resource 的坑。

代码文件如下:

#!/usr/bin/env python3

from gi.repository import Gtk, Gio
import sys
import os

class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.set_default_size(400, 200)
        # 首先从资源中加载 ui template 文件
        self.set_template_from_resource('/in/everyx/doubanfmgtk/application.ui')
        # 要记得初始化
        self.init_template()

class DoubanFMGtkApp(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id='in.everyx.doubanfmgtk',
                                 flags=Gio.ApplicationFlags.FLAGS_NONE)

    def do_activate(self):
        main_window = AppWindow(app=self)
        main_window.show_all()

if __name__ == "__main__":

    # 加在资源文件
    resource = Gio.resource_load(os.path.abspath(os.path.join(
        os.path.abspath("__file__"),
        "../../data/doubanfm-gtk.gresource")))
    # 注册资源文件
    Gio.Resource._register(resource)

    app = DoubanFMGtkApp()
    exit_status = app.run(sys.argv)
    sys.exit(exit_status)

运行后,问题来了,一直不能出现正确的窗口,错误提示如下:

(application.py:19907): Gtk-CRITICAL **: Error building template class '__main__+AppWindow' for an instance of type '__main__+AppWindow': Parsed template definition for type `AppWindow', expected type `__main__+AppWindow'.

再看看 ui 文件

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <template class="AppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child type="titlebar">
      <object class="GtkHeaderBar" id="header">
        <property name="visible">True</property>
        <child type="title">
          <object class="GtkStackSwitcher" id="tabs">
            <property name="visible">True</property>
            <property name="margin">6</property>
            <property name="stack">stack</property>
          </object>
        </child>
        <child>
          <object class="GtkToggleButton" id="search">
            <property name="visible">True</property>
            <property name="sensitive">False</property>
            <style>
              <class name="image-button"/>
            </style>
            <child>
              <object class="GtkImage" id="search-icon">
                <property name="visible">True</property>
                <property name="icon-name">edit-find-symbolic</property>
                <property name="icon-size">1</property>
              </object>
            </child>
          </object>
          <packing>
            <property name="pack-type">end</property>
          </packing>
        </child>
      </object>
    </child>
  </template>
</interface>

其中第四行的 class 明明是 AppWindow,但是在 使用 python 绑定的时候居然要用 __main__+AppWindow 这么奇葩的类名,改过来就运行正常了。终于明白为什么看的 PyGTK 项目的代码中都没有用这种方法来创建窗口了,太反人类了。所以下面就用常见的 GtkBuilder 方法来使用 resource。

上面的 ui 文件中,其实就是将 titlebar 设置为 GtkHeaderBar,改造一下 ui 文件,如下:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <object class="GtkHeaderBar" id="headerbar">
    <property name="visible">True</property>
    <property name="show-close-button">True</property>
    <child type="title">
      <object class="GtkStackSwitcher" id="tabs">
        <property name="visible">True</property>
        <property name="margin">6</property>
        <property name="stack">stack</property>
      </object>
    </child>
    <child>
      <object class="GtkToggleButton" id="search">
        <property name="visible">True</property>
        <property name="sensitive">False</property>
        <style>
          <class name="image-button"/>
        </style>
        <child>
          <object class="GtkImage" id="search-icon">
            <property name="visible">True</property>
            <property name="icon-name">edit-find-symbolic</property>
            <property name="icon-size">1</property>
          </object>
        </child>
      </object>
      <packing>
        <property name="pack-type">end</property>
      </packing>
    </child>
  </object>
</interface>

修改代码

class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.set_default_size(400, 200)

        builder = Gtk.Builder()
        builder.add_from_resource('/in/everyx/doubanfmgtk/headerbar.ui')
        headerbar = builder.get_object('headerbar')

        self.set_titlebar(headerbar)

就能正确加载了。

总结

不要使用 GtkBuilder template,使用 GtkBuilder。代码的界面和逻辑分离,使用 ui 文件控制界面,可以将各个部件,如 headerbar,一些 GtkBox 中的内容合理的利用 ui 文件组织起来,在 python 代码中按照需要再组装成界面。代码结构可以参考Gnome Music 项目