# gtkmm4 程序设计

Murray Cumming
Bernhard Rieder Chapter on "Timeouts".
Jonathon Jongsma Chapter on "Drawing with Cairo". Chapter on "Working with gtkmm's Source Code". Chapter on "Recent Files".
Ole Laursen Parts of chapter on "Internationalization".
Marko Anastasov Chapter on "Printing". Parts of chapter on "Internationalization".
Daniel Elstner Section "Build Structure" of chapter on "Wrapping C Libraries with gmmproc".
Chris Vine Chapter on "Multi-threaded programs".
David King Section on Gtk::Grid.
Pedro Ferreira Chapter on "Keyboard Events".
Kjell Ahlstedt Update from gtkmm 3 to gtkmm 4. Chapter on "Building applications". Chapter on "The DropDown Widget". Chapter on "ListView, GridView, ColumnView".

本书说明了使用gtkmm C++ API 创建用户界面的重要概念，并且介绍了主要的用户界面元素(“widget”)。

2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Murray Cumming

根据由自由软件基金发布的 GNU 自由文档协议版本1.2或更高版本的条款，授权复制、分发和/或修改此文档；并且，没有不可变章节，没有封面文本，并且没有封底文本。您可以通过访问自由软件基金(FSF)的网站或写邮件至：Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 来获得一份 GNU 自由文档协议。

## 序言

### 本书

本书说明了使用gtkmm C++ API 创建用户界面的重要概念，并且介绍了主要的用户界面元素(“widget”)。虽然提到了类、构造函数和方法，但是并不会深入其细节。因此，如果需要完整的 API 信息，您应当顺着链接访问参考文档。

本书假定读者拥有对 C++ 良好的理解，并且知道如何去创建一个 C++ 程序。

我们非常愿意聆听您在使用此文档学习 gtkmm 时碰到的任何问题，并且感激对此作出的改进。请访问 贡献 节以获得进一步的信息。

### gtkmm

gtkmm is a C++ wrapper for GTK, a library used to create graphical user interfaces. It is licensed using the LGPL license, so you can develop open software, free software, or even commercial non-free software using gtkmm without purchasing licenses.

gtkmm 曾经被称为 gtk--，那是因为 GTK+ 的名字中已经有了一个加号。但是，由于 -- 不容易被搜索引擎索引，所以项目名字就渐渐的变成了gtkmm，后来我们就一直使用这个名字。

#### 为什么要使用 gtkmm 而不是 GTK？

gtkmm allows you to write code using normal C++ techniques such as encapsulation, derivation, and polymorphism. As a C++ programmer you probably already realize that this leads to clearer and better organized code.

gtkmm 更加类型安全，因此编译器可以检测出一些 C 只能在运行时才能检测出来的错误。这种使用特定类型的方法同样使得 API 更加清晰，因为你只需要通过看一下函数声明就可以知道应该使用什么类型了。

可以使用继承来衍生新的部件。在 GTK 中使用 C 代码来衍生新部件是非常复杂的，并且很容易出错，因此几乎没有 C 的程序员这么做。作为 C++ 开发人员，你知道继承是一个基本的面向对象技术。

可以使用成员实例，简化了内存管理。所有的 GTK 的 C 部件都是使用指针进行操作的。作为 C++ 程序员，你知道指针应当尽量避免使用。

gtkmm 比 GTK 的代码更加简短，GTK 使用了大量带前缀的函数名以及大量的转型宏。

#### gtkmm 对比 Qt

Trolltech 公司的 Qt 是和 gtkmm 最相似的有竞争力的产品，所以应该对其进行一下讨论。

gtkmm developers tend to prefer gtkmm to Qt because gtkmm does things in a more C++ way. Qt originates from a time when C++ and the standard library were not standardized or well supported by compilers. It therefore duplicates a lot of stuff that is now in the standard library, such as containers and type information. Most significantly, Trolltech modified the C++ language to provide signals, so that Qt classes cannot be used easily with non-Qt classes. gtkmm was able to use standard C++ to provide signals without changing the C++ language. See the FAQ for more detailed differences.

#### gtkmm 是一个封装

gtkmm 并不是一个原生的 C++ 工具集，而是一个对于 C 工具集的 C++ 封装。这种分离接口和实现的方式存在诸多优势。gtkmm 开发人员用了他们大量的时间来讨论如何让 gtkmm 提供一个最清晰的 API，而不必为模糊的技术细节尴尬的进行妥协。我们想其他的 C 程序员、Perl 程序员和 Python 程序员等等一样，对底层的 GTK 代码库进行了一点点贡献。因此，GTK 可以从比某个语言特定的工具集更广泛的用户群中获得好处，有更多的实现、更多的开发人员、更多的测试人员以及更多的用户。

## 安装

### 依赖关系

在试图安装 gtkmm-4.0之前，你必须已经安装了这些包。

- sigc++-3.0

- gtkmm4

- glibmm-2.68

- cairomm-1.16

- pangomm-2.48

这些依赖有它们自己所依赖的软件包，包括下列应用程序和软件库：

- pkg-config

- glib-2.0

- pango

- cairo

- gdk-pixbuf-2.0

- graphene-1.0

### Unix 和 Linux

#### 预编译的包

gtkmm 最新的版本的软件包几乎存在于今天每一个主流的 Linux 发布版本中。所以，如果你使用 Linux，你可能只需要从官方的软件库安装相应的软件包，然后就可以开始使用 gtkmm 了。已经在它们的软件库里面包含了 gtkmm 的发布版本有：Debian、Ubuntu、Red Hat、Fedora、Mandriva、SuSE 等等。

The names of the gtkmm packages vary from distribution to distribution (e.g. libgtkmm-4.0-dev on Debian and Ubuntu or gtkmm4.0-devel on Red Hat and Fedora), so check with your distribution's package management program for the correct package name and install it like you would any other package.

当新的 API/ABI 兼容的 gtkmm 版本发布后，其包的名称将不会改变。否则的话，就说明 API/ABI 不兼容。所以，如果在 Debian 的 libgtkmm-4.0-dev 包中发现提供的实际上是 gtkmm 4.8 的话不要太惊讶。

#### 从源代码安装

If your distribution does not provide a pre-built gtkmm package, or if you want to install a different version than the one provided by your distribution, you can also install gtkmm from source. The source code for gtkmm can be downloaded from .

在安装了所有依赖的库后，下载 gtkmm 源代码、解压缩，并且切换到新创建的目录。gtkmm可以使用Meson构建。 参见你所下载的gtkmm源代码中的`README`。

记住，在 Unix 或 Linux 操作系统上，你也许需要 `root` 权限以安装软件。`su`或者`sudo` 命令允许你输入 `root` 密码，并且临时拥有 `root` 权限。

`configure`脚本或者`meson`将会检查以确认所有必须的依赖软件包都已经正确的安装了。如果你遗漏了某个依赖软件包的话，它会退出并提示错误信息。

By default, gtkmm if built with Meson or Autotools, will be installed under the `/usr/local` directory. On some systems you may need to install to a different location. For instance, on Red Hat Linux systems you might use the `--prefix` option with configure, like one of:

    # meson setup --prefix=/usr <builddir> <srcdir>
    # meson configure --prefix=/usr
    # ./configure --prefix=/usr

在安装到标准系统前缀时，如 `/usr`，你必须非常小心。Linux 发行版会安装软件到 `/usr`，所以安装源代码包到这个位置可能会破坏或与使用软件包管理器安装的软件冲突。理想情况下，你应当将从源代码安装的软件都安装到一个独立位置。

如果你想帮助开发 gtkmm，或实验一些新的功能，你也可以直接从 Git 安装 gtkmm。大多数用户绝对不需要这么做，但是如果你对帮助 gtkmm 开发感兴趣，请看附录：使用 gtkmm 源代码。

### Microsoft Windows

GTK and gtkmm were designed to work well with Microsoft Windows, and the developers encourage its use on the win32 platform. However, Windows has no standard installation system for development libraries. Please see the Windows Installation page or the gtkmm and Win32 appendix for Windows-specific installation instructions and notes.

## 基础

这一章将介绍 gtkmm 编程中一些重要的方面。这些将由一些可以运行的示例代码来演示。然而，这仅仅是一种尝试，你还需要继续看其它章节，以得到更实质性的信息。

你现有的 C++ 知识将会帮助你使用 gtkmm，因为它可以和任何库一同工作。除非我们额外声明，你可以期待 gtkmm 类会像任何其它 C++ 类一样，并且你可以期待在 gtkmm 类上使用你现有的知识。

### 简单的例子

在开始介绍 gtkmm 之前，我们将以一个尽可能简单的程序开始。这个程序创建一个 200 x 200 像素大小的空窗口。

Source Code

File: `base.cc` (For use with gtkmm 4)

    #include <gtkmm.h>

    class MyWindow : public Gtk::Window
    {
    public:
      MyWindow();
    };

    MyWindow::MyWindow()
    {
      set_title("Basic application");
      set_default_size(200, 200);
    }

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.examples.base");

      return app->make_window_and_run<MyWindow>(argc, argv);
    }

我们将会对这个例子的每个部分都进行讲解

    #include <gtkmm.h>

所有的 gtkmm 程序都需要包含一些特定的 gtkmm 头文件；`gtkmm.h` 包含了所有 gtkmm 的内容。通常来说，这并不是一个好主意，因为它包括了一兆左右的头文件。当然，对于这个简单的程序，这就够了。

这个程序的下一部分：

    class MyWindow : public Gtk::Window
    {
    public:
      MyWindow();
    };

    MyWindow::MyWindow()
    {
      set_title("Basic application");
      set_default_size(200, 200);
    }

定义MyWindow类。这个类的构造函数设置了窗口标题和窗口的初始默认尺寸。

`main()`函数的第一条语句：

    auto app = Gtk::Application::create("org.gtkmm.examples.base");

创建一个Gtk::Application对象，将其储存在一个Glib::RefPtr智能指针中。所有的gtkmm应用都需要如此做。该对象的create()方法将初始化gtkmm。

最后一行显示窗口并进入gtkmm主循环，当窗口被关闭。你的`main()`函数将会返回成功或者适当的错误码。argc和argv参数会将命令行传递给你的应用程序，可以在make_window_and_run()被调用时对其进行检查，不过这个简单的应用程序并不会如此做。

    return app->make_window_and_run<MyWindow>(argc, argv);

将源代码放入`simple.cc`后你可以使用以下gcc命令编译上述程序：

    g++ simple.cc -o simple `pkg-config --cflags --libs gtkmm-4.0` -std=c++17

注意，你必须将 `pkg-config` 的调用包含在一对反单引号中。反单引号会导致其内的命令由 shell 执行，并且将命令的输出做为该命令行的一部分来使用。另外请注意，在命令行中`simple.cc`必须位于`pkg-config`调用之前。`-std=c++17`只在你的编译器默认不启用C++17标准的时候才需要。

### 头文件和链接

Although we have shown the compilation command for the simple example, you really should use the Meson build system. The examples used in this book are included in the gtkmm-documentation package, with appropriate build files, so we won't show the build commands in future. The `README` file in gtkmm-documentation describes how to build the examples.

为了简化编译的过程，我们使用了 `pkg-config`，它存在于所有的(也许已经安装的) gtkmm 安装文件中。这个程序“知道”编译使用了 gtkmm 的程序所需要的编译器选项。`--cflags` 选项使 `pkg-config` 输出一个包含编译时需要用到的头文件的目录列表；而使用 `--libs` 选项将得到一个需要编译器去链接的库列表和一个用于寻找它们的目录列表。试着在你的命令行提示符下运行它，看看在你的系统上会有什么样的结果。

However, this is even simpler when using the `dependency()` function in a `meson.build` file with Meson. For instance:

    gtkmm_dep = dependency('gtkmm-4.0', version: '>= 4.6.0')

This checks for the presence of gtkmm and defines `gtkmm_dep` for use in your `meson.build` files. For instance:

    exe_file = executable('my_program', 'my_source1.cc', 'my_source2.cc',
      dependencies: gtkmm_dep,
      win_subsystem: 'windows',
    )

gtkmm-4.0 是当前稳定 API 的名字。更早期的 API 叫做 gtkmm-2.4和gtkmm-3.0，如果需要的话它可以和 gtkmm-2.4 ，gtkmm-3.0 同时安装到系统。gtkmm-2.4 有许多版本，例如，gtkmm-2.10，还有多个版本gtkmm-3.0的api。注意，API 的名字并不会随着每个版本变化而发生变化，因为如果这个发生变化了，则意味着 API 和 ABI 不再同以前保持兼容。理论上，将来可以有 gtkmm-5.0 API 与现在的 gtkmm-4.0 同时安装到系统，而现有应用程序不会受到影响。

If you start by experimenting with a small application that you plan to use just for yourself, it's easier to start with a `meson.build` similar to the `meson.build` files in the Building applications chapter.

If you use the older Autotools build system, see also the GNU site. It has more information about autoconf and automake. There are also some books describing Autotools: "GNU Autoconf, Automake, and Libtool" by Gary Vaughan et al. and "Autotools, A Practitioner's Guide to GNU Autoconf, Automake, and Libtool" by John Calcote.

### 部件

gtkmm 应用程序由一系列包含了如按钮、文本框之类部件的窗口构成。在一些其它的系统上，部件可能被称为“控件”。对于你的应用程序窗口中的每个部件，在你的代码里就会有一个对应的 C++ 对象。所以当你想控制部件行为的时候，只需要调用这个部件对象的相应方法即可。

部件被安置在褚如 frame、notebook 这样的容器部件中，以一种部件包含部件的层次结构的形式。其中一些容器部件，像 Gtk::Grid 这样的容器部件是不可见的，它们只是被用来安置其它部件的。这里有一些示例代码，将两个 Gtk::Button 部件放到一个 Box 容器部件中：

    m_box.append(m_Button1);
    m_box.append(m_Button2);

接下来是如何把这个包含两个按钮的 Gtk::Box 添加到一个 Gtk::Frame 中，它包含一个可视的边框和标题：

    m_frame.set_child(m_box);

本书中的大部分章节都是讲解特定的部件。要得到更多关于添加部件到容器部件的信息，请看 容器部件 这一章。

尽管你可以使用 C++ 代码来指定窗口和部件的外观和布局，但你可能会发现使用 Glade 来设计你的界面，并且使用 `Gtk::Builder` 在运行时动态加载界面是更方便。请参考 Glade 与 Gtk::Builder 这一章。

尽管 gtkmm 部件实例拥有像其它 C++ 类那样的生存时间和作用域，gtkmm 还是有一些可选的节省时间的特性，接下来你会在接下来的一些例子中看到。`Gtk::make_managed()` 允许你创建一个新的部件并声明该部件由放置它的容器部件所拥有。这将允许你创建部件然后将其添加到容器中，而不必承担记住删除它的心智负担，因为当它的父容器被删除时它将被一并删除。如果你需要了解更多关于 gtkmm 内存管理技术的话，请看 内存管理 章节。

### 信号

像大多数GUI工具包一样，gtkmm 是事件驱动的。当一个事件发生时，比如说一个鼠标按键被按下，那么所点击的部件就会发出一个合适的信号。每种部件都可以发出一套不同的信号。为了使这个按钮点击产生一个动作，我们可以设置一个信号处理函数(signal handler)来捕捉这个“clicked”信号。

gtkmm 使用 libsigc++ 来实现信号操作。下面这个例子演示怎样把 Gtk::Button 的“clicked” 信号连接到命名为“on_button_clicked”的信号处理函数上：

    m_button1.signal_clicked().connect( sigc::mem_fun(*this,
      &HelloWorld::on_button_clicked) );

要得到更多关于信号的信息，请参考附录。

要想知道如何实现你自己的信号，而不仅仅是连接到现有的 gtkmm 信号，请参考 附录。

### Glib::ustring

当你得知gtkmm没有在它的接口上使用std::string而是使用了Glib::ustring的时候一定很惊讶。Glib::ustring与std::string非常相似，在某些场合，你甚至可以直接把它当做std::string来使用，并忽略本节后面的内容。但是如果你希望在你的应用程序中使用英语以外的语言的话，那么请继续读下去。

std::string uses 8 bits per character, but 8 bits aren't enough to encode languages such as Arabic, Chinese, and Japanese. Although the encodings for these languages have been specified by the Unicode Consortium, the C and C++ languages do not yet provide any standardized Unicode support for UTF-8 encoding. GTK and GNOME chose to implement Unicode using UTF-8, and that's what is wrapped by Glib::ustring. It provides almost exactly the same interface as std::string, along with automatic conversions to and from std::string.

UTF-8 编码的优点之一是，如果不需要你可以不使用它，所以你不需要一次性的翻新你的代码。std::string 还可以继续使用7位的 ASCII 字符串。但是一旦你想本地化你的程序，比如说汉化，那你就会开始遇到各种奇怪的问题，可能还会崩溃。那时，你所需要做的就是开始使用 Glib::ustring 来取而代之。

要注意的是 UTF-8 和其它一些8位的编码如 ISO-8859-1 并不兼容。比如，德语中的元音变音并不在 ASCII 码的编码范围内，在 UTF-8 编码中需要额外的一个字节来表示。如果你的代码中包含8位的字符串文字，你需要把它们转变成 UTF-8 编码 (比如，巴伐利亚的问候语“Grüß Gott”可能会显示成“Gr\xC3\xBC\xC3\x9F Gott”)。

你应该尽量避免 C 风格的指针算法，以及像 strlen() 这样的函数。在 UTF-8 编码中，每个字符可能占1到6个字节，所以你并不能假定下一个字节一定是另一个字符。所有的这些细节 Glib::ustring 都已经帮你考虑到了，所以你可以从字符的层面上考虑使用 Glib::ustring::substr() 这样的函数，而不用再去考虑字节这样的细节了。

不像 Windows 的 UCS-2 Unicode 解决方案，这并不需要编译器提供特别的选项来处理这样字符串，也不会导致为 Unicode 编译的可执行文件或库与含为 ASCII 的不兼容之类的问题。

Reference

要得到更多关于 UTF-8 字符的信息，请参考国际化这一节。

### 混合使用 C 和 C++ API

你可以使用那些尚未提供 C++ 接口的 C API。对于 C++ 而言这一般不是什么问题，gtkmm 可以提供帮助访问底层的 C 对象，并且也提供了一个简单的方式来从一个 C 对象创建一个 C++ 封装的对象，当然，这个 C API 是需要基于 GObject 系统的。

要用 C 函数使用一个 gtkmm 实例需要一个 C GObject 实例，使用C++的`gobj()`接口函数可以得到一个底层 GObject 实例的指针。例如：

    Gtk::Button button("example");
    gtk_button_do_something_that_gtkmm_cannot(button.gobj());

要从C的GObject实例获得一个gtkmm，请使用许多个`Glib::wrap()`函数的其中一个重载。除非你将可选的take_copy参数设置为`true`，否则C实例的引用计数将不会增加。例如：

    GtkButton* cbutton = get_a_button();
    Gtk::Button* button = Glib::wrap(cbutton);
    button->set_label("Now I speak C++ too!");

如果

- 它是一个部件或者是继承自Gtk::Object的其他类，并且

- 当封装被创建的时候C示例拥有一个浮动引用，并且

- 并未在其上调用`Gtk::manage()`(包括使用`Gtk::make_managed()`创建的) ，或者

- 在其上调用了`Gtk::manage()`，但是从未将其加入到父项中。

`Glib::wrap()`将C和C++示例相互绑定，则应明确删除C++包装器。请不要在C示例死亡前删除C++实例。

在所有其他情况下，当删除对C实例的最后一个引用时，C++实例将会自动被删除。这包括所有的`Glib::wrap()`所返回的Glib::RefPtr。

### 用 gtkmm 写 Hello World

目前我们已经可以自己所学的知识来写一个真正的程序了。根据计算机科学的传统，我们现在以 gtkmm 的方式来介绍 Hello World 程序：

Source Code

File: `helloworld.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_HELLOWORLD_H
    #define GTKMM_EXAMPLE_HELLOWORLD_H

    #include <gtkmm/button.h>
    #include <gtkmm/window.h>

    class HelloWorld : public Gtk::Window
    {

    public:
      HelloWorld();
      ~HelloWorld() override;

    protected:
      //Signal handlers:
      void on_button_clicked();

      //Member widgets:
      Gtk::Button m_button;
    };

    #endif // GTKMM_EXAMPLE_HELLOWORLD_H

File: `helloworld.cc` (For use with gtkmm 4)

    #include "helloworld.h"
    #include <iostream>

    HelloWorld::HelloWorld()
    : m_button("Hello World")   // creates a new button with label "Hello World".
    {
      // Sets the margin around the button.
      m_button.set_margin(10);

      // When the button receives the "clicked" signal, it will call the
      // on_button_clicked() method defined below.
      m_button.signal_clicked().connect(sigc::mem_fun(*this,
                  &HelloWorld::on_button_clicked));

      // This packs the button into the Window (a container).
      set_child(m_button);
    }

    HelloWorld::~HelloWorld()
    {
    }

    void HelloWorld::on_button_clicked()
    {
      std::cout << "Hello World" << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "helloworld.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<HelloWorld>(argc, argv);
    }

在继续讲解之前，可以尝试着编译并运行这个程序，你会看到这样的结果：

Hello World

真让人激动，不是吗？让我们回过头来看一下代码。首先看一下 HelloWorld 类：

    class HelloWorld : public Gtk::Window
    {
    public:
      HelloWorld();
      ~HelloWorld() override;

    protected:
      //Signal handlers:
      void on_button_clicked();

      //Member widgets:
      Gtk::Button m_button;
    };

This class implements the "Hello World" window. It's derived from Gtk::Window, and has a single Gtk::Button as a member. We've chosen to use the constructor to do all of the initialization work for the window, including setting up the signals. Here it is, with the comments omitted:

    HelloWorld::HelloWorld()
    : m_button("Hello World")
    {
      m_button.set_margin(10);
      m_button.signal_clicked().connect(sigc::mem_fun(*this,
        &HelloWorld::on_button_clicked));
      set_child(m_button);
    }

Notice that we've used an initializer statement to give the `m_button` object the label "Hello World".

接下来调用 Button 类的 set_margin() 方法。这将设置按钮周围空间的大小。

然后，把 `m_button` 的 `clicked` 信号挂接到信号处理函数上。这会向 `stdout` (标准输出) 打印出友好的问候语。

接下来调用 Windows 类的 set_child() 方法将`m_button`放入窗口。set_child()方法会将部件放入窗口中。

现在让我们来看看程序中没有注释过的 `main()` 函数：

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");
      return app->make_window_and_run<HelloWorld>(argc, argv);
    }

我们首先实例化一个储存在名为`app`的RefPtr智能指针对象，它的类型为Gtk::Application 。每一个gtkmm程序都有这样的一个东西。

接着我们调用make_window_and_run()创建了我们的HelloWorld类对象，显示这个窗口并进入gtkmm消息循环。在消息循环中，gtkmm是空闲的，它等待用户的操作并作出相应的反应。当用户关闭窗口时，run() 函数将返回，导致`main()`函数的最后一行将被执行。然后程序就结束了。

就像我们之前显示的简单示例一样，这个Hello World程序不使用命令行参数。

## gtkmm3中的变更

gtkmm-3.0是gtkmm API的旧版本它依旧能与gtkmm-2.4和新的gtkmm-4.0 API并行安装。gtkmm-2.4 API的最新版本是gtkmm 2.24。gtkmm3于gtkmm2之间并没有重大的改变，但确实存在一些微小的变动，这些变动导致无法做到令他们保持二进制兼容。如果你从未用过gtkmm-2.4 API 则你可以放心的忽略本章。

gtkmm3库调用`libgtkmm-3.0`而不是`libgtkmm-2.4` 并将其的头文件安装于类似的目录下，所以你的pkg-config应该检索`gtkmm-3.0`而不是`gtkmm-2.4`。

gtkmm3添加了一些新的类：

1. Gtk::AppChooser，Gtk::AppChooserButton,Gtk::AppChooserDialog允许用户选择已安装的应用程序用以打开特定类型的内容。

2. Gtk::Grid是一个新的容器部件他将在未来完全替换Gtk::Box和Gtk::Table。它根据它的子项的属性而不是自身的布局属性对子项进行排列。

3. Gtk::Switch displays On/Off states more explicitly than Gtk::CheckButton. It may be useful, for instance, when allowing users to activate hardware.

gtkmm3还对API进行了一些小的变更，在移植gtkmm-2.4的代码的时候你可能会遇到这些变更。这是一个简短的清单：

1. Gtk::IconView， Gtk::TreeView::Column和Gtk::ComboBox使用Gtk::CellLayout，现在有Gtk::CellArea可以用于指示CellRenderer的排列与对齐方式的详情。

2. Gtk::ComboBox现在从CellLayout派生，从而使更容易对Gtk::CellRenderer进行布局与对齐。

3. Gtk::Adjustment和Gtk::IconSet类和Gdk::Cursor类现在通过Glib::RefPtr使用。

4. Gtk::Box, Gtk::ButtonBox,Gtk::IconView, Gtk::Paned, Gtk::ProgressBar, Gtk::ScaleButton, Gtk::Scrollbar和Gtk::Separator现在从Gtk::Orientable派生，允许在不使用派生类(例如Gtk::HBox)的情况指定其方向(垂直或者水平)。

5. Gtk::IconView, Gtk::TextView, Gtk::TreeView和其他部件派生自Scrollable而不使用他们自己的方法，例如get_vadjustment()拥有自己的set_scroll_adjustments信号。

6. Gtk::Style和Gtk::Rc已被删除，由Gtk::StyleContext和Gtk::StyleProvider替换，例如Gtk::CssProvider。

7. Widget::on_expose_event()被Widget::on_draw()取代，通过提供的Cairo::Context使用cairomm进行绘制，并且不需要你自己调用Cairo::Context::clip()。

8. Color被Gdk::RGBA替代，添加alpha分量以支持透明度。Colormap与其笨拙的颜色分配方式一并被删除。

9. Gdk::Pixmap and Gdk::Bitmap were removed in favor of Gdk::Pixbuf.

10. Gdk::Drawable被删除，与其有关的方法被移动到了Gdk::Window。

11. 我们现在使用std::vector而不是使用中间类型(*Handle)以使API更加的清晰。

在gtkmm 3.0中我们删除了所有弃用的API，尽管在gtkmm 3.x版本中出现了新的被弃用的API。

As a first step to porting your source code to gtkmm-3.0 you should probably ensure that your application builds with the deprecated gtkmm-2.4 API disabled, by defining macro such as GTKMM_DISABLE_DEPRECATED. There are some autotools macros that can help with this by defining them optionally at build time. See the gtkmm 3 porting wiki page for more details.

## gtkmm-4.0和glibmm-2.68中的变更。

gtkmm-4.0是gtkmm API的新版本。它可以与较早发行的gtkmm-2.4和gtkmm-3.0并行安装。gtkmm-3.0 API的最新版本是gtkmm 3.24。gtkmm 4与gtkmm 3没有根本性的区别，虽然有些变更但是保持了二进制兼容。如果你从未使用过gtkmm-3.0 API则你可以放心的忽略本章。

gtkmm 4库调用`libgtkmm-4.0`而不是`libgtkmm-3.0`，并将其的头文件安装于类似的目录下，所以你的pkg-config应该检索`gtkmm-4.0`而不是`gtkmm-3.0`。

gtkmm-4.0与glibmm-2.68结合使用，将设置程序的全局语言环境。较旧的glibmm-2.4不会这样做，而gtkmm-3.0只在一定程度上做到了。简而言之，如果你的gtkmm-3.0应用包含了对`std::locale::global(std::locale(""))`的调用，则你大概率可以直接将其删除。如果你不希望glibmm或者gtkmm为你设置程序的全局语言环境，则应该在调用任何`Glib::init()`函数或Gtk::Application::create()方法之前添加对`Glib::set_init_to_users_preferred_locale(false)`的调用。

Reference

gtkmm-3.0和gtkmm-4.0之间有很多不同之处。以下是不完整的列表。

在gtkmm 4和glibmm 2.68中添加了一些新的类：

1. Glib::ExtraClassInit和Gtk::Snapshot：这些类只用于编写自定义的窗口小部件。参见自定义部件小节。

2. Gtk::EventControllerKey, Gtk::EventControllerMotion, Gtk::EventControllerScroll和Gtk::GestureStylus

3. Gdk::Paintable, Gdk::Texture, Gtk::Picture和Gtk::WidgetPaintable

4. Gdk::Window被重命名为Gdk::Surface(Gtk::Window名称保持不变)。

5. 新添加了Gdk::DrawContext和Gdk::CairoContext。Gdk::DrawingContext已被删除。

6. Gtk::Clipboard被新的Gdk::Clipboard替换。

7. Gdk::DragContext现在被拆分为Gdk::Drag和Gdk::Drop。

API也进行了一些更改，在移植gtkmm-3.0和glibmm-2.4代码时你可能会遇到这些更改。以下是一个简短的变更列表：

1. 需要支持C++17的编译器

2. Gtk::Button，Gtk::ToolButton，Gtk::MenuItem和Gtk::Switch 实现了Gtk::Actionable接口而不是已被删除的Gtk::Activatable接口。

3. Gtk::FontButton实现了Gtk::FontChooser接口。

4. Gtk::Widget：get_preferred_*_vfunc()方法已被measure_vfunc()方法替代。此变更只影响自定义部件。

5. sigc::slot使用sigc::slot<R(Args...)>语法。 例如： sigc::slot<void(int, int)>而不是使用sigc::slot<void, int, int>。

6. Gtk::DrawingArea使用绘制函数替代绘制信号。

7. Glib::ArrayHandle, Glib::StringArrayHandle, Glib::ListHandle和Glib::SListHandle已被删除。他们曾在glibmm-2.4中被使用，而gtkmm-3.0并未使用他们。如果你曾经使用过他们，则将他们替换为标准C++容器，例如std::vector。

8. Gtk::Container已被删除。

9. Gtk::Widget::show_all() has been removed. The default value of Gtk::Widget::property_visible() has been changed from `false` to `true`.

10. Gtk::Widget上的所有事件信号均已被删除。 大多数时候你可以使用Gtk::EventController的子类之一进行替换。例如, 使用Gtk::GestureMultiPress替代signal_button_press_event()以及signal_button_release_event()，使用Gtk::EventControllerKey替代signal_key_press_event()和signal_key_release_event()。

11. Glib::RefPtr是std::shared_ptr的别名。如果你使用返回Glib::RefPtr的create()方法用以创建派生自Glib::ObjectBase的类，则你必须在你的create()方法中调用Glib::make_refptr_for_instance()。

12. Gtk::Box::pack_start()和Gtk::Box::pack_end()已被删除。现在Gtk::Box使用新的append()，prepend()，insert_child_after()和insert_child_at_start()方法。

13. Gtk::ButtonBox已被删除

14. Gtk::RadioButton和Gtk::RadioButtonGroup已被删除。改为使用Gtk::CheckButton或者是Gtk::ToggleButton的set_group()。

gtkmm和glibmm 2.68移除了所有之前标记为弃用的API，未来还会有新的API被标记为弃用。

As a first step to porting your source code to gtkmm-4.0 you should probably ensure that your application builds with the deprecated gtkmm-3.0 and glibmm-2.4 API disabled, by defining the macros GTKMM_DISABLE_DEPRECATED, GDKMM_DISABLE_DEPRECATED, GLIBMM_DISABLE_DEPRECATED and GIOMM_DISABLE_DEPRECATED. There are some autotools macros that can help with this by defining them optionally at build time. See the Porting from gtkmm-2.4 to gtkmm-3.0 wiki page for more details.

See also Migrating from GTK 3.x to GTK 4.

### Deprecations in gtkmm 4.10

Many classes are deprecated since gtkmm 4.10. They can still be used in gtkmm4 applications, provided GTKMM_DISABLE_DEPRECATED and GDKMM_DISABLE_DEPRECATED are not defined. There are also many new classes in gtkmm 4.10, which replace some of the deprecated classes. Some example programs in this tutorial use classes deprecated since gtkmm 4.10. Some other programs use classes available since gtkmm 4.10.

Deprecated classes: AppChooser, AppChooserButton, AppChooserDialog, AppChooserWidget, CellArea, CellAreaBox, CellAreaContext, CellLayout, CellRenderer, CellRendererAccel, CellRendererCombo, CellRendererPixbuf, CellRendererProgress, CellRendererSpin, CellRendererSpinner, CellRendererText, CellRendererToggle, CellView, ComboBox, ComboBoxText, EntryCompletion, IconView, ListStore, ListViewText, StyleContext, TreeDragDest, TreeDragSource, TreeIter and other classes in treeiter.h, TreeModel, TreeModelFilter, TreeModelSort, TreePath, TreeRowReference, TreeSelection, TreeSortable, TreeStore, TreeView, TreeViewColumn, namespace CellRenderer_Generation, namespace TreeView_Private, ColorButton, ColorChooser, ColorChooserDialog, FileChooser, FileChooserDialog, FileChooserNative, FileChooserWidget, FontButton, FontChooser, FontChooserDialog, FontChooserWidget, MessageDialog, TreeModelColumn, TreeModelColumnRecord, InfoBar, Assistant, AssistantPage, LockButton, Statusbar, VolumeButton.

New classes and enums: AlertDialog, ColorDialog, ColorDialogButton, ColumnViewSorter, FileDialog, FontDialog, FontDialogButton, FileLauncher, UriLauncher, ATContext, enums DialogError, FontLevel, Collation.

In most cases there are replacements for the deprecated classes. See the reference documentation.

## 按钮(Buttons)

gtkmm 提供以下四种基本按钮控件：

按钮类

  Gtk::Button. Standard buttons, usually marked with a label or picture. Pushing one triggers an action. See the Button section.

开关按钮(ToggleButton)

  Gtk::ToggleButton. Unlike a normal Button, which springs back up, a ToggleButton stays down until you press it again. It might be useful as an on/off switch. See the ToggleButton section.

复选按钮(CheckButtons)

  Gtk::CheckButton. These act like ToggleButtons, but show their state in small squares, with their label at the side. They should be used in most situations which require an on/off setting. See the CheckButton section.

单选按钮(RadioButton)

  它是根据老式汽车内的收音机的选台器来命名的，选台器是一组选择按钮并且它们是互斥的。按下其中任何一个都会使这一组中的其它按钮都弹起。它们的布局和 CheckButton (一个旁边有标签的小部件)很相似，但是通常看起来不太一样。没有单独的单选按钮类。复选按钮和开关按钮可以充当单选按钮。参见RadioButton 小节。

请注意，由于GTK的主题不同，这些控件外观可能会不同。特别是复选按钮和单选按钮可能会很不一样。

### 按钮(Button)

可以通过两种方式来创建按钮，你可以在 Gtk::Button 构造函数中指定标签字符串，或者以后调用 set_label() 来指定标签。

如果你想为按钮定义一个快捷键，那么可以在标签字符串中的某个字符前面加上一个下划线，并且指定可选的参数助记符(mnemonic)为 true。例如：

    Gtk::Button* pButton = new Gtk::Button("_Something", true);

Gtk::Button 同时也是一个容器，因此你可以在其中放任何其它的部件，比如放一个 Gtk::Image 在里面。

Gtk::Button部件具有`clicked`信号，当按下或释放按钮时将会发出该信号。

Reference

#### 示例

这个示例创建了一个有图片和文字标签的按钮。

按钮示例

Source Code

File: `buttons.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_BUTTONS_H
    #define GTKMM_EXAMPLE_BUTTONS_H

    #include <gtkmm/window.h>
    #include <gtkmm/button.h>

    class Buttons : public Gtk::Window
    {
    public:
      Buttons();
      virtual ~Buttons();

    protected:
      //Signal handlers:
      void on_button_clicked();

      //Child widgets:
      Gtk::Button m_button;
    };

    #endif //GTKMM_EXAMPLE_BUTTONS_H

File: `buttons.cc` (For use with gtkmm 4)

    #include "buttons.h"
    #include <gtkmm/box.h>
    #include <gtkmm/image.h>
    #include <gtkmm/label.h>
    #include <iostream>

    Buttons::Buttons()
    {
      // This corresponds to Gtk::Bin::add_pixlabel("info.xpm", "cool button") in gtkmm3.
      //Create Image and Label widgets:
      auto pmap = Gtk::make_managed<Gtk::Image>("info.xpm");
      auto label = Gtk::make_managed<Gtk::Label>("cool button");
      label->set_expand(true);

      //Put them in a Box:
      auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
      hbox->append(*pmap);
      hbox->append(*label);

      //And put that Box in the button:
      m_button.set_child(*hbox);

      set_title("Pixmap'd buttons!");

      m_button.signal_clicked().connect( sigc::mem_fun(*this,
                  &Buttons::on_button_clicked) );

      m_button.set_margin(10);
      set_child(m_button);
    }

    Buttons::~Buttons()
    {
    }

    void Buttons::on_button_clicked()
    {
      std::cout << "The Button was clicked." << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "buttons.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<Buttons>(argc, argv);
    }

### 开关按钮(ToggleButton)

ToggleButton 和普通的 Button 相似，但是它们在点击后会保持激活(或称为按下的)状态，直到再次点击它为止。

要得到 ToggleButton 的状态，你可以使用 get_active() 方法。如果按钮被按下了，就返回 true。当然，你也可以通过 set_active() 来设置开关按钮的状态。请注意，如果你这样做了，并且它的状态的确改变了，那么这将会引发“clicked”信号。当然，一般来说，这就是你希望看到的结果。

你可以使用 toggled() 方法来改变按钮的状态，而不要强行的是设置它是开还是关：这个方法将会改变按钮的状态，并会引发 `toggled` 信号。

Reference

### 多选按钮(CheckButton)

Gtk::CheckButton 继承自 Gtk::Widget。它与Gtk::ToggleButton类似，二者只有外观不同。因此你可以像Gtk::ToggleButton一样使用同样的方法来检查、设置和转换多选按钮的状态。

Reference

#### 示例

多选按钮(CheckButton)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_BUTTONS_H
    #define GTKMM_EXAMPLE_BUTTONS_H

    #include <gtkmm/window.h>
    #include <gtkmm/checkbutton.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_toggled();

      //Child widgets:
      Gtk::CheckButton m_button;
    };

    #endif //GTKMM_EXAMPLE_BUTTONS_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_button("something")
    {
      set_title("checkbutton example");

      m_button.signal_toggled().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_toggled) );

      m_button.set_margin(10);
      set_child(m_button);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_toggled()
    {
      std::cout << "The Button was toggled: state="
          << (m_button.get_active() ? "true" : "false")
          << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 单选按钮(RadioButton)

单选按钮没有单独的类。当复选按钮和开关按钮在一个组中的时候它们充当单选按钮。任何时候只能选择一个组中的一个按钮。

#### 组

你创建了三个按钮，然后为他们设置了组。在下面的例子中，我们将三个单选按钮放入一个组中。

    auto rb1 = Gtk::make_managed<Gtk::CheckButton>("button1");
    auto rb2 = Gtk::make_managed<Gtk::CheckButton>("button2");
    auto rb3 = Gtk::make_managed<Gtk::CheckButton>("button3");
    rb2->set_group(*rb1);
    rb3->set_group(*rb1);

我们告诉gtkmm把三个CheckButton都放到一个组内，通过get_group()来得到这个组，然后使用set_group()告诉其它的CheckButton与第一个CheckButton共享这个组。

#### 方法

当CheckButton和ToggleButton创建后默认是未选中的，也就是说当你第一次创建一组单选按钮的时候，它们都会是未选中的。所以别忘了使用set_active()来选中一个。

Reference

#### 示例

接下来的例子中演示了对CheckButton进行分组的用法：

单选按钮(RadioButton)

Source Code

File: `radiobuttons.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_RADIOBUTTONS_H
    #define GTKMM_EXAMPLE_RADIOBUTTONS_H

    #include <gtkmm/box.h>
    #include <gtkmm/window.h>
    #include <gtkmm/button.h>
    #include <gtkmm/checkbutton.h>
    #include <gtkmm/separator.h>

    class RadioButtons : public Gtk::Window
    {
    public:
      RadioButtons();
      virtual ~RadioButtons();

    protected:
      //Signal handlers:
      void on_button_clicked();

      //Child widgets:
      Gtk::Box m_Box_Top, m_Box1, m_Box2;
      Gtk::CheckButton m_RadioButton1, m_RadioButton2, m_RadioButton3;
      Gtk::Separator m_Separator;
      Gtk::Button m_Button_Close;
    };

    #endif //GTKMM_EXAMPLE_RADIOBUTTONS_H

File: `main.cc` (For use with gtkmm 4)

    #include "radiobuttons.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<RadioButtons>(argc, argv);
    }

File: `radiobuttons.cc` (For use with gtkmm 4)

    #include "radiobuttons.h"

    RadioButtons::RadioButtons() :
      m_Box_Top(Gtk::Orientation::VERTICAL),
      m_Box1(Gtk::Orientation::VERTICAL, 10),
      m_Box2(Gtk::Orientation::VERTICAL, 10),
      m_RadioButton1("button1"),
      m_RadioButton2("button2"),
      m_RadioButton3("button3"),
      m_Button_Close("close")
    {
      // Set title and border of the window
      set_title("radio buttons");

      // Gtk::CheckButton and Gtk::ToggleButton have set_group() methods.
      // They act as radio buttons, if they are included in a group.

      // Put radio buttons 2 and 3 in the same group as 1:
      m_RadioButton2.set_group(m_RadioButton1);
      m_RadioButton3.set_group(m_RadioButton1);

      // Add outer box to the window (because the window
      // can only contain a single widget)
      set_child(m_Box_Top);

      //Put the inner boxes and the separator in the outer box:
      m_Box_Top.append(m_Box1);
      m_Box_Top.append(m_Separator);
      m_Box_Top.append(m_Box2);
      m_Separator.set_expand();

      // Set the inner boxes' margins
      m_Box1.set_margin(10);
      m_Box2.set_margin(10);

      // Put the radio buttons in Box1:
      m_Box1.append(m_RadioButton1);
      m_Box1.append(m_RadioButton2);
      m_Box1.append(m_RadioButton3);
      m_RadioButton1.set_expand();
      m_RadioButton2.set_expand();
      m_RadioButton3.set_expand();

      // Set the second button active
      m_RadioButton2.set_active(true);

      // Put Close button in Box2:
      m_Box2.append(m_Button_Close);
      m_Button_Close.set_expand();

      // Make the button the default widget
      set_default_widget(m_Button_Close);

      // Connect the toggled signal of the button to
      // RadioButtons::on_button_toggled()
      m_Button_Close.signal_clicked().connect(sigc::mem_fun(*this,
                  &RadioButtons::on_button_clicked) );
    }

    RadioButtons::~RadioButtons()
    {
    }

    void RadioButtons::on_button_clicked()
    {
      set_visible(false); //to close the application.
    }

## 范围(Range)部件

Gtk::Scale inherits from Gtk::Range. Gtk::Scrollbar does not inherit from Gtk::Range, but it shares much functionality with Gtk::Scale. They both contain a "trough" and a "slider" (sometimes called a "thumbwheel" in other GUI environments). Dragging the slider with the pointer moves it within the trough, while clicking in the trough advances the slider towards the location of the click, either completely, or by a designated amount, depending on which mouse button is used. This should be familiar scrollbar behavior.

As will be explained in the Adjustments section, all range widgets are associated with an Adjustment object. To change the lower, upper, and current values used by the widget you need to use the methods of its Adjustment, which you can get with the get_adjustment() method. The range widgets' default constructors create an Adjustment automatically, or you can specify an existing Adjustment, maybe to share it with another widget. See the Adjustments section for further details.

Reference

### 滚动条部件 (Scrollbar)

这些是标准的滚动条。它们应该只被用于滚动其它部件，比如 Gtk::Entry 或 Gtk::Viewport，当然，通常使用 Gtk::ScrolledWindow 会更容易一些。

Gtk::Scrollbar的方向可以是水平的也可以是垂直的。

Reference

### 比例部件 (Scale)

Gtk::Scale 部件 (或者说“滑块”) 可以让用户在指定范围内直观地调节它的值。例如，你可以用它来调节一副预览图片的放大率、或者控制颜色的亮度，或者指定多少分钟后执行屏幕保护。

As with Scrollbars, the orientation can be either horizontal or vertical. The default constructor creates an Adjustment with all of its values set to `0.0`. This isn't useful so you will need to set some Adjustment details to get meaningful behavior.

#### 一些有用的方法

Scale 可以在滑轨旁显示它们的当前值。默认情况下是显示这个数值的，但是你可以通过调用 set_draw_value() 方法来设置是否显示该值。

默认情况下，Scale 部件会显示 Gtk::Adjustment 的 `value` 的数值，并且会被四舍五入到一位小数。你可以通过 set_digits() 方法来改变显示的小数位数。

另外，通过使用 set_value_pos()，可以使值显示在相对于滑块不同的位置。

Reference

### 示例

这个示例显示了一个窗口，其中有三个关联到同一个调整对象，还有几个控制部件用来调整前文讨论过的一些参数，这样你可以很清楚地看到那些参数是如何影响这些部件的行为方式的。

范围(Range)部件

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_RANGEWIDGETS_H
    #define GTKMM_EXAMPLE_RANGEWIDGETS_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_checkbutton_toggled();
      void on_dropdown_position();
      void on_adjustment1_value_changed();
      void on_adjustment2_value_changed();
      void on_button_quit();

      //Child widgets:
      Gtk::Box m_VBox_Top, m_VBox2, m_VBox_HScale;
      Gtk::Box m_HBox_Scales, m_HBox_DropDown, m_HBox_Digits, m_HBox_PageSize;

      Glib::RefPtr<Gtk::Adjustment> m_adjustment, m_adjustment_digits, m_adjustment_pagesize;

      Gtk::Scale m_VScale;
      Gtk::Scale m_HScale, m_Scale_Digits, m_Scale_PageSize;

      Gtk::Separator m_Separator;

      Gtk::CheckButton m_CheckButton;

      Gtk::Scrollbar m_Scrollbar;

      Gtk::DropDown m_DropDown_Position;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLE_RANGEWIDGETS_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <array>
    #include <iostream>

    namespace
    {
    struct PositionTypeStruct
    {
      Gtk::PositionType position;
      Glib::ustring text;
    };

    const std::array<PositionTypeStruct, 4> positionTypes =
    {
      PositionTypeStruct{Gtk::PositionType::TOP,    "Top"},
      PositionTypeStruct{Gtk::PositionType::BOTTOM, "Bottom"},
      PositionTypeStruct{Gtk::PositionType::LEFT,   "Left"},
      PositionTypeStruct{Gtk::PositionType::RIGHT,  "Right"}
    };

    } // anonymous namespace

    ExampleWindow::ExampleWindow()
    :
      m_VBox_Top(Gtk::Orientation::VERTICAL, 0),
      m_VBox2(Gtk::Orientation::VERTICAL, 20),
      m_VBox_HScale(Gtk::Orientation::VERTICAL, 10),
      m_HBox_Scales(Gtk::Orientation::HORIZONTAL, 10),
      m_HBox_DropDown(Gtk::Orientation::HORIZONTAL, 10),
      m_HBox_Digits(Gtk::Orientation::HORIZONTAL, 10),
      m_HBox_PageSize(Gtk::Orientation::HORIZONTAL, 10),

      // Value, lower, upper, step_increment, page_increment, page_size:
      // Note that the page_size value only makes a difference for
      // scrollbar widgets, and the highest value you'll get is actually
      // (upper - page_size).
      m_adjustment( Gtk::Adjustment::create(0.0, 0.0, 101.0, 0.1, 1.0, 1.0) ),
      m_adjustment_digits( Gtk::Adjustment::create(1.0, 0.0, 5.0, 1.0, 2.0) ),
      m_adjustment_pagesize( Gtk::Adjustment::create(1.0, 1.0, 101.0) ),

      m_VScale(m_adjustment, Gtk::Orientation::VERTICAL),
      m_HScale(m_adjustment, Gtk::Orientation::HORIZONTAL),
      m_Scale_Digits(m_adjustment_digits),
      m_Scale_PageSize(m_adjustment_pagesize),

      // A checkbutton to control whether the value is displayed or not:
      m_CheckButton("Display value on scale widgets", 0),

      // Reuse the same adjustment again.
      // Notice how this causes the scales to always be updated
      // continuously when the scrollbar is moved.
      m_Scrollbar(m_adjustment),

      m_Button_Quit("Quit")
    {
      set_title("range controls");
      set_default_size(300, 350);

      //VScale:
      m_VScale.set_digits(1);
      m_VScale.set_value_pos(Gtk::PositionType::TOP);
      m_VScale.set_draw_value();
      m_VScale.set_inverted(); // highest value at top

      //HScale:
      m_HScale.set_digits(1);
      m_HScale.set_value_pos(Gtk::PositionType::TOP);
      m_HScale.set_draw_value();

      set_child(m_VBox_Top);
      m_VBox_Top.append(m_VBox2);
      m_VBox2.set_expand(true);
      m_VBox2.set_margin(10);
      m_VBox2.append(m_HBox_Scales);
      m_HBox_Scales.set_expand(true);

      //Put VScale and HScale (above scrollbar) side-by-side.
      m_HBox_Scales.append(m_VScale);
      m_VScale.set_expand(true);
      m_HBox_Scales.append(m_VBox_HScale);
      m_VBox_HScale.set_expand(true);

      m_VBox_HScale.append(m_HScale);
      m_HScale.set_expand(true);

      //Scrollbar:
      m_VBox_HScale.append(m_Scrollbar);
      m_Scrollbar.set_expand(true);

      //CheckButton:
      m_CheckButton.set_active();
      m_CheckButton.signal_toggled().connect( sigc::mem_fun(*this,
        &ExampleWindow::on_checkbutton_toggled) );
      m_VBox2.append(m_CheckButton);

      //Position DropDown:
      //Create the StringList:
      auto string_list = Gtk::StringList::create({});
      m_DropDown_Position.set_model(string_list);

      // Fill the DropDown's list model:
      for (const auto& positionType : positionTypes)
        string_list->append(positionType.text);

      m_VBox2.append(m_HBox_DropDown);
      m_HBox_DropDown.append(*Gtk::make_managed<Gtk::Label>("Scale Value Position:", 0));
      m_HBox_DropDown.append(m_DropDown_Position);
      m_DropDown_Position.property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dropdown_position));
      m_DropDown_Position.set_selected(0); // Top
      m_DropDown_Position.set_expand(true);

      //Digits:
      m_HBox_Digits.append(*Gtk::make_managed<Gtk::Label>("Scale Digits:", 0));
      m_Scale_Digits.set_digits(0);
      m_Scale_Digits.set_expand(true);
      m_adjustment_digits->signal_value_changed().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_adjustment1_value_changed));
      m_HBox_Digits.append(m_Scale_Digits);

      //Page Size:
      m_HBox_PageSize.append(*Gtk::make_managed<Gtk::Label>("Scrollbar Page Size:", 0));
      m_Scale_PageSize.set_digits(0);
      m_Scale_PageSize.set_expand(true);
      m_adjustment_pagesize->signal_value_changed().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_adjustment2_value_changed));
      m_HBox_PageSize.append(m_Scale_PageSize);

      m_VBox2.append(m_HBox_Digits);
      m_VBox2.append(m_HBox_PageSize);
      m_VBox_Top.append(m_Separator);
      m_VBox_Top.append(m_Button_Quit);

      set_default_widget(m_Button_Quit);
      m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_button_quit));
      m_Button_Quit.set_margin(10);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_checkbutton_toggled()
    {
      m_VScale.set_draw_value(m_CheckButton.get_active());
      m_HScale.set_draw_value(m_CheckButton.get_active());
    }

    void ExampleWindow::on_dropdown_position()
    {
      const auto selected = m_DropDown_Position.get_selected();
      if (selected == GTK_INVALID_LIST_POSITION)
        return;

      const auto postype = positionTypes[selected].position;
      m_VScale.set_value_pos(postype);
      m_HScale.set_value_pos(postype);
    }

    void ExampleWindow::on_adjustment1_value_changed()
    {
      const double val = m_adjustment_digits->get_value();
      m_VScale.set_digits((int)val);
      m_HScale.set_digits((int)val);
    }

    void ExampleWindow::on_adjustment2_value_changed()
    {
      const double val = m_adjustment_pagesize->get_value();
      m_adjustment->set_page_size(val);
      m_adjustment->set_page_increment(val);

      // Note that we don't have to emit the "changed" signal
      // because gtkmm does this for us.
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 杂项部件

### 标签(Label)

标签是用于在窗口中放置不可编辑文本的主要方法，例如将标题放于Entry部件旁。你可以直接在构造函数中指定文本，也可以于稍后调用set_text()或set_markup()方法设置想要显示的文本。

标签会自动调整宽度。你也可以通过在标签锁显示的字符串中放置换行符("\n")来生成多行标签。

The label text can be justified using the set_justify() method. The widget is also capable of word-wrapping, which can be activated with set_wrap().

Gtk::Label supports some simple formatting, for instance allowing you to make some text bold, colored, or larger. You can do this by providing a string to set_markup(), using the Pango Markup syntax. For instance, `<b>bold text</b> and <s>strikethrough text</s>` .

Reference

#### 示例

下面是一个用于说明这些功能的简短示例。这个样例使用框架(Frame)部件以更好的演示标签样式。(框架部件的教程: 框架(Frame)部件)仅当你按Alt键时，`m_Label_Normal`的第一个字符才显示为下划线的样式。

标签(Label)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:

      //Child widgets:
      Gtk::Box m_HBox;
      Gtk::Box m_VBox, m_VBox2;
      Gtk::Frame m_Frame_Normal, m_Frame_Multi, m_Frame_Left, m_Frame_Right,
        m_Frame_LineWrapped, m_Frame_FilledWrapped, m_Frame_Underlined;
      Gtk::Label m_Label_Normal, m_Label_Multi, m_Label_Left, m_Label_Right,
        m_Label_LineWrapped, m_Label_FilledWrapped, m_Label_Underlined;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    :
      m_HBox(Gtk::Orientation::HORIZONTAL, 5),
      m_VBox(Gtk::Orientation::VERTICAL, 5),
      m_VBox2(Gtk::Orientation::VERTICAL, 5),
      m_Frame_Normal("Normal Label"),
      m_Frame_Multi("Multi-line Label"),
      m_Frame_Left("Left Justified Label"),
      m_Frame_Right("Right Justified Label"),
      m_Frame_LineWrapped("Line wrapped label"),
      m_Frame_FilledWrapped("Filled, wrapped label"),
      m_Frame_Underlined("Underlined label"),
      m_Label_Normal("_This is a Normal label", true),
      m_Label_Multi("This is a Multi-line label.\nSecond line\nThird line"),
      m_Label_Left("This is a Left-Justified\nMulti-line label.\nThird line"),
      m_Label_Right("This is a Right-Justified\nMulti-line label.\nThird line"),
      m_Label_Underlined("<u>This label is underlined!</u>\n"
              "<u>T</u>h<u>is one is</u> <u>u</u>n<u>derlin</u>ed "
              "in<u> q</u>u<u>ite a f</u>u<u>nky</u> fashion")
    {
      set_title("Label");

      m_HBox.set_margin(5);
      set_child(m_HBox);

      m_HBox.append(m_VBox);

      m_Frame_Normal.set_child(m_Label_Normal);
      m_VBox.append(m_Frame_Normal);

      m_Frame_Multi.set_child(m_Label_Multi);
      m_VBox.append(m_Frame_Multi);

      m_Label_Left.set_justify(Gtk::Justification::LEFT);
      m_Frame_Left.set_child(m_Label_Left);
      m_VBox.append(m_Frame_Left);

      m_Label_Right.set_justify(Gtk::Justification::RIGHT);
      m_Frame_Right.set_child(m_Label_Right);
      m_VBox.append(m_Frame_Right);

      m_HBox.append(m_VBox2);

      m_Label_LineWrapped.set_text(
              "This is an example of a line-wrapped label.  It "
              /* add a big space to the next line to test spacing */
              "should not be taking up the entire             "
              "width allocated to it, but automatically "
              "wraps the words to fit.  "
              "The time has come, for all good men, to come to "
              "the aid of their party.  "
              "The sixth sheik's six sheep's sick.\n"
              "     It supports multiple paragraphs correctly, "
              "and  correctly   adds "
              "many          extra  spaces. ");
      m_Label_LineWrapped.set_wrap();
      m_Frame_LineWrapped.set_child(m_Label_LineWrapped);
      m_VBox2.append(m_Frame_LineWrapped);

      m_Label_FilledWrapped.set_text(
              "This is an example of a line-wrapped, filled label.  "
              "It should be taking "
              "up the entire              width allocated to it.  "
              "Here is a sentence to prove "
              "my point.  Here is another sentence. "
              "Here comes the sun, do de do de do.\n"
              "    This is a new paragraph.\n"
              "    This is another newer, longer, better "
              "paragraph.  It is coming to an end, "
              "unfortunately.");
      m_Label_FilledWrapped.set_justify(Gtk::Justification::FILL);
      m_Label_FilledWrapped.set_wrap();
      m_Frame_FilledWrapped.set_child(m_Label_FilledWrapped);
      m_VBox2.append(m_Frame_FilledWrapped);

      m_Label_Underlined.set_justify(Gtk::Justification::LEFT);
      m_Label_Underlined.set_use_markup(true);
      m_Frame_Underlined.set_child(m_Label_Underlined);
      m_VBox2.append(m_Frame_Underlined);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 条目(Entry)

#### 简易用法

条目部件允许用户输入文本。你可以使用set_text()方法更改部件当前的内容，还可以使用get_text()方法读取当前的内容。

有时你可能想使Entry部件变为只读的。你可以通过向set_editable()方法传递`false`做到这点。

对于不想在屏幕上回显的密码、密语或者其他信息，你可以通过向set_visibility()方法传递`false`使输入文本被隐藏。

You might want to be notified whenever the user types in a text entry widget. Gtk::Entry provides two signals, `activate` (since gtkmm 4.8) and `changed`, for this purpose. `activate` is emitted when the user presses the Enter key in a text-entry widget; `changed` is emitted when the text in the widget changes. You can use these, for instance, to validate or filter the text the user types. Moving the keyboard focus to another widget may also signal that the user has finished entering text. The `leave` signal in a Gtk::EventControllerFocus can notify you when that happens. The ComboBox with an Entry section contains example programs that use these signals.

If you pass `true` to the set_activates_default() method, pressing Enter in the Gtk::Entry will activate the default widget for the window containing the Gtk::Entry. This is especially useful in dialog boxes. The default widget is usually one of the dialog buttons, which e.g. will close the dialog box. To set a widget as the default widget, use Gtk::Window::set_default_widget().

Reference

##### 简单的条目示例

本示例使用Gtk::Entry。他还有两个CheckButton，你可以使用他们切换条目部件的可见性和可编辑性标志。

条目(Entry)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_checkbox_editable_toggled();
      void on_checkbox_visibility_toggled();
      void on_button_close();

      //Child widgets:
      Gtk::Box m_HBox;
      Gtk::Box m_VBox;
      Gtk::Entry m_Entry;
      Gtk::Button m_Button_Close;
      Gtk::CheckButton m_CheckButton_Editable, m_CheckButton_Visible;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Close("Close"),
      m_CheckButton_Editable("Editable"),
      m_CheckButton_Visible("Visible")
    {
      set_size_request(200, 100);
      set_title("Gtk::Entry");

      set_child(m_VBox);

      m_Entry.set_max_length(50);
      m_Entry.set_text("hello");
      m_Entry.set_text(m_Entry.get_text() + " world");
      m_Entry.select_region(0, m_Entry.get_text_length());
      m_Entry.set_expand(true);
      m_VBox.append(m_Entry);

      m_VBox.append(m_HBox);

      m_HBox.append(m_CheckButton_Editable);
      m_CheckButton_Editable.set_expand(true);
      m_CheckButton_Editable.signal_toggled().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbox_editable_toggled) );
      m_CheckButton_Editable.set_active(true);

      m_HBox.append(m_CheckButton_Visible);
      m_CheckButton_Visible.set_expand(true);
      m_CheckButton_Visible.signal_toggled().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbox_visibility_toggled) );
      m_CheckButton_Visible.set_active(true);

      m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_close) );
      m_VBox.append(m_Button_Close);
      m_Button_Close.set_expand();
      set_default_widget(m_Button_Close);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_checkbox_editable_toggled()
    {
      m_Entry.set_editable(m_CheckButton_Editable.get_active());
    }

    void ExampleWindow::on_checkbox_visibility_toggled()
    {
      m_Entry.set_visibility(m_CheckButton_Visible.get_active());
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 条目补全(Entry Completion)

Gtk::EntryCompletion is deprecated since gtkmm 4.10. There is no replacement in gtkmm.

Entry条目组件可以通过用户输入的前几个字符提供预先准备好选项的下拉列表。例如，在搜索对话框中提供以前输入过的文本作为建议。

要启用此功能，你需要创建一个EntryCompletion对象，然后使用Entry部件的set_completion()方法将其提供给条目部件。

EntryCompletion可以用set_model()指定一个包含所有可能的条目的TreeModel。你应该调用set_text_column()指定该模型的那一行用以匹配可能的文本条目。

或是在不方便生成补全条目列表的时候(太大或者生成太麻烦)，也可以使用set_match_func()指定一个回调槽。这在你想匹配字符串中开头以外的部分的时候这会很有用。

Reference

##### 条目补全示例

这个示例创建一个Gtk::EntryCompletion并将其与Gtk::Entry相关联。补全使用了一个包含了可能条目的Gtk::TreeModel以及一些其他的额外的操作。

条目补全(Entry Completion)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_close();

      //See the comment in the implementation:
      //bool on_completion_match(const Glib::ustring& key, const Gtk::TreeModel::const_iterator& iter);

      //Tree model columns, for the EntryCompletion's filter model:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:
        ModelColumns()
        { add(m_col_id); add(m_col_name); }

        Gtk::TreeModelColumn<unsigned int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
      };

      ModelColumns m_Columns;

      //Child widgets:
      Gtk::Box m_HBox;
      Gtk::Box m_VBox;
      Gtk::Entry m_Entry;
      Gtk::Label m_Label;
      Gtk::Button m_Button_Close;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Label("Press a or b to see a list of possible completions."),
      m_Button_Close("Close")
    {
      //set_size_request(200, 100);
      set_title("Gtk::EntryCompletion");

      set_child(m_VBox);
      m_VBox.append(m_Entry);

      m_VBox.append(m_Label);
      m_Label.set_expand(true);

      m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_close) );
      m_VBox.append(m_Button_Close);
      set_default_widget(m_Button_Close);

      //Add an EntryCompletion:
      auto completion =
          Gtk::EntryCompletion::create();
      m_Entry.set_completion(completion);

      //Create and fill the completion's filter model
      auto refCompletionModel =
          Gtk::ListStore::create(m_Columns);
      completion->set_model(refCompletionModel);

      // For more complex comparisons, use a filter match callback, like this.
      // See the comment below for more details:
      //completion->set_match_func( sigc::mem_fun(*this,
                  //&ExampleWindow::on_completion_match) );

      //Fill the TreeView's model
      auto row = *(refCompletionModel->append());
      row[m_Columns.m_col_id] = 1;
      row[m_Columns.m_col_name] = "Alan Zebedee";

      row = *(refCompletionModel->append());
      row[m_Columns.m_col_id] = 2;
      row[m_Columns.m_col_name] = "Adrian Boo";

      row = *(refCompletionModel->append());
      row[m_Columns.m_col_id] = 3;
      row[m_Columns.m_col_name] = "Bob McRoberts";

      row = *(refCompletionModel->append());
      row[m_Columns.m_col_id] = 4;
      row[m_Columns.m_col_name] = "Bob McBob";

      //Tell the completion what model column to use to
      //- look for a match (when we use the default matching, instead of
      //  set_match_func().
      //- display text in the entry when a match is found.
      completion->set_text_column(m_Columns.m_col_name);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

    /* You can do more complex matching with a handler like this.
     * For instance, you could check for substrings inside the string instead of the start,
     * or you could look for the key in extra model columns as well as the model column that will be displayed.
     * The code here is not actually more complex - it's a reimplementation of the default behaviour.
     *
    bool ExampleWindow::on_completion_match(const Glib::ustring& key, const
            Gtk::TreeModel::const_iterator& iter)
    {
      if(iter)
      {
        const auto row = *iter;

        const auto key_length = key.size();
        auto filter_string = row[m_Columns.m_col_name];

        auto filter_string_start = filter_string.substr(0, key_length);
        //The key is lower-case, even if the user input is not.
        filter_string_start = filter_string_start.lowercase();

        if(key == filter_string_start)
          return true; //A match was found.
      }

      return false; //No match.
    }
    */

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 条目图标

An Entry widget can show an icon at the start or end of the text area. The icon can be specified by methods such as set_icon_from_paintable() or set_icon_from_icon_name(). An application can respond to the user pressing the icon by handling the signal_icon_press signal.

##### 条目图标示例

这个示例显示带有具名搜索图标的Gtk::Entry部件，并且在按下这个图标的时候将该名称打印到终端上。

带图标的条目

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_icon_pressed(Gtk::Entry::IconPosition icon_pos);
      void on_button_close();

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Entry m_Entry;
      Gtk::Button m_Button_Close;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Close("Close")
    {
      set_title("Gtk::Entry");

      set_child(m_VBox);

      m_Entry.set_max_length(50);
      m_Entry.set_text("Hello world");
      m_VBox.append(m_Entry);

      m_Entry.set_icon_from_icon_name("edit-find");
      m_Entry.signal_icon_press().connect( sigc::mem_fun(*this, &ExampleWindow::on_icon_pressed) );

      m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_close) );
      m_VBox.append(m_Button_Close);
      set_default_widget(m_Button_Close);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_icon_pressed(Gtk::Entry::IconPosition /* icon_pos */)
    {
      std::cout << "Icon pressed." << std::endl;
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 条目进度

Entry部件可以在输入的文本之下显示一个进度条。当部件的set_progress_fraction()或set_progress_pulse_step()方法被调用的时候进度条将显示出来。

##### 条目进度示例

这个示例显示一个带有进度条的Gtk::Entry部件。

带有进度条的条目

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      bool on_timeout();
      void on_button_close();

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Entry m_Entry;
      Gtk::Button m_Button_Close;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Close("Close")
    {
      set_title("Gtk::Entry");

      set_child(m_VBox);

      m_Entry.set_max_length(50);
      m_Entry.set_text("Hello world");
      m_VBox.append(m_Entry);

      //Change the progress fraction every 0.1 second:
      Glib::signal_timeout().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_timeout),
        100
      );

      m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_close) );
      m_VBox.append(m_Button_Close);
      set_default_widget(m_Button_Close);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    bool ExampleWindow::on_timeout()
    {
      static double fraction = 0;
      m_Entry.set_progress_fraction(fraction);

      fraction += 0.01;
      if(fraction > 1)
        fraction = 0;

      return true;
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 微调按钮(SpinButton)

SpinButton允许用户从一个数值范围内选择一个值。它包含了一个Entry部件在其侧边有递增和递减按钮。单击按钮会使得值在可能的范围内“旋转”。你也可以直接在Entry部件中输入一个值。

这个值的小数位可以进行调整，步长也可以进行配置。SpinButton具有“自动重复”功能：长按递增、递减按钮该值的变化速度会越来越快。

SpinButton使用调整(Adjustment)对象保存值的范围信息。下述调整属性由微调按钮使用：

- `value`：微调按钮的值

- `lower`：微调按钮允许的最小值

- `upper`：微调按钮允许的最大值

- `step_increment`：鼠标键1(通常是左键)点击一下递增/递减的值

- `page_increment`：鼠标键2(通常是右键)点击一下递增/递减的值

- `page_size`：目前没作用

另外，鼠标键3(通常是中键)用于直接跳到`upper`或`lower` values。

SpinButton可以创建一个默认状态的Adjustment，然后你可以调用get_adjustment()方法访问这个Adjustment，也可以通过SpinButton的构造函数直接使用一个已经存在的Adjustment。

#### 方法

数值的小数位可以使用set_digits()方法进行调整。

你可以使用set_value()方法设置微调按钮的值，也可以使用get_value()方法获得微调按钮的值。

spin()方法的行为和手动点击递增/递减按钮一样。你需要传递一个Gtk::SpinType对象给该方法以指定是递增还是递减。

将`true`传递给set_numeric()方法可以阻止用户在条目框内输入非数字字符。

使用set_wrap()方法指定SpinButton的值到达上下限的时候是否绕回。

使用set_snap_to_ticks()方法可以在向微调按钮设置了无效值的时候强制校正到最接近`step_increment`策略可达到的有效值。

Reference

#### 示例

这是一个使用SpinButton的示例：

微调按钮(SpinButton)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_checkbutton_snap();
      void on_checkbutton_numeric();
      void on_spinbutton_digits_changed();
      void on_button_close();

      enum enumValueFormats
      {
        VALUE_FORMAT_INT,
        VALUE_FORMAT_FLOAT
      };
      void on_button_getvalue(enumValueFormats display);

      //Child widgets:
      Gtk::Frame m_Frame_NotAccelerated, m_Frame_Accelerated;
      Gtk::Box m_HBox_NotAccelerated, m_HBox_Accelerated,
        m_HBox_Buttons;
      Gtk::Box m_VBox_Main, m_VBox, m_VBox_Day, m_VBox_Month, m_VBox_Year,
        m_VBox_Accelerated, m_VBox_Value, m_VBox_Digits;
      Gtk::Label m_Label_Day, m_Label_Month, m_Label_Year,
        m_Label_Value, m_Label_Digits,
        m_Label_ShowValue;
      Glib::RefPtr<Gtk::Adjustment> m_adjustment_day, m_adjustment_month, m_adjustment_year,
        m_adjustment_value, m_adjustment_digits;
      Gtk::SpinButton m_SpinButton_Day, m_SpinButton_Month, m_SpinButton_Year,
        m_SpinButton_Value, m_SpinButton_Digits;
      Gtk::CheckButton m_CheckButton_Snap, m_CheckButton_Numeric;
      Gtk::Button m_Button_Int, m_Button_Float, m_Button_Close;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>
    #include <cstdio>

    ExampleWindow::ExampleWindow()
    :
      m_Frame_NotAccelerated("Not accelerated"),
      m_Frame_Accelerated("Accelerated"),
      m_VBox_Main(Gtk::Orientation::VERTICAL, 5),
      m_VBox(Gtk::Orientation::VERTICAL),
      m_VBox_Day(Gtk::Orientation::VERTICAL),
      m_VBox_Month(Gtk::Orientation::VERTICAL),
      m_VBox_Year(Gtk::Orientation::VERTICAL),
      m_VBox_Accelerated(Gtk::Orientation::VERTICAL),
      m_VBox_Value(Gtk::Orientation::VERTICAL),
      m_VBox_Digits(Gtk::Orientation::VERTICAL),
      m_Label_Day("Day: ", Gtk::Align::START),
      m_Label_Month("Month: ", Gtk::Align::START),
      m_Label_Year("Year: ", Gtk::Align::START),
      m_Label_Value("Value: ", Gtk::Align::START),
      m_Label_Digits("Digits: ", Gtk::Align::START),
      m_adjustment_day( Gtk::Adjustment::create(1.0, 1.0, 31.0, 1.0, 5.0, 0.0) ),
      m_adjustment_month( Gtk::Adjustment::create(1.0, 1.0, 12.0, 1.0, 5.0, 0.0) ),
      m_adjustment_year( Gtk::Adjustment::create(2012.0, 1.0, 2200.0, 1.0, 100.0, 0.0) ),
      m_adjustment_value( Gtk::Adjustment::create(0.0, -10000.0, 10000.0, 0.5, 100.0, 0.0) ),
      m_adjustment_digits( Gtk::Adjustment::create(2.0, 1.0, 5.0, 1.0, 1.0, 0.0) ),
      m_SpinButton_Day(m_adjustment_day),
      m_SpinButton_Month(m_adjustment_month),
      m_SpinButton_Year(m_adjustment_year),
      m_SpinButton_Value(m_adjustment_value, 1.0, 2),
      m_SpinButton_Digits(m_adjustment_digits),
      m_CheckButton_Snap("Snap to 0.5-ticks"),
      m_CheckButton_Numeric("Numeric only input mode"),
      m_Button_Int("Value as Int"),
      m_Button_Float("Value as Float"),
      m_Button_Close("Close")
    {
      set_title("SpinButton");

      m_VBox_Main.set_margin(10);
      set_child(m_VBox_Main);

      m_VBox_Main.append(m_Frame_NotAccelerated);

      m_VBox.set_margin(5);
      m_Frame_NotAccelerated.set_child(m_VBox);

      /* Day, month, year spinners */

      m_VBox.set_spacing(5);
      m_VBox.append(m_HBox_NotAccelerated);

      m_Label_Day.set_expand();
      m_VBox_Day.append(m_Label_Day);

      m_SpinButton_Day.set_wrap();
      m_SpinButton_Day.set_expand();
      m_VBox_Day.append(m_SpinButton_Day);

      m_HBox_NotAccelerated.set_spacing(5);
      m_HBox_NotAccelerated.append(m_VBox_Day);

      m_Label_Month.set_expand();
      m_VBox_Month.append(m_Label_Month);

      m_SpinButton_Month.set_wrap();
      m_SpinButton_Month.set_expand();
      m_VBox_Month.append(m_SpinButton_Month);

      m_HBox_NotAccelerated.append(m_VBox_Month);

      m_Label_Year.set_expand();
      m_VBox_Year.append(m_Label_Year);

      m_SpinButton_Year.set_wrap();
      m_SpinButton_Year.set_expand();
      m_SpinButton_Year.set_size_request(55, -1);
      m_VBox_Year.append(m_SpinButton_Year);

      m_HBox_NotAccelerated.append(m_VBox_Year);

      //Accelerated:
      m_VBox_Main.append(m_Frame_Accelerated);

      m_VBox_Accelerated.set_margin(5);
      m_Frame_Accelerated.set_child(m_VBox_Accelerated);

      m_VBox_Accelerated.set_spacing(5);
      m_VBox_Accelerated.append(m_HBox_Accelerated);

      m_HBox_Accelerated.append(m_VBox_Value);

      m_Label_Value.set_expand();
      m_VBox_Value.append(m_Label_Value);

      m_SpinButton_Value.set_wrap();
      m_SpinButton_Value.set_expand();
      m_SpinButton_Value.set_size_request(100, -1);
      m_VBox_Value.append(m_SpinButton_Value);

      m_HBox_Accelerated.append(m_VBox_Digits);

      m_VBox_Digits.append(m_Label_Digits);
      m_Label_Digits.set_expand();

      m_SpinButton_Digits.set_wrap();
      m_adjustment_digits->signal_value_changed().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_spinbutton_digits_changed) );

      m_VBox_Digits.append(m_SpinButton_Digits);
      m_SpinButton_Digits.set_expand();

      //CheckButtons:
      m_VBox_Accelerated.append(m_CheckButton_Snap);
      m_CheckButton_Snap.set_expand();
      m_CheckButton_Snap.set_active();
      m_CheckButton_Snap.signal_toggled().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbutton_snap) );

      m_VBox_Accelerated.append(m_CheckButton_Numeric);
      m_CheckButton_Numeric.set_expand();
      m_CheckButton_Numeric.set_active();
      m_CheckButton_Numeric.signal_toggled().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbutton_numeric) );

      //Buttons:
      m_VBox_Accelerated.append(m_HBox_Buttons);

      m_Button_Int.signal_clicked().connect( sigc::bind( sigc::mem_fun(*this,
                      &ExampleWindow::on_button_getvalue), VALUE_FORMAT_INT) );
      m_HBox_Buttons.set_spacing(5);
      m_HBox_Buttons.append(m_Button_Int);
      m_Button_Int.set_expand();

      m_Button_Float.signal_clicked().connect( sigc::bind( sigc::mem_fun(*this,
                      &ExampleWindow::on_button_getvalue), VALUE_FORMAT_FLOAT) );
      m_HBox_Buttons.append(m_Button_Float);
      m_Button_Float.set_expand();

      m_VBox_Accelerated.append(m_Label_ShowValue);
      m_Label_ShowValue.set_expand();
      m_Label_ShowValue.set_text("0");

      //Close button:
      m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_close) );
      m_VBox_Main.append(m_Button_Close);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

    void ExampleWindow::on_checkbutton_snap()
    {
      m_SpinButton_Value.set_snap_to_ticks( m_CheckButton_Snap.get_active() );
    }

    void ExampleWindow::on_checkbutton_numeric()
    {
      m_SpinButton_Value.set_numeric( m_CheckButton_Numeric.get_active() );
    }

    void ExampleWindow::on_spinbutton_digits_changed()
    {
      m_SpinButton_Value.set_digits( m_SpinButton_Digits.get_value_as_int() );
    }

    void ExampleWindow::on_button_getvalue(enumValueFormats display)
    {
      gchar buf[32];

      if (display == VALUE_FORMAT_INT)
        sprintf (buf, "%d", m_SpinButton_Value.get_value_as_int());
      else
        sprintf (buf, "%0.*f", m_SpinButton_Value.get_digits(),
                m_SpinButton_Value.get_value());

      m_Label_ShowValue.set_text(buf);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 进度条(ProgressBar)

进度条用于显示正在进行的操作的状态。例如，ProgressBar可以用于显示已经有多少任务被完成。

要修改显示的值，请调用set_fraction()方法，向该方法传递一个0.0到1.0之间的double值。

ProgressBar默认是水平且从左向右的，但是你可以使用set_orientation()方法来改变这个行为。

Reference

#### 活动模式

Besides indicating the amount of progress that has occurred, the progress bar can also be used to indicate that there is some activity; this is done by placing the progress bar in activity mode. In this mode, the progress bar displays a small rectangle which moves back and forth. Activity mode is useful in situations where the progress of an operation cannot be calculated as a value range (e.g., receiving a file of unknown length).

为此，你还需要定时调用pulse()方法。你还可以使用set_pulse_step()方法控制矩形的移动步长。

进度条还可以使用set_text()方法来在其边上显示可配置的文本字符串。

#### 示例

进度条(ProgressBar)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_checkbutton_text();
      void on_checkbutton_activity();
      void on_checkbutton_inverted();

      bool on_timeout();
      void on_button_close();

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Grid m_Grid;
      Gtk::ProgressBar m_ProgressBar;
      Gtk::Separator m_Separator;
      Gtk::CheckButton m_CheckButton_Text, m_CheckButton_Activity, m_CheckButton_Inverted;
      Gtk::Button m_Button_Close;

      sigc::connection m_connection_timeout;
      bool m_bActivityMode;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL, 5),
      m_CheckButton_Text("Show text"),
      m_CheckButton_Activity("Activity mode"),
      m_CheckButton_Inverted("Right to Left"),
      m_Button_Close("Close"),
      m_bActivityMode(false)
    {
      set_resizable();
      set_title("Gtk::ProgressBar");

      m_VBox.set_margin(10);
      set_child(m_VBox);

      m_VBox.append(m_ProgressBar);
      m_ProgressBar.set_margin_end(5);
      m_ProgressBar.set_halign(Gtk::Align::CENTER);
      m_ProgressBar.set_valign(Gtk::Align::CENTER);
      m_ProgressBar.set_size_request(100, -1);
      m_ProgressBar.set_text("some text");
      m_ProgressBar.set_show_text(false);

      //Add a timer callback to update the value of the progress bar:
      m_connection_timeout = Glib::signal_timeout().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_timeout), 50 );

      m_VBox.append(m_Separator);
      m_VBox.append(m_Grid);
      m_Grid.set_expand(true);
      m_Grid.set_row_homogeneous(true);

      //Add a check button to select displaying of the trough text:
      m_Grid.attach(m_CheckButton_Text, 0, 0);
      m_CheckButton_Text.set_margin(5);
      m_CheckButton_Text.signal_toggled().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbutton_text) );

      //Add a check button to toggle activity mode:
      m_Grid.attach(m_CheckButton_Activity, 0, 1);
      m_CheckButton_Activity.set_margin(5);
      m_CheckButton_Activity.signal_toggled().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbutton_activity) );

      //Add a check button to select growth from left to right or from right to left:
      m_Grid.attach(m_CheckButton_Inverted, 0, 2);
      m_CheckButton_Inverted.set_margin(5);
      m_CheckButton_Inverted.signal_toggled().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_checkbutton_inverted) );

      //Add a button to exit the program.
      m_VBox.append(m_Button_Close);
      m_Button_Close.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_close) );
      set_default_widget(m_Button_Close);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_checkbutton_text()
    {
      const bool show_text = m_CheckButton_Text.get_active();
      m_ProgressBar.set_show_text(show_text);
    }

    void ExampleWindow::on_checkbutton_activity()
    {
      m_bActivityMode = m_CheckButton_Activity.get_active();

      if(m_bActivityMode)
        m_ProgressBar.pulse();
      else
        m_ProgressBar.set_fraction(0.0);
    }

    void ExampleWindow::on_checkbutton_inverted()
    {
      const bool inverted = m_CheckButton_Inverted.get_active();
      m_ProgressBar.set_inverted(inverted);
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

    /* Update the value of the progress bar so that we get
     * some movement */
    bool ExampleWindow::on_timeout()
    {
      if(m_bActivityMode)
        m_ProgressBar.pulse();
      else
      {
        double new_val = m_ProgressBar.get_fraction() + 0.01;

        if(new_val > 1.0)
          new_val = 0.0;

        //Set the new value:
        m_ProgressBar.set_fraction(new_val);
      }

      //As this is a timeout function, return true so that it
      //continues to get called
      return true;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 信息栏(InfoBar)

InfoBar可用于显示一些信息、小物品或者简短的问题。他显示在窗口的顶部而不是和Dialog一样打开一个新的窗口。但是他的API和Gtk::Dialog的API十分相似。

Reference

#### 示例

The InfoBar widget is deprecated since gtkmm 4.10. The example shows an info bar consisting of a Box with a Label and a Button.

信息栏(InfoBar)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_infobar_ok();
      void on_button_quit();
      void on_button_clear();
      void on_textbuffer_changed();
      void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
        const Glib::Error& error);

      void set_infobar_background();

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;

      Gtk::Box m_InfoBar;
      Gtk::Label m_Message_Label;
      Gtk::Button m_Button_OK;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
      Gtk::Button m_Button_Clear;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    // The Gtk::InfoBar widget is deprecated since gtk/gtkmm 4.10.
    // A Box with a Label and Button is often a good replacement.
    // If you don't care about its background color, it's even easier
    // than the InfoBar replacement in this example program.

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL, 6),
      m_InfoBar(Gtk::Orientation::HORIZONTAL, 6),
      m_Button_OK("_OK", true),
      m_ButtonBox(Gtk::Orientation::HORIZONTAL, 6),
      m_Button_Quit("_Quit", true),
      m_Button_Clear("_Clear", true)
    {
      set_title("Infobar example");
      set_default_size(400, 200);

      m_VBox.set_margin(6);
      set_child(m_VBox);

      // Add the message label to the InfoBar:
      m_InfoBar.append(m_Message_Label);
      m_Message_Label.set_margin_start(5);
      m_Message_Label.set_hexpand(true);
      m_Message_Label.set_halign(Gtk::Align::START);

      // Add an ok button to the InfoBar:
      m_InfoBar.append(m_Button_OK);
      m_Button_OK.set_margin(5);

      // Add the InfoBar to the vbox:
      m_VBox.append(m_InfoBar);

      // Create the buffer and set it for the TextView:
      m_refTextBuffer = Gtk::TextBuffer::create();
      m_TextView.set_buffer(m_refTextBuffer);

      // Add the TextView, inside a ScrolledWindow:
      m_ScrolledWindow.set_child(m_TextView);

      // Show the scrollbars only when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);

      // Add button box:
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Clear);
      m_ButtonBox.append(m_Button_Quit);
      m_Button_Clear.set_hexpand(true);
      m_Button_Clear.set_halign(Gtk::Align::END);

      // Connect signals:
      m_Button_OK.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_infobar_ok) );
      m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );
      m_Button_Clear.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_clear) );
      m_refTextBuffer->signal_changed().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_textbuffer_changed) );

      // Keep the InfoBar hidden until a message needs to be shown:
      m_InfoBar.set_visible(false);
      set_infobar_background();

      // Make the clear button insensitive until text is typed in the buffer.  When
      // the button is sensitive and it is pressed, the InfoBar is displayed with a
      // message.
      m_Button_Clear.set_sensitive(false);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_infobar_ok()
    {
      // Clear the message and hide the info bar:
      m_Message_Label.set_text("");
      m_InfoBar.set_visible(false);
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_button_clear()
    {
      m_refTextBuffer->set_text("");
      m_Message_Label.set_text("Cleared the text.");
      m_InfoBar.set_visible(true);
    }

    void ExampleWindow::on_textbuffer_changed()
    {
      m_Button_Clear.set_sensitive(m_refTextBuffer->size() > 0);
    }

    void ExampleWindow::set_infobar_background()
    {
      // Concerning color in CSS files, see https://www.w3.org/TR/css-color-3.
      const std::string css = "#infobarbox { background-color: beige; }";

      m_InfoBar.set_name("infobarbox");
      auto css_provider = Gtk::CssProvider::create();
      Gtk::StyleProvider::add_provider_for_display(
        m_InfoBar.get_display(), css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

      css_provider->load_from_data(css);
      css_provider->signal_parsing_error().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_parsing_error));
    }

    void ExampleWindow::on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
      const Glib::Error& error)
    {
      std::cerr << "on_parsing_error(): " << error.what() << std::endl;
      if (section)
      {
        const auto file = section->get_file();
        if (file)
        {
          std::cerr << "  URI = " << file->get_uri() << std::endl;
        }

        auto start_location = section->get_start_location();
        auto end_location = section->get_end_location();
        std::cerr << "  start_line = " << start_location.get_lines()+1
                  << ", end_line = " << end_location.get_lines()+1 << std::endl;
        std::cerr << "  start_position = " << start_location.get_line_chars()
                  << ", end_position = " << end_location.get_line_chars() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 工具提示框(Tooltips)

工具提示框是将鼠标悬停于部件上几秒后弹出的信息窗口。使用set_tooltip_text()可以为任意的Widget设置一个文本字符串作为提示。Gtk::Tooltip被用作更高级的提示工具，例如显示图像和文本。

Widget Reference

Tooltip Reference

#### 示例

工具提示框(Tooltip)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:

      ExampleWindow();
      virtual ~ExampleWindow();

    protected:

      //Methods:
      void prepare_textview();
      void connect_signals();

      //Signal handlers:
      void on_markup_checkbutton_toggled();
      bool on_textview_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip);
      bool on_button_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip);

      //Child widgets:
      Gtk::Box m_vbox;

      Gtk::CheckButton m_checkbutton;
      Gtk::Label m_label;

      Gtk::ScrolledWindow m_scrolled_window;
      Gtk::TextView m_text_view;
      Glib::RefPtr<Gtk::TextBuffer> m_ref_text_buffer;
      Glib::RefPtr<Gtk::TextTag> m_ref_bold_tag;

      Gtk::Button m_button;
      Gtk::Box m_button_tooltip_widget;
    };

    #endif // GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <vector>

    const Glib::ustring app_title = "gtkmm tooltips example";
    const Glib::ustring non_markedup_tip = "A tooltip without markup.";
    const Glib::ustring markedup_tip = "<i>Markup</i> in a tooltip.";

    ExampleWindow::ExampleWindow()
      :
      m_vbox(Gtk::Orientation::VERTICAL, 3),
      m_checkbutton("Click to alternate markup in tooltip"),
      m_label("A label"),
      m_button("Button with a custom tooltip widget")
    {
      //Set up window and the top-level container:
      set_title(app_title);

      m_vbox.set_margin(10);
      set_child(m_vbox);

      //Check button with markup in tooltip:
      m_checkbutton.set_tooltip_text(non_markedup_tip);
      m_vbox.append(m_checkbutton);

      //Label:
      m_label.set_tooltip_text("Another tooltip");
      m_vbox.append(m_label);

      //Textview:
      prepare_textview();

      //Button:
      //When only connecting to the query-tooltip signal, and not using any
      //of set_tooltip_text() or set_tooltip_markup(), we need to explicitly
      //tell GTK that the widget has a tooltip which we'll show.
      m_button.set_has_tooltip();
      m_vbox.append(m_button);

      //Button's custom tooltip widget:
      auto label = Gtk::make_managed<Gtk::Label>("A label in a custom tooltip widget");
      m_button_tooltip_widget.append(*label);

      connect_signals();
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::prepare_textview()
    {
      Gtk::TextIter iter;
      std::vector<Glib::RefPtr<Gtk::TextTag>> tags;

      //Set up a scrolled window:
      m_scrolled_window.set_child(m_text_view);
      m_scrolled_window.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_scrolled_window.set_expand();
      m_vbox.append(m_scrolled_window);

      //Create a text buffer with some text:
      m_ref_text_buffer = Gtk::TextBuffer::create();

      iter = m_ref_text_buffer->end();
      m_ref_text_buffer->insert(iter, "Hover over the text ");

      //Insert some text with a tag.
      //In the tooltip signal handler below, we will show a tooltip
      //when mouse pointer is above this tagged text.
      m_ref_bold_tag = m_ref_text_buffer->create_tag("bold");
      m_ref_bold_tag->set_property("weight", Pango::Weight::BOLD);

      tags.push_back(m_ref_bold_tag);

      iter = m_ref_text_buffer->end();
      m_ref_text_buffer->insert_with_tags(iter, "in bold", tags);

      iter = m_ref_text_buffer->end();
      m_ref_text_buffer->insert(iter, " to see its tooltip");

      m_text_view.set_buffer(m_ref_text_buffer);

      m_text_view.set_size_request(320, 50);

      //When only connecting to the query-tooltip signal, and not using any
      //of set_tooltip_text() or set_tooltip_markup(), we need to explicitly
      //tell GTK that the widget has a tooltip which we'll show.
      m_text_view.set_has_tooltip();
    }

    void ExampleWindow::connect_signals()
    {
      m_checkbutton.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_markup_checkbutton_toggled));

      m_text_view.signal_query_tooltip().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_textview_query_tooltip), true);

      m_button.signal_query_tooltip().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_query_tooltip), true);
    }

    void ExampleWindow::on_markup_checkbutton_toggled()
    {
      if (m_checkbutton.get_active())
      {
        m_checkbutton.set_tooltip_markup(markedup_tip);
      }
      else
      {
        m_checkbutton.set_tooltip_markup(non_markedup_tip);
      }
    }

    bool ExampleWindow::on_textview_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
    {
      Gtk::TextIter iter;

      if (keyboard_tooltip)
      {
        int offset = m_ref_text_buffer->property_cursor_position().get_value();
        iter = m_ref_text_buffer->get_iter_at_offset(offset);
      }
      else
      {
        int mouse_x, mouse_y, trailing;
        m_text_view.window_to_buffer_coords(Gtk::TextWindowType::TEXT,
                                            x, y, mouse_x, mouse_y);
        m_text_view.get_iter_at_position(iter, trailing, mouse_x, mouse_y);
      }

      if (!iter.has_tag(m_ref_bold_tag))
        return false;

      //Show a tooltip if the cursor or mouse pointer is over the text
      //with the specific tag:
      tooltip->set_markup("<b>Information</b> attached to a text tag");
      tooltip->set_icon("dialog-information");
      return true;
    }

    bool ExampleWindow::on_button_query_tooltip(int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
    {
      tooltip->set_custom(m_button_tooltip_widget);
      return true;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 容器(Container)部件

容器部件与其他部件一样都是从Gtk::Widget派生而来。一些容器部件可以容纳多个子部件，例如Gtk::Grid，因此通常他们具有更为复杂的接口。还有些容器部件只能容纳一个子部件，例如Gtk::Frame

### 单项容器

大多数单项容器部件具有其子部件的set_child()和unset_child()方法。Gtk::Button和Gtk::Window从技术上来说是单项容器，但是我们已经在其他地方讨论过它们。

我们还讨论了Gtk::Paned部件，它允许你将窗口分为两个单独的"窗格"，这个部件实际上可以包含两个子部件，但是他只能包含固定数量的部件，因此也将其称为单项容器。

#### 框架(Frame)

框架可以将一个或一组部件封装在一个框内，还可以为他们选择标题。例如你可以将一组ToggleButton或者CheckButton放入一个Frame中。

Reference

##### 示例

框架(Frame)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:

      //Child widgets:
      Gtk::Frame m_Frame;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    {
     /* Set some window properties */
      set_title("Frame Example");
      set_size_request(300, 300);

      /* Sets the margin around the frame. */
      m_Frame.set_margin(10);

      set_child(m_Frame);

      /* Set the frames label */
      m_Frame.set_label("Gtk::Frame Widget");

      /* Align the label at the right of the frame */
      m_Frame.set_label_align(Gtk::Align::END);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 窗格(Paned)

窗格用一个可移动的分隔符将一个部件一分为二。其可以水平（使两个窗格并排）也可以竖直（使两个窗格一个在另一个之上）的分隔部件。

与本小节的其他小部件不一样的是，窗格部件包含两个子部件而不是一个部件（每个窗格中一个）。因此你应该用set_start_child()和set_end_child()替代set_child()方法。

如果你需要，你可以使用set_position()调整分割线的位置。

Reference

##### 示例

窗格(Paned)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include "messageslist.h"
    #include "messagetext.h"
    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:

      //Child widgets:
      Gtk::Paned m_VPaned;
      MessagesList m_MessagesList;
      MessageText m_MessageText;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `messageslist.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MESSAGESLIST_H
    #define GTKMM_EXAMPLE_MESSAGESLIST_H

    #include <gtkmm.h>

    class MessagesList: public Gtk::ScrolledWindow
    {
    public:
      MessagesList();
      ~MessagesList() override;

    protected:
      // Signal handlers:
      void on_setup_message(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_message(const Glib::RefPtr<Gtk::ListItem>& list_item);

      Glib::RefPtr<Gtk::StringList> m_refStringList; // The List Model.
      Gtk::ListView m_ListView; // The List View.
    };
    #endif //GTKMM_EXAMPLE_MESSAGESLIST_H

File: `messagetext.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MESSAGETEXT_H
    #define GTKMM_EXAMPLE_MESSAGETEXT_H

    #include <gtkmm.h>

    class MessageText : public Gtk::ScrolledWindow
    {
    public:
      MessageText();
      virtual ~MessageText();

      void insert_text();

    protected:
      Gtk::TextView m_TextView;
    };

    #endif //GTKMM_EXAMPLE_MESSAGETEXT_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VPaned(Gtk::Orientation::VERTICAL)
    {
      set_title ("Paned Windows");
      set_default_size(450, 400);
      m_VPaned.set_margin(10);

      /* Add a vpaned widget to our toplevel window */
      set_child(m_VPaned);

      /* Now add the contents of the two halves of the window */
      m_VPaned.set_start_child(m_MessagesList);
      m_VPaned.set_end_child(m_MessageText);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `messageslist.cc` (For use with gtkmm 4)

    #include "messageslist.h"

    MessagesList::MessagesList()
    {
      /* Create a new scrolled window, with scrollbars only if needed */
      set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);

      set_child(m_ListView);

      /* create list store */
      m_refStringList = Gtk::StringList::create({});
      auto selection_model = Gtk::SingleSelection::create(m_refStringList);
      m_ListView.set_model(selection_model);

      /* Add some messages to the window */
      for (int i = 1; i <= 10; ++i)
        m_refStringList->append(Glib::ustring::format("message #", i));

      // Create a ListItemFactory to use for populating list items.
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::mem_fun(*this, &MessagesList::on_setup_message));
      factory->signal_bind().connect(sigc::mem_fun(*this, &MessagesList::on_bind_message));
      m_ListView.set_factory(factory);
    }

    MessagesList::~MessagesList()
    {
    }

    void MessagesList::on_setup_message(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto label = Gtk::make_managed<Gtk::Label>();
      label->set_halign(Gtk::Align::START);
      list_item->set_child(*label);
    }

    void MessagesList::on_bind_message(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto pos = list_item->get_position();
      if (pos == GTK_INVALID_LIST_POSITION)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(m_refStringList->get_string(pos));
    }

File: `messagetext.cc` (For use with gtkmm 4)

    #include "messagetext.h"

    MessageText::MessageText()
    {
      set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);

      set_child(m_TextView);
      insert_text();
    }

    MessageText::~MessageText()
    {
    }

    void MessageText::insert_text()
    {
      auto refTextBuffer = m_TextView.get_buffer();

      auto iter = refTextBuffer->get_iter_at_offset(0);
      refTextBuffer->insert(iter,
        "From: pathfinder@nasa.gov\n"
        "To: mom@nasa.gov\n"
        "Subject: Made it!\n"
        "\n"
        "We just got in this morning. The weather has been\n"
        "great - clear but cold, and there are lots of fun sights.\n"
        "Sojourner says hi. See you soon.\n"
        " -Path\n");
    }

#### 滚动窗口

ScrolledWindow部件会创建一个可以滚动的区域。你可以将任何类型的部件插入到ScrolledWindow中，无论该部件有多大你都可以通过滚动条来访问它。请注意虽然ScrolledWindow的名称像是Gtk::Window的衍生部件，但其实不是。

滚动窗口具有用于确定是否要显示Scrollbar(滚动条)的scrollbar policies(滚动条策略)。你可以使用set_policy()方法来设置策略。例如设置策略为`Gtk::PolicyType::AUTOMATIC`或是`Gtk::PolicyType::ALWAYS`。当策略为`Gtk::PolicyType::AUTOMATIC`的时候，只有在被包含的部件大于可见区域的时候滚动条才会被显示出来。而`Gtk::PolicyType::ALWAYS`将使滚动条始终显示。

Reference

##### 示例

这是一个简单的示例，其将100个开关按钮放入滚动窗口中。你可以尝试调整窗口的大小，以查看滚动条的反应。

滚动窗口

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_button_close();

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::Grid m_Grid;
      Gtk::Button m_ButtonClose;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL, 10),
      m_ButtonClose("_Close", true)
    {
      set_title("Gtk::ScrolledWindow example");
      set_size_request(300, 300);

      set_child(m_VBox);
      m_VBox.set_margin(10);

      /* The policy is one of Gtk::PolicyType::AUTOMATIC, or Gtk::PolicyType::ALWAYS.
       * Gtk::PolicyType::AUTOMATIC will automatically decide whether you need
       * scrollbars, whereas Gtk::PolicyType::ALWAYS will always leave the scrollbars
       * there. The first one is the horizontal scrollbar, the second, the vertical. */
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::ALWAYS);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);

      /* set the spacing to 10 on x and 10 on y */
      m_Grid.set_row_spacing(10);
      m_Grid.set_column_spacing(10);

      /* pack the grid into the scrolled window */
      m_ScrolledWindow.set_child(m_Grid);

      /* this simply creates a grid of toggle buttons
       * to demonstrate the scrolled window. */
      for (int i = 0; i < 10; i++)
      {
        for (int j = 0; j < 10; j++)
        {
          auto s = Glib::ustring::compose("button (%1,%2)", i, j);
          auto pButton = Gtk::make_managed<Gtk::ToggleButton>(s);
          m_Grid.attach(*pButton, i, j);
        }
      }

      /* Add a "close" button to the bottom of the dialog */
      m_VBox.append(m_ButtonClose);
      m_ButtonClose.set_halign(Gtk::Align::END);
      m_ButtonClose.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_close));

      /* Hitting the "Enter" key will cause this button to activate. */
      m_ButtonClose.grab_focus();
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_close()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 定比框架(AspectFrame)

AspectFrame部件和Frame部件类似，但它强制子部件的aspect ratio(长宽比)（长与宽的比例）保持不变。并在有必要的时候添加额外的空间。例如，这将允许你显示照片并且无需在用户调整窗口大小的时候对照片进行水平/垂直变形。

Reference

##### 示例

下面的程序使用Gtk::AspectFrame显示一个绘图区域，无论用户如何调整顶层窗口大小宽高比依旧保持2:1。

定比框架(AspectFrame)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:

      //Child widgets:
      Gtk::AspectFrame m_AspectFrame;
      Gtk::Frame m_Frame;
      Gtk::DrawingArea m_DrawingArea;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_AspectFrame(
        Gtk::Align::CENTER, /* center x */
        Gtk::Align::CENTER, /* center y */
        2.0, /* xsize/ysize = 2 */
        false /* ignore child's aspect */),
      m_Frame("2x1" /* label */)
    {
      set_title("Aspect Frame");

      // Set a child widget to the aspect frame */
      // Ask for a 200x200 window, but the AspectFrame will give us a 200x100
      // window since we are forcing a 2x1 aspect ratio */
      m_DrawingArea.set_content_width(200);
      m_DrawingArea.set_content_height(200);
      m_Frame.set_child(m_DrawingArea);
      m_AspectFrame.set_child(m_Frame);
      m_AspectFrame.set_margin(10);

      // Add the aspect frame to our toplevel window:
      set_child(m_AspectFrame);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 其他单项容器

还有其他的单项容器。请参阅参考文档获取完整列表。这里是一些本教程其他地方未曾提及的显示容器的示例程序的链接。

Source Code, Expander

Source Code, Popover

### 多项容器

多项容器部件除set_child()和unset_child()方法以外还有其他的方法。不同容器有不同的添加/删除子部件的方法。例如Gtk::Box具有append()和remove()以及其他方法。多项容器的remove()方法需要传入一个用于指示需要删除的部件的参数。

#### 组装(Packing)

你可能已经注意到了gtkmm窗口看起来是“弹性”的 -他们通常可以以不同方式进行拉伸。这是部件装填(widget packing)系统在起作用。

许多GUI工具包使用可视化编辑器的时候都要求你使用部件相对于窗口的绝对位置。这将导致以下几个问题：

- 在调整窗口大小的时候部件不会重新排布自己。窗口变小的时候一些部件会被隐藏窗口变大的时候会出现很多无用的空间。

- 将文本翻译成其他语言或者用其他字体显示的时候，无法预测显示文本所需要的空间。而在Unix上不可能预料在每个主题和窗口管理器下的显示效果。

- 即时更改窗口布局以显示额外的部件将会很复杂，这需要对每个部件的位置进行繁琐的重新计算。

gtkmm使用装填系统解决这些问题。无需为窗口中的每个部件指定大小和位置，而是将部件放入行、列、和或网格中。gtkmm可以基于包含的部件的大小自动调整你的窗口大小。部件的大小由它包含的文本量、最大/小尺寸（由你指定的）、部件之间如何共享可用空间（你要求的方式）共同决定。你可以通过为每个部件指定边距与居中值来完善布局。然后gtkmm会使用以上的信息在用户操作窗口的时候合理、顺畅的调整部件们的大小和位置。

gtkmm arranges widgets hierarchically, using containers. A container widget contains other widgets. Most gtkmm widgets are containers. Windows, Notebook tabs, and Buttons are all container widgets. There are two flavors of containers: single-child containers and multiple-child containers. Most container widgets in gtkmm are single-child containers, including Gtk::Window.

对，没错，一个窗口最多只能包含一个部件。那么我们要如何将多个部件放于窗口中呢？答案是通过向窗口放入一个多项容器实现。最合理的部件是Gtk::Grid和Gtk::Box。

- Gtk::Grid将其子部件按行和列排列。使用attach()和attach_next_to()方法向网格(Grid)部件中插入子部件。

- Gtk::Box将其子部件水平或垂直排列。使用append()方法向盒(Box)部件插入子部件。

还有几个容器我们将在之后讨论。

如果你之前从未使用组装工具包，那可能需要一段时间来适应它。然后你会发现你不需要和使用其他工具包一样依赖可视化编辑工具。

#### 改进后的Hello World

让我们看看改进后的`helloworld`，以展示我们所学到的内容。

Hello World 2

Source Code

File: `helloworld.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_HELLOWORLD_H
    #define GTKMM_EXAMPLE_HELLOWORLD_H

    #include <gtkmm/box.h>
    #include <gtkmm/button.h>
    #include <gtkmm/window.h>

    class HelloWorld : public Gtk::Window
    {
    public:
      HelloWorld();
      ~HelloWorld() override;

    protected:

      // Signal handlers:
      // Our new improved on_button_clicked().
      void on_button_clicked(const Glib::ustring& data);

      // Child widgets:
      Gtk::Box m_box1;
      Gtk::Button m_button1, m_button2;
    };

    #endif // GTKMM_EXAMPLE_HELLOWORLD_H

File: `helloworld.cc` (For use with gtkmm 4)

    #include "helloworld.h"
    #include <iostream>

    HelloWorld::HelloWorld()
    : m_button1("Button 1"),
      m_button2("Button 2")
    {
      // This just sets the title of our new window.
      set_title("Hello Buttons!");

      // Sets the margin around the box.
      m_box1.set_margin(10);

      // put the box into the main window.
      set_child(m_box1);

      // Now when the button is clicked, we call the on_button_clicked() function
      // with a pointer to "button 1" as its argument.
      m_button1.signal_clicked().connect(sigc::bind(
                  sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1"));

      // We use Gtk::Box::append() to pack this button into the box,
      // which has been packed into the window.
      // A widget's default behaviour is not to expand if extra space is available.
      // A container widget by default expands if any of the contained widgets
      // wants to expand.
      m_box1.append(m_button1);
      m_button1.set_expand();

      // call the same signal handler with a different argument,
      // passing a pointer to "button 2" instead.
      m_button2.signal_clicked().connect(sigc::bind(
                  sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 2"));

      m_box1.append(m_button2);
      m_button2.set_expand();

      // Gtk::Widget::set_visible(true) is seldom needed. All widgets are visible by default.
    }

    HelloWorld::~HelloWorld()
    {
    }

    // Our new improved signal handler.  The data passed to this method is
    // printed to stdout.
    void HelloWorld::on_button_clicked(const Glib::ustring& data)
    {
      std::cout << "Hello World - " << data << " was pressed" << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "helloworld.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<HelloWorld>(argc, argv);
    }

After building and running this program, try resizing the window to see the behavior. Also, try playing with set_expand(), set_hexpand(), set_vexpand(), set_halign() and set_valign() while reading the Boxes section.

#### 盒子(Boxes)

如上例所示，这些都是不可见容器我们可以将容器装入其中。将部件装填到水平盒中的时候部件从左到右水平插入。将部件装填到垂直盒中的时候部件从上到下垂直插入。你可以在盒中或盒侧组合使用盒以达到你所需的效果。

##### 添加部件

###### 子部件装填选项

append()方法将部件放置于这些容器中。它们将从顶部开始。按Box的垂直方向向下移动，或者按Box的水平方向从左向右移动。如果你不愿意按此顺序添加子部件，请使用insert_child_after()或insert_child_at_start()方法。我们将在示例中使用append()。

有几个选项可以控制部件的打包方式，但是初次使用可能会难以理清效果。你可以在子部件上使用set_expand()、set_hexpand()、set_vexpand()、set_halign()、set_valign()方法修改装填策略。如果你感到困难，可以试试gladeGUI设计器，看看能不能对你有所帮助。你甚至可以使用Gtk::BuilderAPI在运行时加载GUI。

如图所示，有五种不同的基本样式。

组装盒1

每一行包含一个有多个按钮的水平Box。行中的每一个按钮都使用相同参数的set_hexpand()、set_halign()、set_margin_start()和set_margin_end()方法装入Box。

Reference

###### 容器装填选项

这是Box部件的构造函数，以及设置每个容器的打包选项的方法：

    Gtk::Box(Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL, int spacing = 0);
    void set_orientation(Gtk::Orientation orientation);
    void set_spacing(int spacing);
    void set_homogeneous(bool homogeneous = true);

将`true`传递给set_homogeneous()方法将导致容器部件所包含的所有部件都有一样的大小。spacing是每个部件之间要保留的最小像素数。

间距（创建盒的时候设置）与边距（为每个子部件分别设置）的区别是：在对象之间添加间距，在部件的一侧或多侧添加边距。下图能使你更清楚的理解这一点。所显示的边距是该行中每个按钮的左右边距。

组装盒2

##### Gtk::Application和命令行选项

下面的示例程序需要一个命令行选项。源代码展示了两种Gtk::Application处理命令行选项的方式。

- 在`main()`中处理选项并且通过设置`argc = 1`将他们从Gtk::Application中隐藏，然后调用Gtk::Application::run()。

- 将所有的命令行选项传递给Gtk::Application::run()并为Gtk::Application::create()方法设置标志`Gio::Application::Flags::HANDLES_COMMAND_LINE`。

  你必须在调用`signal_command_line().connect()`时设置可选参数`after = false`，因为此时必须在默认的信号处理程序之前调用你的信号处理程序。你还必须调用Gio::Application::activate()处理程序，除非你希望你的应用程序不显示主窗口直接退出（Gio::Application是Gtk::Application的基类）。

##### 示例

这是上述截图所运行示例的源代码。在运行此示例时请提供1到3之间的数字作为命令行选项以查看正在使用不同装填选项的行为。

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(int which);
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit_clicked();

      //Child widgets:
      Gtk::Button m_button;
      Gtk::Box m_box1;
      Gtk::Box m_boxQuit;
      Gtk::Button m_buttonQuit;

      Gtk::Label m_Label1, m_Label2;

      Gtk::Separator m_separator1, m_separator2;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `packbox.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_PACKBOX_H
    #define GTKMM_EXAMPLE_PACKBOX_H

    #include <gtkmm.h>

    class PackBox : public Gtk::Box
    {
    public:
      PackBox(bool homogeneous = false, int spacing = 0, bool expand = false,
        Gtk::Align align = Gtk::Align::FILL, int margin = 0);

    protected:
      Gtk::Button m_buttons[4];
    };

    #endif //GTKMM_EXAMPLE_PACKBOX_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"
    #include "packbox.h"

    ExampleWindow::ExampleWindow(int which)
    : m_box1(Gtk::Orientation::VERTICAL),
      m_buttonQuit("Quit")
    {
      set_title("Gtk::Box example");

      m_separator1.set_margin_top(5);
      m_separator1.set_margin_bottom(5);
      m_separator2.set_margin_top(5);
      m_separator2.set_margin_bottom(5);

      switch(which)
      {
        case 1:
        {
          m_Label1.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 0); set_homogeneous(false);");

          // Align the label to the left side.
          m_Label1.set_halign(Gtk::Align::START);
          m_Label1.set_valign(Gtk::Align::START);

          // Pack the label into the vertical box (vbox box1).  Remember that
          // widgets added to a vbox will be packed one on top of the other in
          // order.
          m_box1.append(m_Label1);

          // Create a PackBox - homogeneous = false, spacing = 0,
          // expand = false, Gtk::Align::FILL, margin = 0
          // These are the default values.
          auto pPackBox = Gtk::make_managed<PackBox>();
          m_box1.append(*pPackBox);

          // Create a PackBox - homogeneous = false, spacing = 0,
          // expand = true, Gtk::Align::CENTER, margin = 0
          pPackBox = Gtk::make_managed<PackBox>(false, 0, true, Gtk::Align::CENTER);
          m_box1.append(*pPackBox);

          // Create a PackBox - homogeneous = false, spacing = 0,
          // expand = true, Gtk::Align::FILL, margin = 0
          pPackBox = Gtk::make_managed<PackBox>(false, 0, true);
          m_box1.append(*pPackBox);

          // pack the separator into the vbox.  Remember each of these
          // widgets are being packed into a vbox, so they'll be stacked
          // vertically.
          m_box1.append(m_separator1);

          // create another new label, and show it.
          m_Label2.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 0); set_homogeneous(true);");
          m_Label2.set_halign(Gtk::Align::START);
          m_Label2.set_valign(Gtk::Align::START);
          m_box1.append(m_Label2);

          // Args are: homogeneous, spacing, expand, align, margin
          pPackBox = Gtk::make_managed<PackBox>(true, 0, true, Gtk::Align::CENTER);
          m_box1.append(*pPackBox);

          // Args are: homogeneous, spacing, expand, align, margin
          pPackBox = Gtk::make_managed<PackBox>(true, 0, true);
          m_box1.append(*pPackBox);

          m_box1.append(m_separator2);

          break;
        }

        case 2:
        {
          m_Label1.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 10); set_homogeneous(false);");
          m_Label1.set_halign(Gtk::Align::START);
          m_Label1.set_valign(Gtk::Align::START);
          m_box1.append(m_Label1);

          auto pPackBox = Gtk::make_managed<PackBox>(false, 10, true, Gtk::Align::CENTER);
          m_box1.append(*pPackBox);

          pPackBox = Gtk::make_managed<PackBox>(false, 10, true);
          m_box1.append(*pPackBox);

          m_box1.append(m_separator1);

          m_Label2.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 0); set_homogeneous(false);");
          m_Label2.set_halign(Gtk::Align::START);
          m_Label2.set_valign(Gtk::Align::START);
          m_box1.append(m_Label2);

          pPackBox = Gtk::make_managed<PackBox>(false, 0, false, Gtk::Align::FILL, 10);
          m_box1.append(*pPackBox);

          pPackBox = Gtk::make_managed<PackBox>(false, 0, true, Gtk::Align::FILL, 10);
          m_box1.append(*pPackBox);

          m_box1.append(m_separator2);

          break;
        }

        case 3:
        {
          // This demonstrates the ability to use Gtk::Align::END to
          // right justify widgets.  First, we create a new box as before.
          auto pPackBox = Gtk::make_managed<PackBox>();

          // create the label that will be put at the end.
          m_Label1.set_text("end");

          // pack it using Gtk::Align::END, so it is put on the right side
          // of the PackBox.
          m_Label1.set_halign(Gtk::Align::END);
          m_Label1.set_hexpand(true);
          pPackBox->append(m_Label1);

          m_box1.append(*pPackBox);

          // This explicitly sets the separator to 700 pixels wide by 5 pixels
          // high.  This is so the hbox we created will also be 700 pixels wide,
          // and the "end" label will be separated from the other labels in the
          // hbox.  Otherwise, all the widgets in the hbox would be packed as
          // close together as possible.
          m_separator1.set_size_request(700, 5);

          // pack the separator into the vbox.
          m_box1.append(m_separator1);

          break;
        }

        default:
        {
          std::cerr << "Unexpected command-line option." << std::endl;
          break;
        }
      }

      // Connect the signal to hide the window:
      m_buttonQuit.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit_clicked) );

      // pack the button into the quitbox.
      m_boxQuit.append(m_buttonQuit);
      m_buttonQuit.set_hexpand(true);
      m_buttonQuit.set_halign(Gtk::Align::CENTER);
      m_box1.append(m_boxQuit);

      // pack the vbox (box1) which now contains all our widgets, into the
      // main window.
      set_child(m_box1);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit_clicked()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>
    #include <iostream>
    #include <cstdlib>

    #define GTK_APPLICATION_RECEIVES_COMMAND_LINE_ARGUMENTS 0

    #if GTK_APPLICATION_RECEIVES_COMMAND_LINE_ARGUMENTS
    namespace
    {
    int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>& command_line,
                        Glib::RefPtr<Gtk::Application>& app)
    {
      int argc = 0;
      char** argv = command_line->get_arguments(argc);

      for (int i = 0; i < argc; ++i)
        std::cout << "argv[" << i << "] = " << argv[i] << std::endl;

      app->activate(); // Without activate() the window won't be shown.
      return EXIT_SUCCESS;
    }
    } // anonymous namespace
    #endif

    int main(int argc, char *argv[])
    {
      if (argc != 2)
      {
        std::cerr << "Usage: example <num>, where <num> is 1, 2, or 3." << std::endl;
        return EXIT_FAILURE;
      }

      int argc1 = argc;

    #if GTK_APPLICATION_RECEIVES_COMMAND_LINE_ARGUMENTS
      // The Gio::Application::Flags::HANDLES_COMMAND_LINE flag and the
      // on_command_line() signal handler are not necessary. This program is simpler
      // without them, and with argc = 1 in the call to Gtk::Application::make_window_and_run().
      // They are included to show a program with Gio::Application::Flags::HANDLES_COMMAND_LINE.
      // Gio::Application::Flags::NON_UNIQUE makes it possible to run several instances of
      // this application simultaneously.
      auto app = Gtk::Application::create(
        "org.gtkmm.example", Gio::Application::Flags::HANDLES_COMMAND_LINE | Gio::Application::Flags::NON_UNIQUE);

      // Note after = false.
      // Only one signal handler is invoked. This signal handler must run before
      // the default signal handler, or else it won't run at all.
      app->signal_command_line().connect(sigc::bind(sigc::ptr_fun(&on_command_line), app), false);
    #else
      // Gio::Application::Flags::NON_UNIQUE makes it possible to run several instances of
      // this application simultaneously.
      argc1 = 1; // Don't give the command line arguments to Gtk::Application.
      auto app = Gtk::Application::create("org.gtkmm.example", Gio::Application::Flags::NON_UNIQUE);
    #endif

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc1, argv, std::atoi(argv[1]));
    }

File: `packbox.cc` (For use with gtkmm 4)

    #include "packbox.h"
    #include <map>

    namespace
    {
      const std::map<Gtk::Align, Glib::ustring> align_string = {
        {Gtk::Align::FILL, "Gtk::Align::FILL"},
        {Gtk::Align::START, "Gtk::Align::START"},
        {Gtk::Align::END, "Gtk::Align::END"},
        {Gtk::Align::CENTER, "Gtk::Align::CENTER"},
        {Gtk::Align::BASELINE, "Gtk::Align::BASELINE"},
      };
    }

    PackBox::PackBox(bool homogeneous, int spacing, bool expand, Gtk::Align align, int margin)
    : Gtk::Box(Gtk::Orientation::HORIZONTAL, spacing)
    {
      set_homogeneous(homogeneous);

      m_buttons[0].set_label("box.append(button);");
      m_buttons[1].set_label("expand=" + Glib::ustring(expand ? "true" : "false"));
      m_buttons[2].set_label(align_string.at(align));
      m_buttons[3].set_label("margin=" + Glib::ustring::format(margin));

      for (auto& button : m_buttons)
      {
        append(button);
        button.set_hexpand(expand);
        button.set_halign(align);
        button.set_margin_start(margin);
        button.set_margin_end(margin);
      }
    }

#### 网格(Grid)

Grid在行与列中动态的摆放他的子部件。不需要在其构造函数当中指定维数。

使用attach()方法可以跨越多行/列插入子部件，也可以使用attach_next_to()将子部件插入已经存在于网格中的部件的旁边。可以通过调用set_row_homogeneous()和set_column_homogeneous()令网格中所有子部件具有一致的高度或宽度。

You can set the margin and expand properties of the child Widgets to control their spacing and their behavior when the Grid is resized.

Reference

##### 示例

本示例创建一个窗口其中具有一个包含三个按钮的网格。前两个按钮位于最上行并从左向右排列。第三个按钮位于第一个按钮的下方，位于最下行并横跨两列。

网格(Grid)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    private:
      // Signal handlers:
      void on_button_quit();
      void on_button_numbered(const Glib::ustring& data);

      // Child widgets:
      Gtk::Grid m_grid;
      Gtk::Button m_button_1, m_button_2, m_button_quit;
    };

    #endif /* GTKMM_EXAMPLEWINDOW_H */

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_button_1("button 1"),
      m_button_2("button 2"),
      m_button_quit("Quit")
    {
      set_title("Gtk::Grid");

      m_grid.set_margin(12);
      set_child(m_grid);

      m_grid.attach(m_button_1, 0, 0);
      m_grid.attach(m_button_2, 1, 0);
      m_grid.attach_next_to(m_button_quit, m_button_1, Gtk::PositionType::BOTTOM, 2, 1);

      m_button_1.signal_clicked().connect(
        sigc::bind( sigc::mem_fun(*this, &ExampleWindow::on_button_numbered), "button 1") );
      m_button_2.signal_clicked().connect(
        sigc::bind( sigc::mem_fun(*this, &ExampleWindow::on_button_numbered), "button 2") );

      m_button_quit.signal_clicked().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_button_quit) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void
    ExampleWindow::on_button_numbered(const Glib::ustring& data)
    {
      std::cout << data << " was pressed" << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 笔记本(Notebook)

Notebook有一组堆叠好的`pages`，每个页面(page)包含一个部件。带标签的`tabs`允许用户选择页面。Notebook一次只显示一页，这样可以将好几组部件放于很小的空间当中。例如他们经常在首选项对话框中使用。

使用append_page()、prepend_page()和insert_page()方法可以添加带标签的页面到`Notebook`中，你需要为这些方法提供子部件和标签名。

如果你想知道当前可见的页面是那一个，可以使用get_current_page()方法。该方法返回一个页码，你可以将页码传递给get_nth_page()获取指向该页当前子部件的指针。

要以编程方式改变所选的页面，请使用set_current_page()方法。

Reference

##### 示例

笔记本(Notebook)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();
      void on_notebook_switch_page(Gtk::Widget* page, guint page_num);

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Notebook m_Notebook;
      Gtk::Label m_Label1, m_Label2;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Label1("Contents of tab 1"),
      m_Label2("Contents of tab 2"),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::Notebook example");
      set_default_size(400, 200);

      m_VBox.set_margin(10);
      set_child(m_VBox);

      //Add the Notebook, with the button underneath:
      m_Notebook.set_margin(10);
      m_Notebook.set_expand();
      m_VBox.append(m_Notebook);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::CENTER);
      m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );

      //Add the Notebook pages:
      m_Notebook.append_page(m_Label1, "First");
      m_Notebook.append_page(m_Label2, "Second");

      m_Notebook.signal_switch_page().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_notebook_switch_page) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_notebook_switch_page(Gtk::Widget* /* page */, guint page_num)
    {
      std::cout << "Switched to tab with index " << page_num << std::endl;

      //You can also use m_Notebook.get_current_page() to get this index.
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 助手(Assistant)

Gtk::Assistant is deprecated since gtkmm 4.10. There is no replacement in gtkmm. libadwaita (a C library) has replacement parts (like AdwCarousel). In some cases, a Gtk::Notebook might be an acceptable replacement.

Assistant将一个复杂的操作切分为数个步骤。每个步骤都是一个页面，包含一个标题、一个子部件和一个可操作区域。助手的操作区域有一个导航按钮，这些按钮会根据页面的类型自动更新（使用set_page_type()设置页面类型）。

使用append_page()、prepend_page和insert_page()方法可以向Assistant添加页面，你需要每个页面提供子部件。

要确定当前可见的页面，请使用get_current_page()方法，然后将返回值传递给get_nth_page()，其会返回一个指向实际部件的指针。要想用编程的方式改变当前页，请使用set_current_page()方法。

要为页面设置标题，请使用set_page_title()方法。

要将部件添加到操作区域，请使用add_action_widget()方法。他们将与默认按钮一并被装填。使用remove_action_widget()方法可以删除部件。

Reference

##### 示例

助手(Assistant)

Source Code

File: `exampleassistant.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEASSISTANT_H
    #define GTKMM_EXAMPLEASSISTANT_H

    #include <gtkmm.h>

    class ExampleAssistant : public Gtk::Assistant
    {
    public:
      ExampleAssistant();
      virtual ~ExampleAssistant();

      void get_result(bool& check_state, Glib::ustring& entry_text);

    private:
      // Signal handlers:
      void on_assistant_apply();
      void on_assistant_cancel();
      void on_assistant_close();
      void on_assistant_prepare(Gtk::Widget* widget);
      void on_entry_changed();

      // Member functions:
      void print_status();

      // Child widgets:
      Gtk::Box m_box;
      Gtk::Label m_label1, m_label2;
      Gtk::CheckButton m_check;
      Gtk::Entry m_entry;
    };

    #endif /* GTKMM_EXAMPLEASSISTANT_H */

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include "exampleassistant.h"
    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    private:
      // Signal handlers:
      void on_button_clicked();
      void on_assistant_apply();

      // Child widgets:
      Gtk::Grid m_grid;
      Gtk::Button m_button;
      Gtk::Label m_label1, m_label2;
      Gtk::CheckButton m_check;
      Gtk::Entry m_entry;
      ExampleAssistant m_assistant;
    };

    #endif /* GTKMM_EXAMPLEWINDOW_H */

File: `exampleassistant.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "exampleassistant.h"

    ExampleAssistant::ExampleAssistant()
    : m_box(Gtk::Orientation::HORIZONTAL, 12),
      m_label1("Type text to allow the assistant to continue:"),
      m_label2("Confirmation page"),
      m_check("Optional extra information")
    {
      set_title("Gtk::Assistant example");
      set_default_size(400, 200);

      m_box.append(m_label1);
      m_box.append(m_entry);
      m_label1.set_wrap();
      m_label1.set_valign(Gtk::Align::CENTER);
      m_entry.set_valign(Gtk::Align::CENTER);

      append_page(m_box);
      append_page(m_check);
      append_page(m_label2);

      set_page_title(*get_nth_page(0), "Page 1");
      set_page_title(*get_nth_page(1), "Page 2");
      set_page_title(*get_nth_page(2), "Confirmation");

      set_page_complete(m_check, true);
      set_page_complete(m_label2, true);

      set_page_type(m_box, Gtk::AssistantPage::Type::INTRO);
      set_page_type(m_label2, Gtk::AssistantPage::Type::CONFIRM);

      signal_apply().connect(sigc::mem_fun(*this,
        &ExampleAssistant::on_assistant_apply));
      signal_cancel().connect(sigc::mem_fun(*this,
        &ExampleAssistant::on_assistant_cancel));
      signal_close().connect(sigc::mem_fun(*this,
        &ExampleAssistant::on_assistant_close));
      signal_prepare().connect(sigc::mem_fun(*this,
        &ExampleAssistant::on_assistant_prepare));

      m_entry.signal_changed().connect(sigc::mem_fun(*this,
        &ExampleAssistant::on_entry_changed));
    }

    ExampleAssistant::~ExampleAssistant()
    {
    }

    void ExampleAssistant::get_result(bool& check_state, Glib::ustring& entry_text)
    {
      check_state = m_check.get_active();
      entry_text = m_entry.get_text();
    }

    void ExampleAssistant::on_assistant_apply()
    {
      std::cout << "Apply was clicked";
      print_status();
    }

    void ExampleAssistant::on_assistant_cancel()
    {
      std::cout << "Cancel was clicked";
      print_status();
      set_visible(false);
    }

    void ExampleAssistant::on_assistant_close()
    {
      std::cout << "Assistant was closed";
      print_status();
      set_visible(false);
    }

    void ExampleAssistant::on_assistant_prepare(Gtk::Widget* /* widget */)
    {
      set_title(Glib::ustring::compose("Gtk::Assistant example (Page %1 of %2)",
        get_current_page() + 1, get_n_pages()));
    }

    void ExampleAssistant::on_entry_changed()
    {
      // The page is only complete if the entry contains text.
      if(m_entry.get_text_length())
        set_page_complete(m_box, true);
      else
        set_page_complete(m_box, false);
    }

    void ExampleAssistant::print_status()
    {
      std::cout << ", entry contents: \"" << m_entry.get_text()
        << "\", checkbutton status: " << m_check.get_active() << std::endl;
    }

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include "exampleassistant.h"

    ExampleWindow::ExampleWindow()
    : m_button("Show the assistant"),
      m_label1("State of assistant checkbutton:", Gtk::Align::START, Gtk::Align::CENTER),
      m_label2("Contents of assistant entry:", Gtk::Align::START, Gtk::Align::CENTER)
    {
      set_title("Gtk::Assistant example");

      m_grid.set_row_homogeneous(true);
      m_grid.set_column_spacing(5);
      m_grid.set_margin(12);

      m_grid.attach(m_button, 0, 0, 2, 1);
      m_button.set_hexpand(true);
      m_button.set_valign(Gtk::Align::CENTER);

      m_grid.attach(m_label1, 0, 1, 1, 1);

      m_grid.attach(m_label2, 0, 2, 1, 1);

      m_grid.attach(m_check, 1, 1, 1, 1);
      m_check.set_halign(Gtk::Align::START);

      m_grid.attach(m_entry, 1, 2, 1, 1);
      m_entry.set_hexpand(true);

      set_child(m_grid);

      m_button.signal_clicked().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_button_clicked));
      m_assistant.signal_apply().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_assistant_apply));

      m_check.set_sensitive(false);
      m_entry.set_sensitive(false);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_assistant_apply()
    {
      bool check_state;
      Glib::ustring entry_text;

      m_assistant.get_result(check_state, entry_text);
      m_check.set_active(check_state);
      m_entry.set_text(entry_text);
    }

    void ExampleWindow::on_button_clicked()
    {
      m_assistant.set_visible(true);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 其他多项容器

还有其他多项容器。请参阅文档以获取完整列表。这里是一些本教程未提及的显示容器的示例程序连接。

Source Code, ActionBar

Source Code, FlowBox

## ListView, GridView, ColumnView

Lists are intended to be used whenever developers want to display many objects in roughly the same way. They are perfectly fine to be used for very short lists of only 2 or 3 items, but generally scale fine to thousands of items.

Lists are meant to be used with changing data, both with the items themselves changing as well as the list adding and removing items. Of course, they work just as well with static data.

Some examples are shown in this chapter. There are more examples in the listmodelviews directory in gtkmm-documentation's examples.

### The Data Model

The data model is a class that implements the Gio::ListModel interface. Examples of such classes are Gio::ListStore (not to be confused with the deprecated Gtk::ListStore), Gtk:StringList, Gtk:DirectoryList and Pango::FontMap.

The elements in a model are called items. All items are instances of a subclass of Glib::Object. For instance, you might have a ColumnView with one integer and one text column, like so:

    class ModelColumns : public Glib::Object
    {
    public:
      int m_col_id;
      Glib::ustring m_col_name;

      static Glib::RefPtr<ModelColumns> create(
        int col_id, const Glib::ustring& col_name)
      {
        return Glib::make_refptr_for_instance<ModelColumns>(
          new ModelColumns(col_id, col_name));
      }

    protected:
      ModelColumns(int col_id, const Glib::ustring& col_name)
      : m_col_id(col_id), m_col_name(col_name)
      {}
    };

    Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;

Every item in a model has a position which is the unsigned integer that describes where in the model the item is located. The first item in a model is at position 0. The position of an item can of course change as other items are added to or removed from the model.

Gio::ListStore Reference

StringList Reference

DirectoryList Reference

### The Selection Model

The selection model is a class that implements the Gtk::SelectionModel interface. You can choose between NoSelection, SingleSelection and MultiSelection.

NoSelection Reference

SingleSelection Reference

MultiSelection Reference

### The Factory

Data from the data model is added to the view by a factory, which is a subclass of ListItemFactory. There is only one such subclass in gtkmm, SignalListItemFactory. Data from the model is added to the view with signal handlers connected to a SignalListItemFactory.

SignalListItemFactory Reference

### 视图

The View is the widget that displays the model data and allows the user to interact with it. The View can show all of the model's columns, or just some, and it can show them in various ways.

An important requirement for views (especially views of long lists) is that they need to know which items are not visible so they can be recycled. Views achieve that by implementing the Scrollable interface and expecting to be placed directly into a ScrolledWindow.

There are different view widgets to choose from.

#### ListView

The ListView shows a 1-dimensional list with one column.

Reference

##### 示例

ListView

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_button_quit();
      void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::Label m_Heading;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::ListView m_ListView;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gtk::StringList> m_StringList;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Heading("<b>Name</b>", Gtk::Align::START),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::ListView (Gtk::StringList) example");
      set_default_size(300, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ListView, inside a ScrolledWindow, with the heading above
      // and the button underneath.
      m_Heading.set_use_markup();
      m_VBox.append(m_Heading);
      m_ScrolledWindow.set_child(m_ListView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_quit));

      // Create the list model:
      m_StringList = Gtk::StringList::create({"Billy Bob", "Joey Jojo", "Rob McRoberts"});

      // Set list model and selection model.
      auto selection_model = Gtk::SingleSelection::create(m_StringList);
      selection_model->set_autoselect(false);
      selection_model->set_can_unselect(true);
      m_ListView.set_model(selection_model);
      m_ListView.add_css_class("data-table"); // high density table

      // Add the factory for the ListView's single column.
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_label));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      m_ListView.set_factory(factory);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", Gtk::Align::START));
    }

    void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto pos = list_item->get_position();
      if (pos == GTK_INVALID_LIST_POSITION)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(m_StringList->get_string(pos));
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### GridView

The GridView shows a 2-dimensional grid.

Reference

##### 示例

GridView

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include <gdkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      class ModelColumns;
      // Signal handlers:
      void on_button_quit();
      void on_item_activated(unsigned int position);
      void on_selection_changed();
      void on_setup_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item);
      int on_model_sort(const Glib::RefPtr<const ModelColumns>& a, const Glib::RefPtr<const ModelColumns>& b);

      void add_entry(const std::string& filename, const Glib::ustring& description);

      // A Gio::ListStore stores filename, description and texture.
      class ModelColumns : public Glib::Object
      {
      public:
        std::string m_filename;
        Glib::ustring  m_description;
        Glib::RefPtr<Gdk::Texture> m_texture;

        static Glib::RefPtr<ModelColumns> create(const std::string& filename,
          const Glib::ustring& description, const Glib::RefPtr<Gdk::Texture>& texture)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(filename, description, texture));
        }

      protected:
        ModelColumns(const std::string& filename, const Glib::ustring& description,
          const Glib::RefPtr<Gdk::Texture>& texture)
        : m_filename(filename), m_description(description), m_texture(texture)
        { }
      }; // ModelColumns

      Glib::RefPtr<Gio::ListStore<ModelColumns>> m_data_model;
      Glib::RefPtr<Gtk::SingleSelection> m_selection_model;
      Glib::RefPtr<Gtk::SignalListItemFactory> m_factory;

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::GridView m_GridView;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <array>
    #include <iostream>

    namespace
    {
      struct GridEntry
      {
        std::string m_filename;
        Glib::ustring m_description;
      };

      std::array<GridEntry, 8> entries =
      {
        GridEntry{"mozilla-firefox.png", "<b>Mozilla Firefox</b> Logo"},
        GridEntry{"xmms.xpm",            "<b>XMMS</b> Logo"},
        GridEntry{"gnome-dice-1.svg",    "<b>Gnome Dice 1</b> Logo"},
        GridEntry{"gnome-dice-2.svg",    "<b>Gnome Dice 2</b> Logo"},
        GridEntry{"gnome-dice-3.svg",    "<b>Gnome Dice 3</b> Logo"},
        GridEntry{"gnome-dice-4.svg",    "<b>Gnome Dice 4</b> Logo"},
        GridEntry{"gnome-dice-5.svg",    "<b>Gnome Dice 5</b> Logo"},
        GridEntry{"gnome-dice-6.svg",    "<b>Gnome Dice 6</b> Logo"}
      };

    } // anonymous namespace

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::GridView (Gio::ListStore) example");
      set_default_size(400, 400);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the GridView inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_GridView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(6);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_quit));

      // Create the data model:
      m_data_model = Gio::ListStore<ModelColumns>::create();
      m_selection_model = Gtk::SingleSelection::create(m_data_model);
      m_selection_model->set_autoselect(false);

      m_factory = Gtk::SignalListItemFactory::create();
      m_factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_listitem));
      m_factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_listitem));

      // Fill the Gio::ListStore's data model and sort it.
      for (const auto& entry : entries)
        add_entry(entry.m_filename, entry.m_description);
      m_data_model->sort(sigc::mem_fun(*this, &ExampleWindow::on_model_sort));

      m_GridView.set_model(m_selection_model);
      m_GridView.set_factory(m_factory);

      m_GridView.signal_activate().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_item_activated));
      m_selection_model->property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_selection_changed));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      // Each ListItem contains a vertical Box with an Image and a Label.
      auto vBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
      auto picture = Gtk::make_managed<Gtk::Picture>();
      picture->set_can_shrink(false);
      picture->set_halign(Gtk::Align::CENTER);
      picture->set_valign(Gtk::Align::END);
      vBox->append(*picture);
      vBox->append(*Gtk::make_managed<Gtk::Label>());
      list_item->set_child(*vBox);
    }

    void ExampleWindow::on_bind_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto vBox = dynamic_cast<Gtk::Box*>(list_item->get_child());
      if (!vBox)
        return;
      auto picture = dynamic_cast<Gtk::Picture*>(vBox->get_first_child());
      if (!picture)
        return;
      auto label = dynamic_cast<Gtk::Label*>(picture->get_next_sibling());
      if (!label)
        return;
      picture->set_paintable(col->m_texture);
      label->set_markup(col->m_description);
    }

    void ExampleWindow::on_item_activated(unsigned int position)
    {
      auto col = m_data_model->get_item(position);
      if (!col)
        return;

      const std::string filename = col->m_filename;
      const Glib::ustring description = col->m_description;

      std::cout  << "Item activated: filename=" << filename
        << ", description=" << description << std::endl;
    }

    void ExampleWindow::on_selection_changed()
    {
      auto position = m_selection_model->get_selected();
      auto col = m_data_model->get_item(position);
      if (!col)
        return;

      const std::string filename = col->m_filename;
      const Glib::ustring description = col->m_description;

      std::cout  << "Selection Changed to: filename=" << filename
        << ", description=" << description << std::endl;
    }

    int ExampleWindow::on_model_sort(const Glib::RefPtr<const ModelColumns>& a,
      const Glib::RefPtr<const ModelColumns>& b)
    {
      return (a->m_description < b->m_description) ? -1 : ((a->m_description > b->m_description) ? 1 : 0);
    }

    void ExampleWindow::add_entry(const std::string& filename,
            const Glib::ustring& description )
    {
      try
      {
         auto pixbuf = Gdk::Pixbuf::create_from_file(filename);
         auto texture = Gdk::Texture::create_for_pixbuf(pixbuf);
         m_data_model->append(ModelColumns::create(filename, description, texture));
      }
      catch (const Gdk::PixbufError& ex)
      {
        std::cerr << "Gdk::PixbufError: " << ex.what() << std::endl;
      }
      catch (const Glib::FileError& ex)
      {
        std::cerr << "Glib::FileError: " << ex.what() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### ColumnView

The ColumnView shows a 1-dimensional list with one or more columns.

Reference

##### 示例

ColumnView

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_button_quit();
      void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
      void on_setup_progressbar(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_number(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_percentage(const Glib::RefPtr<Gtk::ListItem>& list_item);

      // A Gio::ListStore item.
      class ModelColumns : public Glib::Object
      {
      public:
        unsigned int m_col_id;
        Glib::ustring m_col_name;
        short m_col_number;
        int m_col_percentage;

        static Glib::RefPtr<ModelColumns> create(unsigned int col_id,
          const Glib::ustring& col_name, short col_number, int col_percentage)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(col_id, col_name, col_number, col_percentage));
        }

      protected:
        ModelColumns(unsigned int col_id, const Glib::ustring& col_name,
          short col_number, int col_percentage)
        : m_col_id(col_id), m_col_name(col_name), m_col_number(col_number),
          m_col_percentage(col_percentage)
        {}
      }; // ModelColumns

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::ColumnView m_ColumnView;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::ColumnView (Gio::ListStore) example");
      set_default_size(500, 250);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ColumnView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_ColumnView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_quit));

      // Create the List model:
      m_ListStore = Gio::ListStore<ModelColumns>::create();
      m_ListStore->append(ModelColumns::create(1, "Billy Bob", 10, 15));
      m_ListStore->append(ModelColumns::create(2, "Joey Jojo", 20, 40));
      m_ListStore->append(ModelColumns::create(3, "Rob McRoberts", 30, 70));

      // Set list model and selection model.
      auto selection_model = Gtk::SingleSelection::create(m_ListStore);
      selection_model->set_autoselect(false);
      selection_model->set_can_unselect(true);
      m_ColumnView.set_model(selection_model);
      m_ColumnView.add_css_class("data-table"); // high density table

      // Make the columns reorderable.
      // This is not necessary, but it's nice to show the feature.
      m_ColumnView.set_reorderable(true);

      // Add the ColumnView's columns:

      // Id column
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::END));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
      auto column = Gtk::ColumnViewColumn::create("ID", factory);
      m_ColumnView.append_column(column);

      // Name column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::START));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      column = Gtk::ColumnViewColumn::create("Name", factory);
      m_ColumnView.append_column(column);

      // Number column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::END));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_number));
      column = Gtk::ColumnViewColumn::create("Formatted number", factory);
      m_ColumnView.append_column(column);

      // Percentage column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_progressbar));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_percentage));
      column = Gtk::ColumnViewColumn::create("Some percentage", factory);
      m_ColumnView.append_column(column);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_label(
      const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
    }

    void ExampleWindow::on_setup_progressbar(
      const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto progressbar = Gtk::make_managed<Gtk::ProgressBar>();
      progressbar->set_show_text(true);
      list_item->set_child(*progressbar);
    }

    void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(Glib::ustring::format(col->m_col_id));
    }

    void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(col->m_col_name);
    }

    void ExampleWindow::on_bind_number(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      // 10 digits, using leading zeroes.
      label->set_text(Glib::ustring::sprintf("%010d", col->m_col_number));
    }

    void ExampleWindow::on_bind_percentage(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto progressbar = dynamic_cast<Gtk::ProgressBar*>(list_item->get_child());
      if (!progressbar)
        return;
      progressbar->set_fraction(col->m_col_percentage * 0.01);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 排序

The list can be sorted by wrapping it in a SortListModel. There are two ways to do this.

- In a ColumnView, get the ColumnViewSorter from the ColumnView and set it to the SortListModel. Set a Sorter to each ColumnViewColumn. Then the user of your app can sort the items by clicking on a column heading.

- In any view, set a Sorter such as a StringSorter or a NumericSorter to the SortListModel.

SortListModel Reference

StringSorter Reference

NumericSorter Reference

#### 示例

SortListModel

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_button_nosort();
      void on_button_sortheadings();
      void on_button_sortlength();
      void on_button_quit();
      void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
      void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);

      // A Gio::ListStore item.
      class ModelColumns : public Glib::Object
      {
      public:
        unsigned int m_col_id;
        Glib::ustring m_col_name;

        static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(col_id, col_name));
        }

      protected:
        ModelColumns(int col_id, const Glib::ustring& col_name)
        : m_col_id(col_id), m_col_name(col_name)
        {}
      }; // ModelColumns

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::ColumnView m_ColumnView;
      Gtk::Box m_ButtonBox;
      Gtk::ToggleButton m_Button_NoSort;
      Gtk::ToggleButton m_Button_SortHeadings;
      Gtk::ToggleButton m_Button_SortLength;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gtk::SingleSelection> m_SelectionModel;
      Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
      Glib::RefPtr<Gtk::SortListModel> m_SortListModel;
      Glib::RefPtr<Gtk::ColumnViewColumn> m_IdColumn;
      Glib::RefPtr<Gtk::ColumnViewColumn> m_NameColumn;
      Glib::RefPtr<Gtk::NumericSorter<Glib::ustring::size_type>> m_LengthSorter;
      Glib::RefPtr<Gtk::NumericSorter<unsigned int>> m_IdSorter;
      Glib::RefPtr<Gtk::StringSorter> m_NameSorter;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_NoSort("Don't Sort"),
      m_Button_SortHeadings("Sort Headings"),
      m_Button_SortLength("Sort Length"),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::ColumnView (Gtk::SortListModel) example");
      set_default_size(400, 350);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ColumnView, inside a ScrolledWindow, with the buttons underneath:
      m_ScrolledWindow.set_child(m_ColumnView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.set_margin(5);
      m_ButtonBox.set_spacing(10);
      m_ButtonBox.append(m_Button_NoSort);
      m_ButtonBox.append(m_Button_SortHeadings);
      m_ButtonBox.append(m_Button_SortLength);
      m_ButtonBox.append(m_Button_Quit);
      m_Button_NoSort.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_nosort));
      m_Button_SortHeadings.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_sortheadings));
      m_Button_SortLength.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_sortlength));
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_quit));

      // Gtk::CheckButton and Gtk::ToggleButton have set_group() methods.
      // They act as radio buttons, if they are included in a group.
      m_Button_SortHeadings.set_group(m_Button_NoSort);
      m_Button_SortLength.set_group(m_Button_NoSort);

      // Create the List model.
      m_ListStore = Gio::ListStore<ModelColumns>::create();
      m_ListStore->append(ModelColumns::create(1, "Billy Bob"));
      m_ListStore->append(ModelColumns::create(11, "Billy Bob Junior"));
      m_ListStore->append(ModelColumns::create(12, "Sue Bob"));
      m_ListStore->append(ModelColumns::create(2, "Joey Jojo"));
      m_ListStore->append(ModelColumns::create(3, "Rob McRoberts"));
      m_ListStore->append(ModelColumns::create(31, "Xavier McRoberts"));

      // Create the sort model.
      auto size_expression = Gtk::ClosureExpression<Glib::ustring::size_type>::create(
        [](const Glib::RefPtr<Glib::ObjectBase>& item)->Glib::ustring::size_type
        {
          const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
          return col ? col->m_col_name.size() : 0;
        });
      m_LengthSorter = Gtk::NumericSorter<Glib::ustring::size_type>::create(size_expression);
      m_LengthSorter->set_sort_order(Gtk::SortType::ASCENDING);
      m_SortListModel = Gtk::SortListModel::create(m_ListStore, m_LengthSorter);

      // Set list model and selection model.
      m_SelectionModel = Gtk::SingleSelection::create(m_ListStore);
      m_SelectionModel->set_autoselect(false);
      m_SelectionModel->set_can_unselect(true);
      m_ColumnView.set_model(m_SelectionModel);
      m_ColumnView.add_css_class("data-table"); // high density table

      // Make the columns reorderable.
      // This is not necessary, but it's nice to show the feature.
      m_ColumnView.set_reorderable(true);

      // Add the ColumnView's columns:

      // Id column
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::END));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
      m_IdColumn = Gtk::ColumnViewColumn::create("ID", factory);
      m_ColumnView.append_column(m_IdColumn);
      auto uint_expression = Gtk::ClosureExpression<unsigned int>::create(
        [](const Glib::RefPtr<Glib::ObjectBase>& item)->unsigned int
        {
          const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
          return col ? col->m_col_id : 0;
        });
      m_IdSorter = Gtk::NumericSorter<unsigned int>::create(uint_expression);

      // Name column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::START));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      m_NameColumn = Gtk::ColumnViewColumn::create("Name", factory);
      m_ColumnView.append_column(m_NameColumn);
      auto ustring_expression = Gtk::ClosureExpression<Glib::ustring>::create(
        [](const Glib::RefPtr<Glib::ObjectBase>& item)->Glib::ustring
        {
          const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
          return col ? col->m_col_name : "";
        });
      m_NameSorter = Gtk::StringSorter::create(ustring_expression);

      // Show the list model without sorting.
      m_Button_NoSort.set_active(true);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_nosort()
    {
      if (m_Button_NoSort.get_active())
      {
        m_SelectionModel->set_model(m_ListStore);
        m_IdColumn->set_sorter({});
        m_NameColumn->set_sorter({});
      }
    }

    void ExampleWindow::on_button_sortheadings()
    {
      if (m_Button_SortHeadings.get_active())
      {
        m_SortListModel->set_sorter(m_ColumnView.get_sorter());
        m_SelectionModel->set_model(m_SortListModel);
        m_IdColumn->set_sorter(m_IdSorter);
        m_NameColumn->set_sorter(m_NameSorter);
      }
    }

    void ExampleWindow::on_button_sortlength()
    {
      if (m_Button_SortLength.get_active())
      {
        m_SortListModel->set_sorter(m_LengthSorter);
        m_SelectionModel->set_model(m_SortListModel);
        m_IdColumn->set_sorter({});
        m_NameColumn->set_sorter({});
      }
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_label(
      const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
    }

    void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(Glib::ustring::format(col->m_col_id));
    }

    void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(col->m_col_name);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### Filtering

The list can be filtered by wrapping it in a FilterListModel. Set a Filter such as a StringFilter or a BoolFilter to the FilterListModel.

FilterListModel Reference

StringFilter Reference

BoolFilter Reference

#### 示例

FilterListModel

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_button_showall();
      void on_button_showj();
      void on_button_showm();
      void on_button_quit();
      void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
      void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);

      // A Gio::ListStore item.
      class ModelColumns : public Glib::Object
      {
      public:
        unsigned int m_col_id;
        Glib::ustring m_col_name;

        static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(col_id, col_name));
        }

      protected:
        ModelColumns(int col_id, const Glib::ustring& col_name)
        : m_col_id(col_id), m_col_name(col_name)
        {}
      }; // ModelColumns

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::ColumnView m_ColumnView;
      Gtk::Box m_ButtonBox;
      Gtk::ToggleButton m_Button_ShowAll;
      Gtk::ToggleButton m_Button_ShowJ;
      Gtk::ToggleButton m_Button_ShowM;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gtk::SingleSelection> m_SelectionModel;
      Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
      Glib::RefPtr<Gtk::FilterListModel> m_FilterListModel;
      Glib::RefPtr<Gtk::StringFilter> m_StringFilter;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_ShowAll("Show All"),
      m_Button_ShowJ("Show J"),
      m_Button_ShowM("Show M"),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::ColumnView (Gtk::FilterListModel) example");
      set_default_size(400, 350);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ColumnView, inside a ScrolledWindow, with the buttons underneath:
      m_ScrolledWindow.set_child(m_ColumnView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.set_margin(5);
      m_ButtonBox.set_spacing(10);
      m_ButtonBox.append(m_Button_ShowAll);
      m_ButtonBox.append(m_Button_ShowJ);
      m_ButtonBox.append(m_Button_ShowM);
      m_ButtonBox.append(m_Button_Quit);
      m_Button_ShowAll.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_showall));
      m_Button_ShowJ.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_showj));
      m_Button_ShowM.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_showm));
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_quit));

      // Gtk::CheckButton and Gtk::ToggleButton have set_group() methods.
      // They act as radio buttons, if they are included in a group.
      m_Button_ShowJ.set_group(m_Button_ShowAll);
      m_Button_ShowM.set_group(m_Button_ShowAll);

      // Create the List model.
      m_ListStore = Gio::ListStore<ModelColumns>::create();
      m_ListStore->append(ModelColumns::create(1, "Billy Bob"));
      m_ListStore->append(ModelColumns::create(11, "Billy Bob Junior"));
      m_ListStore->append(ModelColumns::create(12, "Sue Bob"));
      m_ListStore->append(ModelColumns::create(2, "Joey Jojo"));
      m_ListStore->append(ModelColumns::create(3, "Rob McRoberts"));
      m_ListStore->append(ModelColumns::create(31, "Xavier McRoberts"));

      // Create the filter model.
      auto expression = Gtk::ClosureExpression<Glib::ustring>::create(
        [](const Glib::RefPtr<Glib::ObjectBase>& item)->Glib::ustring
        {
          const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
          return col ? col->m_col_name : "";
        });
      m_StringFilter = Gtk::StringFilter::create(expression);
      m_StringFilter->set_ignore_case(false);
      m_StringFilter->set_match_mode(Gtk::StringFilter::MatchMode::SUBSTRING);
      m_FilterListModel = Gtk::FilterListModel::create(m_ListStore, m_StringFilter);

      // Set list model and selection model.
      m_SelectionModel = Gtk::SingleSelection::create(m_ListStore);
      m_SelectionModel->set_autoselect(false);
      m_SelectionModel->set_can_unselect(true);
      m_ColumnView.set_model(m_SelectionModel);
      m_ColumnView.add_css_class("data-table"); // high density table

      // Make the columns reorderable.
      // This is not necessary, but it's nice to show the feature.
      m_ColumnView.set_reorderable(true);

      // Add the ColumnView's columns:

      // Id column
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::END));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
      auto column = Gtk::ColumnViewColumn::create("ID", factory);
      m_ColumnView.append_column(column);

      // Name column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleWindow::on_setup_label), Gtk::Align::START));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      column = Gtk::ColumnViewColumn::create("Name", factory);
      m_ColumnView.append_column(column);

      // Show the list model without filtering.
      m_Button_ShowAll.set_active(true);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_showall()
    {
      if (m_Button_ShowAll.get_active())
        m_SelectionModel->set_model(m_ListStore);
    }

    void ExampleWindow::on_button_showj()
    {
      if (m_Button_ShowJ.get_active())
      {
       m_StringFilter->set_search("J");
        m_SelectionModel->set_model(m_FilterListModel);
      }
    }

    void ExampleWindow::on_button_showm()
    {
      if (m_Button_ShowM.get_active())
      {
       m_StringFilter->set_search("M");
        m_SelectionModel->set_model(m_FilterListModel);
      }
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_setup_label(
      const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
    }

    void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(Glib::ustring::format(col->m_col_id));
    }

    void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(col->m_col_name);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### Displaying Trees

While the deprecated TreeView provided built-in support for trees, the list widgets, and in particular Gio::ListModel, do not. However, gtkmm provides functionality to make trees look and behave like lists for the people who still want to display lists. This is achieved by using the TreeListModel to flatten a tree into a list. The TreeExpander widget can then be used inside a listitem to allow users to expand and collapse rows.

TreeListModel Reference

TreeExpander Reference

#### 示例

TreeListModel

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_button_quit();
      void on_setup_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_setup_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);

      Glib::RefPtr<Gio::ListModel> create_model(
        const Glib::RefPtr<Glib::ObjectBase>& item = {});

      // A Gio::ListStore item.
      class ModelColumns : public Glib::Object
      {
      public:
        int m_col_id;
        Glib::ustring m_col_name;

        static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(col_id, col_name));
        }

      protected:
        ModelColumns(int col_id, const Glib::ustring& col_name)
        : m_col_id(col_id), m_col_name(col_name)
        {}
      }; // ModelColumns

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::ColumnView m_ColumnView;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gtk::TreeListModel> m_TreeListModel;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::ColumnView (Gtk::TreeListModel) example");
      set_default_size(300, 350);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ColumnView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_ColumnView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_quit));

      // Create the root model:
      auto root = create_model();

      // Set list model and selection model.
      // passthrough must be false when Gtk::TreeExpander is used in the view.
      m_TreeListModel = Gtk::TreeListModel::create(root,
        sigc::mem_fun(*this, &ExampleWindow::create_model),
        /* passthrough */ false, /* autoexpand */ false);
      auto selection_model = Gtk::MultiSelection::create(m_TreeListModel);
      m_ColumnView.set_model(selection_model);
      m_ColumnView.add_css_class("data-table"); // high density table

      // Make the columns reorderable.
      // This is not necessary, but it's nice to show the feature.
      m_ColumnView.set_reorderable(true);

      // Add the ColumnView's columns:

      // Id column
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_id));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
      auto column = Gtk::ColumnViewColumn::create("ID", factory);
      m_ColumnView.append_column(column);

      // Name column
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_name));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
      column = Gtk::ColumnViewColumn::create("Name", factory);
      m_ColumnView.append_column(column);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    Glib::RefPtr<Gio::ListModel> ExampleWindow::create_model(
      const Glib::RefPtr<Glib::ObjectBase>& item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(item);
      auto result = Gio::ListStore<ModelColumns>::create();
      if (!col)
      {
        // Top names
        result->append(ModelColumns::create(1, "Billy Bob"));
        result->append(ModelColumns::create(2, "Joey Jojo"));
        result->append(ModelColumns::create(3, "Rob McRoberts"));
      }
      else
      {
        switch (col->m_col_id)
        {
        case 1:
          result->append(ModelColumns::create(11, "Billy Bob Junior"));
          result->append(ModelColumns::create(12, "Sue Bob"));
          break;
        case 3:
          result->append(ModelColumns::create(31, "Xavier McRoberts"));
          break;
        }
      }

      // If result is empty, it's a leaf in the tree, i.e. an item without children.
      // Returning an empty RefPtr (not a RefPtr with an empty Gio::ListModel)
      // signals that the item is not expandable.
      return (result->get_n_items() > 0) ? result : Glib::RefPtr<Gio::ListModel>();
    }

    void ExampleWindow::on_setup_id(
      const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      // Each ListItem contains a TreeExpander, which contains a Label.
      // The Label shows the ModelColumns::m_col_id. That's done in on_bind_id().
      auto expander = Gtk::make_managed<Gtk::TreeExpander>();
      auto label = Gtk::make_managed<Gtk::Label>();
      label->set_halign(Gtk::Align::END);
      expander->set_child(*label);
      list_item->set_child(*expander);
    }

    void ExampleWindow::on_setup_name(
      const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      list_item->set_child(*Gtk::make_managed<Gtk::Label>("", Gtk::Align::START));
    }

    void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      // When TreeListModel::property_passthrough() is false, ListItem::get_item()
      // is a TreeListRow. TreeExpander needs the TreeListRow.
      // The ModelColumns item is returned by TreeListRow::get_item().
      auto row = std::dynamic_pointer_cast<Gtk::TreeListRow>(list_item->get_item());
      if (!row)
        return;
      auto col = std::dynamic_pointer_cast<ModelColumns>(row->get_item());
      if (!col)
        return;
      auto expander = dynamic_cast<Gtk::TreeExpander*>(list_item->get_child());
      if (!expander)
        return;
      expander->set_list_row(row);
      auto label = dynamic_cast<Gtk::Label*>(expander->get_child());
      if (!label)
        return;
      label->set_text(Glib::ustring::format(col->m_col_id));
    }

    void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto row = std::dynamic_pointer_cast<Gtk::TreeListRow>(list_item->get_item());
      if (!row)
        return;
      auto col = std::dynamic_pointer_cast<ModelColumns>(row->get_item());
      if (!col)
        return;
      auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
      if (!label)
        return;
      label->set_text(col->m_col_name);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 树形视图(TreeView)部件

Gtk::TreeView is deprecated since gtkmm 4.10. In new code, use Gtk::ListView for lists and Gtk::ColumnView for tabular lists.

Gtk::TreeView部件的每一行都可以包含数据列表或数据树。

### 模型(Model)

每一个Gtk::TreeView都有一个与之关联的Gtk::TreeModel，模型中包含了TreeView将要显示的数据。每个Gtk::TreeModel都可以被多个Gtk::TreeView使用。例如，这将允许以多种不同的方式同时显示和编辑一致的数据。也允许不同的视图显示同一个模型的不同列的数据，就像是多个SQL查询（或“视图”）显示同一个数据库表的不同字段一样。

理论上你可以实现自己的模型，但通常情况直接使用ListStore或TreeStore可以省略很多麻烦。

Reference

#### 列表储存(ListStore)的行

ListStore包含简单的数据行，每个行都没有孩子。

树形视图 - 列表储存

Reference

#### 具有层次的树状储存

TreeStore包含数据行，并且每一行都可能有子行。

树形视图 - 树状储存

Reference

#### 模型列

The TreeModelColumnRecord class is used to keep track of the columns and their data types. You add TreeModelColumn instances to the ColumnRecord and then use those TreeModelColumns when getting and setting the data in model rows. You will probably find it convenient to derive a new TreeModelColumnRecord which has your TreeModelColumn instances as member data.

    class ModelColumns : public Gtk::TreeModelColumnRecord
    {
    public:

      ModelColumns()
        { add(m_col_text); add(m_col_number); }

      Gtk::TreeModelColumn<Glib::ustring> m_col_text;
      Gtk::TreeModelColumn<int> m_col_number;
    };

    ModelColumns m_Columns;

你可以在创建模型的时候指定ColumnRecord，如下所示：

    Glib::RefPtr<Gtk::ListStore> refListStore =
        Gtk::ListStore::create(m_Columns);

TreeModelColumnRecord描述的是结构而不是数据，它可以在多个模型之间共享，这样效率更高。但是实例（例如此处的`m_Columns`）通常不是静态的，因为通常需要在glibmm初始化后示例化它。最好的解决方案是使他成为惰性初始化的单例，以便在第一个模型访问他的时候按需构造。

#### 添加行

使用append()、prepend()或insert()方法可以向模型添加行。

    auto iter = m_refListStore->append();

你可以通过解引用迭代器获得行：

    auto row = *iter;

##### 添加子行

Gtk::TreeStore模型可以存在子物件。可以通过append()、prepend()或insert()方法添加，如下所示：

    auto iter_child =
        m_refTreeStore->append(row.children());

#### 设置值

你可以使用operator[]重载设置行中的特定列的数据，你需要使用创建模型时的TreeModelColumn指定列。

    row[m_Columns.m_col_text] = "sometext";

#### 获取值

你可以使用operator[]重载获取行中特定列的数据，你需要使用创建模型时的TreeModelColumn指定列。

    auto strText = row[m_Columns.m_col_text];
    auto number = row[m_Columns.m_col_number];

如果你使用了不合适的类型，编译器会报错。例如，编译器将生成一个编译错误：

    //compiler error - no conversion from ustring to int.
    int number = row[m_Columns.m_col_text];

#### “隐藏”列

你可能想要将额外的数据和每一行关联。如果是这样，你只需要将其添加到模型的一个行中而不将其添加到视图中即可。

### 视图

视图实际上是一个用于显示模型(Gtk::TreeModel)数据并允许用户与其交互的部件(Gtk::TreeView)。视图可以显示模型的所有列也可以只显示一部分列还可以以不同的方式显示列。

Reference

#### 使用模型

你可以在构造Gtk::TreeView的时候为其指定一个Gtk::TreeModel，也可以构筑完成后使用set_model()方法指定，如下所示：

    m_TreeView.set_model(m_refListStore);

#### 为视图添加列

你可以使用append_column()方法告知视图以一定的顺序于某个列标题下显示模型的某个列。

    m_TreeView.append_column("Messages", m_Columns.m_col_text);

当使用这种简单的append_column()重载的时候，TreeView将会使用适当的CellRenderer显示模型数据。例如将字符串和数字显示在简单的Gtk::Entry部件中，布尔量使用Gtk::CheckButton显示。这通常是你所希望的行为。对于其他类型的行，你必须使用TreeViewColumn::set_cell_data_func()连接一个将你的类型转换为字符串表示形式的回调，或者是从CellRenderer派生一个合适的类。请注意，默认情况下`(unsigned) short`不被支持 - 作为替代你可以使用`(unsigned) int`或者`(unsigned) long`作为行类型。

#### 每个视图列不不止一个模型列

要在一个视图列中呈现多个模型列，你需要手动创建TreeView::Column部件，并使用pack_start()方法向其添加模型列。

然后使用append_column()将视图列添加到视图中。请注意，Gtk::TreeView::append_column()被重载为接受预建的Gtk::TreeView::Column部件，或者只接受由TreeModelColumn生成的合适的Gtk::TreeView::Column部件。

这是一些示例代码，在同一列中有一个像素缓冲区(pixbuf)图标和一个文本名：

    auto pColumn = Gtk::make_managed<Gtk::TreeView::Column>("Icon Name");

    // m_columns.icon and m_columns.iconname are columns in the model.
    // pColumn is the column in the TreeView:
    pColumn->pack_start(m_columns.icon, /* expand= */ false);
    pColumn->pack_start(m_columns.iconname);

    m_TreeView.append_column(*pColumn);

#### 指定单元格渲染器(CellRenderer)详情

The default CellRenderers and their default behavior will normally suffice, but you might occasionally need finer control. For instance, this example code from `gtkmm/demos/gtk-demo/example_treeview_treestore.cc`, appends a Gtk::CellRenderer widget and instructs it to render the data from various model columns through various aspects of its appearance.

    auto cols_count = m_TreeView.append_column_editable("Alex", m_columns.alex);
    auto pColumn = m_TreeView.get_column(cols_count-1);
    if(pColumn)
    {
      auto pRenderer = static_cast<Gtk::CellRendererToggle*>(pColumn->get_first_cell());
      pColumn->add_attribute(pRenderer->property_visible(), m_columns.visible);
      pColumn->add_attribute(pRenderer->property_activatable(), m_columns.world);

你还可以通过连接到CellRenderer的信号以检测用户的操作。例如：

    auto pRenderer = Gtk::make_managed<Gtk::CellRendererToggle>();
    pRenderer->signal_toggled().connect(
        sigc::bind( sigc::mem_fun(*this,
            &Example_TreeView_TreeStore::on_cell_toggled), m_columns.dave)
    );

#### 可编辑单元格

##### 自动储存的可编辑单元格

用户可以就地编辑TreeView中的单元格。若要如此，请使用Gtk::TreeView的insert_column_editable()和append_column_editable()方法而不是insert_column()和append_column()方法。编辑单元格后新的值将立刻储存在模型中。请注意，这些方法是模版，只能为简单的列类型实例化（例如，Glib::ustring、`int`和`long`）。

##### 为可编辑单元格实现自定义逻辑。

但是你可能不希望立刻储存新的值。例如你可能希望对输入进行检测，符合预期的范围才储存。

为此，你应该使用常规的Gtk::TreeView::insert_column()和Gtk::TreeView::append_column()方法，然后使用get_column_cell_renderer()方法获取该列使用的Gtk::CellRenderer。

然后你应该讲Gtk::CellRenderer*转换为你期望的特定版本的CellRenderer（例如转换为CellRendererText，以便调用特定的API。

例如，对于CellRendererText，你应该将单元格的editable属性设置为`true`，如下所示：

    cell->property_editable() = true;

对于CellRendererToggle，你应该改为设置activatable属性。

你可以连接到适当的"edited"信号。例如，连接到Gtk::CellRendererText::signal_edited()或者是Gtk::CellRendererToggle::signal_toggled()。如果该列包含多个CellRenderer，则你需要在视图上使用Gtk::TreeView::get_column()方法然后再调用get_cells()获取目标单元格。

在你的信号处理函数中，你应该检查新值如果其对于你的应用程序而言是合适的则将其储存到你的模型中。

### 遍历模型行

Gtk::TreeModel通过children()方法提供了标准C++容器库风格的子代迭代器。你可以使用熟悉的begin()和end()方法递增迭代器，如下所示：

    auto children = refModel->children();
    for (auto iter = children.begin(), end = children.end(); iter != end; ++iter)
    {
      auto row = *iter;
      //Do something with the row - see above for set/get.
    }

如果你总是想迭代整个范围，则可以使用C++基于范围的更简洁的`for`循环语法：

    for (auto row: refModel->children())
    {
      //Do something with the row - see above for set/get.
    }

#### 子行

在使用Gtk::TreeStore的时候，其行可以存在子行，子行也可以存在子行。使用Gtk::TreeModel::Row::children()方法可以获取Row的子行：

    Gtk::TreeModel::Children children = row.children();

### 选择

要找出用户选择了那些行，请从TreeView的Gtk::TreeView::Selection对象获取。

    auto refTreeSelection = m_TreeView.get_selection();

#### 单选或多选

默认情况下，只能选定一行，但是你可以通过设置选择模式选择多行，如下所示：

    refTreeSelection->set_mode(Gtk::SelectionMode::MULTIPLE);

#### 选定的行

对于单选模式，你可以如下所示调用get_selected()：

    auto iter = refTreeSelection->get_selected();
    if(iter) //If anything is selected
    {
      auto row = *iter;
      //Do something with the row.
    }

对于多选模式，你需要调用get_selected_rows()或者定义一个回调然后将其传递给selected_foreach()、selected_foreach_path()或selected_foreach_iter()，如下所示：

    refTreeSelection->selected_foreach_iter(
        sigc::mem_fun(*this, &TheClass::selected_row_callback) );

    void TheClass::selected_row_callback(
        const Gtk::TreeModel::const_iterator& iter)
    {
      auto row = *iter;
      //Do something with the row.
    }

#### "changed"信号

要响应用户单击某行或者某个范围的行，请按以下方式连接信号：

    refTreeSelection->signal_changed().connect(
        sigc::mem_fun(*this, &Example_IconTheme::on_selection_changed)
    );

#### 防止行被选择

或许用户不应该能选择列表或树中的每个项目。例如在gtk-demo中，你可以选择一个演示来查看他的源代码，但是你选一个演示类别没有意义。

要控制一个行是否能被选择，请使用set_select_function()方法为其提供一个sigc::slot回调。例如：

    m_refTreeSelection->set_select_function( sigc::mem_fun(*this,
        &DemoWindow::select_function) );

接着

    bool DemoWindow::select_function(
        const Glib::RefPtr<Gtk::TreeModel>& model,
        const Gtk::TreeModel::Path& path, bool)
    {
      const auto iter = model->get_iter(path);
      return iter->children().empty(); // only allow leaf nodes to be selected
    }

#### 修改选择

要更改选择，请指定Gtk::TreeModel::iterator或Gtk::TreeModel::Row，然后如下所示：

    auto row = m_refModel->children()[5]; //The sixth row.
    if(row)
      refTreeSelection->select(row.get_iter());

或者

    auto iter = m_refModel->children().begin()
    if(iter)
      refTreeSelection->select(iter);

### 排序

标准树模型（TreeStore和ListStore）派生自TreeSortable，因此它们提供了排序功能。例如，调用set_sort_column()按指定的列对模型进行排序。或者向set_sort_func()提供一个回调函数以实现更复杂的排序算法。

TreeSortable Reference

#### 单击列进行排序

为了使用户可以点击TreeView的列标题以对TreeView内容进行排序，请在需要此功能的列上调用Gtk::TreeView::Column::set_sort_column()，以提供此功能。如下所示：

    auto pColumn = treeview.get_column(0);
    if(pColumn)
      pColumn->set_sort_column(m_columns.m_col_id);

#### 对同一模型进行独立排序的视图

TreeView可以让你用两个TreeView部件显示同一个TreeModel。如果你需要让这这些树形视图对模型进行不同的排序，则你应该使用TreeModelSort而不仅仅是使用Gtk::TreeViewColumn::set_sort_column()。TreeModelSort是一个包含其他模型的模型，其提供了该模型的将已排序版本。你可以如下所示的将已排序版本模型添加到TreeView：

    auto sorted_model = Gtk::TreeModelSort::create(model);
    sorted_model->set_sort_column(columns.m_col_name, Gtk::SortType::ASCENDING);
    treeview.set_model(sorted_model);

请注意，这样做树形视图将会提供已排序模型的迭代器。你必须将其转换为基础的子模型迭代器以便对这个模型进行操作。例如：

    void ExampleWindow::on_button_delete()
    {
      auto refTreeSelection = m_treeview.get_selection();
      if(refTreeSelection)
      {
        auto sorted_iter = m_refTreeSelection->get_selected();
        if(sorted_iter)
        {
          auto iter = m_refModelSort->convert_iter_to_child_iter(sorted_iter);
          m_refModel->erase(iter);
        }
      }
    }

TreeModelSort Reference

### 拖放

Gtk::TreeView already implements simple drag-and-drop when used with the Gtk::ListStore or Gtk::TreeStore models (since gtk 4.8). If necessary, it also allows you to implement more complex behavior when items are dragged and dropped, using the normal Drag and Drop API.

#### 可重排序的行

如果调用Gtk::TreeView::set_reorderable()则你可以在树视图中移动其本身的项目。TreeStore的示例对此进行了演示。

但是，这不允许你控制哪些项目可以拖动以及可以将他们拖动到哪里。如果你想做到这些，你需要从`Gtk::TreeStore`或`Gtk::ListStore`创建派生的`Gtk::TreeModel`，然后覆盖`Gtk::TreeDragSource::row_draggable_vfunc()`和`Gtk::TreeDragDest::row_drop_possible_vfunc()`虚方法。你可以检查提供的`Gtk::TreeModel::Path`并返回`true`或`false`来允许或禁止拖放。

在drag_and_drop示例中对此进行了演示。

### 弹出上下文菜单

Lots of people need to implement right-click context menus for TreeViews so we will explain how to do that here to save you some time. It's much the same as a normal context menu, as described in the menus chapter. You use a Gtk::GestureClick to detect the mouse click.

This is demonstrated in the Popup Context Menu example. In that example a derived TreeView is used, but that's not necessary.

### 示例

Some TreeView examples are shown here. There are more examples in the treeview directory in gtkmm-documentation's examples.

If neither ListStore nor TreeStore is suitable for your application, look at the custom TreeModel example. It shows how you can make your own implementation of the TreeModel interface.

#### 列表储存(ListStore)

这个例子有一个使用Gtk::ListStore模型的Gtk::TreeView部件。

树形视图 - 列表储存

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); add(m_col_number); add(m_col_percentage);}

        Gtk::TreeModelColumn<unsigned int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
        Gtk::TreeModelColumn<short> m_col_number;
        Gtk::TreeModelColumn<int> m_col_percentage;
      };

      ModelColumns m_Columns;

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TreeView m_TreeView;
      Glib::RefPtr<Gtk::ListStore> m_refTreeModel;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::TreeView (ListStore) example");
      set_default_size(400, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      //Add the TreeView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_TreeView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );

      //Create the Tree model:
      m_refTreeModel = Gtk::ListStore::create(m_Columns);
      m_TreeView.set_model(m_refTreeModel);

      //Fill the TreeView's model
      auto row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 1;
      row[m_Columns.m_col_name] = "Billy Bob";
      row[m_Columns.m_col_number] = 10;
      row[m_Columns.m_col_percentage] = 15;

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 2;
      row[m_Columns.m_col_name] = "Joey Jojo";
      row[m_Columns.m_col_number] = 20;
      row[m_Columns.m_col_percentage] = 40;

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 3;
      row[m_Columns.m_col_name] = "Rob McRoberts";
      row[m_Columns.m_col_number] = 30;
      row[m_Columns.m_col_percentage] = 70;

      //Add the TreeView's view columns:
      //This number will be shown with the default numeric formatting.
      m_TreeView.append_column("ID", m_Columns.m_col_id);
      m_TreeView.append_column("Name", m_Columns.m_col_name);

      m_TreeView.append_column_numeric("Formatted number", m_Columns.m_col_number,
              "%010d" /* 10 digits, using leading zeroes. */);

      //Display a progress bar instead of a decimal number:
      auto cell = Gtk::make_managed<Gtk::CellRendererProgress>();
      int cols_count = m_TreeView.append_column("Some percentage", *cell);
      auto pColumn = m_TreeView.get_column(cols_count - 1);
      if(pColumn)
      {
        pColumn->add_attribute(cell->property_value(), m_Columns.m_col_percentage);
      }

      //Make all the columns reorderable:
      //This is not necessary, but it's nice to show the feature.
      //You can use TreeView::set_column_drag_function() to more
      //finely control column drag and drop.
      for(guint i = 0; i < 2; i++)
      {
        auto column = m_TreeView.get_column(i);
        column->set_reorderable();
      }
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 树储存(TreeStore)

这个示例和ListStore示例非常的相似，但是使用的是Gtk::TreeStore模型并且添加了子行。

树形视图 - 树状储存

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();
      void on_treeview_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column);

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); }

        Gtk::TreeModelColumn<int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
      };

      ModelColumns m_Columns;

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TreeView m_TreeView;
      Glib::RefPtr<Gtk::TreeStore> m_refTreeModel;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::TreeView (TreeStore) example");
      set_default_size(400, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      //Add the TreeView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_TreeView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );

      //Create the Tree model:
      m_refTreeModel = Gtk::TreeStore::create(m_Columns);
      m_TreeView.set_model(m_refTreeModel);

      //All the items to be reordered with drag-and-drop:
      m_TreeView.set_reorderable();

      //Fill the TreeView's model
      auto row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 1;
      row[m_Columns.m_col_name] = "Billy Bob";

      auto childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 11;
      childrow[m_Columns.m_col_name] = "Billy Bob Junior";

      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 12;
      childrow[m_Columns.m_col_name] = "Sue Bob";

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 2;
      row[m_Columns.m_col_name] = "Joey Jojo";

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 3;
      row[m_Columns.m_col_name] = "Rob McRoberts";

      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 31;
      childrow[m_Columns.m_col_name] = "Xavier McRoberts";

      //Add the TreeView's view columns:
      m_TreeView.append_column("ID", m_Columns.m_col_id);
      m_TreeView.append_column("Name", m_Columns.m_col_name);

      //Connect signal:
      m_TreeView.signal_row_activated().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_treeview_row_activated) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_treeview_row_activated(const Gtk::TreeModel::Path& path,
            Gtk::TreeViewColumn* /* column */)
    {
      const auto iter = m_refTreeModel->get_iter(path);
      if(iter)
      {
        const auto row = *iter;
        std::cout << "Row activated: ID=" << row[m_Columns.m_col_id] << ", Name="
            << row[m_Columns.m_col_name] << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 可编辑单元格

这个示例与ListStore示例相同，但是使用TreeView::append_column_editable()代替TreeView::append_column()。

树形视图 - 可编辑单元格

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();

      void treeviewcolumn_validated_on_cell_data(Gtk::CellRenderer* renderer, const Gtk::TreeModel::const_iterator& iter);
      void cellrenderer_validated_on_editing_started(Gtk::CellEditable* cell_editable, const Glib::ustring& path);
      void cellrenderer_validated_on_edited(const Glib::ustring& path_string, const Glib::ustring& new_text);
      void on_message_response(int response_id, Gtk::MessageDialog* dialog);

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); add(m_col_foo); add(m_col_number); add(m_col_number_validated); }

        Gtk::TreeModelColumn<unsigned int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
        Gtk::TreeModelColumn<bool> m_col_foo;
        Gtk::TreeModelColumn<int> m_col_number;
        Gtk::TreeModelColumn<int> m_col_number_validated;
      };

      ModelColumns m_Columns;

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TreeView m_TreeView;
      Glib::RefPtr<Gtk::ListStore> m_refTreeModel;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;

      //For the validated column:
      //You could also use a CellRendererSpin or a CellRendererProgress:
      Gtk::CellRendererText m_cellrenderer_validated;
      Gtk::TreeView::Column m_treeviewcolumn_validated;
      bool m_validate_retry;
      Glib::ustring m_invalid_text_for_retry;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include "examplewindow.h"

    using std::sprintf;
    using std::strtol;

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit"),
      m_validate_retry(false)
    {
      set_title("Gtk::TreeView Editable Cells example");
      set_default_size(400, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      //Add the TreeView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_TreeView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );

      //Create the Tree model:
      m_refTreeModel = Gtk::ListStore::create(m_Columns);
      m_TreeView.set_model(m_refTreeModel);

      //Fill the TreeView's model
      auto row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 1;
      row[m_Columns.m_col_name] = "Billy Bob";
      row[m_Columns.m_col_foo] = true;
      row[m_Columns.m_col_number] = 10;

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 2;
      row[m_Columns.m_col_name] = "Joey Jojo";
      row[m_Columns.m_col_foo] = true;
      row[m_Columns.m_col_number] = 20;

      row = *(m_refTreeModel->append());

      row[m_Columns.m_col_id] = 3;
      row[m_Columns.m_col_name] = "Rob McRoberts";
      row[m_Columns.m_col_foo] = false;
      row[m_Columns.m_col_number] = 30;

      //Add the TreeView's view columns:
      //We use the *_editable convenience methods for most of these,
      //because the default functionality is enough:
      m_TreeView.append_column_editable("ID", m_Columns.m_col_id);
      m_TreeView.append_column_editable("Name", m_Columns.m_col_name);
      m_TreeView.append_column_editable("foo", m_Columns.m_col_foo);
      m_TreeView.append_column_numeric_editable("Number", m_Columns.m_col_number,
              "%010d");

      //For this column, we create the CellRenderer ourselves, and connect our own
      //signal handlers, so that we can validate the data that the user enters, and
      //control how it is displayed.
      m_treeviewcolumn_validated.set_title("validated (<10)");
      m_treeviewcolumn_validated.pack_start(m_cellrenderer_validated);
      m_TreeView.append_column(m_treeviewcolumn_validated);

      //Tell the view column how to render the model values:
      m_treeviewcolumn_validated.set_cell_data_func(m_cellrenderer_validated,
              sigc::mem_fun(*this,
                  &ExampleWindow::treeviewcolumn_validated_on_cell_data) );

      //Make the CellRenderer editable, and handle its editing signals:
      m_cellrenderer_validated.property_editable() = true;

      m_cellrenderer_validated.signal_editing_started().connect(
              sigc::mem_fun(*this,
            &ExampleWindow::cellrenderer_validated_on_editing_started) );

      m_cellrenderer_validated.signal_edited().connect( sigc::mem_fun(*this,
                  &ExampleWindow::cellrenderer_validated_on_edited) );

      //If this was a CellRendererSpin then you would have to set the adjustment:
      //m_cellrenderer_validated.property_adjustment() = m_spin_adjustment;
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::treeviewcolumn_validated_on_cell_data(
            Gtk::CellRenderer* /* renderer */,
            const Gtk::TreeModel::const_iterator& iter)
    {
      //Get the value from the model and show it appropriately in the view:
      if(iter)
      {
        const auto row = *iter;
        int model_value = row[m_Columns.m_col_number_validated];

        //This is just an example.
        //In this case, it would be easier to use append_column_editable() or
        //append_column_numeric_editable()
        char buffer[32];
        sprintf(buffer, "%d", model_value);

        auto view_text = buffer;
        m_cellrenderer_validated.property_text() = view_text;
      }
    }

    void ExampleWindow::cellrenderer_validated_on_editing_started(
            Gtk::CellEditable* cell_editable, const Glib::ustring& /* path */)
    {
      //Start editing with previously-entered (but invalid) text,
      //if we are allowing the user to correct some invalid data.
      if(m_validate_retry)
      {
        //This is the CellEditable inside the CellRenderer.
        auto celleditable_validated = cell_editable;

        //It's usually an Entry, at least for a CellRendererText:
        auto pEntry = dynamic_cast<Gtk::Entry*>(celleditable_validated);
        if(pEntry)
        {
          pEntry->set_text(m_invalid_text_for_retry);
          m_validate_retry = false;
          m_invalid_text_for_retry.clear();
        }
      }
    }

    void ExampleWindow::cellrenderer_validated_on_edited(
            const Glib::ustring& path_string,
            const Glib::ustring& new_text)
    {
      Gtk::TreePath path(path_string);

      //Convert the inputed text to an integer, as needed by our model column:
      char* pchEnd = nullptr;
      int new_value = strtol(new_text.c_str(), &pchEnd, 10);

      if(new_value >= 10)
      {
        //Prevent entry of numbers higher than 10.

        //Tell the user:
        auto dialog = new Gtk::MessageDialog(*this,
                "The number must be less than 10. Please try again.",
                false, Gtk::MessageType::ERROR, Gtk::ButtonsType::OK, true /* modal */);
        dialog->signal_response().connect(sigc::bind(
          sigc::mem_fun(*this, &ExampleWindow::on_message_response), dialog));

        dialog->set_visible(true);

        //Start editing again, with the bad text, so that the user can correct it.
        //A real application should probably allow the user to revert to the
        //previous text.

        //Set the text to be used in the start_editing signal handler:
        m_invalid_text_for_retry = new_text;
        m_validate_retry = true;

        //Start editing again, when the message dialog has been closed:
        m_TreeView.set_cursor(path, m_treeviewcolumn_validated,
                m_cellrenderer_validated, true /* start_editing */);
      }
      else
      {
        //Get the row from the path:
        auto iter = m_refTreeModel->get_iter(path);
        if(iter)
        {
          auto row = *iter;

          //Put the new value in the model:
          row[m_Columns.m_col_number_validated] = new_value;
        }
      }
    }

    void ExampleWindow::on_message_response(int /* response_id */, Gtk::MessageDialog* dialog)
    {
      delete dialog;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 拖放

这个示例和TreeStore示例十分相似，但是额外有两个用于指示行是否可以进行拖放和接受拖放的列。它通过从Gtk::TreeStore来覆盖虚函数，如拖放树视图小节所述。

树形视图 - 拖放

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "treemodel_dnd.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TreeView m_TreeView;
      Glib::RefPtr<TreeModel_Dnd> m_refTreeModel;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `treemodel_dnd.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_TREEMODEL_DND_H
    #define GTKMM_EXAMPLE_TREEMODEL_DND_H

    #include <gtkmm.h>

    class TreeModel_Dnd : public Gtk::TreeStore
    {
    protected:
      TreeModel_Dnd();

    public:

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); add(m_col_draggable); add(m_col_receivesdrags); }

        Gtk::TreeModelColumn<int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
        Gtk::TreeModelColumn<bool> m_col_draggable;
        Gtk::TreeModelColumn<bool> m_col_receivesdrags;
      };

      ModelColumns m_Columns;

      static Glib::RefPtr<TreeModel_Dnd> create();

    protected:
      //Overridden virtual functions:
      bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override;
      bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Glib::ValueBase& value) const override;
    };

    #endif //GTKMM_EXAMPLE_TREEMODEL_DND_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("_Quit", true)
    {
      set_title("Gtk::TreeView (Drag and Drop) example");
      set_default_size(400, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      //Add the TreeView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_TreeView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );

      //Create the Tree model:
      //Use our derived model, which overrides some Gtk::TreeDragDest and
      //Gtk::TreeDragSource virtual functions:
      //The columns are declared in the overridden TreeModel.
      m_refTreeModel = TreeModel_Dnd::create();
      m_TreeView.set_model(m_refTreeModel);

      //Enable Drag-and-Drop of TreeView rows:
      //See also the derived TreeModel's *_vfunc overrides.
      m_TreeView.enable_model_drag_source();
      m_TreeView.enable_model_drag_dest();

      //Fill the TreeView's model
      auto row = *(m_refTreeModel->append());
      row[m_refTreeModel->m_Columns.m_col_id] = 1;
      row[m_refTreeModel->m_Columns.m_col_name] = "Billy Bob";
      row[m_refTreeModel->m_Columns.m_col_draggable] = true;
      row[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;

      auto childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_refTreeModel->m_Columns.m_col_id] = 11;
      childrow[m_refTreeModel->m_Columns.m_col_name] = "Billy Bob Junior";
      childrow[m_refTreeModel->m_Columns.m_col_draggable] = true;
      childrow[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;

      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_refTreeModel->m_Columns.m_col_id] = 12;
      childrow[m_refTreeModel->m_Columns.m_col_name] = "Sue Bob";
      childrow[m_refTreeModel->m_Columns.m_col_draggable] = true;
      childrow[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;

      row = *(m_refTreeModel->append());
      row[m_refTreeModel->m_Columns.m_col_id] = 2;
      row[m_refTreeModel->m_Columns.m_col_name] = "Joey Jojo";
      row[m_refTreeModel->m_Columns.m_col_draggable] = true;
      row[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;

      row = *(m_refTreeModel->append());
      row[m_refTreeModel->m_Columns.m_col_id] = 3;
      row[m_refTreeModel->m_Columns.m_col_name] = "Rob McRoberts";
      row[m_refTreeModel->m_Columns.m_col_draggable] = true;
      row[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;

      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_refTreeModel->m_Columns.m_col_id] = 31;
      childrow[m_refTreeModel->m_Columns.m_col_name] = "Xavier McRoberts";
      childrow[m_refTreeModel->m_Columns.m_col_draggable] = true;
      childrow[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;

      //Add the TreeView's view columns:
      m_TreeView.append_column("ID", m_refTreeModel->m_Columns.m_col_id);
      m_TreeView.append_column("Name", m_refTreeModel->m_Columns.m_col_name);
      m_TreeView.append_column_editable("Draggable",
              m_refTreeModel->m_Columns.m_col_draggable);
      m_TreeView.append_column_editable("Receives Drags",
              m_refTreeModel->m_Columns.m_col_receivesdrags);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `treemodel_dnd.cc` (For use with gtkmm 4)

    #include "treemodel_dnd.h"
    #include <iostream>

    TreeModel_Dnd::TreeModel_Dnd()
    {
      //We can't just call Gtk::TreeModel(m_Columns) in the initializer list
      //because m_Columns does not exist when the base class constructor runs.
      //And we can't have a static m_Columns instance, because that would be
      //instantiated before the gtkmm type system.
      //So, we use this method, which should only be used just after creation:
      set_column_types(m_Columns);
    }

    Glib::RefPtr<TreeModel_Dnd> TreeModel_Dnd::create()
    {
      return Glib::make_refptr_for_instance<TreeModel_Dnd>( new TreeModel_Dnd() );
    }

    bool
    TreeModel_Dnd::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
    {
      // Make the value of the "draggable" column determine whether this row can
      // be dragged:

      const const_iterator iter = get_iter(path);
      if(iter)
      {
        ConstRow row = *iter;
        bool is_draggable = row[m_Columns.m_col_draggable];
        return is_draggable;
      }

      return Gtk::TreeStore::row_draggable_vfunc(path);
    }

    bool
    TreeModel_Dnd::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest,
            const Glib::ValueBase& value) const
    {
      //Make the value of the "receives drags" column determine whether a row can be
      //dragged into it:

      //dest is the path that the row would have after it has been dropped:
      //But in this case we are more interested in the parent row:
      auto dest_parent = dest;
      bool dest_is_not_top_level = dest_parent.up();
      if(!dest_is_not_top_level || dest_parent.empty())
      {
        //The user wants to move something to the top-level.
        //Let's always allow that.
      }
      else
      {
        //Get an iterator for the row at this path:
        const const_iterator iter_dest_parent = get_iter(dest_parent);
        if(iter_dest_parent)
        {
          ConstRow row = *iter_dest_parent;
          bool receives_drags = row[m_Columns.m_col_receivesdrags];
          return receives_drags;
        }
      }

      // You could also examine the row being dragged (via value)
      // if you must look at both rows to see whether a drop should be allowed.
      // You could use
      //   Glib::RefPtr<const Gtk::TreeModel> model_dragged_row;
      //   Gtk::TreeModel::Path path_dragged_row;
      //   Gtk::TreeModel::Path::get_row_drag_data(value,
      //     model_dragged_row, path_dragged_row);

      return Gtk::TreeStore::row_drop_possible_vfunc(dest, value);
    }

#### 弹出上下文菜单

This example is much like the ListStore example, but derives a custom TreeView to encapsulate the tree model code and popup menu code in our derived class. See the TreeView Popup Context Menu section.

树形视图 - 弹出上下文菜单

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "treeview_withpopup.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      TreeView_WithPopup m_TreeView;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `treeview_withpopup.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_TREEVIEW_WITHPOPUP_H
    #define GTKMM_EXAMPLE_TREEVIEW_WITHPOPUP_H

    #include <gtkmm.h>

    class TreeView_WithPopup : public Gtk::TreeView
    {
    public:
      TreeView_WithPopup();
      virtual ~TreeView_WithPopup();

    protected:
      // Signal handler for showing popup menu:
      void on_popup_button_pressed(int n_press, double x, double y);

      //Signal handler for popup menu items:
      void on_menu_file_popup_generic();

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); }

        Gtk::TreeModelColumn<unsigned int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
      };

      ModelColumns m_Columns;

      //The Tree model:
      Glib::RefPtr<Gtk::ListStore> m_refTreeModel;

      Gtk::PopoverMenu m_MenuPopup;
    };

    #endif //GTKMM_EXAMPLE_TREEVIEW_WITHPOPUP_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include <iostream>
    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Gtk::TreeView (ListStore) example");
      set_default_size(400, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      //Add the TreeView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_TreeView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `treeview_withpopup.cc` (For use with gtkmm 4)

    #include "treeview_withpopup.h"
    #include <iostream>

    TreeView_WithPopup::TreeView_WithPopup()
    {
      //Create the Tree model:
      m_refTreeModel = Gtk::ListStore::create(m_Columns);
      set_model(m_refTreeModel);

      //Fill the TreeView's model
      auto row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 1;
      row[m_Columns.m_col_name] = "right-click on this";

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 2;
      row[m_Columns.m_col_name] = "or this";

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 3;
      row[m_Columns.m_col_name] = "or this, for a popup context menu";

      //Add the TreeView's view columns:
      append_column("ID", m_Columns.m_col_id);
      append_column("Name", m_Columns.m_col_name);

      // Catch button press events:
      auto refGesture = Gtk::GestureClick::create();
      refGesture->set_button(GDK_BUTTON_SECONDARY);
      refGesture->signal_pressed().connect(
        sigc::mem_fun(*this, &TreeView_WithPopup::on_popup_button_pressed));
      add_controller(refGesture);

      // Fill popup menu:
      auto gmenu = Gio::Menu::create();
      gmenu->append("_Edit", "popup.edit");
      gmenu->append("_Process", "popup.process");
      gmenu->append("_Remove", "popup.remove");

      m_MenuPopup.set_parent(*this);
      m_MenuPopup.set_menu_model(gmenu);
      m_MenuPopup.set_has_arrow(false);

      // Create actions:
      auto refActionGroup = Gio::SimpleActionGroup::create();

      refActionGroup->add_action("edit",
        sigc::mem_fun(*this, &TreeView_WithPopup::on_menu_file_popup_generic));
      refActionGroup->add_action("process",
        sigc::mem_fun(*this, &TreeView_WithPopup::on_menu_file_popup_generic));
      refActionGroup->add_action("remove",
        sigc::mem_fun(*this, &TreeView_WithPopup::on_menu_file_popup_generic));

      insert_action_group("popup", refActionGroup);
    }

    TreeView_WithPopup::~TreeView_WithPopup()
    {
    }

    void TreeView_WithPopup::on_popup_button_pressed(int /* n_press */, double x, double y)
    {
      const Gdk::Rectangle rect(x, y, 1, 1);
      m_MenuPopup.set_pointing_to(rect);
      m_MenuPopup.popup();
    }

    void TreeView_WithPopup::on_menu_file_popup_generic()
    {
      std::cout << "A popup menu item was selected." << std::endl;

      auto refSelection = get_selection();
      if(refSelection)
      {
        auto iter = refSelection->get_selected();
        if(iter)
        {
          int id = (*iter)[m_Columns.m_col_id];
          std::cout << "  Selected ID=" << id << std::endl;
        }
      }
    }

## The DropDown Widget

The DropDown widget is an alternative to the deprecated ComboBox. It uses list models instead of tree models, and the content is displayed using widgets instead of cell renderers.

The DropDown widget offers a list of choices in a dropdown menu. If appropriate, it can show extra information about each item, such as text, a picture, or a check button. The DropDown widget can optionally have an Entry in the dropdown menu, allowing the user to search in a long list.

The list is provided via a Gio::ListModel, and data from this model is added to the DropDown's view with signal handlers connected to a SignalListItemFactory. This provides flexibility, but the StringList class provides a simpler text-based specialization in case that flexibility is not required.

Reference

### 模型

The model for a DropDown can be defined and filled exactly as for a ListView or a ColumnView. It must be a subclass of Glib::Object. For instance, you might have a DropDown with one integer and one text column, like so:

    class ModelColumns : public Glib::Object
    {
    public:
      int m_col_id;
      Glib::ustring m_col_name;

      static Glib::RefPtr<ModelColumns> create(
        int col_id, const Glib::ustring& col_name)
      {
        return Glib::make_refptr_for_instance<ModelColumns>(
          new ModelColumns(col_id, col_name));
      }

    protected:
      ModelColumns(int col_id, const Glib::ustring& col_name)
      : m_col_id(col_id), m_col_name(col_name)
      {}
    };

    Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;

After appending rows to this model, you should provide the model to the DropDown with the set_model() method. Unless you use the StringList model, you also need to set a ListItemFactory with set_factory(). If you want the items in the dropdown menu to look different from the item in the DropDown widget, you also need to set a separate ListItemFactory with set_list_factory().

### The selected item

To discover what item, if any, the user has selected from the DropDown, call DropDown::get_selected(). This returns an unsigned int that you can use to get the selected data from the model. For instance, you might read an integer ID value from the model, even though you have chosen only to show the human-readable description in the DropDown. For instance:

    unsigned int sel = m_DropDown.get_selected();
    if (sel != GTK_INVALID_LIST_POSITION)
    {
      // Get the data for the selected row, using our knowledge of the list model:
      auto id = m_ListStore->get_item(sel).m_col_id;
      set_some_id_chosen(id); // Your own function.
    }
    else
      set_nothing_chosen(); // Your own function.

### 响应变更

You might need to react to every change of selection in the DropDown, for instance to update other widgets. To do so, you should connect to property_selected().signal_changed(). For instance:

    m_DropDown.property_selected().signal_changed().connect(
      sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));

### Simple String Example

Simple DropDown

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/dropdown.h>
    #include <gtkmm/stringlist.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handler:
      void on_dropdown_changed();

      // Child widget:
      Gtk::DropDown m_DropDown;

      Glib::RefPtr<Gtk::StringList> m_StringList;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    {
      set_title("DropDown example");

      set_child(m_DropDown);

      // Fill the dropdown:
      const std::vector<Glib::ustring> strings{
        "1 minute", "2 minutes", "5 minutes", "20 minutes"
      };
      m_StringList = Gtk::StringList::create(strings);
      m_DropDown.set_model(m_StringList);
      m_DropDown.set_selected(0);

      // Connect signal handler:
      m_DropDown.property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_dropdown_changed()
    {
      const auto selected = m_DropDown.get_selected();
      std::cout << "DropDown changed: Row=" << selected
        << ", String=" << m_StringList->get_string(selected) << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### Examples with a Search Entry

The dropdown menu may contain an Entry that allows to search for items in the list. Call set_enable_search() and set_expression(). For instance:

    m_DropDown.set_enable_search(true);
    auto expression = Gtk::ClosureExpression<Glib::ustring>::create(
      sigc::mem_fun(*this, &ExampleWindow::get_col_name));
    m_DropDown.set_expression(expression);

    //-------
    Glib::ustring ExampleWindow::get_col_name(const Glib::RefPtr<Glib::ObjectBase>& item)
    {
      const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
      return col ? col->m_col_name : "";
    }

#### String Example

Search String

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/dropdown.h>
    #include <gtkmm/stringlist.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handler:
      void on_dropdown_changed();

      // Child widget:
      Gtk::DropDown m_DropDown;

      Glib::RefPtr<Gtk::StringList> m_StringList;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/expression.h>
    #include <gtkmm/stringobject.h>
    #include <iostream>

    ExampleWindow::ExampleWindow()
    {
      set_title("Searchable DropDown example");

      set_child(m_DropDown);

      // Fill the dropdown:
      const std::vector<Glib::ustring> many_times{
        "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes",
        "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes",
        "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours",
        "8 hours", "9 hours", "10 hours", "11 hours", "12 hours"
      };
      m_StringList = Gtk::StringList::create(many_times);
      m_DropDown.set_model(m_StringList);
      m_DropDown.set_selected(0);

      // Show a search entry.
      m_DropDown.set_enable_search(true);
      auto expression = Gtk::ClosureExpression<Glib::ustring>::create(
        [](const Glib::RefPtr<Glib::ObjectBase>& item)->Glib::ustring
        {
          // An item in a StringList is a StringObject.
          const auto string_object = std::dynamic_pointer_cast<Gtk::StringObject>(item);
          return string_object ? string_object->get_string() : "";
        });
      m_DropDown.set_expression(expression);

      // Connect signal handler:
      m_DropDown.property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_dropdown_changed()
    {
      const auto selected = m_DropDown.get_selected();
      std::cout << "DropDown changed: Row=" << selected
        << ", String=" << m_StringList->get_string(selected) << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### Font Example

This example uses a Pango::FontMap as its model. This is possible because Pango::FontMap implements the Gio::ListModel interface. Of course you can use a FontDialogButton instead.

Search Font

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handler:
      void on_dropdown_changed();

      Glib::ustring get_family_name(const Glib::RefPtr<Glib::ObjectBase>& item);

      // Child widget:
      Gtk::DropDown m_DropDown;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <pangomm/cairofontmap.h>
    #include <iostream>
    //#include <typeinfo>

    ExampleWindow::ExampleWindow()
    {
      set_title("Searchable DropDown example");

      // Add the DropDown to the window.
      set_child(m_DropDown);

      // Show a search entry.
      m_DropDown.set_enable_search(true);
      auto expression = Gtk::ClosureExpression<Glib::ustring>::create(
        sigc::mem_fun(*this, &ExampleWindow::get_family_name));
      m_DropDown.set_expression(expression);

      // Pango::FontMap is a Gio::ListModel.
      auto model = Pango::CairoFontMap::get_default();
      m_DropDown.set_model(model);
      m_DropDown.set_selected(0);

      // Connect signal handler.
      m_DropDown.property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_dropdown_changed()
    {
      const auto selected = m_DropDown.get_selected();
      const auto item = m_DropDown.get_selected_item();
      std::cout << "DropDown changed: Row=" << selected
        << ", Font=" << get_family_name(item) << std::endl;

      // Pango::FontMap is often a list of items of type Pango::FontFamily.
      // From the description of Pango::CairoFontMap: "The actual type of the font map
      // will depend on the particular font technology Cairo was compiled to use."
      // If get_family_name() does not return a family name, you can add
      // #include <typeinfo> and query the type of the items with:
      //   std::cout << typeid(*item.get()).name() << std::endl;
    }

    Glib::ustring ExampleWindow::get_family_name(const Glib::RefPtr<Glib::ObjectBase>& item)
    {
      auto family = std::dynamic_pointer_cast<Pango::FontFamily>(item);
      return family ? family->get_name() : "";
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### Complex Example

This is a more complex example with two SignalListItemFactory objects and their signal handlers. This example would be simpler without the checkmark in the dropdown menu.

Search Font

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_setup_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_setup_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_bind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_unbind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_selected_item_changed(const Glib::RefPtr<Gtk::ListItem>& list_item);
      void on_dropdown_changed();

      void create_model();
      void liststore_add_item(const Glib::ustring& title, const Glib::ustring& icon,
        const Glib::ustring& description);

      // A Gio::ListStore item.
      class ModelColumns : public Glib::Object
      {
      public:
        Glib::ustring m_title;
        Glib::ustring m_icon;
        Glib::ustring m_description;

        static Glib::RefPtr<ModelColumns> create(const Glib::ustring& title,
          const Glib::ustring& icon, const Glib::ustring& description)
        {
          return Glib::make_refptr_for_instance<ModelColumns>(
            new ModelColumns(title, icon, description));
        }

      protected:
        ModelColumns(const Glib::ustring& title, const Glib::ustring& icon,
          const Glib::ustring& description)
        : m_title(title), m_icon(icon), m_description(description)
        {}
      }; // ModelColumns

      // Child widget:
      Gtk::DropDown m_DropDown;

      Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    {
      set_title("DropDown example");

      // Add the DropDown to the window.
      set_child(m_DropDown);

      // Factory for presenting the selected item.
      auto factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_selected_item));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_selected_item));
      m_DropDown.set_factory(factory);

      // Factory for presenting the items in the dropdown list.
      factory = Gtk::SignalListItemFactory::create();
      factory->signal_setup().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_setup_list_item));
      factory->signal_bind().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_bind_list_item));
      m_DropDown.set_list_factory(factory);

      // Create the model and fill it.
      create_model();
      m_DropDown.set_model(m_ListStore);
      m_DropDown.set_selected(0);

      // Connect signal handler:
      m_DropDown.property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_dropdown_changed()
    {
      const auto selected = m_DropDown.get_selected();
      std::cout << "DropDown changed: Row=" << selected
        << ", Title=" << m_ListStore->get_item(selected)->m_title << std::endl;
    }

    void ExampleWindow::create_model()
    {
      m_ListStore = Gio::ListStore<ModelColumns>::create();

      liststore_add_item("Digital Output", "audio-card-symbolic", "Built-in Audio");
      liststore_add_item("Headphones", "audio-headphones-symbolic", "Built-in Audio");
      liststore_add_item("Digital Output", "audio-card-symbolic", "Thinkpad Tunderbolt 3 Dock USB Audio");
      liststore_add_item("Analog Output", "audio-card-symbolic", "Thinkpad Tunderbolt 3 Dock USB Audio");
    }

    void ExampleWindow::liststore_add_item(const Glib::ustring& title,
      const Glib::ustring& icon, const Glib::ustring& description)
    {
      m_ListStore->append(ModelColumns::create(title, icon, description));
    }

    void ExampleWindow::on_setup_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
      box->append(*Gtk::make_managed<Gtk::Image>());
      box->append(*Gtk::make_managed<Gtk::Label>());
      list_item->set_child(*box);
    }

    void ExampleWindow::on_bind_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto box = dynamic_cast<Gtk::Box*>(list_item->get_child());
      if (!box)
        return;
      auto image = dynamic_cast<Gtk::Image*>(box->get_first_child());
      if (!image)
        return;
      image->set_from_icon_name(col->m_icon);
      auto label = dynamic_cast<Gtk::Label*>(image->get_next_sibling());
      if (!label)
        return;
      label->set_text(col->m_title);
    }

    void ExampleWindow::on_setup_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
      auto vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 2);

      hbox->append(*Gtk::make_managed<Gtk::Image>());
      hbox->append(*vbox);
      auto title = Gtk::make_managed<Gtk::Label>();
      title->set_xalign(0.0);
      vbox->append(*title);
      auto description = Gtk::make_managed<Gtk::Label>();
      description->set_xalign(0.0);
      description->add_css_class("dim-label");
      vbox->append(*description);
      auto checkmark = Gtk::make_managed<Gtk::Image>();
      checkmark->set_from_icon_name("object-select-symbolic");
      checkmark->set_visible(false);
      hbox->append(*checkmark);

      list_item->set_child(*hbox);
    }

    void ExampleWindow::on_bind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
      if (!col)
        return;
      auto hbox = dynamic_cast<Gtk::Box*>(list_item->get_child());
      if (!hbox)
        return;
      auto image = dynamic_cast<Gtk::Image*>(hbox->get_first_child());
      if (!image)
        return;
      image->set_from_icon_name(col->m_icon);
      auto vbox = dynamic_cast<Gtk::Box*>(image->get_next_sibling());
      if (!vbox)
        return;
      auto title = dynamic_cast<Gtk::Label*>(vbox->get_first_child());
      if (!title)
        return;
      title->set_text(col->m_title);
      auto description = dynamic_cast<Gtk::Label*>(title->get_next_sibling());
      if (!description)
        return;
      description->set_text(col->m_description);

      auto connection = m_DropDown.property_selected_item().signal_changed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::on_selected_item_changed),
        list_item));
      list_item->set_data("connection", new sigc::connection(connection),
        Glib::destroy_notify_delete<sigc::connection>);
      on_selected_item_changed(list_item);
    }

    void ExampleWindow::on_unbind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      if (auto connection = static_cast<sigc::connection*>(list_item->get_data("connection")))
      {
        connection->disconnect();
        list_item->set_data("connection", nullptr);
      }
    }

    void ExampleWindow::on_selected_item_changed(const Glib::RefPtr<Gtk::ListItem>& list_item)
    {
      auto hbox = dynamic_cast<Gtk::Box*>(list_item->get_child());
      if (!hbox)
        return;
      auto checkmark = dynamic_cast<Gtk::Image*>(hbox->get_last_child());
      if (!checkmark)
        return;
      checkmark->set_visible(m_DropDown.get_selected_item() == list_item->get_item());
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 组合框(Combo Boxes)

Gtk::ComboBox and Gtk::ComboBoxText are deprecated since gtkmm 4.10. Use Gtk::DropDown in new code.

ComboBox部件在下拉菜单中提供了选项列表（或树）。如果需要它可以为每个项目显示额外的信息，例如文本、图片、复选按钮或是进度条。ComboBox部件通常将显示用户的可选项，但是它可以有一个Entry以允许用户在没有合适的选项的时候输入任意文本。

这个列表通过TreeModel提供，这个模型的列通过ComboBox::pack_start()方法添加到组合框的视图中。这提供了灵活性和编译时的类型安全，在不需要灵活性的情况下，ComboBoxText类提供了更简单的基于文本的特化支持。

Reference

### 模型

可以像TreeView一样定义和填充组合框的模型。你可以与如下所示的一样派生具有一个整数行和一个文本行的组合框类：

    class ModelColumns : public Gtk::TreeModel::ColumnRecord
    {
    public:
      ModelColumns()
      { add(m_col_id); add(m_col_name); }

      Gtk::TreeModelColumn<int> m_col_id;
      Gtk::TreeModelColumn<Glib::ustring> m_col_name;
    };

    ModelColumns m_columns;

将行追加到此模型中后，你应该使用set_model()方法将模型提供给ComboBox。然后使用pack_start()或pack_end()方法指定哪个行将要被组合框显示。和树状视图一样，你可以通过将TreeModelColumn传递给pack方法来使用默认的单元格渲染器，也可以实例化一个特定的CellRenderer并使用add_attribute()或set_cell_data_func()指定具体映射到哪行。请注意这些方法在CellLayout基类中。

### 所选项

要了解用户从组合框中选择了哪一项可以调用ComboBox::get_active()。这将返回一个TreeModel::iterator，你可以解引用它得到一个Row，然后从其中读取该列的值。例如，你可以在组合框中只显示人类可读的描述，然后你可以读取模型中储存于该行的整数ID值（于并未被显示的列）。例如：

    Gtk::TreeModel::iterator iter = m_Combo.get_active();
    if(iter)
    {
      auto row = *iter;

      //Get the data for the selected row, using our knowledge
      //of the tree model:
      auto id = row[m_Columns.m_col_id];
      set_something_id_chosen(id); //Your own function.
    }
    else
      set_nothing_chosen(); //Your own function.

### 响应变更

你可能需要对组合框中每个选择的变更进行响应，例如更新其他的部件。为此你应该处理`changed`。例如：

    m_combo.signal_changed().connect( sigc::mem_fun(*this,
          &ExampleWindow::on_combo_changed) );

### 完整例子

组合框(ComboBox)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/comboboxtext.h>
    #include <gtkmm/liststore.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      void on_cell_data_extra(const Gtk::TreeModel::const_iterator& iter);

      //Signal handlers:
      void on_combo_changed();

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); add(m_col_extra);}

        Gtk::TreeModelColumn<int> m_col_id;
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
        Gtk::TreeModelColumn<Glib::ustring> m_col_extra;
      };

      ModelColumns m_Columns;

      //Child widgets:
      Gtk::ComboBox m_Combo;
      Gtk::CellRendererText m_cell;
      Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    {
      set_title("ComboBox example");

      //Create the Tree model:
      //m_refTreeModel = Gtk::TreeStore::create(m_Columns);
      m_refTreeModel = Gtk::ListStore::create(m_Columns);
      m_Combo.set_model(m_refTreeModel);

      //Fill the ComboBox's Tree Model:
      auto iter = m_refTreeModel->append();
      auto row = *iter;
      row[m_Columns.m_col_id] = 1;
      row[m_Columns.m_col_name] = "Billy Bob";
      row[m_Columns.m_col_extra] = "something";
      m_Combo.set_active(iter);
      /*
      auto childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 11;
      childrow[m_Columns.m_col_name] = "Billy Bob Junior";

      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 12;
      childrow[m_Columns.m_col_name] = "Sue Bob";
      */

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 2;
      row[m_Columns.m_col_name] = "Joey Jojo";
      row[m_Columns.m_col_extra] = "yadda";

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = 3;
      row[m_Columns.m_col_name] = "Rob McRoberts";
      row[m_Columns.m_col_extra] = "";

      /*
      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 31;
      childrow[m_Columns.m_col_name] = "Xavier McRoberts";
      */

      //Add the model columns to the Combo (which is a kind of view),
      //rendering them in the default way:
      m_Combo.pack_start(m_Columns.m_col_id);
      m_Combo.pack_start(m_Columns.m_col_name);

      //An example of adding a cell renderer manually,
      //instead of using pack_start(model_column, Gtk::PackOptions::EXPAND_WIDGET)
      //so we have more control:
      m_Combo.set_cell_data_func(m_cell,
        sigc::mem_fun(*this, &ExampleWindow::on_cell_data_extra));
      m_Combo.pack_start(m_cell);

      //Add the ComboBox to the window.
      set_child(m_Combo);

      //Connect signal handler:
      m_Combo.signal_changed().connect( sigc::mem_fun(*this, &ExampleWindow::on_combo_changed) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_cell_data_extra(const Gtk::TreeModel::const_iterator& iter)
    {
      auto row = *iter;
      const Glib::ustring extra = row[m_Columns.m_col_extra];

      //Some arbitrary logic just to show that this is where you can do such things:

      //Transform the value, deciding how to represent it as text:
      if(extra.empty())
        m_cell.property_text() = "(none)";
      else
        m_cell.property_text() = "-" + extra + "-";

      //Change other cell renderer properties too:
      m_cell.property_foreground() = (extra == "yadda" ? "red" : "green");
    }

    void ExampleWindow::on_combo_changed()
    {
      const auto iter = m_Combo.get_active();
      if(iter)
      {
        const auto row = *iter;
        if(row)
        {
          //Get the data for the selected row, using our knowledge of the tree
          //model:
          int id = row[m_Columns.m_col_id];
          Glib::ustring name = row[m_Columns.m_col_name];

          std::cout << " ID=" << id << ", name=" << name << std::endl;
        }
      }
      else
        std::cout << "invalid iter" << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 简单文字示例

文本组合框(ComboBoxText)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/comboboxtext.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_combo_changed();

      //Child widgets:
      Gtk::ComboBoxText m_Combo;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    {
      set_title("ComboBoxText example");

      //Fill the combo:
      m_Combo.append("something");
      m_Combo.append("something else");
      m_Combo.append("something or other");
      m_Combo.set_active(1);

      set_child(m_Combo);

      //Connect signal handler:
      m_Combo.signal_changed().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_combo_changed) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_combo_changed()
    {
      Glib::ustring text = m_Combo.get_active_text();
      if(!(text.empty()))
        std::cout << "Combo changed: " << text << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 带有条目的组合框

通过指定ComboBox构造函数的`has_entry`参数为`true`，可以令其包含一个Entry部件用以输入任意文本。

#### 文本列

为了使Entry可以与下拉列表的选项进行交互，你必须使用set_entry_text_column()指定哪些模型列是文本列。例如：

    m_combo.set_entry_text_column(m_columns.m_col_name);

当你从下拉菜单中选择一个选项的时候，该列的值将被放于Entry中。

#### 条目

由于用户可以输入任意文本，被使用中的模型行可能不足以告诉我们用户输入了什么，因此，你应该使用ComboBox::get_entry()方法检索组合框中的Entry部件并在其上调用get_text()。

#### 响应变更

When the user enters arbitrary text, it may not be enough to connect to the `changed` signal, which is emitted for every typed character. It is not emitted when the user presses the Enter key. Pressing the Enter key or moving the keyboard focus to another widget may signal that the user has finished entering text. To be notified of these events, connect to the Entry's `activate` signal (available since gtkmm 4.8), and add a Gtk::EventControllerFocus and connect to its `leave` signal, like so

    auto entry = m_Combo.get_entry();
    if (entry)
    {
      // Alternatively you can connect to m_Combo.signal_changed().
      entry->signal_changed().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_entry_changed));
      entry->signal_activate().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_entry_activate));
      // The Entry shall receive focus-leave events.
      auto controller = Gtk::EventControllerFocus::create();
      m_ConnectionFocusLeave = controller->signal_leave().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_entry_focus_leave));
      entry->add_controller(controller);
    }

The `changed` signals of ComboBox and Entry are both emitted for every change. It doesn't matter which one you connect to. But the EventControllerFocus must be added to the Entry.

#### 完整例子

带条目的组合框

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/combobox.h>
    #include <gtkmm/liststore.h>
    #include <gtkmm/version.h>

    #define HAS_SIGNAL_ACTIVATE GTKMM_CHECK_VERSION(4,7,1)

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_entry_changed();
    #if HAS_SIGNAL_ACTIVATE
      void on_entry_activate();
    #endif
      void on_entry_focus_leave();

      //Signal connection:
      sigc::connection m_ConnectionFocusLeave;

      //Tree model columns:
      class ModelColumns : public Gtk::TreeModel::ColumnRecord
      {
      public:

        ModelColumns()
        { add(m_col_id); add(m_col_name); }

        Gtk::TreeModelColumn<Glib::ustring> m_col_id; //The data to choose - this must be text.
        Gtk::TreeModelColumn<Glib::ustring> m_col_name;
      };

      ModelColumns m_Columns;

      //Child widgets:
      Gtk::ComboBox m_Combo;
      Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/eventcontrollerfocus.h>
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_Combo(true /* has_entry */)
    {
      set_title("ComboBox example");

      //Create the Tree model:
      //m_refTreeModel = Gtk::TreeStore::create(m_Columns);
      m_refTreeModel = Gtk::ListStore::create(m_Columns);
      m_Combo.set_model(m_refTreeModel);

      //Fill the ComboBox's Tree Model:
      auto row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = "1";
      row[m_Columns.m_col_name] = "Billy Bob";
      /*
      auto childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 11;
      childrow[m_Columns.m_col_name] = "Billy Bob Junior";

      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 12;
      childrow[m_Columns.m_col_name] = "Sue Bob";
      */

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = "2";
      row[m_Columns.m_col_name] = "Joey Jojo";

      row = *(m_refTreeModel->append());
      row[m_Columns.m_col_id] = "3";
      row[m_Columns.m_col_name] = "Rob McRoberts";

      /*
      childrow = *(m_refTreeModel->append(row.children()));
      childrow[m_Columns.m_col_id] = 31;
      childrow[m_Columns.m_col_name] = "Xavier McRoberts";
      */

      //Add the model columns to the Combo (which is a kind of view),
      //rendering them in the default way:
      //This is automatically rendered when we use set_entry_text_column().
      //m_Combo.pack_start(m_Columns.m_col_id, Gtk::PackOptions::EXPAND_WIDGET);
      m_Combo.pack_start(m_Columns.m_col_name);

      m_Combo.set_entry_text_column(m_Columns.m_col_id);
      m_Combo.set_active(1);

      //Add the ComboBox to the window.
      set_child(m_Combo);

      //Connect signal handlers:
      auto entry = m_Combo.get_entry();
      if (entry)
      {
        // Alternatively you can connect to m_Combo.signal_changed().
        entry->signal_changed().connect(sigc::mem_fun(*this,
          &ExampleWindow::on_entry_changed));
    #if HAS_SIGNAL_ACTIVATE
        entry->signal_activate().connect(sigc::mem_fun(*this,
          &ExampleWindow::on_entry_activate));
    #endif
        // The Entry shall receive focus-leave events.
        auto controller = Gtk::EventControllerFocus::create();
        m_ConnectionFocusLeave = controller->signal_leave().connect(
          sigc::mem_fun(*this, &ExampleWindow::on_entry_focus_leave));
        entry->add_controller(controller);
      }
      else
        std::cout << "No Entry ???" << std::endl;
    }

    ExampleWindow::~ExampleWindow()
    {
      // The focus-leave signal may be emitted while m_Combo is being destructed.
      // The signal handler can generate critical messages, if it's called when
      // m_Combo has been partly destructed.
      m_ConnectionFocusLeave.disconnect();
    }

    void ExampleWindow::on_entry_changed()
    {
      auto entry = m_Combo.get_entry();
      if (entry)
      {
        std::cout << "on_entry_changed(): Row=" << m_Combo.get_active_row_number()
          << ", ID=" << entry->get_text() << std::endl;
      }
    }

    #if HAS_SIGNAL_ACTIVATE
    void ExampleWindow::on_entry_activate()
    {
      auto entry = m_Combo.get_entry();
      if (entry)
      {
        std::cout << "on_entry_activate(): Row=" << m_Combo.get_active_row_number()
          << ", ID=" << entry->get_text() << std::endl;
      }
    }
    #endif

    void ExampleWindow::on_entry_focus_leave()
    {
      auto entry = m_Combo.get_entry();
      if (entry)
      {
        std::cout << "on_entry_focus_leave(): Row=" << m_Combo.get_active_row_number()
          << ", ID=" << entry->get_text() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 简单文字示例

带条目的文本组合框

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/comboboxtext.h>
    #include <gtkmm/version.h>

    #define HAS_SIGNAL_ACTIVATE GTKMM_CHECK_VERSION(4,7,1)

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_combo_changed();
    #if HAS_SIGNAL_ACTIVATE
      void on_entry_activate();
    #endif
      void on_entry_focus_leave();

      //Signal connection:
      sigc::connection m_ConnectionFocusLeave;

      //Child widgets:
      Gtk::ComboBoxText m_Combo;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/eventcontrollerfocus.h>
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_Combo(true /* has_entry */)
    {
      set_title("ComboBoxText example");

      //Fill the combo:
      m_Combo.append("something");
      m_Combo.append("something else");
      m_Combo.append("something or other");
      m_Combo.set_active(0);

      set_child(m_Combo);

      //Connect signal handlers:
      auto entry = m_Combo.get_entry();
      // Alternatively you can connect to entry->signal_changed().
      m_Combo.signal_changed().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_combo_changed) );

      if (entry)
      {
    #if HAS_SIGNAL_ACTIVATE
        entry->signal_activate().connect(sigc::mem_fun(*this,
          &ExampleWindow::on_entry_activate));
    #endif
        // The Entry shall receive focus-leave events.
        auto controller = Gtk::EventControllerFocus::create();
        m_ConnectionFocusLeave = controller->signal_leave().connect(
          sigc::mem_fun(*this, &ExampleWindow::on_entry_focus_leave));
        entry->add_controller(controller);
      }
      else
        std::cout << "No Entry ???" << std::endl;

      m_Combo.property_has_frame() = false;
    }

    ExampleWindow::~ExampleWindow()
    {
      // The focus-leave signal may be emitted while m_Combo is being destructed.
      // The signal handler can generate critical messages, if it's called when
      // m_Combo has been partly destructed.
      m_ConnectionFocusLeave.disconnect();
    }

    void ExampleWindow::on_combo_changed()
    {
      std::cout << "on_combo_changed(): Row=" << m_Combo.get_active_row_number()
        << ", Text=" << m_Combo.get_active_text() << std::endl;
    }

    #if HAS_SIGNAL_ACTIVATE
    void ExampleWindow::on_entry_activate()
    {
      std::cout << "on_entry_activate(): Row=" << m_Combo.get_active_row_number()
        << ", Text=" << m_Combo.get_active_text() << std::endl;
    }
    #endif

    void ExampleWindow::on_entry_focus_leave()
    {
      std::cout << "on_entry_focus_leave(): Row=" << m_Combo.get_active_row_number()
        << ", Text=" << m_Combo.get_active_text() << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 文本视图(TextView)

TextView部件可以用于显示和编辑大量的格式化文本。与TreeView一样，它具有模型/视图设计。TextBuffer是它预期的模型。

### 缓冲区

Gtk::TextBuffer是一个包含Gtk::TextView数据的模型，和Gtk::TreeView所使用的Gtk::TreeModel类似。这允许复数个Gtk::TextView共享同一个TextBuffer，并允许以不同方式显示文本缓冲区。你也可以维护多个Gtk::TextBuffer并让同一个Gtk::TextView部件于不同时候分别显示它们。

TextView会创建一个由它拥有的默认TextBuffer，你可以通过get_buffer()方法访问这个缓冲区。

Reference

#### 迭代器

Gtk::TextBuffer::iterator和Gtk::TextBuffer::const_iterator表示文本缓冲区中两个字符之间的位置。只要以影响缓冲区中字符数量的方式修改了缓冲区，所有越过修改范围的迭代器都将失效。因此，迭代器不能用于保存越过缓冲区被修改部分的位置。如果想保存位置，请使用Gtk::TextBuffer::Mark。

Reference

#### 标签和格式

##### 标签(Tags)

要指定缓冲区中的一些文本具有特定的格式，你需要定义一个标签来保存格式信息，并将该标签应用于文本区域。例如，定义标签及其属性：

    auto refTagMatch = Gtk::TextBuffer::Tag::create();
    refTagMatch->property_background() = "orange";

你可以在使用Tag的create()方法时为其指定一个名称，但这不是必须的。

Tag类还有许多其他的特性。

Reference

##### 标签表(TagTable)

每个Gtk::TextBuffer使用一个Gtk::TextBuffer::TagTable，其中包含了该缓冲区的Tag。复数个TextBuffer可以共享一个TagTable。当你创建Tag的时候，你应该将其加入TagTable。例如：

    auto refTagTable = Gtk::TextBuffer::TagTable::create();
    refTagTable->add(refTagMatch);
    //Hopefully a future version of gtkmm will have a set_tag_table() method,
    //for use after creation of the buffer.
    auto refBuffer = Gtk::TextBuffer::create(refTagTable);

你也可以使用get_tag_table()获取并修改TextBuffer的默认TagTable，而不是显示创建一个。

Reference

##### 应用标签

如果你创建了一个Tag并将其加入到TagTable，则可以将其应用于TextBuffer的一部分，以便使用该格式显示某些文本。你可以通过使用Gtk::TextBuffer::iterator指示文本的开始和结束范围。例如：

    refBuffer->apply_tag(refTagMatch, iterRangeStart, iterRangeStop);

或者你可以在首次插入文本的时候指定标签：

    refBuffer->insert_with_tag(iter, "Some text", refTagMatch);

你可以多次使用apply_tag()或insert_with_tags()将不同的Tag应用于同一文本。Tag可能会为同一属性指定不同的值，但你可以使用Tag::set_priority()解决这些冲突。

#### 标记(Marks)

当文本发生更改的时候TextBuffer迭代器通常会失效，但是你可以使用Gtk::TextBuffer::Mark记住位置，例如：

    auto refMark = refBuffer->create_mark(iter);

你可以在之后使用get_iter()方法为Mark的新位置创建迭代器。

有两个内置的Mark - `insert`和`selection_bound`，你可以使用TextBuffer的get_insert()和get_selection_bound()方法访问它们。

Reference

#### 视图

如上所述，每个TextView都有一个TextBuffer，并且多个TextView可以共享同一个TextBuffer。

和TreeView一样，你可以将TextView放入ScrolledWindow中以允许用户使用滚动条查看并在整个文本区域中移动。

Reference

##### 默认格式

TextView具有多种可以让你修改特定视图中缓冲区的显示方式的方法。如果它们指定同一区域，则其中一些可能会覆写缓冲区中的Gtk::TextTag。例如：set_left_margin()、set_right_margin()、set_indent()等。

##### 滚动

Gtk::TextView有多种scroll_to()方法。这些可以确保你能看到缓冲区中的特定部分。例如你的应用程序可能会使用Gtk::TextView::scroll_to()方法显示找到的文本。

### 部件和子锚(ChildAnchors)

You can embed widgets, such as Gtk::Buttons, in the text. Each such child widget needs a ChildAnchor. ChildAnchors are associated with iterators. For instance, to create a child anchor at a particular position, use Gtk::TextBuffer::create_child_anchor():

    auto refAnchor = refBuffer->create_child_anchor(iter);

然后使用Gtk::TextView::add_child_at_anchor()将部件添加到该位置：

    m_TextView.add_child_at_anchor(m_Button, refAnchor);

Reference

### 示例

#### 简单的例子

文本视图(TextView)

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:

      void fill_buffers();

      //Signal handlers:
      void on_button_quit();
      void on_button_buffer1();
      void on_button_buffer2();

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer1, m_refTextBuffer2;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit, m_Button_Buffer1, m_Button_Buffer2;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Quit("_Quit", true),
      m_Button_Buffer1("Use buffer 1"),
      m_Button_Buffer2("Use buffer 2")
    {
      set_title("Gtk::TextView example");
      set_default_size(400, 200);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      //Add the TreeView, inside a ScrolledWindow, with the button underneath:
      m_ScrolledWindow.set_child(m_TextView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);

      //append buttons:
      m_VBox.append(m_ButtonBox);

      m_Button_Buffer1.set_hexpand(true);
      m_Button_Buffer1.set_halign(Gtk::Align::END);
      m_ButtonBox.append(m_Button_Buffer1);
      m_ButtonBox.append(m_Button_Buffer2);
      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(5);
      m_ButtonBox.set_spacing(5);

      //Connect signals:
      m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );
      m_Button_Buffer1.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_buffer1) );
      m_Button_Buffer2.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_buffer2) );

      fill_buffers();
      on_button_buffer1();
    }

    void ExampleWindow::fill_buffers()
    {
      m_refTextBuffer1 = Gtk::TextBuffer::create();
      m_refTextBuffer1->set_text("This is the text from TextBuffer #1.");

      m_refTextBuffer2 = Gtk::TextBuffer::create();
      m_refTextBuffer2->set_text(
              "This is some alternative text, from TextBuffer #2.");

    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_button_buffer1()
    {
      m_TextView.set_buffer(m_refTextBuffer1);
    }

    void ExampleWindow::on_button_buffer2()
    {
      m_TextView.set_buffer(m_refTextBuffer2);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 菜单和工具栏

菜单和工具栏都有特有的API，但是通常你应该创建在菜单和工具栏都可以引用的Gio::SimpleAction以便一并处理它们。这样你可以响应动作被激活而不需要分别响应菜单和工具栏。你可以通过动作启用或者禁用菜单和工具栏项。Gtk::Builder可以创建菜单和工具栏。

这涉及到了Gio::SimpleActionGroup、Gio::SimpleAction和Gtk::Builder类的使用，所有的这些类都需要通过返回RefPtr的create()方法进行实例化。

### 动作(Actions)

首先创建Gio::SimpleAction并使用Gio::ActionMap::add_action()方法将其加入Gio::SimpleActionGroup中（Gio::ActionMap是Gio::SimpleActionGroup的基类。）。然后将动作组使用Gtk::Widget::insert_action_group()加入到你的窗口中。

add_action()的参数用于指定动作名，该名称由菜单项和工具栏按钮使用。你还可以在调用add_action()时指定信号处理函数。在菜单项或工具栏按钮被激活的时候此信号处理函数被调用。

例如：

    m_refActionGroup = Gio::SimpleActionGroup::create();

    m_refActionGroup->add_action("new", sigc::mem_fun(*this, &ExampleWindow::on_action_file_new));
    m_refActionGroup->add_action("open", sigc::mem_fun(*this, &ExampleWindow::on_action_file_open));
    m_refActionGroup->add_action("quit", sigc::mem_fun(*this, &ExampleWindow::on_action_file_quit));

    insert_action_group("example", m_refActionGroup);

如果你使用Gtk::ApplicationWindow，则你不需要自己创建动作组。因为Gio::ActionGroup和Gio::ActionMap是Gtk::ApplicationWindow的基类。

### 菜单和工具栏

接下来你应该创建一个Gtk::Builder。此时使用Gtk::Application::set_accel_for_action()告诉应用程序响应键盘快捷键也是一个好主意。

例如：

    m_refBuilder = Gtk::Builder::create();

    app->set_accel_for_action("example.new", "<Primary>n");
    app->set_accel_for_action("example.quit", "<Primary>q");
    app->set_accel_for_action("example.copy", "<Primary>c");
    app->set_accel_for_action("example.paste", "<Primary>v");

然后，你可以定义菜单和工具栏的实际可见布局，并将这个UI布局添加到Builder。这个"UI字符串"使用XML格式，你应该在其中使用你已经创建的动作的名称。例如：

    Glib::ustring ui_info =
      "<interface>"
      "  <menu id='menubar'>"
      "    <submenu>"
      "      <attribute name='label' translatable='yes'>_File</attribute>"
      "      <section>"
      "        <item>"
      "          <attribute name='label' translatable='yes'>_New</attribute>"
      "          <attribute name='action'>example.new</attribute>"
      "        </item>"
      "      </section>"
      "      <section>"
      "        <item>"
      "          <attribute name='label' translatable='yes'>_Quit</attribute>"
      "          <attribute name='action'>example.quit</attribute>"
      "        </item>"
      "      </section>"
      "    </submenu>"
      "    <submenu>"
      "      <attribute name='label' translatable='yes'>_Edit</attribute>"
      "      <item>"
      "        <attribute name='label' translatable='yes'>_Copy</attribute>"
      "        <attribute name='action'>example.copy</attribute>"
      "      </item>"
      "      <item>"
      "        <attribute name='label' translatable='yes'>_Paste</attribute>"
      "        <attribute name='action'>example.paste</attribute>"
      "      </item>"
      "    </submenu>"
      "  </menu>"
      "</interface>";

    m_refBuilder->add_from_string(ui_info);
    m_refBuilder->add_from_resource("/toolbar/toolbar.glade");

这里我们指定菜单项的名称，因为用户可以在菜单中看到它们。因此你应该添加`translatable='yes'`使这些的字符串可以被翻译。

To instantiate a Gtk::PopoverMenuBar and toolbar (a horizontal Gtk::Box) which you can actually show, you should use the Builder::get_object() and Builder::get_widget() methods, and then add the widgets to a container. For instance:

    auto gmenu = m_refBuilder->get_object<Gio::Menu>("menubar");
    auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
    m_Box.append(*pMenuBar);

    auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
    m_Box.append(*toolbar);

### 弹出菜单

Menus are normally just added to a window, but they can also be displayed temporarily as the result of a mouse button click. For instance, a context menu might be displayed when the user clicks their right mouse button.

例如：

    Glib::ustring ui_info =
      "<interface>"
      "  <menu id='menu-examplepopup'>"
      "    <section>"
      "      <item>"
      "        <attribute name='label' translatable='yes'>Edit</attribute>"
      "        <attribute name='action'>examplepopup.edit</attribute>"
      "      </item>"
      "      <item>"
      "        <attribute name='label' translatable='yes'>Process</attribute>"
      "        <attribute name='action'>examplepopup.process</attribute>"
      "      </item>"
      "      <item>"
      "        <attribute name='label' translatable='yes'>Remove</attribute>"
      "        <attribute name='action'>examplepopup.remove</attribute>"
      "      </item>"
      "    </section>"
      "  </menu>"
      "</interface>";

    m_refBuilder->add_from_string(ui_info);

    auto gmenu = m_refBuilder->get_object<Gio::Menu>("menu-examplepopup");
    m_MenuPopup.set_menu_model(gmenu);

To show the popup menu, use a Gtk::EventControllerClick and connect to its `pressed` signal. In the signal handler, use Gtk::PopoverMenu's popup() method. For instance:

    void ExampleWindow::on_label_pressed(int /* n_press */, double x, double y)
    {
      const Gdk::Rectangle rect(x, y, 1, 1);
      m_MenuPopup.set_pointing_to(rect);
      m_MenuPopup.popup();
    }

### Gio::Resource和glib-compile-resources

应用程序和库经常包含是应用程序的一部分的二进制或文本数据，而不是用户数据。例如Gtk::Builder使用的`.glade`文件、启动画面、Gio::Menu标记XML、CSS文件、图标等。这些通常这些文件放于`$datadir/appname`目录下，也可以将其转换为文本字面量手动包含在代码中。

Gio::ResourceAPI和glib-compile-resources程序提供了一个方便且有效的替代方案，这个方案有一些良好的性质。你可以将这些文件作为普通文件进行维护，因此你可以轻松的编辑它们，在构建过程中，这些文件将会被组合成一个二进制捆绑包，并将其链接到可执行文件中。这意味着加载资源文件会很高效（因为他们在启动时已经被加载到了内存中，并与其他实例共享）且很简单（无需对IO错误进行检查和在文件系统中查找文件）。这也使得创建可重定位应用程序变得更为简单。

Resource bundles are created by the glib-compile-resources program which takes an xml file that describes the bundle, and a set of files that the xml references. These are combined into a binary resource bundle.

Gio::Resource Reference

一个示例：

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/toolbar">
        <file preprocess="xml-stripblanks">toolbar.glade</file>
        <file>rain.png</file>
      </gresource>
    </gresources>

这将创建一个包含以下文件的资源包:

- `/toolbar/toolbar.glade`

- `/toolbar/rain.png`

然后你可以使用glib-compile-resources将XML编译为可以用Gio::Resource::create_from_file()加载的二进制包。但是使用--generate-source生成C源文件直接链接到你的应用程序更为常见。例如：

    $ glib-compile-resources --target=resources.c --generate-source toolbar.gresource.xml

一旦创建并注册了Gio::Resource，就可以使用API调用（例如：Gio::Resource::open_stream_from_global_resources()流式获取数据或是Gio::Resource::lookup_data_in_global_resources()直接获取指向数据的指针）在进程的全局访问数据。你还可以使用类似于resource:///toolbar/rain.png的URI通过Gio::File访问资源数据。

通常你不需要Gio::Resource实例，因为可以使用Gdk::Pixbuf::create_from_resource()、Gtk::Builder::add_from_resource()和Gtk::Image::set_from_resource()直接加载资源数据。

### 示例

#### 应用程序菜单和主菜单实例

该程序拥有一个应用程序菜单，一个菜单栏和一个工具栏。这个类是自Gtk::Application和Gtk::ApplicationWindow派生而来的。

应用程序和主菜单

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPLICATION_H
    #define GTKMM_EXAMPLEAPPLICATION_H

    #include <gtkmm.h>

    class ExampleApplication : public Gtk::Application
    {
    protected:
      ExampleApplication();

    public:
      static Glib::RefPtr<ExampleApplication> create();

    protected:
      //Overrides of default signal handlers:
      void on_startup() override;
      void on_activate() override;

    private:
      void create_window();

      void on_window_hide(Gtk::Window* window);
      void on_menu_file_new_generic();
      void on_menu_file_quit();
      void on_menu_help_about();

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
    };

    #endif /* GTKMM_EXAMPLEAPPLICATION_H */

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_menu_others();

      void on_menu_choices(const Glib::ustring& parameter);
      void on_menu_choices_other(int parameter);
      void on_menu_toggle();

      //Child widgets:
      Gtk::Box m_Box;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;

      //Two sets of choices:
      Glib::RefPtr<Gio::SimpleAction> m_refChoice;
      Glib::RefPtr<Gio::SimpleAction> m_refChoiceOther;

      Glib::RefPtr<Gio::SimpleAction> m_refToggle;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"
    #include "examplewindow.h"
    #include <iostream>

    ExampleApplication::ExampleApplication()
    : Gtk::Application("org.gtkmm.example.main_menu")
    {
      Glib::set_application_name("Main Menu Example");
    }

    Glib::RefPtr<ExampleApplication> ExampleApplication::create()
    {
      return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
    }

    void ExampleApplication::on_startup()
    {
      //Call the base class's implementation:
      Gtk::Application::on_startup();

      //Create actions for menus and toolbars.
      //We can use add_action() because Gtk::Application derives from Gio::ActionMap.

      //File|New sub menu:
      add_action("newstandard",
        sigc::mem_fun(*this, &ExampleApplication::on_menu_file_new_generic));

      add_action("newfoo",
        sigc::mem_fun(*this, &ExampleApplication::on_menu_file_new_generic));

      add_action("newgoo",
        sigc::mem_fun(*this, &ExampleApplication::on_menu_file_new_generic));

      //File menu:
      add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_menu_file_quit));

      //Help menu:
      add_action("about", sigc::mem_fun(*this, &ExampleApplication::on_menu_help_about));

      // Set accelerator keys:
      set_accel_for_action("app.newstandard", "<Primary>n");
      set_accel_for_action("app.quit", "<Primary>q");
      set_accel_for_action("win.copy", "<Primary>c");
      set_accel_for_action("win.paste", "<Primary>v");

      m_refBuilder = Gtk::Builder::create();

      //Layout the actions in a menubar and a menu:
      Glib::ustring ui_info =
        "<interface>"
        "  <!-- menubar -->"
        "  <menu id='menu-example'>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_File</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>New _Standard</attribute>"
        "          <attribute name='action'>app.newstandard</attribute>"
        "          <attribute name='accel'>&lt;Primary&gt;n</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>New _Foo</attribute>"
        "          <attribute name='action'>app.newfoo</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>New _Goo</attribute>"
        "          <attribute name='action'>app.newgoo</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Quit</attribute>"
        "          <attribute name='action'>app.quit</attribute>"
        "          <attribute name='accel'>&lt;Primary&gt;q</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_Edit</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Copy</attribute>"
        "          <attribute name='action'>win.copy</attribute>"
        "          <attribute name='accel'>&lt;Primary&gt;c</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Paste</attribute>"
        "          <attribute name='action'>win.paste</attribute>"
        "          <attribute name='accel'>&lt;Primary&gt;v</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Something</attribute>"
        "          <attribute name='action'>win.something</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_Choices</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Choice _A</attribute>"
        "          <attribute name='action'>win.choice</attribute>"
        "          <attribute name='target'>a</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Choice _B</attribute>"
        "          <attribute name='action'>win.choice</attribute>"
        "          <attribute name='target'>b</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_Other Choices</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Choice 1</attribute>"
        "          <attribute name='action'>win.choiceother</attribute>"
        "          <attribute name='target' type='i'>1</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Choice 2</attribute>"
        "          <attribute name='action'>win.choiceother</attribute>"
        "          <attribute name='target' type='i'>2</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Some Toggle</attribute>"
        "          <attribute name='action'>win.sometoggle</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_Help</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_About Window</attribute>"
        "          <attribute name='action'>win.about</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_About App</attribute>"
        "          <attribute name='action'>app.about</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "  </menu>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_info);
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "Building menus failed: " << ex.what();
      }

      //Get the menubar and the app menu, and add them to the application:
      auto gmenu = m_refBuilder->get_object<Gio::Menu>("menu-example");
      if (!gmenu)
      {
        g_warning("GMenu not found");
      }
      else
      {
        set_menubar(gmenu);
      }
    }

    void ExampleApplication::on_activate()
    {
      //std::cout << "debug1: " << G_STRFUNC << std::endl;
      // The application has been started, so let's show a window.
      // A real application might want to reuse this window in on_open(),
      // when asked to open a file, if no changes have been made yet.
      create_window();
    }

    void ExampleApplication::create_window()
    {
      auto win = new ExampleWindow();

      //Make sure that the application runs for as long this window is still open:
      add_window(*win);

      //Delete the window when it is hidden.
      //That's enough for this simple example.
      win->signal_hide().connect(sigc::bind(
        sigc::mem_fun(*this, &ExampleApplication::on_window_hide), win));

      win->set_show_menubar();
      win->set_visible(true);
    }

    void ExampleApplication::on_window_hide(Gtk::Window* window)
    {
      delete window;
    }

    void ExampleApplication::on_menu_file_new_generic()
    {
      std::cout << "A File|New menu item was selected." << std::endl;
    }

    void ExampleApplication::on_menu_file_quit()
    {
      std::cout << G_STRFUNC << std::endl;
      quit(); // Not really necessary, when Gtk::Widget::set_visible(false) is called.

      // Gio::Application::quit() will make Gio::Application::run() return,
      // but it's a crude way of ending the program. The window is not removed
      // from the application. Neither the window's nor the application's
      // destructors will be called, because there will be remaining reference
      // counts in both of them. If we want the destructors to be called, we
      // must remove the window from the application. One way of doing this
      // is to hide the window.
      std::vector<Gtk::Window*> windows = get_windows();
      if (windows.size() > 0)
        windows[0]->set_visible(false); // In this simple case, we know there is only one window.
    }

    void ExampleApplication::on_menu_help_about()
    {
      std::cout << "Help|About App was selected." << std::endl;
    }

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : Gtk::ApplicationWindow(),
      m_Box(Gtk::Orientation::VERTICAL)
    {
      set_title("Main menu example");
      set_default_size(300, 100);

      // ExampleApplication displays the menubar. Other stuff, such as a toolbar,
      // is put into the box.
      set_child(m_Box);

      // Create actions for menus and toolbars.
      // We can use add_action() because Gtk::ApplicationWindow derives from Gio::ActionMap.
      // This Action Map uses a "win." prefix for the actions.
      // Therefore, for instance, "win.copy", is used in ExampleApplication::on_startup()
      // to layout the menu.

      //Edit menu:
      add_action("copy", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));
      add_action("paste", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));
      add_action("something", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));

      //Choices menus, to demonstrate Radio items,
      //using our convenience methods for string and int radio values:
      m_refChoice = add_action_radio_string("choice",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_choices), "a");

      m_refChoiceOther = add_action_radio_integer("choiceother",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_choices_other), 1);

      m_refToggle = add_action_bool("sometoggle",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_toggle), false);

      //Help menu:
      add_action("about", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));

      //Create the toolbar and add it to a container widget:

      m_refBuilder = Gtk::Builder::create();

      Glib::ustring ui_info =
        "<!-- Generated with glade 3.18.3 and then changed manually -->"
        "<interface>"
        "  <object class='GtkBox' id='toolbar'>"
        "    <property name='can_focus'>False</property>"
        "    <child>"
        "      <object class='GtkButton' id='toolbutton_new'>"
        "        <property name='can_focus'>False</property>"
        "        <property name='tooltip_text' translatable='yes'>New Standard</property>"
        "        <property name='action_name'>app.newstandard</property>"
        "        <property name='icon_name'>document-new</property>"
        "        <property name='hexpand'>False</property>"
        "        <property name='vexpand'>False</property>"
        "      </object>"
        "    </child>"
        "    <child>"
        "      <object class='GtkButton' id='toolbutton_quit'>"
        "        <property name='can_focus'>False</property>"
        "        <property name='tooltip_text' translatable='yes'>Quit</property>"
        "        <property name='action_name'>app.quit</property>"
        "        <property name='icon_name'>application-exit</property>"
        "        <property name='hexpand'>False</property>"
        "        <property name='vexpand'>False</property>"
        "      </object>"
        "    </child>"
        "  </object>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_info);
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "Building toolbar failed: " <<  ex.what();
      }

      auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
      if (!toolbar)
        g_warning("toolbar not found");
      else
        m_Box.append(*toolbar);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_menu_others()
    {
      std::cout << "A menu item was selected." << std::endl;
    }

    void ExampleWindow::on_menu_choices(const Glib::ustring& parameter)
    {
      //The radio action's state does not change automatically:
      m_refChoice->change_state(parameter);

      Glib::ustring message;
      if (parameter == "a")
        message = "Choice a was selected.";
      else
        message = "Choice b was selected.";

      std::cout << message << std::endl;
    }

    void ExampleWindow::on_menu_choices_other(int parameter)
    {
      //The radio action's state does not change automatically:
      m_refChoiceOther->change_state(parameter);

      Glib::ustring message;
      if (parameter == 1)
        message = "Choice 1 was selected.";
      else
        message = "Choice 2 was selected.";

      std::cout << message << std::endl;
    }

    void ExampleWindow::on_menu_toggle()
    {
      bool active = false;
      m_refToggle->get_state(active);

      //The toggle action's state does not change automatically:
      active = !active;
      m_refToggle->change_state(active);

      Glib::ustring message;
      if (active)
        message = "Toggle is active.";
      else
        message = "Toggle is not active.";

      std::cout << message << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"

    int main(int argc, char* argv[])
    {
      auto application = ExampleApplication::create();

      // Start the application, showing the initial window,
      // and opening extra windows for any files that it is asked to open,
      // for instance as a command-line parameter.
      // run() will return when the last window has been closed by the user.
      const int status = application->run(argc, argv);
      return status;
    }

#### 主菜单示例

该程序包含一个菜单栏和一个工具栏。一个从Gtk::Window派生的类。

主菜单

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
      virtual ~ExampleWindow();

    private:
      //Signal handlers:
      void on_action_file_new();
      void on_action_file_quit();
      void on_action_others();
      void on_action_toggle();

      //Child widgets:
      Gtk::Box m_Box;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::SimpleActionGroup> m_refActionGroup;
      Glib::RefPtr<Gio::SimpleAction> m_refActionRain;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm.h>
    #include <iostream>

    ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
    : m_Box(Gtk::Orientation::VERTICAL)
    {
      set_title("main_menu example");
      set_default_size(200, 200);

      set_child(m_Box); //We can put a MenuBar at the top of the box and other stuff below it.

      //Define the actions:
      m_refActionGroup = Gio::SimpleActionGroup::create();

      m_refActionGroup->add_action("new",
        sigc::mem_fun(*this, &ExampleWindow::on_action_file_new) );
      m_refActionGroup->add_action("open",
        sigc::mem_fun(*this, &ExampleWindow::on_action_others) );

      m_refActionRain = m_refActionGroup->add_action_bool("rain",
        sigc::mem_fun(*this, &ExampleWindow::on_action_toggle), false);

      m_refActionGroup->add_action("quit",
        sigc::mem_fun(*this, &ExampleWindow::on_action_file_quit) );

      m_refActionGroup->add_action("cut",
        sigc::mem_fun(*this, &ExampleWindow::on_action_others) );
      m_refActionGroup->add_action("copy",
        sigc::mem_fun(*this, &ExampleWindow::on_action_others) );
      m_refActionGroup->add_action("paste",
        sigc::mem_fun(*this, &ExampleWindow::on_action_others) );

      insert_action_group("example", m_refActionGroup);

      //Define how the actions are presented in the menus and toolbars:
      m_refBuilder = Gtk::Builder::create();

      //Layout the actions in a menubar and toolbar:
      const Glib::ustring ui_info =
        "<interface>"
        "  <menu id='menubar'>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_File</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_New</attribute>"
        "          <attribute name='action'>example.new</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Open</attribute>"
        "          <attribute name='action'>example.open</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Rain</attribute>"
        "          <attribute name='action'>example.rain</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Quit</attribute>"
        "          <attribute name='action'>example.quit</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_Edit</attribute>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Cut</attribute>"
        "        <attribute name='action'>example.cut</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Copy</attribute>"
        "        <attribute name='action'>example.copy</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Paste</attribute>"
        "        <attribute name='action'>example.paste</attribute>"
        "      </item>"
        "    </submenu>"
        "  </menu>"
        "</interface>";

      // Set accelerator keys:
      app->set_accel_for_action("example.new", "<Primary>n");
      app->set_accel_for_action("example.open", "<Primary>o");
      app->set_accel_for_action("example.quit", "<Primary>q");
      app->set_accel_for_action("example.cut", "<Primary>x");
      app->set_accel_for_action("example.copy", "<Primary>c");
      app->set_accel_for_action("example.paste", "<Primary>v");

      try
      {
        m_refBuilder->add_from_string(ui_info);
        m_refBuilder->add_from_resource("/toolbar/toolbar.glade");
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "Building menus and toolbar failed: " <<  ex.what();
      }

      //Get the menubar:
      auto gmenu = m_refBuilder->get_object<Gio::Menu>("menubar");
      if (!gmenu)
        g_warning("GMenu not found");
      else
      {
        auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);

        //Add the PopoverMenuBar to the window:
        m_Box.append(*pMenuBar);
      }

      //Get the toolbar and add it to a container widget:
      auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
      if (!toolbar)
        g_warning("toolbar not found");
      else
        m_Box.append(*toolbar);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_action_file_quit()
    {
      set_visible(false); //Closes the main window to stop the app->make_window_and_run().
    }

    void ExampleWindow::on_action_file_new()
    {
       std::cout << "A File|New menu item was selected." << std::endl;
    }

    void ExampleWindow::on_action_others()
    {
      std::cout << "A menu item was selected." << std::endl;
    }

    void ExampleWindow::on_action_toggle()
    {
      std::cout << "The toggle menu item was selected." << std::endl;

      bool active = false;
      m_refActionRain->get_state(active);

      //The toggle action's state does not change automatically:
      active = !active;
      m_refActionRain->change_state(active);

      Glib::ustring message;
      if(active)
        message = "Toggle is active.";
      else
        message = "Toggle is not active";

      std::cout << message << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include <gtkmm.h>
    #include "examplewindow.h"

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv, app);
    }

File: `toolbar.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/toolbar">
        <file preprocess="xml-stripblanks">toolbar.glade</file>
        <file>rain.png</file>
      </gresource>
    </gresources>

#### 弹出菜单示例

弹出菜单

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    #include <memory>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_label_pressed(int n_press, double x, double y);

      void on_menu_file_popup_generic();

      //Child widgets:
      Gtk::Box m_Box;
      Gtk::Label m_Label;
      Gtk::PopoverMenu m_MenuPopup;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gtk::GestureClick> m_refGesture;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
    : m_Box(Gtk::Orientation::VERTICAL),
      m_Label("Right-click to see the popup menu.")
    {
      set_title("popup example");
      set_default_size(200, 200);

      set_child(m_Box);

      // Catch button_press events:
      m_Box.append(m_Label);
      m_Label.set_expand();
      m_refGesture = Gtk::GestureClick::create();
      m_refGesture->set_button(GDK_BUTTON_SECONDARY);
      m_refGesture->signal_pressed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_label_pressed));
      m_Label.add_controller(m_refGesture);

      //Create actions:
      auto refActionGroup = Gio::SimpleActionGroup::create();

      //File|New sub menu:
      //These menu actions would normally already exist for a main menu, because a
      //context menu should not normally contain menu items that are only available
      //via a context menu.

      refActionGroup->add_action("edit",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_popup_generic));

      refActionGroup->add_action("process",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_popup_generic));

      refActionGroup->add_action("remove",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_popup_generic));

      insert_action_group("examplepopup", refActionGroup);

      // Set accelerator keys:
      app->set_accel_for_action("examplepopup.edit", "<Primary>e");
      app->set_accel_for_action("examplepopup.process", "<Primary>p");
      app->set_accel_for_action("examplepopup.remove", "<Primary>r");

      m_refBuilder = Gtk::Builder::create();

      //Layout the actions in a menubar and toolbar:
      Glib::ustring ui_info =
        "<interface>"
        "  <menu id='menu-examplepopup'>"
        "    <section>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>Edit</attribute>"
        "        <attribute name='action'>examplepopup.edit</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>Process</attribute>"
        "        <attribute name='action'>examplepopup.process</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>Remove</attribute>"
        "        <attribute name='action'>examplepopup.remove</attribute>"
        "      </item>"
        "    </section>"
        "  </menu>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building menus failed: " <<  ex.what();
      }

      //Get the menu:
      auto gmenu = m_refBuilder->get_object<Gio::Menu>("menu-examplepopup");
      if(!gmenu)
        g_warning("GMenu not found");

      m_MenuPopup.set_parent(m_Label);
      m_MenuPopup.set_menu_model(gmenu);
      m_MenuPopup.set_has_arrow(false);
    }

    ExampleWindow::~ExampleWindow()
    {
      m_MenuPopup.unparent();
    }

    void ExampleWindow::on_menu_file_popup_generic()
    {
       std::cout << "A popup menu item was selected." << std::endl;
    }

    void ExampleWindow::on_label_pressed(int /* n_press */, double x, double y)
    {
      const Gdk::Rectangle rect(x, y, 1, 1);
      m_MenuPopup.set_pointing_to(rect);
      m_MenuPopup.popup();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv, app);
    }

## 调整(Adjustments)

gtkmm有各种可以使用鼠标和键盘进行视觉上的调整的部件，例如Range部件（在范围部件小节介绍）。还有些部件显示较大区域的可调整部分，例如Viewport部件。这些部件的Gtk::Adjustment对象表达了它的共有部分的API。

如此一来，应用程序可以对更改做出反应。例如当用户移动滚动条的时候Gtk::Adjustment发出`value_changed`信号。你可以在信号处理函数中使用get_value()方法获取新的值。

### 创建调整

Gtk::Adjustment由它的create()方法创建，该方法的声明如下：

    Glib::RefPtr<Gtk::Adjustment> Gtk::Adjustment::create(
      double value,
      double lower,
      double upper,
      double step_increment = 1,
      double page_increment = 10,
      double page_size = 0);

其中value参数是调整的初始值，通常对应于可调整部件的最上边或者最左边。lower和upper参数指定调整可以容纳的值的范围。step_increment和page_increment指定了一小一大两个增量，增量控制用户在更改调整值的时候一次可以改变的量。page_size参数通常以某种方式与平移部件能见到的区域大小。upper参数被用于表示平移子部件最底端或最右端的坐标。

### 轻松使用调整

可调整部件大致可以分为使用并要求这些值使用特定单位的和将它们视作任意数字的。

将value视作任意数的部件组包括Range（Scrollbar和Scale）、ScaleButton、SpinButton。这些部件通常由用户使用鼠标和键盘直接进行"调整"。他们会将lower和upper视为用户可以操纵调整的value的范围。默认情况下它们只会修改调整的value。

另一组部件包括Viewport和ScrolledWindow。所有的这些部件都将调整的值视作像素值。通常你也可以使用滚动条间接的修改调整的值。虽然所有使用调整的部件都可以创建自己的调整也可以使用你提供的调整，但是通常情况下你都会希望使用这些部件自行创建的调整。

如果你使滚动条和文本视图部件共享一个调整对象，则你操纵滚动条的时候文本视图将被自动调整。你可以这样设置：

    // creates its own adjustments
    Gtk::TextView textview;
    // uses the newly-created adjustment for the scrollbar as well
    Gtk::Scrollbar vscrollbar(textview.get_vadjustment(), Gtk::Orientation::VERTICAL);

### 调整的内部

如果你想要创建一个自己的处理函数以响应用户对Range或SpinButton部件的调整。你可以使用Gtk::Adjustment的get_value()和set_value()方法访问其值：

如前所述，Gtk::Adjustment可以发射信号。而所有可调整部件都会将信号处理函数连接到其调整对象的`value_changed`信号你的程序也可以如此。这使得当Scrollbar和其他可调整部件共享一个Adjustment对象时，如果你操纵滚动条与其共享调整对象的其他可调整部件将会自动更新。

因此，例如，如果你有一个Scale部件并且你想要当调整值变更以后更改图片的旋转角度，你可以创建一个如下所示的信号处理函数：

    void cb_rotate_picture(MyPicture* picture)
    {
      picture->set_rotation(adj->get_value());
    ...

并且将其连接到比例部件的调整对象上，如下所示：

    adj->signal_value_changed().connect(sigc::bind<MyPicture*>(sigc::mem_fun(*this,
        &cb_rotate_picture), picture));

如果一个部件重新配置其Adjustment的upper和lower字段（例如：用户向文本部件添加了更多的文本），那么它将发出`changed`信号。

Range部件通常会将处理程序连接到此信号，并更改其外观响应变更 - 例如：滚动条中的滑块将随着Adjustment中lower与upper的差值的增大逐渐变小，反之则逐渐增大。

除非你正在编写新的范围部件，否则你可能永远不需要将处理程序连接到这个信号。

    adjustment->signal_changed();

## 对话框(Dialogs)

Gtk::Dialog and the classes derived from it, are deprecated since gtkmm 4.10. They can still be used in gtkmm4 applications, provided GTKMM_DISABLE_DEPRECATED and GDKMM_DISABLE_DEPRECATED are not defined. Some of the dialog classes are replaced by classes that are available since gtkmm 4.10.

The examples in this chapter use classes that are available since gtkmm 4.10. Similar examples with the deprecated classes are available in the gtkmm-4-0 branch in the git repository.

Dialogs are used as secondary windows, to provide specific information or to ask questions. Gtk::Dialog windows contain a few pre-packed widgets to ensure consistency, and a `response` signal which is emitted when the user dismisses the dialog.

有几个派生自Dialog的类可能对你而言很有用。Gtk::MessageDialog多被用于简单的通知。但有时候你也需要派生自己的对话框类以提供更为复杂的功能。

若要将部件添加到自定义的对话框中，你需要将其装入通过get_content_area()获得的Gtk::Box中。如果你只需要向Dialog的底部添加一个Button，那么你可以直接使用add_button()方法。

The `response` signal handler receives an `int`. This may be a value from the Gtk::ResponseType if the user closed the dialog by clicking a standard button, or it could be the custom response value that you specified when using add_button().

To show the dialog, call set_visible(true). If the same dialog instance will be shown several times, you must also call set_hide_on_close(), or else the dialog will be destroyed when it's closed. Connect to the `response` signal, if you want to know which button was pressed. The `response` signal handler is also where you should hide the dialog.

Reference

### AlertDialog and MessageDialog

MessageDialog (deprecated since gtkmm 4.10) and AlertDialog (available since gtkmm 4.10) are convenience classes, used to create simple, standard message dialogs, with a message and buttons for user response.

AlertDialog Reference

MessageDialog Reference

#### 示例

AlertDialog

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_button_info_clicked();
      void on_button_question_clicked();
      void on_question_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result);

      //Child widgets:
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Info;
      Gtk::Button m_Button_Question;

      Glib::RefPtr<Gtk::AlertDialog> m_pDialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/messagedialog.h>
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_ButtonBox(Gtk::Orientation::VERTICAL),
      m_Button_Info("Show Info AlertDialog"),
      m_Button_Question("Show Question AlertDialog")
    {
      set_title("Gtk::AlertDialog example");

      set_child(m_ButtonBox);

      m_ButtonBox.append(m_Button_Info);
      m_Button_Info.set_expand(true);
      m_Button_Info.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_info_clicked) );

      m_ButtonBox.append(m_Button_Question);
      m_Button_Question.set_expand(true);
      m_Button_Question.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_question_clicked) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_info_clicked()
    {
      if (!m_pDialog)
        m_pDialog = Gtk::AlertDialog::create();
      else
      {
        // Reset values that may have been set by on_button_question_clicked().
        m_pDialog->set_buttons({});
        m_pDialog->set_default_button(-1);
        m_pDialog->set_cancel_button(-1);
      }

      m_pDialog->set_message("This is an INFO AlertDialog.");
      m_pDialog->set_detail("And this is the secondary text that explains things.");
      m_pDialog->show(*this);
    }

    void ExampleWindow::on_button_question_clicked()
    {
      if (!m_pDialog)
        m_pDialog = Gtk::AlertDialog::create();
      m_pDialog->set_message("This is a QUESTION AlertDialog.");
      m_pDialog->set_detail("And this is the secondary text that explains things.");
      m_pDialog->set_buttons({"Cancel", "Retry", "OK"});
      m_pDialog->set_default_button(2); // OK button or Return key
      m_pDialog->set_cancel_button(0); // Cancel button or Escape key
      m_pDialog->choose(*this,
        sigc::mem_fun(*this, &ExampleWindow::on_question_dialog_finish));
    }

    void ExampleWindow::on_question_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
    {
      try
      {
        const int response_id = m_pDialog->choose_finish(result);
        switch (response_id)
        {
        case 0:
          std::cout << "Cancel clicked." << std::endl;
          break;
        case 1:
          std::cout << "Retry clicked." << std::endl;
          break;
        case 2:
          std::cout << "OK clicked." << std::endl;
          break;
        default:
          std::cout << "Unexpected response: " << response_id << std::endl;
          break;
        }
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by m_pDialog->choose_finish(result).
        std::cout << "DialogError, " << err.what() << std::endl;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### FileDialog and FileChooserDialog

The FileChooserDialog (deprecated since gtkmm 4.10) and FileDialog (available since gtkmm 4.10) are suitable for use with "Open" or "Save" menu items.

Most of the useful member methods for FileChooserDialog are actually in the Gtk::FileChooser base class.

FileDialog Reference

FileChooserDialog Reference

#### 示例

FileDialog

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_button_file_clicked();
      void on_button_folder_clicked();
      void on_file_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
        const Glib::RefPtr<Gtk::FileDialog>& dialog);
      void on_folder_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
        const Glib::RefPtr<Gtk::FileDialog>& dialog);

      //Child widgets:
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_File;
      Gtk::Button m_Button_Folder;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_ButtonBox(Gtk::Orientation::VERTICAL),
      m_Button_File("Choose File"),
      m_Button_Folder("Choose Folder")
    {
      set_title("Gtk::FileDialog example");

      set_child(m_ButtonBox);

      m_ButtonBox.append(m_Button_File);
      m_Button_File.set_expand(true);
      m_Button_File.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_file_clicked) );

      m_ButtonBox.append(m_Button_Folder);
      m_Button_Folder.set_expand(true);
      m_Button_Folder.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_folder_clicked) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_file_clicked()
    {
      auto dialog = Gtk::FileDialog::create();

      // Add filters, so that only certain file types can be selected:
      auto filters = Gio::ListStore<Gtk::FileFilter>::create();

      auto filter_text = Gtk::FileFilter::create();
      filter_text->set_name("Text files");
      filter_text->add_mime_type("text/plain");
      filters->append(filter_text);

      auto filter_cpp = Gtk::FileFilter::create();
      filter_cpp->set_name("C/C++ files");
      filter_cpp->add_mime_type("text/x-c");
      filter_cpp->add_mime_type("text/x-c++");
      filter_cpp->add_mime_type("text/x-c-header");
      filters->append(filter_cpp);

      auto filter_any = Gtk::FileFilter::create();
      filter_any->set_name("Any files");
      filter_any->add_pattern("*");
      filters->append(filter_any);

      dialog->set_filters(filters);

      // Show the dialog and wait for a user response:
      dialog->open(sigc::bind(sigc::mem_fun(
        *this, &ExampleWindow::on_file_dialog_finish), dialog));
    }

    void ExampleWindow::on_file_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
      const Glib::RefPtr<Gtk::FileDialog>& dialog)
    {
      // Handle the response:
      try
      {
        auto file = dialog->open_finish(result);

        // Notice that this is a std::string, not a Glib::ustring.
        auto filename = file->get_path();
        std::cout << "File selected: " <<  filename << std::endl;
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by dialog->open_finish(result).
        std::cout << "No file selected. " << err.what() << std::endl;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
      }
    }

    void ExampleWindow::on_button_folder_clicked()
    {
      auto dialog = Gtk::FileDialog::create();

      // Show the dialog and wait for a user response:
      dialog->select_folder(sigc::bind(sigc::mem_fun(
        *this, &ExampleWindow::on_folder_dialog_finish), dialog));
    }

    void ExampleWindow::on_folder_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
      const Glib::RefPtr<Gtk::FileDialog>& dialog)
    {
      // Handle the response:
      try
      {
        auto folder = dialog->select_folder_finish(result);
        std::cout << "Folder selected: " << folder->get_path() << std::endl;
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by dialog->select_folder_finish(result).
        std::cout << "No folder selected. " << err.what() << std::endl;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### ColorDialog and ColorChooserDialog

The ColorChooserDialog (deprecated since gtkmm 4.10) and ColorDialog (available since gtkmm 4.10) allow the user to choose a color. The ColorButton (deprecated since gtkmm 4.10) and ColorDialogButton (available since gtkmm 4.10) open a color selection dialog when it is clicked.

ColorDialog Reference

ColorChooserDialog Reference

#### 示例

ColorDialog

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_color_button_color_set();
      void on_button_dialog_clicked();
      void on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result);

      //Draw function:
      void on_drawing_area_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::ColorDialogButton m_ColorDialogButton;
      Gtk::Button m_Button_Dialog;
      Gtk::DrawingArea m_DrawingArea; //To show the color.

      Glib::RefPtr<Gtk::ColorDialog> m_pDialog;
      Gdk::RGBA m_Color;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL, 5),
      m_Button_Dialog("Choose Color")
    {
      set_title("Gtk::ColorDialog example");
      set_default_size(200, 200);

      m_pDialog = Gtk::ColorDialog::create();
      m_ColorDialogButton.set_dialog(m_pDialog);

      set_child(m_VBox);

      m_VBox.append(m_ColorDialogButton);
      m_ColorDialogButton.property_rgba().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_color_button_color_set));

      m_VBox.append(m_Button_Dialog);
      m_Button_Dialog.signal_clicked().connect(sigc::mem_fun(*this,
        &ExampleWindow::on_button_dialog_clicked) );

      //Set start color:
      m_Color.set_red(0.0);
      m_Color.set_green(0.0);
      m_Color.set_blue(1.0);
      m_Color.set_alpha(1.0); //opaque
      m_ColorDialogButton.set_rgba(m_Color);

      m_VBox.append(m_DrawingArea);
      m_DrawingArea.set_expand(true);
      m_DrawingArea.set_draw_func(sigc::mem_fun(*this, &ExampleWindow::on_drawing_area_draw));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_color_button_color_set()
    {
      //Store the chosen color:
      m_Color = m_ColorDialogButton.get_rgba();
      m_DrawingArea.queue_draw();
    }

    void ExampleWindow::on_button_dialog_clicked()
    {
      m_pDialog->choose_rgba(*this, m_Color,
        sigc::mem_fun(*this, &ExampleWindow::on_dialog_finish));
    }

    void ExampleWindow::on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
    {
      try
      {
        // If this call changes the color, it will trigger a call to
        // on_color_button_color_set().
        m_ColorDialogButton.set_rgba(m_pDialog->choose_rgba_finish(result));

        // This is what you should do if m_ColorDialogButton and
        // on_color_button_color_set() did not exist:
        //m_Color = m_pDialog->choose_rgba_finish(result);
        //m_DrawingArea.queue_draw();
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by m_pDialog->choose_rgba_finish(result).
        std::cout << "No color selected. " << err.what() << std::endl;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
      }
    }

    void ExampleWindow::on_drawing_area_draw(const Cairo::RefPtr<Cairo::Context>& cr, int, int)
    {
      Gdk::Cairo::set_source_rgba(cr, m_Color);
      cr->paint();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### FontDialog and FontChooserDialog

The FontChooserDialog (deprecated since gtkmm 4.10) and FontDialog (available since gtkmm 4.10) allow the user to choose a font. The FontButton (deprecated since gtkmm 4.10) and FontDialogButton (available since gtkmm 4.10) open a font chooser dialog when it is clicked.

FontDialog Reference

FontChooserDialog Reference

#### 示例

FontDialog

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_font_button_font_set();
      void on_button_dialog_clicked();
      void on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result);

      //Child widgets:
      Gtk::Box m_ButtonBox;
      Gtk::FontDialogButton m_FontDialogButton;
      Gtk::Button m_Button_Dialog;

      Glib::RefPtr<Gtk::FontDialog> m_pDialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_ButtonBox(Gtk::Orientation::VERTICAL),
      m_Button_Dialog("Choose Font")
    {
      set_title("Gtk::FontDialog example");

      set_child(m_ButtonBox);

      m_pDialog = Gtk::FontDialog::create();

      m_ButtonBox.append(m_FontDialogButton);
      m_FontDialogButton.set_dialog(m_pDialog);
      m_FontDialogButton.set_font_desc(Pango::FontDescription("Sans 10"));
      m_FontDialogButton.set_expand(true);
      m_FontDialogButton.set_use_font(true);
      m_FontDialogButton.set_use_size(true);
      m_FontDialogButton.property_font_desc().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_font_button_font_set));

      m_ButtonBox.append(m_Button_Dialog);
      m_Button_Dialog.set_expand(true);
      m_Button_Dialog.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_dialog_clicked));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_font_button_font_set()
    {
      auto font_name = m_FontDialogButton.get_font_desc().to_string();
      std::cout << "Font chosen: " << font_name << std::endl;
    }

    void ExampleWindow::on_button_dialog_clicked()
    {
      // Get the previously selected font description from the FontDialogButton.
      m_pDialog->choose_font(*this,
        sigc::mem_fun(*this, &ExampleWindow::on_dialog_finish),
        m_FontDialogButton.get_font_desc());
    }

    void ExampleWindow::on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
    {
      try
      {
        // If this call changes the font, it will trigger a call to
        // on_font_button_font_set().
        m_FontDialogButton.set_font_desc(m_pDialog->choose_font_finish(result));
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by m_pDialog->choose_font_finish(result).
        std::cout << "No font selected. " << err.what() << std::endl;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 非模态关于对话框(AboutDialog)

AboutDialog提供了一个简单的显示程序有关信息（例如标志、名称、版权、网站、许可证）的方式。

本章中的大部分对话框都是模态的，也就是说在显示它们的时候应用程序的其他部分是被冻结的。你也可以创建一个非模态的对话框，这样的对话框不会冻结应用程序的其他窗口。下面的示例显示了一个非模态的AboutDialog。通常你不会想要将该对话框做成非模态，但这不表示非模态对话框在其它情况下没有用。例如：在gedit中搜索和替换对话框就是非模态的。

Reference

#### 示例

关于对话框

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_clicked();

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Label m_Label;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button;
      Gtk::AboutDialog m_Dialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Label("The AboutDialog is non-modal. "
        "You can select parts of this text while the AboutDialog is shown."),
      m_ButtonBox(Gtk::Orientation::VERTICAL),
      m_Button("Show AboutDialog")
    {
      set_title("Gtk::AboutDialog example");
      set_default_size(400, 150);

      set_child(m_VBox);

      m_VBox.append(m_Label);
      m_Label.set_expand(true);
      m_Label.set_wrap(true);
      m_Label.set_selectable(true);

      m_VBox.append(m_ButtonBox);
      m_ButtonBox.append(m_Button);
      m_Button.set_expand(true);
      m_Button.set_halign(Gtk::Align::CENTER);
      m_Button.set_valign(Gtk::Align::CENTER);
      m_Button.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_clicked) );

      m_Dialog.set_transient_for(*this);
      m_Dialog.set_hide_on_close();

      m_Dialog.set_logo(Gdk::Texture::create_from_resource("/about/gtkmm_logo.gif"));
      m_Dialog.set_program_name("Example application");
      m_Dialog.set_version("1.0.0");
      m_Dialog.set_copyright("Murray Cumming");
      m_Dialog.set_comments("This is just an example application.");
      m_Dialog.set_license("LGPL");

      m_Dialog.set_website("http://www.gtkmm.org");
      m_Dialog.set_website_label("gtkmm website");

      std::vector<Glib::ustring> list_authors;
      list_authors.push_back("Murray Cumming");
      list_authors.push_back("Somebody Else");
      list_authors.push_back("AN Other");
      m_Dialog.set_authors(list_authors);

      m_Button.grab_focus();
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_clicked()
    {
      m_Dialog.set_visible(true);

      //Bring it to the front, in case it was already shown:
      m_Dialog.present();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `aboutdialog.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/about">
        <file>gtkmm_logo.gif</file>
      </gresource>
    </gresources>

### Custom Dialog

When none of the predefined dialog classes suit your needs, you can make your own dialog by deriving a class from Window and fill it with the widgets you need.

Window Reference

#### 示例

Window Dialog

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "namedialog.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_button_clicked();
      void on_dialog_response(const Glib::ustring& response);
      void on_dialog_hidden();

      //Child widgets:
      Gtk::Button m_Button;

      NameDialog m_Dialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `namedialog.h` (For use with gtkmm 4)

    #ifndef GTKMM_NAME_DIALOG_H_
    #define GTKMM_NAME_DIALOG_H_

    #include <gtkmm.h>

    class NameDialog : public Gtk::Window
    {
    public:
      NameDialog();
      ~NameDialog() override;

      void buttons_clicked_connect(const sigc::slot<void(const Glib::ustring&)>& slot);
      Glib::ustring get_entry1() const;
      Glib::ustring get_entry2() const;

    protected:
      //Member widgets:
      Gtk::Grid m_Grid;
      Gtk::Image m_Image;
      Gtk::Label m_Label1;
      Gtk::Label m_Label2;
      Gtk::Entry m_Entry1;
      Gtk::Entry m_Entry2;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_OK;
      Gtk::Button m_Button_Cancel;
    };

    #endif /* GTKMM_NAME_DIALOG_H_ */

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_Button("Show Dialog"),
      m_Dialog()
    {
      set_title("Custom Dialog example");

      set_child(m_Button);
      m_Button.signal_clicked().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_clicked));

      m_Dialog.set_default_size(250, 100);
      m_Dialog.set_transient_for(*this);
      m_Dialog.set_modal();
      m_Dialog.set_hide_on_close();
      m_Dialog.buttons_clicked_connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dialog_response));
      m_Dialog.signal_hide().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dialog_hidden));
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_clicked()
    {
      m_Dialog.set_visible(true);
    }

    void ExampleWindow::on_dialog_response(const Glib::ustring& response)
    {
      m_Dialog.set_visible(false);

      if (response == "OK")
        std::cout << "Name: " << m_Dialog.get_entry1() << " "
          << m_Dialog.get_entry2() << std::endl;
      else
        std::cout << response << " button clicked" << std::endl;
    }

    void ExampleWindow::on_dialog_hidden()
    {
      std::cout << "Dialog hidden" << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `namedialog.cc` (For use with gtkmm 4)

    NameDialog::NameDialog()
    : m_Label1("_First name", true),
      m_Label2("_Second name", true),
      m_ButtonBox(Gtk::Orientation::HORIZONTAL, 5),
      m_Button_OK("_OK", true),
      m_Button_Cancel("_Cancel", true)
    {
      set_destroy_with_parent(true);

      set_title("Name Dialog");
      set_child(m_Grid);

      m_Grid.set_row_spacing(4);
      m_Grid.set_column_spacing(4);
      m_Grid.set_expand(true);

      m_Image.set_from_icon_name("dialog-question");
      m_Image.set_icon_size(Gtk::IconSize::LARGE);
      m_Grid.attach(m_Image, 0, 0, 1, 2);

      m_Grid.attach(m_Label1, 1, 0);
      m_Grid.attach(m_Entry1, 2, 0);
      m_Label1.set_mnemonic_widget(m_Entry1);

      m_Grid.attach(m_Label2, 1, 1);
      m_Grid.attach(m_Entry2, 2, 1);
      m_Label2.set_mnemonic_widget(m_Entry2);

      m_Grid.attach(m_ButtonBox, 0, 2, 3, 1);
      m_ButtonBox.set_halign(Gtk::Align::END);
      m_ButtonBox.append(m_Button_OK);
      m_ButtonBox.append(m_Button_Cancel);
    }

    void NameDialog::buttons_clicked_connect(
      const sigc::slot<void(const Glib::ustring&)>& slot)
    {
      m_Button_OK.signal_clicked().connect(sigc::bind(slot, "OK"));
      m_Button_Cancel.signal_clicked().connect(sigc::bind(slot, "Cancel"));
    }

    Glib::ustring NameDialog::get_entry1() const
    {
      return m_Entry1.get_text();
    }

    Glib::ustring NameDialog::get_entry2() const
    {
      return m_Entry2.get_text();
    }

    NameDialog::~NameDialog()
    {
    }

## 绘图区域部件(DrawingArea Widget)

DrawingArea部件是一个空白的窗口，让你可以自由的创造任何你想要的图形。伴随着这种自由而来的是绘制这个部件的责任。在部件第一次显示或者部件被覆盖后又露出的时候它需要重绘自己。大多数部件都包含执行此操作的代码，但是DrawingArea并没有。因此你可以编写自己的绘图函数来确定如何绘制绘部件的内容。你可以调用set_draw_func()成员函数来设置绘图函数。

GTK uses the Cairo drawing API. With gtkmm, you may use the cairomm C++ API for cairo.

你可以使用开罗绘制非常复杂的形状，但你只有一些很基本的方法可用。开罗提供了绘制直线、曲线、圆弧（包括圆）的方法。你可以通过对这些图形进行组合从而创建更为复杂的形状和路径，然后用纯色、渐变、模式等进行填充。除此之外，开罗还可以进行复杂的转换、合成图像和渲染抗锯齿文本等。

开罗和Pango

虽然开罗可以用于渲染文本，但不意味着它能替代Pango。如果你需要进行更高级的文本渲染（例如环绕和省略文本）则Pango会是更好的选择。只有当文本是图形的一部分的时候才应该使用开罗绘制文本。

在本教程的这一部分中，我们将介绍开罗的基本绘图模型，详细描述每个基本绘制元素（包括示例），然后介绍一个使用开罗绘制自定义时钟部件的简单应用程序。

### 开罗绘制模型

开罗的基本概念包括定义'不可见'路径然后描绘(stroking)或者填充(filling)它们使其可见。

要在gtkmm中使用开罗进行绘制，必须先获取Cairo::Context对象。这个类保存了所有描述如何进行绘制的图形状态参数。状态参数包含了诸如线宽、颜色、要绘制的表面和许多其他内容的信息。这允许实际上的绘图函数只需要使用较少的参数从而简化接口。通常你使用set_draw_func()设置的绘图函数将Cairo::Context作为输入数据。也可以通过调用Gdk::Surface::create_cairo_context()和Gdk::CairoContext::cairo_create()函数创建一个Cairo::Context。由于Cairo::Context是一个使用引用计数的对象，因此cairo_create()返回Cairo::RefPtr<Cairo::Context>对象（请注意区分Gdk::CairoContext与Cairo::Context。）。

以下示例显示如何设置一个开罗上下文(Cairo Context)的前景色为红色且宽度为2。任何使用该上下文的绘图函数都将使用这些设置。

    Gtk::DrawingArea myArea;
    auto gdkCairoContext = myArea.get_surface()->create_cairo_context();
    auto myContext = gdkCairoContext->cairo_create();
    myContext->set_source_rgb(1.0, 0.0, 0.0);
    myContext->set_line_width(2.0);

每一个Cairo::Context都有一个特定的Gdk::Surface与之相关联，因此上面示例的第一行创建了一个Gtk::DrawingArea部件，接下来两行使用与其相关联的Gdk::Surface创建一个Cairo::Context对象。最后两行更改上下文的图形状态。

There are a number of graphics state variables that can be set for a Cairo context. The most common context attributes are color (using set_source_rgb() or set_source_rgba() for translucent colors), line width (using set_line_width()), line dash pattern (using set_dash()), line cap style (using set_line_cap()), and line join style (using set_line_join()), and font styles (using set_font_size(), set_font_face() and others). There are many other settings as well, such as transformation matrices, fill rules, whether to perform antialiasing, and others. For further information, see the cairomm API documentation.

可以将Cairo::Context当前的状态保存到其内部用于保存状态的栈中，并在之后将状态还原为保存时的状态。要做到这个请调用save()和restore()方法。当你需要临时更改某些图形设置（例如线宽和颜色）以绘制某些内容然后又退回到之前的状态时这将很有用。在这种情况下，你可以先调用Cairo::Context::save()，然后修改图形设置并绘制图形，再调用Cairo::Context::restore()恢复之前的图形状态。你可以多次嵌套调用save()和restore()；每次restore()调用都会将恢复状态到与之相对应的save()所保存的状态。

好的做法是在一组save()/restore()调用之间进行所有对图形状态的更改。例如，如果你有一个将Cairo::Context的引用作为参数的函数，你可以这样实现它：

    void doSomething(const Cairo::RefPtr<Cairo::Context>& context, int x)
    {
        context->save();
        // change graphics state
        // perform drawing operations
        context->restore();
    }

你通过调用set_draw_func()所设置的绘图函数是由你要绘制的Gtk::DrawingArea部件用开罗上下文调用的。所以你不需要在绘图函数中对此开罗上下文进行保存和恢复（部件会在调用绘图函数时做保存与恢复）。

### 画直线

现在我们已经了解了开罗图形库的基本知识，是时候开始画图了。我们将从画最简单的元素：直线 开始。但是在此之前你需要对开罗的坐标系有所了解。开罗的坐标系原点在窗口的左上角，从左向右X的值逐渐增大，从上往下Y的值逐渐增大。

由于开罗图形库是为了支持多个输出目标（X window系统、PNG图片、OpenGL等）编写的，因此用户空间坐标和设备空间坐标存在一些区别。默认情况下这两个坐标系是一对一的，这样可以将整数值大致映射为屏幕上的像素值，不过有需要时可以通过Cairo::Context::scale()函数对此行为进行调整。例如：你可能会需要使一个窗口的宽和高在调整比例后的坐标系中取值范围均处于0到1之间（单位正方形）。

#### 示例

在这个示例中，我们将构建一个很小但功能齐全的gtkmm程序，并将一些线绘制到窗口中。使用函数Cairo::Context::move_to()和Cairo::Context::line_to()创建一个路径。move_to()函数的行为类似于将笔从纸上抬起并放到其他地方 -- 在你移动到的点和你之前所在的点直接不会画出一条线。使用line_to()函数可以在两点之间画线。

创建完你的路径以后。你依旧没有画任何看得见的东西。要想让路径可见，你需要使用stroke()函数，该函数将使用你的Cairo::Context对象中保存的线宽与样式绘制当前的路径。描绘完成后，当前路径将被清除，以便你可以从下一个路径开始。

多数的开罗绘图函数都拥有一个_preserve()变体。通常情况下clip()、fill()、stroke()函数会清除当前的路径。如果你使用他们的_preserve()变体，则当前路径会被保留，以便下一个绘图函数能继续在一样的路径上绘制。

绘图区域 - 线

Source Code

File: `myarea.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYAREA_H
    #define GTKMM_EXAMPLE_MYAREA_H

    #include <gtkmm/drawingarea.h>

    class MyArea : public Gtk::DrawingArea
    {
    public:
      MyArea();
      virtual ~MyArea();

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
    };

    #endif // GTKMM_EXAMPLE_MYAREA_H

File: `main.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <gtkmm/application.h>
    #include <gtkmm/window.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

    protected:
      MyArea m_area;
    };

    ExampleWindow::ExampleWindow()
    {
      set_title("DrawingArea");
      set_child(m_area);
    }

    int main(int argc, char** argv)
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `myarea.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <cairomm/context.h>

    MyArea::MyArea()
    {
      set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
    }

    MyArea::~MyArea()
    {
    }

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      // coordinates for the center of the window
      int xc, yc;
      xc = width / 2;
      yc = height / 2;

      cr->set_line_width(10.0);

      // draw red lines out from the center of the window
      cr->set_source_rgb(0.8, 0.0, 0.0);
      cr->move_to(0, 0);
      cr->line_to(xc, yc);
      cr->line_to(0, height);
      cr->move_to(xc, yc);
      cr->line_to(width, yc);
      cr->stroke();
    }

这个程序包含一个MyArea类，它是Gtk::DrawingArea的子类，并且包含on_draw()成员函数。通过在MyArea的构造函数中调用set_draw_func()使其变成绘制函数。每当需要重绘绘图区域的图像的时候，on_draw()就会被调用。这将会向我们的绘制函数传递一个指向Cairo::Context的Cairo::RefPtr指针。实际进行绘图的代码使用set_source_rgb()设置我们将用于绘图的颜色，这个函数接受定义颜色所需的红色、绿色、蓝色分量作为参数（参数的有效值介于0到1之间）。设置完颜色后，我们使用move_to()和line_to()函数创建一个新的路径，然后使用stroke()绘制了这个路径。

使用相对坐标绘图

在上面的例子中，我们使用绝对坐标进行绘图。你也可以使用相对坐标进行绘图。对于直线而言，你可以通过Cairo::Context::rel_line_to()完成用相对坐标绘图。

#### 线型

除了绘制基本直线以外，你还可以对线条进行很多自定义。你已经看到了对线条颜色和线条宽度的示例，但还有其他示例。

如果你于一个路径绘制了一系列的线条，你可能希望它们以某种方式被连接起来。开罗提供了三种不同的连接线条的方式：斜接(Miter)、斜切(Bevel)、圆(Round)。这些连接方式的示意图如下：

开罗的不同连接类型

线条连接样式由Cairo::Context::set_line_join()函数进行设置。

线尾也有不同的样式。默认样式是从线条的开始到结尾准确的停止。这被叫做平端。其他的可选性有圆（使用圆形结束，以终点为圆心）和方（使用正方形结束，以终点为正方形的中心）。这可以使用Cairo::Context::set_line_cap()函数使用。

你还可以自定义其他内容，比如创建虚线等。更多有关信息请参阅开罗API文档。

#### 画细线

如果你尝试画一条宽度为一个像素的线条，你可能会注意到这条线有时会变得模糊并且比应有的宽度更宽。发生这种情况的原因是开若尝试在所选位置向两边绘制（一边一半），因此如果你如果正好位于像素交点上，还想绘制一个像素宽度的线条，那是不可能做到的（像素是最小的单位）。所以当像素的宽度是奇数的时候就会出现这种情况（不只是一个像素的时候）。

The trick is to position in the middle of the pixel where you want the line to be drawn, and thus guaranteeing you get the desired results. See Cairo FAQ.

绘图区域 - 细线

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm/window.h>
    #include <gtkmm/box.h>
    #include <gtkmm/checkbutton.h>
    #include "myarea.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_toggled();

    private:
      Gtk::Box m_HBox;
      MyArea m_Area_Lines;
      Gtk::CheckButton m_Button_FixLines;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `myarea.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYAREA_H
    #define GTKMM_EXAMPLE_MYAREA_H

    #include <gtkmm/drawingarea.h>

    class MyArea : public Gtk::DrawingArea
    {
    public:
      MyArea();
      virtual ~MyArea();

      void fix_lines(bool fix = true);

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

    private:
      double m_fix;
    };

    #endif // GTKMM_EXAMPLE_MYAREA_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_HBox(Gtk::Orientation::HORIZONTAL),
      m_Button_FixLines("Fix lines")
    {
      set_title("Thin lines example");

      m_HBox.append(m_Area_Lines);
      m_HBox.append(m_Button_FixLines);

      set_child(m_HBox);

      m_Button_FixLines.signal_toggled().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_button_toggled));

      // Synchonize the drawing in m_Area_Lines with the state of the toggle button.
      on_button_toggled();
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_toggled()
    {
      m_Area_Lines.fix_lines(m_Button_FixLines.get_active());
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `myarea.cc` (For use with gtkmm 4)

    #include "myarea.h"

    MyArea::MyArea()
    : m_fix (0)
    {
      set_content_width(200);
      set_content_height(100);
      set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
    }

    MyArea::~MyArea()
    {
    }

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      cr->set_line_width(1.0);

      // draw one line, every two pixels
      // without the 'fix', you won't notice any space between the lines,
      // since each one will occupy two pixels (width)
      for (int i = 0; i < width; i += 2)
      {
        cr->move_to(i + m_fix, 0);
        cr->line_to(i + m_fix, height);
      }

      cr->stroke();
    }

    // Toogle between both values (0 or 0.5)
    void MyArea::fix_lines(bool fix)
    {
      // to get the width right, we have to draw in the middle of the pixel
      m_fix = fix ? 0.5 : 0.0;

      // force the redraw of the image
      queue_draw();
    }

### 画曲线

除了绘制直线以外，开罗还允许你用Cairo::Context::curve_to()和Cairo::Context::rel_curve_to()函数轻松的绘制曲线（从技术上而言是三次贝塞尔曲线）。这些函数需要一个目标点的坐标和两个'控制'点的坐标。这最好用一个示例来解释，所以让我们开始吧。

#### 示例

这个简单的示例程序使用开罗绘制了一条曲线并显示了曲线两端的控制点。

绘图区域 - 线

Source Code

File: `myarea.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYAREA_H
    #define GTKMM_EXAMPLE_MYAREA_H

    #include <gtkmm/drawingarea.h>

    class MyArea : public Gtk::DrawingArea
    {
    public:
      MyArea();
      virtual ~MyArea();

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
    };

    #endif // GTKMM_EXAMPLE_MYAREA_H

File: `main.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <gtkmm/application.h>
    #include <gtkmm/window.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

    protected:
      MyArea m_area;
    };

    ExampleWindow::ExampleWindow()
    {
      set_title("DrawingArea");
      set_child(m_area);
    }

    int main(int argc, char** argv)
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `myarea.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <cairomm/context.h>

    MyArea::MyArea()
    {
      set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
    }

    MyArea::~MyArea()
    {
    }

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      double x0=0.1, y0=0.5, // start point
             x1=0.4, y1=0.9,  // control point #1
             x2=0.6, y2=0.1,  // control point #2
             x3=0.9, y3=0.5;  // end point

      // scale to unit square (0 to 1 width and height)
      cr->scale(width, height);

      cr->set_line_width(0.05);
      // draw curve
      cr->move_to(x0, y0);
      cr->curve_to(x1, y1, x2, y2, x3, y3);
      cr->stroke();
      // show control points
      cr->set_source_rgba(1, 0.2, 0.2, 0.6);
      cr->move_to(x0, y0);
      cr->line_to (x1, y1);
      cr->move_to(x2, y2);
      cr->line_to (x3, y3);
      cr->stroke();
    }

这个示例与绘制直线示例的唯一区别在它们的on_draw()函数，不过这里引入了一些新的概念和函数，所以让我们简要的介绍一下它们。

我们调用Cairo::Context::scale()并传入绘图区域的宽和高，这将使用户空间坐标系被缩放为"1个单位"（宽高的范围变成[0.0,1.0]）。在这其实并没有什么特别的要对坐标系进行缩放的理由，只是为了使绘图操作有时候更易用。

对Cairo::Context::curve_to()的调用应该是不言自明的。第一组坐标定义了曲线起点的控制点。第二组坐标定义了曲线终点的控制点，而最后一组坐标定义了目标点。为了使控制点的概念更容易被看清，从每个控制点到与之关联的曲线上的端点上都画了一条线。请注意，这些控制点线都是半透明的。这是通过调用set_source_rgb()的变体函数set_source_rgba()实现的。这个函数使用第四个参数来接受指定颜色的alpha值（值的有效区间为0到1）。

### 画圆弧和圆

在开罗中，画圆弧、圆、椭圆使用的是同一个函数：Cairo::Context::arc()。这个函数有五个参数。前两个参数是圆弧中心点的坐标，第三个参数是圆弧的半径，最后两个参数是圆弧的起点和终点。所有的角度都使用弧度制，所以画圆和画一个弧度为0到2*M_PI的圆弧是一样的。角度0沿着X轴的正方向（对于用户空间而言）。角度M_PI/2（角度制的90°）沿着Y轴的正方向（对于用户空间而言）。角度沿着X轴的正方向向Y轴的正方向逐渐增加。所以使用默认的变换矩阵的时候，角度沿着顺时针方向增加（请记住Y轴的正方向指向下方）。

想要绘制椭圆的话，你可以在X和Y方向用不同的量缩放当前的变换矩阵。例如，如果你想要绘制一个以`x`，`y`为中心，大小为`width`，`height`的椭圆：

    context->save();
    context->translate(x, y);
    context->scale(width / 2.0, height / 2.0);
    context->arc(0.0, 0.0, 1.0, 0.0, 2 * M_PI);
    context->restore();

#### 示例

这是一个简单的示例程序，它在绘图区域绘制了一个圆弧、一个圆和一个椭圆。

绘图区域 - 圆弧

Source Code

File: `myarea.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYAREA_H
    #define GTKMM_EXAMPLE_MYAREA_H

    #include <gtkmm/drawingarea.h>

    class MyArea : public Gtk::DrawingArea
    {
    public:
      MyArea();
      virtual ~MyArea();

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
    };

    #endif // GTKMM_EXAMPLE_MYAREA_H

File: `main.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <gtkmm/application.h>
    #include <gtkmm/window.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

    protected:
      MyArea m_area;
    };

    ExampleWindow::ExampleWindow()
    {
      set_title("DrawingArea");
      set_child(m_area);
    }

    int main(int argc, char** argv)
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `myarea.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <cairomm/context.h>
    #include <cmath>

    MyArea::MyArea()
    {
      set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
    }

    MyArea::~MyArea()
    {
    }

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      const int lesser = std::min(width, height);

      // coordinates for the center of the window
      const int xc = width / 2;
      const int yc = height / 2;

      cr->set_line_width(lesser * 0.02);  // outline thickness changes
                                          // with window size

      // first draw a simple unclosed arc
      cr->save();
      cr->arc(width / 3.0, height / 4.0, lesser / 4.0, -(M_PI / 5.0), M_PI);
      cr->close_path();   // line back to start point
      cr->set_source_rgb(0.0, 0.8, 0.0);
      cr->fill_preserve();
      cr->restore();  // back to opaque black
      cr->stroke();   // outline it

      // now draw a circle
      cr->save();
      cr->arc(xc, yc, lesser / 4.0, 0.0, 2.0 * M_PI); // full circle
      cr->set_source_rgba(0.0, 0.0, 0.8, 0.6);    // partially translucent
      cr->fill_preserve();
      cr->restore();  // back to opaque black
      cr->stroke();

      // and finally an ellipse
      double ex, ey, ew, eh;
      // center of ellipse
      ex = xc;
      ey = 3.0 * height / 4.0;
      // ellipse dimensions
      ew = 3.0 * width / 4.0;
      eh = height / 3.0;

      cr->save();

      cr->translate(ex, ey);  // make (ex, ey) == (0, 0)
      cr->scale(ew / 2.0, eh / 2.0);  // for width: ew / 2.0 == 1.0
                                      // for height: eh / 2.0 == 1.0

      cr->arc(0.0, 0.0, 1.0, 0.0, 2 * M_PI);  // 'circle' centered at (0, 0)
                                              // with 'radius' of 1.0

      cr->set_source_rgba(0.8, 0.0, 0.0, 0.7);
      cr->fill_preserve();
      cr->restore();  // back to opaque black
      cr->stroke();
    }

关于这个示例代码有几件事需要注意。首先这个示例与之前的示例的区别也只在on_draw()函数中，因此我们重点接受这个函数。另外，函数的第一部分与之前的也几乎一样，所以我们将会跳过该部分。

请注意，在这个情况下我们几乎使用窗口的宽和高表示了包括线条宽度在内的所有的内容（通过缩放用户坐标系）。所以当我们调整窗口大小的时候，所有的内容都会随着窗口一同缩放。还请注意，函数中有三个进行绘图的部分都用一组save()/restore()包裹，以便我们在每次绘完图都能返回已知状态。

在绘制圆弧的部分介绍了一个新的函数：close_path()。这个函数的效果实际上就是在当前点和路径的第一个点之间画一条直线。但调用close_path()所绘制的线条和使用line_to()手工绘制一条回到起点的线条会有很大的不同。如果你使用close_path()，则这些线将会很好的连接到一起。如果你改用line_to()，那么这些线将在同一个点结束但是开罗不会对此做任何特殊的连接。

逆时针绘图

函数Cairo::Context::arc_negative()的功能与Cairo::Context::arc()函数完全一致，但是他们的绘制方向是相反的。

### 绘制文本

#### 使用Pango绘制文本

通过Pango布局绘制文本。创建一个Pango::Layout最简单的方法是使用Gtk::Widget::create_pango_layout()。创建布局以后，可以通过各种方式操纵布局，包括更改文本、字体等。然后可以使用Pango::Layout::show_in_cairo_context()方法渲染布局。

#### 示例

这是一个绘制一些文本的示例程序，其中的一些文本上下颠倒。打印一章包含了另一个绘制文本的示例。

绘图区域 - 文本

Source Code

File: `myarea.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYAREA_H
    #define GTKMM_EXAMPLE_MYAREA_H

    #include <gtkmm/drawingarea.h>

    class MyArea : public Gtk::DrawingArea
    {
    public:
      MyArea();
      virtual ~MyArea();

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

    private:
      void draw_rectangle(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
      void draw_text(const Cairo::RefPtr<Cairo::Context>& cr, int rectangle_width, int rectangle_height);

    };

    #endif // GTKMM_EXAMPLE_MYAREA_H

File: `main.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <gtkmm/application.h>
    #include <gtkmm/window.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

    protected:
      MyArea m_area;
    };

    ExampleWindow::ExampleWindow()
    {
      set_title("Drawing text example");
      set_child(m_area);
    }

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `myarea.cc` (For use with gtkmm 4)

    #include "myarea.h"

    MyArea::MyArea()
    {
      set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
    }

    MyArea::~MyArea()
    {
    }

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      const int rectangle_width = width;
      const int rectangle_height = height / 2;

      // Draw a black rectangle
      cr->set_source_rgb(0.0, 0.0, 0.0);
      draw_rectangle(cr, rectangle_width, rectangle_height);

      // and some white text
      cr->set_source_rgb(1.0, 1.0, 1.0);
      draw_text(cr, rectangle_width, rectangle_height);

      // flip the image vertically
      // see http://www.cairographics.org/documentation/cairomm/reference/classCairo_1_1Matrix.html
      // the -1 corresponds to the yy part (the flipping part)
      // the height part is a translation (we could have just called cr->translate(0, height) instead)
      // it's height and not height / 2, since we want this to be on the second part of our drawing
      // (otherwise, it would draw over the previous part)
      Cairo::Matrix matrix(1.0, 0.0, 0.0, -1.0, 0.0, height);

      // apply the matrix
      cr->transform(matrix);

      // white rectangle
      cr->set_source_rgb(1.0, 1.0, 1.0);
      draw_rectangle(cr, rectangle_width, rectangle_height);

      // black text
      cr->set_source_rgb(0.0, 0.0, 0.0);
      draw_text(cr, rectangle_width, rectangle_height);
    }

    void MyArea::draw_rectangle(const Cairo::RefPtr<Cairo::Context>& cr,
                                int width, int height)
    {
      cr->rectangle(0, 0, width, height);
      cr->fill();
    }

    void MyArea::draw_text(const Cairo::RefPtr<Cairo::Context>& cr,
                           int rectangle_width, int rectangle_height)
    {
      // http://developer.gnome.org/pangomm/unstable/classPango_1_1FontDescription.html
      Pango::FontDescription font;

      font.set_family("Monospace");
      font.set_weight(Pango::Weight::BOLD);

      // http://developer.gnome.org/pangomm/unstable/classPango_1_1Layout.html
      auto layout = create_pango_layout("Hi there!");

      layout->set_font_description(font);

      int text_width;
      int text_height;

      //get the text dimensions (it updates the variables -- by reference)
      layout->get_pixel_size(text_width, text_height);

      // Position the text in the middle
      cr->move_to((rectangle_width-text_width)/2, (rectangle_height-text_height)/2);

      layout->show_in_cairo_context(cr);
    }

### 绘制图片

有一个方法可以将Gdk::Pixbuf绘制到Cairo::Context。Gdk::Pixbuf缓冲区是对像素集合的有效封装，它提供了从文件读取以及各种操纵像素的方式。

最常见的Gdk::Pixbuf创建方法是用Gdk::Pixbuf::create_from_file()或Gdk::Pixbuf::create_from_resource()，它们可以将图像文件（例如png文件）读取到pixbuf中用于渲染。

通过使用Gdk::Cairo::set_source_pixbuf()将Gdk::Pixbuf设置为开罗上下文的源模式使其可以被渲染。然后使用Cairo::Context::paint()（画整个图像），或者使用Cairo::Context::rectangle()和Cairo::Context::fill()（用图像填充整个矩形）。set_source_pixbuf()不是Cairo::Context的成员函数。Cairo::Context是它的第一个参数。

这是一小段将他们结合在一起的代码（请注意，通常你不应该在每次调用绘制信号处理函数的时候都加载一次图片）：

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      auto image = Gdk::Pixbuf::create_from_file("myimage.png");
      // Draw the image at 110, 90, except for the outermost 10 pixels.
      Gdk::Cairo::set_source_pixbuf(cr, image, 100, 80);
      cr->rectangle(110, 90, image->get_width()-20, image->get_height()-20);
      cr->fill();
    }

#### 示例

这是一个绘制图像的简单示例程序。该程序从资源文件加载图像。请参阅Gio::Resource和glib-compile-resources小节。使用glib-compile-resources将资源文件编译为C源文件，然后将该源文件与C++代码一起编译链接。例如：

    $ glib-compile-resources --target=resources.c --generate-source image.gresource.xml

绘图区域 - 图像

Source Code

File: `myarea.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYAREA_H
    #define GTKMM_EXAMPLE_MYAREA_H

    #include <gtkmm/drawingarea.h>
    #include <gdkmm/pixbuf.h>

    class MyArea : public Gtk::DrawingArea
    {
    public:
      MyArea();
      virtual ~MyArea();

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

      Glib::RefPtr<Gdk::Pixbuf> m_image;
    };

    #endif // GTKMM_EXAMPLE_MYAREA_H

File: `main.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <gtkmm/application.h>
    #include <gtkmm/window.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

    protected:
      MyArea m_area;
    };

    ExampleWindow::ExampleWindow()
    {
      set_title("DrawingArea");
      set_default_size(300, 200);
      set_child(m_area);
    }

    int main(int argc, char** argv)
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `myarea.cc` (For use with gtkmm 4)

    #include "myarea.h"
    #include <cairomm/context.h>
    #include <giomm/resource.h>
    #include <gdkmm/general.h> // set_source_pixbuf()
    #include <glibmm/fileutils.h>
    #include <iostream>

    MyArea::MyArea()
    {
      try
      {
        // The fractal image has been created by the XaoS program.
        // http://xaos.sourceforge.net
        m_image = Gdk::Pixbuf::create_from_resource("/image/fractal_image.png");
      }
      catch(const Gio::ResourceError& ex)
      {
        std::cerr << "ResourceError: " << ex.what() << std::endl;
      }
      catch(const Gdk::PixbufError& ex)
      {
        std::cerr << "PixbufError: " << ex.what() << std::endl;
      }

      // Show at least a quarter of the image.
      if (m_image)
      {
        set_content_width(m_image->get_width()/2);
        set_content_height(m_image->get_height()/2);
      }

      // Set the draw function.
      set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
    }

    MyArea::~MyArea()
    {
    }

    void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      if (!m_image)
        return;

      // Draw the image in the middle of the drawing area, or (if the image is
      // larger than the drawing area) draw the middle part of the image.
      Gdk::Cairo::set_source_pixbuf(cr, m_image,
        (width - m_image->get_width())/2, (height - m_image->get_height())/2);
      cr->paint();
    }

File: `image.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/image">
        <file>fractal_image.png</file>
      </gresource>
    </gresources>

### 示例程序：使用开罗创建时钟

既然我们已经将使用开罗画图的基础知识介绍完了，那么现在让我们尝试将所有的内容放在一起，创建一个可以执行某些操作的简单应用程序。以下示例使用开罗创建一个自定义的Clock部件。这个时钟有秒针、分针、时针，并且每一秒更新一次。

Source Code

File: `clock.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_CLOCK_H
    #define GTKMM_EXAMPLE_CLOCK_H

    #include <gtkmm/drawingarea.h>

    class Clock : public Gtk::DrawingArea
    {
    public:
      Clock();
      virtual ~Clock();

    protected:
      void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

      bool on_timeout();

      double m_radius;
      double m_line_width;
    };

    #endif // GTKMM_EXAMPLE_CLOCK_H

File: `clock.cc` (For use with gtkmm 4)

    #include <ctime>
    #include <cmath>
    #include <cairomm/context.h>
    #include <glibmm/main.h>
    #include "clock.h"

    Clock::Clock()
    : m_radius(0.42), m_line_width(0.05)
    {
      Glib::signal_timeout().connect( sigc::mem_fun(*this, &Clock::on_timeout), 1000 );
      set_draw_func(sigc::mem_fun(*this, &Clock::on_draw));
    }

    Clock::~Clock()
    {
    }

    void Clock::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
    {
      // scale to unit square and translate (0, 0) to be (0.5, 0.5), i.e.
      // the center of the window
      cr->scale(width, height);
      cr->translate(0.5, 0.5);
      cr->set_line_width(m_line_width);

      cr->save();
      cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
      cr->paint();
      cr->restore();
      cr->arc(0, 0, m_radius, 0, 2 * M_PI);
      cr->save();
      cr->set_source_rgba(1.0, 1.0, 1.0, 0.8);
      cr->fill_preserve();
      cr->restore();
      cr->stroke_preserve();
      cr->clip();

      //clock ticks
      for (int i = 0; i < 12; i++)
      {
        double inset = 0.05;

        cr->save();
        cr->set_line_cap(Cairo::Context::LineCap::ROUND);

        if(i % 3 != 0)
        {
          inset *= 0.8;
          cr->set_line_width(0.03);
        }

        cr->move_to(
          (m_radius - inset) * cos (i * M_PI / 6),
          (m_radius - inset) * sin (i * M_PI / 6));
        cr->line_to (
          m_radius * cos (i * M_PI / 6),
          m_radius * sin (i * M_PI / 6));
        cr->stroke();
        cr->restore(); /* stack-pen-size */
      }

      // store the current time
      time_t rawtime;
      time(&rawtime);
      struct tm * timeinfo = localtime (&rawtime);

      // compute the angles of the indicators of our clock
      double minutes = timeinfo->tm_min * M_PI / 30;
      double hours = timeinfo->tm_hour * M_PI / 6;
      double seconds= timeinfo->tm_sec * M_PI / 30;

      cr->save();
      cr->set_line_cap(Cairo::Context::LineCap::ROUND);

      // draw the seconds hand
      cr->save();
      cr->set_line_width(m_line_width / 3);
      cr->set_source_rgba(0.7, 0.7, 0.7, 0.8); // gray
      cr->move_to(0, 0);
      cr->line_to(sin(seconds) * (m_radius * 0.9),
        -cos(seconds) * (m_radius * 0.9));
      cr->stroke();
      cr->restore();

      // draw the minutes hand
      cr->set_source_rgba(0.117, 0.337, 0.612, 0.9);   // blue
      cr->move_to(0, 0);
      cr->line_to(sin(minutes + seconds / 60) * (m_radius * 0.8),
        -cos(minutes + seconds / 60) * (m_radius * 0.8));
      cr->stroke();

      // draw the hours hand
      cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
      cr->move_to(0, 0);
      cr->line_to(sin(hours + minutes / 12.0) * (m_radius * 0.5),
        -cos(hours + minutes / 12.0) * (m_radius * 0.5));
      cr->stroke();
      cr->restore();

      // draw a little dot in the middle
      cr->arc(0, 0, m_line_width / 3.0, 0, 2 * M_PI);
      cr->fill();
    }

    bool Clock::on_timeout()
    {
      // force our program to redraw the entire clock.
      queue_draw();
      return true;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "clock.h"
    #include <gtkmm/application.h>
    #include <gtkmm/window.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

    protected:
      Clock m_clock;
    };

    ExampleWindow::ExampleWindow()
    {
      set_title("Cairomm Clock");
      set_child(m_clock);
    }

    int main(int argc, char** argv)
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

和之前一样，所有有趣的东西都在绘图函数on_draw()中完成。在深入研究绘图函数之前，请注意在Clock部件的构造函数中将处理函数on_timeout()连接到了用计时器上，该计时器的超时时间为1000毫秒（1秒）。这意味着on_timeout()将每秒被调用一次。这个函数的唯一任务就是使窗口无效，以便gtkmm强制重绘它。

现在让我们研究实际执行绘图的函数。到现在on_draw()函数的第一部分你应该很熟悉了。此示例再次将坐标系缩放为单位正方形，以便更轻松的按窗口大小的百分比绘制时钟。此外坐标系是按比例缩放的，所以(0,0)坐标位于窗口的正中央。

Cairo::Context::paint()方法用于设置窗口的背景色。这个函数不接受任何参数，并使用处于活跃状态的源颜色填充当前表面（或表面裁剪的一部分）。设置背景色以后，我们画一个圆作为时钟的轮廓，用白色填充它然后用黑色进行描边。请注意，这两个操作都用_preserve变体以保留当前路径，然后对相同的路径进行剪辑以确保我们下一行不会超过时钟的轮廓。

绘制轮廓后，我们围绕时钟为每个小时绘制可读线并为12、3、6、9绘制相对更大的刻度线。接着我们就可以实现时钟的计时功能了 -- 获取当前时间的时、分、秒然后以正确的角度将指针绘制出来。

## 拖放

The Gtk::DragSource and Gtk::DropTarget event controllers have methods and signals which are used for Drag and Drop.

### Sources and Targets

Things are dragged from `sources` to be dropped on `targets`. Each source and target has information about the data formats that it can send or receive, provided by Gdk::ContentFormats. A drop target will only accept a dragged item if they both share a compatible format. Appropriate signals will then be emitted, telling the signal handlers which format was used.

Gdk::ContentFormats对象包含了可用的GType和MIME类型（媒体类型）的相关信息。

### 方法

Widgets can be identified as sources or targets using Gtk::DragSource and Gtk::DropTarget event controllers.

    auto source = Gtk::DragSource::create();
    m_source_widget.add_controller(source);

Some DragSource methods:

- `void set_content(const Glib::RefPtr<Gdk::ContentProvider>& content)`: Sets a content provider on the drag source.

- `void set_actions(Gdk::DragAction actions)`: Sets the actions on the drag source. For instance `Gdk::DragAction::COPY | Gdk::DragAction::MOVE`.

- `void set_icon(const Glib::RefPtr<const Gdk::Paintable>& paintable, int hot_x, int hot_y)`: Sets a paintable to use as icon during DND operations.

    auto target = Gtk::DropTarget::create(gtype, actions);
    m_target_widget.add_controller(target);

Some DropTarget methods:

- `void set_gtypes(const std::vector<GType>& types)`: Sets the supported types for this drop target.

- `void set_actions(Gdk::DragAction actions)`: Sets the actions that this drop target supports.

- `Glib::ValueBase get_value() const`: Gets the current drop data, as a Glib::Value.

- `void reject()`: Rejects the ongoing drop operation. This function should be used when delaying the decision on whether to accept a drag or not until after reading the data.

### 信号

When a drop target has accepted a dragged item, certain signals will be emitted, depending on what action has been selected. For instance, the user might have held down the Shift key to specify a `move` rather than a `copy`. Remember that the user can only select the actions which you have specified in your calls to Gtk::DragSource::set_actions() and Gtk::DropTarget::set_actions().

The source widget will emit these DragSource signals:

- `drag_begin`: Provides a Gdk::Drag.

- `prepare`: Shall return a Gdk::ContentProvider, with the data to use for the drag that is about to start.

- `drag_end`: Provides a Gdk::Drag, and a bool that tells if the drag was performing a `move` and the data should be deleted.

- `drag_cancel`: Emitted on the drag source when a drag has failed.

The target widget will emit these DropTarget signals:

- `enter`: Provides coordinates. Shall return the preferred Gdk::DragAction.

- `motion`: Provides coordinates. Shall return the preferred Gdk::DragAction.

- `leave`: Emitted on the drop site when the pointer leaves the widget.

- `accept`: Provides a Gdk::Drop. You can call the status() method of the Gdk::Drop to indicate which actions will be accepted.

- `drop`: Provides the data being dropped and coordinates. Shall return a bool indicating whether the drop was accepted.

The following signals call only one signal handler when emitted. When you connect a handler to such a signal, your signal handler must be called before (instead of) the default handler, otherwise it won't be called. Set the `after` parameter in connect() to `false`.

- Gtk::DragSource::signal_prepare()

- Gtk::DropTarget::signal_enter()

- Gtk::DropTarget::signal_motion()

- Gtk::DropTarget::signal_accept()

- Gtk::DropTarget::signal_drop()

### 示例

这是一个非常简单的示例，演示了拖放的`Copy`操作：

拖放

Source Code

File: `dndwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_DNDWINDOW_H
    #define GTKMM_EXAMPLE_DNDWINDOW_H

    #include <gdkmm/drop.h>
    #include <gtkmm/box.h>
    #include <gtkmm/label.h>
    #include <gtkmm/window.h>
    #include <gtkmm/button.h>

    class DnDWindow : public Gtk::Window
    {

    public:
      DnDWindow();
      virtual ~DnDWindow();

    protected:
      //Signal handlers:
      Glib::RefPtr<Gdk::ContentProvider> on_label_drag_prepare_data(double x, double y);
      bool on_button_drop_drop_data(const Glib::ValueBase& value, double x, double y);

      //Member widgets:
      Gtk::Box m_HBox;
      Gtk::Label m_Label_Drag;
      Gtk::Button m_Button_Drop;
    };

    #endif // GTKMM_EXAMPLE_DNDWINDOW_H

File: `dndwindow.cc` (For use with gtkmm 4)

    #include "dndwindow.h"
    #include <gdkmm/contentprovider.h>
    #include <gtkmm/dragsource.h>
    #include <gtkmm/droptarget.h>
    #include <iostream>

    DnDWindow::DnDWindow()
    : m_Label_Drag("Drag Here\n"),
      m_Button_Drop("Drop here\n")
    {
      set_title("DnD example");

      set_child(m_HBox);

      //Drag site:

      //Make m_Label_Drag a DnD drag source:
      auto source = Gtk::DragSource::create();
      source->set_actions(Gdk::DragAction::COPY);
      source->signal_prepare().connect(
        sigc::mem_fun(*this, &DnDWindow::on_label_drag_prepare_data), false);
      m_Label_Drag.add_controller(source);

      m_HBox.append(m_Label_Drag);
      m_Label_Drag.set_expand(true);

      //Drop site:

      //Make m_Button_Drop a DnD drop destination:
      const GType ustring_type = Glib::Value<Glib::ustring>::value_type();
      auto target = Gtk::DropTarget::create(ustring_type, Gdk::DragAction::COPY);
      target->signal_drop().connect(
        sigc::mem_fun(*this, &DnDWindow::on_button_drop_drop_data), false);
      m_Button_Drop.add_controller(target);

      m_HBox.append(m_Button_Drop);
      m_Button_Drop.set_expand(true);
    }

    DnDWindow::~DnDWindow()
    {
    }

    // In this simple example where just a small amount of data is copied,
    // it would be reasonable to store the ContentProvider in the DragSource.
    // Then this signal handler would be unnecessary.
    Glib::RefPtr<Gdk::ContentProvider> DnDWindow::on_label_drag_prepare_data(double, double)
    {
      Glib::Value<Glib::ustring> ustring_value;
      ustring_value.init(ustring_value.value_type());
      ustring_value.set("I'm Data!");
      return Gdk::ContentProvider::create(ustring_value);
    }

    bool DnDWindow::on_button_drop_drop_data(const Glib::ValueBase& value, double, double)
    {
      if (G_VALUE_HOLDS(value.gobj(), Glib::Value<Glib::ustring>::value_type()))
      {
        // We got the value type that we expected.
        Glib::Value<Glib::ustring> ustring_value;
        ustring_value.init(value.gobj());
        const Glib::ustring dropped_string = ustring_value.get();

        std::cout << "Received \"" << dropped_string << "\" in button " << std::endl;
        return true;
      }
      else
      {
        std::cout << "Received unexpected data type \""
          << G_VALUE_TYPE_NAME(value.gobj()) << "\" in button " << std::endl;
        return false;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "dndwindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<DnDWindow>(argc, argv);
    }

在examples/others/dnd中有一个更复杂的示例。

## 剪切板(Clipboard)

Gtk::Entry和Gtk::TextView之类的部件提供了简单的文本复制粘贴功能，但如果你有自己的数据格式需要处理则需要为此编写特定的代码。例如，绘图程序需要编写特定的代码以支持在视图内或文档之间进行复制粘贴。

你可以使用Gtk::Widget::get_clipboard()或Gdk::Display::get_clipboard()获取剪切板实例。

你的应用程序不需要等待剪切板操作，特别是用户选择复制然后选择粘贴的期间。Gdk::Clipboard的多数方法使用sigc::slot，它们指定了回调函数。当Gdk::Clipboard准备就绪时，它将会调用这些回调函数，并向其提供所请求的数据。

Reference

### 格式

不同的应用程序包含不同类型的数据，并且它们可能使用多种可用格式提供数据。gtkmm称这些数据类型为`format`。

例如，gedit可以提供和接收`text/plain`MIME类型，所以你可以从任何提供该格式的应用程序中将复制粘贴给gedit。或者两个不同的图像编辑程序可能会提供和接收各种图像格式。只要其中一个应用程序可以接收其他应用程序提供的一种格式，那么你就可以将数据从一个复制到另一个。

剪切板数据可以使用多种二进制格式。本章和示例程序假定数据是8位文本。这将使我们可以对剪切板数据使用XML格式。但这可能不适合二进制数据（例如图像）。

拖放API使用一样的机制。剪切板和拖放操作都应该使用一样的数据格式。

### 复制

当用户要复制某些数据的时候，你应该将数据复制到Clipboard。例如：

    void ExampleWindow::on_button_copy()
    {
      get_clipboard()->set_text("example_custom_target");
    }

### 粘贴

当用户要从Clipboard中粘贴数据时，你应该请求一种特定的格式并提供一个回调函数，该回调函数将与实际数据一并被调用。例如：

    void ExampleWindow::on_button_paste()
    {
      get_clipboard()->read_text_async(sigc::mem_fun(*this,
                  &ExampleWindow::on_clipboard_received));
    }

这是一个回调函数的示例：

    void ExampleWindow::on_clipboard_received(Glib::RefPtr<Gio::AsyncResult>& result)
    {
      auto text = get_clipboard()->read_text_finish(result);
      //Do something with the pasted data.
    }

#### 发现可用格式

要找出当前Clipboard可以用于粘贴的格式，请调用get_formats()方法。然后调用Gdk::ContentFormats方法来确定你的应用程序所支持的格式是否可用。

### 示例

#### 简单

这个示例允许使用标准文本格式复制粘贴应用程序指定的数据。尽管着很简单，但是它并不够理想，因为他不能将Clipboard的数据标识为特定的类型。

剪切板 - 简单

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_copy();
      void on_button_paste();

      void on_clipboard_text_received(Glib::RefPtr<Gio::AsyncResult>& result);

      Glib::ustring m_strData;

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::Label m_Label;

      Gtk::Grid m_Grid;
      Gtk::ToggleButton m_ButtonA1, m_ButtonA2, m_ButtonB1, m_ButtonB2;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Copy, m_Button_Paste;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Label("Select cells in the grid, click Copy, then open a second "
            "instance of this example to try pasting the copied data."),
      m_ButtonA1("A1"), m_ButtonA2("A2"), m_ButtonB1("B1"), m_ButtonB2("B2"),
      m_Button_Copy("_Copy", /* mnemonic= */ true), m_Button_Paste("_Paste", true)
    {
      set_title("Gtk::Clipboard example");

      m_VBox.set_margin(12);
      set_child(m_VBox);

      m_VBox.append(m_Label);

      //Fill Grid:
      m_VBox.append(m_Grid);
      m_Grid.set_expand(true);
      m_Grid.set_row_homogeneous(true);
      m_Grid.set_column_homogeneous(true);
      m_Grid.attach(m_ButtonA1, 0, 0);
      m_Grid.attach(m_ButtonA2, 1, 0);
      m_Grid.attach(m_ButtonB1, 0, 1);
      m_Grid.attach(m_ButtonB2, 1, 1);

      //Add ButtonBox to bottom:
      m_VBox.append(m_ButtonBox);
      m_VBox.set_spacing(6);

      //Fill ButtonBox:
      m_ButtonBox.append(m_Button_Copy);
      m_Button_Copy.set_hexpand(true);
      m_Button_Copy.set_halign(Gtk::Align::END);
      m_Button_Copy.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_copy) );
      m_ButtonBox.append(m_Button_Paste);
      m_Button_Paste.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_paste) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_copy()
    {
      //Build a string representation of the stuff to be copied:
      //Ideally you would use XML, with an XML parser here:
      m_strData = m_ButtonA1.get_active() ? "1" : "0";
      m_strData += m_ButtonA2.get_active() ? "1" : "0";
      m_strData += m_ButtonB1.get_active() ? "1" : "0";
      m_strData += m_ButtonB2.get_active() ? "1" : "0";

      // Gdk::Clipboard::set_text() does not take a copy of the text.
      // The text can only be pasted (in this program or in some other program)
      // for as long as it exists.
      get_clipboard()->set_text(m_strData);
    }

    void ExampleWindow::on_button_paste()
    {
      //Tell the clipboard to call our method when it is ready:
      get_clipboard()->read_text_async(sigc::mem_fun(*this,
                  &ExampleWindow::on_clipboard_text_received));
    }

    void ExampleWindow::on_clipboard_text_received(Glib::RefPtr<Gio::AsyncResult>& result)
    {
      Glib::ustring text;
      try
      {
        text = get_clipboard()->read_text_finish(result);
      }
      catch (const Glib::Error& err)
      {
        // Print an error about why pasting failed.
        // Usually you probably want to ignore such failures,
        // but for demonstration purposes, we show the error.
        std::cout << "Pasting failed: " << err.what() << std::endl;
      }

      //See comment in on_button_copy() about this silly clipboard format.
      if(text.size() >= 4)
      {
        m_ButtonA1.set_active( text[0] == '1' );
        m_ButtonA2.set_active( text[1] == '1' );
        m_ButtonB1.set_active( text[2] == '1' );
        m_ButtonB2.set_active( text[3] == '1' );
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      // Gio::Application::Flags::NON_UNIQUE because it shall be possible to run
      // several instances of this application simultaneously.
      auto app = Gtk::Application::create(
        "org.gtkmm.example", Gio::Application::Flags::NON_UNIQUE);

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

#### 理想

这个与简单示例类型，但是它

1. 定义了一个自定义剪切板目标，尽管格式依旧是文本。

2. 它使用Gdk::ContentFormats::signal_changed()信号并在剪切板不可用的时候禁用粘贴按钮。

剪切板 - 理想

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_copy();
      void on_button_paste();

      void on_clipboard_content_changed();

      void on_clipboard_received(Glib::RefPtr<Gio::AsyncResult>& result);
      void on_clipboard_received_status(Glib::RefPtr<Gio::AsyncResult>& result);

      void update_paste_status(); //Disable the paste button if there is nothing to paste.

      Glib::ustring m_strData;

      //Child widgets:
      Gtk::Box m_VBox;

      Gtk::Label m_Label;

      Gtk::Grid m_Grid;
      Gtk::ToggleButton m_ButtonA1, m_ButtonA2, m_ButtonB1, m_ButtonB2;

      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Copy, m_Button_Paste;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>
    #include <string>

    namespace
    {

    const char example_format_custom[] = "gtkmmclipboardexample";

    } // anonymous namespace

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Label("Select cells in the grid, click Copy, then open a second instance "
              "of this example to try pasting the copied data.\nOr try pasting the "
              "text representation into gedit."),
      m_ButtonA1("A1"), m_ButtonA2("A2"), m_ButtonB1("B1"), m_ButtonB2("B2"),
      m_Button_Copy("_Copy", /* mnemonic= */ true), m_Button_Paste("_Paste", true)
    {
      set_title("Gtk::Clipboard example");

      m_VBox.set_margin(12);
      set_child(m_VBox);

      m_VBox.append(m_Label);

      //Fill Grid:
      m_VBox.append(m_Grid);
      m_Grid.set_expand(true);
      m_Grid.set_row_homogeneous(true);
      m_Grid.set_column_homogeneous(true);
      m_Grid.attach(m_ButtonA1, 0, 0);
      m_Grid.attach(m_ButtonA2, 1, 0);
      m_Grid.attach(m_ButtonB1, 0, 1);
      m_Grid.attach(m_ButtonB2, 1, 1);

      //Add ButtonBox to bottom:
      m_VBox.append(m_ButtonBox);
      m_VBox.set_spacing(6);

      //Fill ButtonBox:
      m_ButtonBox.append(m_Button_Copy);
      m_Button_Copy.set_hexpand(true);
      m_Button_Copy.set_halign(Gtk::Align::END);
      m_Button_Copy.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_copy) );
      m_ButtonBox.append(m_Button_Paste);
      m_Button_Paste.signal_clicked().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_button_paste) );

      //Connect a signal handler that will be called when the contents of
      //the clipboard change.
      get_clipboard()->signal_changed().connect(sigc::mem_fun(*this,
                  &ExampleWindow::on_clipboard_content_changed));

      update_paste_status();
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_copy()
    {
      //Build a string representation of the stuff to be copied:
      //Ideally you would use XML, with an XML parser here:
      m_strData = example_format_custom;
      m_strData += m_ButtonA1.get_active() ? " A1" : "";
      m_strData += m_ButtonA2.get_active() ? " A2" : "";
      m_strData += m_ButtonB1.get_active() ? " B1" : "";
      m_strData += m_ButtonB2.get_active() ? " B2" : "";

      // Gdk::Clipboard::set_text() does not take a copy of the text.
      // The text can only be pasted (in this program or in some other program)
      // for as long as it exists.
      get_clipboard()->set_text(m_strData);
    }

    void ExampleWindow::on_button_paste()
    {
      //Tell the clipboard to call our method when it is ready:
      get_clipboard()->read_text_async(sigc::mem_fun(*this,
                  &ExampleWindow::on_clipboard_received));
    }

    void ExampleWindow::on_clipboard_content_changed()
    {
      update_paste_status();
    }

    void ExampleWindow::on_clipboard_received(Glib::RefPtr<Gio::AsyncResult>& result)
    {
      Glib::ustring text;
      try
      {
        text = get_clipboard()->read_text_finish(result);
      }
      catch (const Glib::Error& err)
      {
        // Print an error about why pasting failed.
        // Usually you probably want to ignore such failures,
        // but for demonstration purposes, we show the error.
        std::cout << "Pasting failed: " << err.what() << std::endl;
      }

      if (text.find(example_format_custom) == 0)
      {
        // It's the expected format.
        m_ButtonA1.set_active(text.find("A1") != std::string::npos);
        m_ButtonA2.set_active(text.find("A2") != std::string::npos);
        m_ButtonB1.set_active(text.find("B1") != std::string::npos);
        m_ButtonB2.set_active(text.find("B2") != std::string::npos);
      }
      else
      {
        // Unexpected format. Disable the Paste button.
        std::cout << "Unexpected pasted text: \"" << text << "\"" << std::endl;
        m_Button_Paste.set_sensitive(false);
      }
    }

    void ExampleWindow::update_paste_status()
    {
      // Disable the paste button if there is nothing to paste.
      get_clipboard()->read_text_async(sigc::mem_fun(*this,
                  &ExampleWindow::on_clipboard_received_status));
    }

    void ExampleWindow::on_clipboard_received_status(Glib::RefPtr<Gio::AsyncResult>& result)
    {
      Glib::ustring text;
      try
      {
        text = get_clipboard()->read_text_finish(result);
      }
      catch (const Glib::Error&)
      {
      }

      const bool bPasteIsPossible = text.find(example_format_custom) == 0;

      // Enable/Disable the Paste button appropriately:
      m_Button_Paste.set_sensitive(bPasteIsPossible);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      // Gio::Application::Flags::NON_UNIQUE because it shall be possible to run
      // several instances of this application simultaneously.
      auto app = Gtk::Application::create(
        "org.gtkmm.example", Gio::Application::Flags::NON_UNIQUE);

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 打印

在应用程序级开发，gtkmm的打印API提供了跨应用程序兼容的对话框，并允许使用开罗通用绘制API和Pango驱动的文本渲染。在此通用API实现中，使用了特定平台的后端和特定打印机的驱动。

### 打印操作

主要对象是分配给每个打印操作的Gtk::PrintOperation。要处理绘制页请连接到它的信号，或从其继承并覆盖默认的虚信号处理函数。PrintOperation将自动处理所有影响打印循环的设置。

#### 信号

PrintOperation::run()方法启动打印循环，在此期间会发出以下信号：

- `begin_print`：你必须处理该信号，因为这是你使用提供的Gtk::PrintContext创建和设置Pango::Layout的地方，以及对你的打印输出进行分页的地方。

- `paginate`：分页可能很慢，所以如果需要监视它，你可以调用PrintOperation::set_show_progress()方法并处理此信号。

- 对于每个要渲染的页面，都会发出以下信号：

  - `request_page_setup`：提供一个PrintContext、页码和Gtk::PageSetup。如果你需要逐页的修改页面设置，请处理此信号。

  - `draw_page`：你必须处理此信号，该信号提供一个PrintContext和页码。你应该使用PrintContext创建一个Cairo::Context，并将提供的页面绘制到其中。要渲染文本，请遍历你在`begin_print`处理程序中创建的Pango::Layout。

- `end_print`：它的处理程序是用于释放与PrintOperation相关的所有资源的安全场所。如果你通过继承PrintOperation创建了自定义类，则直接在自定义类的析构函数中进行此操作会更简单。

- `done`: This signal is emitted when printing is finished, meaning when the print data is spooled. Note that the provided `Gtk::PrintOperation::Result` may indicate that an error occurred. In any case you probably want to notify the user about the final status.

- `status_changed`: 直到打印作业完成，只要打印作业状态发生了更改就会发出此信号。假脱机后可以调用PrintOperation::set_track_print_status()监视作业状态。要查看状态请调用get_status()或get_status_string()。

Reference

### 页面设置

PrintOperation类有一个叫做set_default_page_setup()的方法，其用于选择默认的纸张尺寸、方向和页边距。要在你的应用程序中显示页面设置对话框，请使用Gtk::run_page_setup_dialog()方法，该方法返回一个Gtk::PageSetup对象，这个对象保存了所选的设置。使用这个对象更新PrintOperation并访问所选的Gtk::PaperSize、`Gtk::PageOrientation`和特定打印机的边距。

你应该保存所选的Gtk::PageSetup以便在下次打开页面设置对话框的时候使用。

例如：

    // Within a class that inherits from Gtk::Window and keeps m_refPageSetup
    // and m_refSettings as members...
    auto new_page_setup = Gtk::run_page_setup_dialog(*this, m_refPageSetup, m_refSettings);
    m_refPageSetup = new_page_setup;

Reference

在`draw_page`处理程序中，开罗坐标系会自动旋转到当前页的方向。它通常在打印机的页边距之内，不过你可以使用PrintOperation::set_use_full_page()方法来更改它。默认的度量单位是设备像素。要选择其他单位请使用PrintOperation::set_unit()方法。

### 渲染文本

文本渲染是使用Pango完成的。Pango::Layout对象应通过调用PrintContext::create_pango_layout()方法来创建。PrintContext对象还通过get_width()和get_height()方法提供了页面范围。页数可以通过调用PrintOperation::set_n_pages()进行设置。要在`on_draw_page`中渲染文本，请使用PrintContext::get_cairo_context()获取Cairo::Context并显示在请求的页码中出现的Pango::LayoutLine。

请看具体示例如何做到这点。

### 异步操作

在默认情况下，PrintOperation::run()在打印操作完成时返回。如果你需要运行非阻塞的打印操作，请调用PrintOperation::set_allow_async()。请注意，并非所有平台都支持set_allow_async()，但`done`信号仍然会被发出。

run()可能会返回`PrintOperation::Result::IN_PROGRESS`。要跟踪状态并处理结果或错误，你需要实现`done`和`status_changed`信号的信号处理函数。

例如：

    // in class ExampleWindow's method...
    auto op = PrintOperation::create();
    // ...set up op...
    op->signal_done().connect(sigc::bind(sigc::mem_fun(
      *this, &ExampleWindow::on_printoperation_done), op));
    // run the op

然后，检查错误并连接到`status_changed`信号，例如：

    void ExampleWindow::on_printoperation_done(Gtk::PrintOperation::Result result,
      const Glib::RefPtr<PrintOperation>& op)
    {
      if (result == Gtk::PrintOperation::Result::ERROR)
        //notify user
      else if (result == Gtk::PrintOperation::Result::APPLY)
        //Update PrintSettings with the ones used in this PrintOperation

      if (! op->is_finished())
        op->signal_status_changed().connect(sigc::bind(sigc::mem_fun(
          *this, &ExampleWindow::on_printoperation_status_changed), op));
    }

最后检查状态。例如：

    void ExampleWindow::on_printoperation_status_changed(const Glib::RefPtr<PrintOperation>& op)
    {
      if (op->is_finished())
        //the print job is finished
      else
        //get the status with get_status() or get_status_string()

      //update UI
    }

### 导出为PDF

"打印到文件"选项在打印对话框可用，不需要额外实现。但是有时候从代码直接生成PDF文件会很有用。例如：

    auto op = Gtk::PrintOperation::create();
    // ...set up op...
    op->set_export_filename("test.pdf");
    auto res = op->run(Gtk::PrintOperation::Action::EXPORT);

### 扩展打印对话框

你可以在打印对话框中添加自定义标签：

- 通过PrintOperation::set_custom_tab_label()设置标签的标题，创建一个新的部件然后从`create_custom_widget`的信号处理函数中返回它。你可能希望它是一个容器部件，在其中包含一些其他部件。

- 从部件的`custom_widget_apply`的信号处理函数中获取数据。

尽管`custom_widget_apply`信号提供了你之前创建的部件，但是为了简化操作，你可以保存一些用户输入作为部件类的成员。例如，你可能希望你有一个叫做`m_Entry`的Gtk::Entry拥有CustomPrintOperation作为类成员：

    Gtk::Widget* CustomPrintOperation::on_create_custom_widget()
    {
      set_custom_tab_label("My custom tab");

      auto hbox = new Gtk::Box(Gtk::Orientation::HORIZONTAL, 8);
      hbox->set_margin(6);

      auto label = Gtk::make_managed<Gtk::Label>("Enter some text: ");
      hbox->append(*label);

      hbox->append(m_Entry);

      return hbox;
    }

    void CustomPrintOperation::on_custom_widget_apply(Gtk::Widget* /* widget */)
    {
      auto user_input = m_Entry.get_text();
      //...
    }

examples/book/printing/advanced示例对此进行了演示。

### 预览

原生的GTK打印对话框有一个预览按钮，但是你也可以自己从应用程序开始预览：

    // in a class that inherits from Gtk::Window...
    auto op = PrintOperation::create();
    // ...set up op...
    op->run(Gtk::PrintOperation::Action::PREVIEW, *this);

On Unix, the default preview handler uses an external viewer program. On Windows, the native preview dialog will be shown. If necessary you may override this behavior and provide a custom preview dialog. See the example located in /examples/book/printing/advanced.

### 示例

#### 简单

以下示例演示了如何从用户界面打印一些输出。它显示了如何实现`on_begin_print`和`on_draw_page`，以及如何跟踪打印状态和更新打印设置。

打印 - 简单

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    // This file is part of the printing/simple and printing/advanced examples

    #include <gtkmm.h>

    class PrintFormOperation;

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
      virtual ~ExampleWindow();

    protected:

      void build_main_menu(const Glib::RefPtr<Gtk::Application>& app);

      void print_or_preview(Gtk::PrintOperation::Action print_action);

      //PrintOperation signal handlers.
      //We handle these so can get necessary information to update the UI or print settings.
      //Our derived PrintOperation class also overrides some default signal handlers.
      void on_printoperation_status_changed();
      void on_printoperation_done(Gtk::PrintOperation::Result result);

      //Action signal handlers:
      void on_menu_file_new();
      void on_menu_file_page_setup();
      void on_menu_file_print_preview();
      void on_menu_file_print();
      void on_menu_file_quit();

      //Printing-related objects:
      Glib::RefPtr<Gtk::PageSetup> m_refPageSetup;
      Glib::RefPtr<Gtk::PrintSettings> m_refSettings;
      Glib::RefPtr<PrintFormOperation> m_refPrintFormOperation;

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Grid m_Grid;

      Gtk::Label m_NameLabel;
      Gtk::Entry m_NameEntry;

      Gtk::Label m_SurnameLabel;
      Gtk::Entry m_SurnameEntry;

      Gtk::Label m_CommentsLabel;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;

      Gtk::Label m_Statusbar;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;

      Glib::RefPtr<Gtk::AlertDialog> m_refDialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `printformoperation.h` (For use with gtkmm 4)

    #ifndef GTKMM_PRINT_FORM_OPERATION_H
    #define GTKMM_PRINT_FORM_OPERATION_H

    #include <gtkmm.h>
    #include <pangomm.h>
    #include <vector>

    //We derive our own class from PrintOperation,
    //so we can put the actual print implementation here.
    class PrintFormOperation : public Gtk::PrintOperation
    {
     public:
      static Glib::RefPtr<PrintFormOperation> create();
      virtual ~PrintFormOperation();

      void set_name(const Glib::ustring& name) { m_Name = name; }
      void set_comments(const Glib::ustring& comments) { m_Comments = comments; }

     protected:
      PrintFormOperation();

      //PrintOperation default signal handler overrides:
      void on_begin_print(const Glib::RefPtr<Gtk::PrintContext>& context) override;
      void on_draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, int page_nr) override;

      Glib::ustring m_Name;
      Glib::ustring m_Comments;
      Glib::RefPtr<Pango::Layout> m_refLayout;
      std::vector<int> m_PageBreaks; // line numbers where a page break occurs
    };

    #endif // GTKMM_PRINT_FORM_OPERATION_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include "printformoperation.h"
    #include <iostream>

    ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_NameLabel("Name"),
      m_SurnameLabel("Surname"),
      m_CommentsLabel("Comments")
    {
      m_refPageSetup = Gtk::PageSetup::create();
      m_refSettings = Gtk::PrintSettings::create();

      set_title("gtkmm Printing Example");
      set_default_size(400, 300);

      set_child(m_VBox);

      build_main_menu(app);

      m_VBox.append(m_Grid);

      //Arrange the widgets inside the grid:
      m_Grid.set_expand(true);
      m_Grid.set_row_spacing(5);
      m_Grid.set_column_spacing(5);
      m_Grid.attach(m_NameLabel, 0, 0);
      m_Grid.attach(m_NameEntry, 1, 0);

      m_Grid.attach(m_SurnameLabel, 0, 1);
      m_Grid.attach(m_SurnameEntry, 1, 1);

      //Add the TextView, inside a ScrolledWindow:
      m_ScrolledWindow.set_child(m_TextView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);

      m_Grid.attach(m_CommentsLabel, 0, 2);
      m_Grid.attach(m_ScrolledWindow, 1, 2);
      m_ScrolledWindow.set_expand(true);

      m_refTextBuffer = Gtk::TextBuffer::create();
      m_TextView.set_buffer(m_refTextBuffer);

      m_Statusbar.set_hexpand(true);
      m_VBox.append(m_Statusbar);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::build_main_menu(const Glib::RefPtr<Gtk::Application>& app)
    {
      //Create actions for menus and toolbars:
      auto refActionGroup = Gio::SimpleActionGroup::create();

      //File menu:
      refActionGroup->add_action("new",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));

      refActionGroup->add_action("pagesetup",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_page_setup));

      refActionGroup->add_action("printpreview",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print_preview));

      refActionGroup->add_action("print",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print));

      refActionGroup->add_action("quit",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit));

      insert_action_group("example", refActionGroup);

      // When the menubar is a child of a Gtk::Window, keyboard accelerators are not
      // automatically fetched from the Gio::Menu.
      // See the examples/book/menus/main_menu example for an alternative way of
      // adding the menubar when using Gtk::ApplicationWindow.
      app->set_accel_for_action("example.new", "<Primary>n");
      app->set_accel_for_action("example.print", "<Primary>p");
      app->set_accel_for_action("example.quit", "<Primary>q");

      m_refBuilder = Gtk::Builder::create();

      // Layout the actions in a menubar:
      Glib::ustring ui_menu_info =
        "<interface>"
        "  <menu id='menu-example'>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_File</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_New</attribute>"
        "          <attribute name='action'>example.new</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Page _Setup...</attribute>"
        "          <attribute name='action'>example.pagesetup</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Print Preview</attribute>"
        "          <attribute name='action'>example.printpreview</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Print...</attribute>"
        "          <attribute name='action'>example.print</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Quit</attribute>"
        "          <attribute name='action'>example.quit</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "  </menu>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_menu_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building menus failed: " << ex.what();
      }

      // Layout the actions in a toolbar:
      Glib::ustring ui_toolbar_info =
        "<!-- Generated with glade 3.18.3 and then changed manually -->"
        "<interface>"
          "<object class='GtkBox' id='toolbar'>"
            "<property name='can_focus'>False</property>"
            "<property name='spacing'>3</property>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_new'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>New</property>"
                "<property name='action_name'>example.new</property>"
                "<property name='icon_name'>document-new</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_print'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>Print</property>"
                "<property name='action_name'>example.print</property>"
                "<property name='icon_name'>document-print</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkSeparator' id='separator1'>"
                "<property name='can_focus'>False</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_quit'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>Quit</property>"
                "<property name='action_name'>example.quit</property>"
                "<property name='icon_name'>application-exit</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
          "</object>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_toolbar_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building toolbar failed: " << ex.what();
      }

      // Get the menubar and add it to a container widget:
      auto object = m_refBuilder->get_object("menu-example");
      auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
      if (!gmenu)
        g_warning("GMenu not found");
      else
      {
        auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);

        // Add the PopoverMenuBar to the window:
        m_VBox.append(*pMenuBar);
      }

      // Get the toolbar and add it to a container widget:
      auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
      if (!toolbar)
        g_warning("toolbar not found");
      else
        m_VBox.append(*toolbar);
    }

    void ExampleWindow::on_printoperation_status_changed()
    {
      Glib::ustring status_msg;

      if (m_refPrintFormOperation->is_finished())
      {
        status_msg = "Print job completed.";
      }
      else
      {
        //You could also use get_status().
        status_msg = m_refPrintFormOperation->get_status_string();
      }

      m_Statusbar.set_text(status_msg);
    }

    void ExampleWindow::on_printoperation_done(Gtk::PrintOperation::Result result)
    {
      //Printing is "done" when the print data is spooled.

      if (result == Gtk::PrintOperation::Result::ERROR)
      {
        if (!m_refDialog)
          m_refDialog = Gtk::AlertDialog::create("Error printing form");
        m_refDialog->show(*this);
      }
      else if (result == Gtk::PrintOperation::Result::APPLY)
      {
        //Update PrintSettings with the ones used in this PrintOperation:
        m_refSettings = m_refPrintFormOperation->get_print_settings();
      }

      if (!m_refPrintFormOperation->is_finished())
      {
        //We will connect to the status-changed signal to track status
        //and update a status bar. In addition, you can, for example,
        //keep a list of active print operations, or provide a progress dialog.
        m_refPrintFormOperation->signal_status_changed().connect(sigc::mem_fun(*this,
                        &ExampleWindow::on_printoperation_status_changed));
      }
    }

    void ExampleWindow::print_or_preview(Gtk::PrintOperation::Action print_action)
    {
      //Create a new PrintOperation with our PageSetup and PrintSettings:
      //(We use our derived PrintOperation class)
      m_refPrintFormOperation = PrintFormOperation::create();

      m_refPrintFormOperation->set_name(m_NameEntry.get_text() + " " + m_SurnameEntry.get_text());
      m_refPrintFormOperation->set_comments(m_refTextBuffer->get_text(false /*Don't include hidden*/));
      // In the printing/advanced example, the font will be set through a custom tab
      // in the print dialog.

      m_refPrintFormOperation->set_track_print_status();
      m_refPrintFormOperation->set_default_page_setup(m_refPageSetup);
      m_refPrintFormOperation->set_print_settings(m_refSettings);

      m_refPrintFormOperation->signal_done().connect(sigc::mem_fun(*this,
                      &ExampleWindow::on_printoperation_done));
      try
      {
        m_refPrintFormOperation->run(print_action /* print or preview */, *this);
      }
      catch (const Gtk::PrintError& ex)
      {
        //See documentation for exact Gtk::PrintError error codes.
        std::cerr << "An error occurred while trying to run a print operation:"
            << ex.what() << std::endl;
      }
    }

    void ExampleWindow::on_menu_file_new()
    {
      //Clear entries and textview:
      m_NameEntry.set_text("");
      m_SurnameEntry.set_text("");
      m_refTextBuffer->set_text("");
      m_TextView.set_buffer(m_refTextBuffer);
    }

    void ExampleWindow::on_menu_file_page_setup()
    {
      //Show the page setup dialog, asking it to start with the existing settings:
      auto new_page_setup =
          Gtk::run_page_setup_dialog(*this, m_refPageSetup, m_refSettings);

      //Save the chosen page setup dialog for use when printing, previewing, or
      //showing the page setup dialog again:
      m_refPageSetup = new_page_setup;
    }

    void ExampleWindow::on_menu_file_print_preview()
    {
      print_or_preview(Gtk::PrintOperation::Action::PREVIEW);
    }

    void ExampleWindow::on_menu_file_print()
    {
      print_or_preview(Gtk::PrintOperation::Action::PRINT_DIALOG);
    }

    void ExampleWindow::on_menu_file_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv, app);
    }

File: `printformoperation.cc` (For use with gtkmm 4)

    #include "printformoperation.h"

    PrintFormOperation::PrintFormOperation()
    {
    }

    PrintFormOperation::~PrintFormOperation()
    {
    }

    Glib::RefPtr<PrintFormOperation> PrintFormOperation::create()
    {
      return Glib::make_refptr_for_instance<PrintFormOperation>(new PrintFormOperation());
    }

    void PrintFormOperation::on_begin_print(
            const Glib::RefPtr<Gtk::PrintContext>& print_context)
    {
      //Create and set up a Pango layout for PrintData based on the passed
      //PrintContext: We then use this to calculate the number of pages needed, and
      //the lines that are on each page.
      m_refLayout = print_context->create_pango_layout();

      Pango::FontDescription font_desc("sans 12");
      m_refLayout->set_font_description(font_desc);

      const double width = print_context->get_width();
      const double height = print_context->get_height();

      m_refLayout->set_width(static_cast<int>(width * Pango::SCALE));

      //Set and mark up the text to print:
      Glib::ustring marked_up_form_text;
      marked_up_form_text += "<b>Name</b>: " + m_Name + "\n\n";
      marked_up_form_text += "<b>Comments</b>: " + m_Comments;

      m_refLayout->set_markup(marked_up_form_text);

      //Set the number of pages to print by determining the line numbers
      //where page breaks occur:
      const int line_count = m_refLayout->get_line_count();

      Glib::RefPtr<Pango::LayoutLine> layout_line;
      double page_height = 0;

      for (int line = 0; line < line_count; ++line)
      {
        Pango::Rectangle ink_rect, logical_rect;

        layout_line = m_refLayout->get_line(line);
        layout_line->get_extents(ink_rect, logical_rect);

        const double line_height = logical_rect.get_height() / 1024.0;

        if (page_height + line_height > height)
        {
          m_PageBreaks.push_back(line);
          page_height = 0;
        }

        page_height += line_height;
      }

      set_n_pages(m_PageBreaks.size() + 1);
    }

    void PrintFormOperation::on_draw_page(
            const Glib::RefPtr<Gtk::PrintContext>& print_context, int page_nr)
    {
      //Decide which lines we need to print in order to print the specified page:
      int start_page_line = 0;
      int end_page_line = 0;

      if(page_nr == 0)
      {
        start_page_line = 0;
      }
      else
      {
        start_page_line = m_PageBreaks[page_nr - 1];
      }

      if(page_nr < static_cast<int>(m_PageBreaks.size()))
      {
        end_page_line = m_PageBreaks[page_nr];
      }
      else
      {
        end_page_line = m_refLayout->get_line_count();
      }

      //Get a Cairo Context, which is used as a drawing board:
      Cairo::RefPtr<Cairo::Context> cairo_ctx = print_context->get_cairo_context();

      //We'll use black letters:
      cairo_ctx->set_source_rgb(0, 0, 0);

      //Render Pango LayoutLines over the Cairo context:
      auto iter = m_refLayout->get_iter();

      double start_pos = 0;
      int line_index = 0;

      do
      {
        if(line_index >= start_page_line)
        {
          auto layout_line = iter.get_line();
          auto logical_rect = iter.get_line_logical_extents();
          int baseline = iter.get_baseline();

          if (line_index == start_page_line)
          {
            start_pos = logical_rect.get_y() / 1024.0;
          }

          cairo_ctx->move_to(logical_rect.get_x() / 1024.0,
            baseline / 1024.0 - start_pos);

          layout_line->show_in_cairo_context(cairo_ctx);
        }

        line_index++;
      }
      while(line_index < end_page_line && iter.next_line());
    }

## 最近使用过的文件

gtkmm提供了一种管理最近使用文件的简便方法。该功能在Gtk::RecentManager类中实现。

最近使用文件列表中的每一项都由其URI进行标识，并可以具有相关的元数据。元数据可以用于指定如何显示文件、如何描述文件、及其它的MIME类型、哪个应用程序注册了它、对于注册的应用程序是不是私有的等。

### 最近管理器(RecentManager)

RecentManager充当最近使用文件的数据库。你可以使用此类注册新文件、从列表中删除文件、查找最近使用文件。每个用户都拥有一个最近使用的文件列表。

你可以创建一个新的RecentManager，但你可能只想用默认的那个。你可以通过get_default()获取默认RecentManager的引用。

#### 将项目添加到最近文件列表

在最简单的情况下，要添加项目到最近文件列表只需要提供一个URI即可。例如：

    auto recent_manager = Gtk::RecentManager::get_default();
    recent_manager->add_item(uri);

如果你想要使用元数据注册一个文件，你可以将RecentManager::Data参数传递给add_item()。可以在特定的文件项上设置的元数据如下所示：

- `app_exec`：用于启动此资源的命令行。此字符串可以包含"f"和"u"转义字符，它们将分别扩展为资源文件路径和URI

- `app_name`：此资源注册的应用程序名

- `description`：资源的简单描述（UTF-8编码字符串）

- `display_name`：用于显示的资源名（UTF-8编码字符串）

- `groups`：与该项相关联的组列表。组本质上是与特定资源相关联的任意字符串。可以将它们视为资源的类别（例如：电子邮件、图形等）或标签

- `is_private`：此资源是否仅对已注册该资源的应用程序可见

- `mime_type`：此资源的MIME类型

除了向列表中添加项目以外，你还可以从列表项中删除、修改、查找项目。

#### 在最近文件列表中查找项目

RecentManager提供了几个函数用以查找最近使用过的文件。你可以使用lookup_item()函数通过URI来查找特定项目，该函数将返回一个RecentInfo类对象。如果指定的URI于最近文件列表中不存在，则lookup_item()将抛出一个RecentManagerError异常。例如：

    Glib::RefPtr<Gtk::RecentInfo> info;
    try
    {
      info = recent_manager->lookup_item(uri);
    }
    catch(const Gtk::RecentManagerError& ex)
    {
      std::cerr << "RecentManagerError: " << ex.what() << std::endl;
    }
    if (info)
    {
      // item was found
    }

本质上，RecentInfo是个包含了一个最近使用文件的所有元数据的对象。你可以使用该对象查找这里所列出的任何属性。

如果你不想要查找一个特定的URI，而是想获取整个最近使用的项目列表，RecentManager为此提供了get_items()函数。这个函数返回值是一个std::vector，其包含了所有最近使用的文件。以下代码演示了你该如何获取最近使用文件列表：

    auto info_list = recent_manager->get_items();

可以使用Gtk::Settings::property_gtk_recent_files_max_age()设置最近使用文件列表中项目的最长使用期限。默认时长是：30天。

#### 修改最近使用文件列表

有时候你会需要修改最近使用文件列表。例如，当一个文件被移动或重命名，你需要更新该文件在最近使用文件列表中的位置，以使其不会指向错误的位置。你可以使用move_item()来更新项目的位置。

除了更改文件的URI以外，你还可以从列表中删除项目或是一次性清除所有项目。前者使用remove_item()，后者使用purge_items()。

move_item()、remove_item()和purge_items()并不影响URI所引用的真实文件，它们只是对最近文件列表进行修改。

### FileChooser and FileDialog

FileChooser是一个可以由用于显示文件列表的部件实现的接口。gtkmm提供了三种用于选择最近文件或其他文件的内置实现：FileChooserWidget、FileChooserDialog、FileChooserNative。

FileChooserWidget是一个用于显示最近使用文件列表或其他文件的简单部件。FileChooserWidget是FileChooserDialog的基本构建块，但是你根据需要可以将其嵌入到用户界面中。

FileChooser and the classes that implement it are deprecated since gtkmm 4.10. They have been replaced by FileDialog, which is available since gtkmm 4.10.

#### Simple FileDialog example

Shown below is a simple example of how to use the FileDialog class in a program. This simple program has a menubar with a File Dialog menu item. When you select this menu item, a dialog pops up showing a list of files. If you select Recent in the sidebar, the list of recently used files is shown.

如果这是你第一次使用使用了最近文件框架的应用程序，那么对话框最初可能是空的。否则它应该显示其他应用程序注册的最近使用文件列表。

After selecting the File Dialog menu item and the Recent sidebar item, you should see something similar to the following window.

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_menu_file_files_dialog();
      void on_menu_file_quit();
      void on_menu_file_new();
      void on_dialog_finish(Glib::RefPtr<Gio::AsyncResult>& result);

      //Child widgets:
      Gtk::Box m_Box;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::SimpleActionGroup> m_refActionGroup;

      Glib::RefPtr<Gtk::RecentManager> m_refRecentManager;
      Glib::RefPtr<Gtk::FileDialog> m_refFileDialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
    : m_Box(Gtk::Orientation::VERTICAL),
      m_refRecentManager(Gtk::RecentManager::get_default())
    {
      set_title("Recent files example");
      set_default_size(300, 150);

      //We can put a PopoverMenuBar at the top of the box and other stuff below it.
      set_child(m_Box);

      //Create actions for menus and toolbars:
      m_refActionGroup = Gio::SimpleActionGroup::create();

      //File menu:
      m_refActionGroup->add_action("new",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));

      //A menu item to open the file dialog:
      m_refActionGroup->add_action("files-dialog",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_files_dialog));

      m_refActionGroup->add_action("quit",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit) );

      insert_action_group("example", m_refActionGroup);

      m_refBuilder = Gtk::Builder::create();

      // When the menubar is a child of a Gtk::Window, keyboard accelerators are not
      // automatically fetched from the Gio::Menu.
      // See the examples/book/menus/main_menu example for an alternative way of
      // adding the menubar when using Gtk::ApplicationWindow.
      app->set_accel_for_action("example.new", "<Primary>n");
      app->set_accel_for_action("example.files-dialog", "<Primary>o");
      app->set_accel_for_action("example.quit", "<Primary>q");

      //Layout the actions in a menubar and a toolbar:
      const char* ui_info =
        "<interface>"
        "  <menu id='menubar'>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_File</attribute>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_New</attribute>"
        "        <attribute name='action'>example.new</attribute>"
        "        <attribute name='accel'>&lt;Primary&gt;n</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>File _Dialog</attribute>"
        "        <attribute name='action'>example.files-dialog</attribute>"
        "        <attribute name='accel'>&lt;Primary&gt;o</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Quit</attribute>"
        "        <attribute name='action'>example.quit</attribute>"
        "        <attribute name='accel'>&lt;Primary&gt;q</attribute>"
        "      </item>"
        "    </submenu>"
        "  </menu>"
        "  <object class='GtkBox' id='toolbar'>"
        "    <property name='can_focus'>False</property>"
        "    <child>"
        "      <object class='GtkButton' id='toolbutton_new'>"
        "        <property name='can_focus'>False</property>"
        "        <property name='tooltip_text' translatable='yes'>New</property>"
        "        <property name='action_name'>example.new</property>"
        "        <property name='icon_name'>document-new</property>"
        "        <property name='hexpand'>False</property>"
        "        <property name='vexpand'>False</property>"
        "      </object>"
        "    </child>"
        "    <child>"
        "      <object class='GtkButton' id='toolbutton_quit'>"
        "        <property name='can_focus'>False</property>"
        "        <property name='tooltip_text' translatable='yes'>Quit</property>"
        "        <property name='action_name'>example.quit</property>"
        "        <property name='icon_name'>application-exit</property>"
        "        <property name='hexpand'>False</property>"
        "        <property name='vexpand'>False</property>"
        "      </object>"
        "    </child>"
        "  </object>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building menubar and toolbar failed: " <<  ex.what();
      }

      //Get the menubar and toolbar widgets, and add them to a container widget:
      auto object = m_refBuilder->get_object("menubar");
      auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
      if (gmenu)
      {
        //Menubar:
        auto pMenubar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
        m_Box.append(*pMenubar);
      }
      else
        g_warning("GMenu not found");

      auto pToolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
      if (pToolbar)
        //Toolbar:
        m_Box.append(*pToolbar);
      else
        g_warning("toolbar not found");
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_menu_file_new()
    {
      std::cout << " New File" << std::endl;
    }

    void ExampleWindow::on_menu_file_quit()
    {
      set_visible(false); //Closes the main window to stop the app->make_window_and_run().
    }

    void ExampleWindow::on_menu_file_files_dialog()
    {
      if (!m_refFileDialog)
      {
        m_refFileDialog = Gtk::FileDialog::create();
        m_refFileDialog->set_modal(true);
      }
      m_refFileDialog->open(*this, sigc::mem_fun(*this, &ExampleWindow::on_dialog_finish));
    }

    void ExampleWindow::on_dialog_finish(Glib::RefPtr<Gio::AsyncResult>& result)
    {
      Glib::RefPtr<Gio::File> file;
      try
      {
        file = m_refFileDialog->open_finish(result);
      }
      catch (const Gtk::DialogError& err)
      {
        std::cout << "No file selected, " << err.what() << std::endl;
        return;
      }

      auto selected_uri = file->get_uri();
      std::cout << "URI selected = " << selected_uri << std::endl;
      std::cout << (m_refRecentManager->has_item(selected_uri) ? "A" : "Not a")
        << " recently used file" << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv, app);
    }

ExampleWindow的构造函数使用Builder创建菜单和工具栏（更多有关信息请参阅）。然后将菜单和工具栏添加到窗口中。

#### 筛选文件

对于任意FileChooser类，如果你不想显示文件列表中的所有项目，你可以筛选列表以仅显示所需的项目。你可以使用FileFilter帮助你对列表进行筛选。这个类允许你按文件名（add_pattern()）或MIME类型（add_mime_type()）筛选文件。

创建并设置筛选器以使其只匹配你所需项目之后，你可以使用FileChooser::add_filter()函数将筛选器应用于你所选的部件。

## 键盘事件

Event signals differ in some ways from other signals. These differences are described in the Event signals section in the appendix. Here we will use keyboard events to show how events can be used in a program.

### 总览

Whenever you press or release a key, an event is emitted. You can add an event controller and connect a signal handler to handle such events.

The event signal handler will receive arguments that depend on the type of event. For key press events the arguments are (guint `keyval`, guint `keycode`, Gdk::ModifierType `state`). As described in the appendix, the key press event signal handler returns a bool value, to indicate that the signal is fully handled (`true`) or allow event propagation (`false`).

To determine which key was pressed or released, you read the value of the `keyval` argument and compare it with a constant in the `<gdk/gdkkeysyms.h>` header file. The states of modifier keys (shift, ctrl, etc.) are available as bit-flags in `state`.

这是一个简单的例子：

    bool MyClass::on_key_pressed(guint keyval, guint, Gdk::ModifierType state)
    {
      if (keyval == GDK_KEY_1 &&
        (state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK |
         Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
      {
        handle_alt_press();
        return true;
      }
      return false;
    }

    // in MyClass constructor
    auto controller = Gtk::EventControllerKey::create();
    controller->signal_key_pressed().connect(
      sigc::mem_fun(*this, &MyClass::on_key_pressed), false);
    add_controller(controller);

#### 示例

In this example there are three keyboard shortcuts: Alt+1 selects the first radio button, Alt+2 selects the second one, and the Esc key hides (closes) the window.

键盘事件 - 简单

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:

      ExampleWindow();
      virtual ~ExampleWindow();

    private:
      // Signal handler:
      bool on_window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);

      Gtk::Box m_container;
      Gtk::CheckButton m_first;
      Gtk::CheckButton m_second;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    {
      set_title("Keyboard Events");
      m_container.set_margin(10);
      set_child(m_container);

      // Radio buttons:
      m_first.set_label("First");
      m_second.set_label("Second");

      m_second.set_group(m_first);
      m_first.set_active();

      // Main Container:
      m_container.set_orientation(Gtk::Orientation::HORIZONTAL);
      m_container.append(m_first);
      m_container.append(m_second);

      // Events.
      auto controller = Gtk::EventControllerKey::create();
      controller->signal_key_pressed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_window_key_pressed), false);
      add_controller(controller);
    }

    bool ExampleWindow::on_window_key_pressed(guint keyval, guint, Gdk::ModifierType state)
    {
      //Gdk::ModifierType::ALT_MASK -> the 'Alt' key(mask)
      //GDK_KEY_1 -> the '1' key
      //GDK_KEY_2 -> the '2' key

      //select the first radio button, when we press alt + 1
      if((keyval == GDK_KEY_1) &&
        (state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
      {
        m_first.set_active();
        //returning true, cancels the propagation of the event
        return true;
      }
      else if((keyval == GDK_KEY_2) &&
        (state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
      {
        //and the second radio button, when we press alt + 2
        m_second.set_active();
        return true;
      }
      else if(keyval == GDK_KEY_Escape)
      {
        //close the window, when the 'esc' key is pressed
        set_visible(false);
        return true;
      }

      //the event has not been handled
      return false;
    }

    ExampleWindow::~ExampleWindow()
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

### 事件传播

As described in the appendix event signals are propagated in 3 phases:

1. Capture phase - runs from the toplevel down to the event widget.

2. Target phase - runs only on the event widget.

3. Bubble phase - runs from the event widget up to the toplevel.

A keyboard event is first sent to the toplevel window (Gtk::Window), where it will be checked for any keyboard shortcuts that may be set (accelerator keys and mnemonics, used for selecting menu items from the keyboard). After this (and assuming the event wasn't handled), it is propagated down until it reaches the widget which has keyboard focus. The event will then propagate up until it reaches the top-level widget, or until you stop the propagation by returning `true` from an event handler.

请注意，取消事件之后，不再会调用其他函数（哪怕它来自同一个部件）。

#### 示例

In this example there are 9 EventControllerKeys, 3 in each of a Gtk::Window, a Gtk::Box and a Gtk::Label. In each of the widgets there is one event controller for each propagation phase.

本示例的目的是显示事件发出时进行的步骤。

When you write in the label, a key press event will be emitted, which will go first, in the capture phase, to the toplevel window (Gtk::Window), then down to its child, the box, then to the box's child, the label with the keyboard focus. In the target phase the event goes only to the widget with the keyboard focus (the label). In the bubble phase the event goes first to the widget with the keyboard focus (the label), then to its parent (the box), then to the box's parent (the window). If the event propagates all the way down to the label and then up to the window without being stopped, the text you're writing will appear in the Label above the Label you're writing in.

键盘事件 - 事件传播

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EVENT_PROPAGATION_H
    #define GTKMM_EVENT_PROPAGATION_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:

      ExampleWindow();
      ~ExampleWindow() override;

    private:
      // Signal handlers:
      bool label2_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
      bool box_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
      bool window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);

      bool m_first = true;
      Gtk::Box m_container;
      Gtk::Frame m_frame;
      Gtk::Label m_label1 {"A label"};
      Gtk::Label m_label2 {"Write here"};
      Gtk::CheckButton m_checkbutton_can_propagate_down {"Can propagate down in the capture phase"};
      Gtk::CheckButton m_checkbutton_can_propagate_up {"Can propagate up in the bubble phase"};
    };

    #endif //GTKMM_EVENT_PROPAGATION_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow()
    {
      set_title("Event Propagation");
      m_container.set_margin(10);
      set_child(m_container);

      m_frame.set_child(m_label2);
      m_label2.set_selectable();
      m_checkbutton_can_propagate_down.set_active();
      m_checkbutton_can_propagate_up.set_active();

      // Main container
      m_container.set_orientation(Gtk::Orientation::VERTICAL);
      m_container.append(m_label1);
      m_container.append(m_frame);
      m_container.append(m_checkbutton_can_propagate_down);
      m_container.append(m_checkbutton_can_propagate_up);

      // Event controllers
      const bool after = false; // Run before or after the default signal handlers.

      // Called in the capture phase of the event handling.
      auto controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "capture"), after);
      m_label2.add_controller(controller);

      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "capture"), after);
      m_container.add_controller(controller);

      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "capture"), after);
      add_controller(controller);

      // Called in the target phase of the event handling.
      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "target"), after);
      m_label2.add_controller(controller);

      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "target"), after);
      m_container.add_controller(controller);

      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "target"), after);
      add_controller(controller);

      // Called in the bubble phase of the event handling.
      // This is the default, if set_propagation_phase() is not called.
      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "bubble"), after);
      m_label2.add_controller(controller);

      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "bubble"), after);
      m_container.add_controller(controller);

      controller = Gtk::EventControllerKey::create();
      controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
      controller->signal_key_pressed().connect(
        sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "bubble"), after);
      add_controller(controller);
    }

    // By changing the return value we allow, or don't allow, the event to propagate to other elements.
    bool ExampleWindow::label2_key_pressed(guint keyval, guint, Gdk::ModifierType, const Glib::ustring& phase)
    {
      std::cout << "Label,  " << phase << " phase" << std::endl;

      if (phase == "bubble")
      {
        const gunichar unichar = gdk_keyval_to_unicode(keyval);
        if (unichar != 0)
        {
          if (m_first)
          {
            m_label2.set_label("");
            m_first = false;
          }
          if (unichar == '\b')
            m_label2.set_label("");
          else
          {
            const Glib::ustring newchar(1, unichar);
            m_label2.set_label(m_label2.get_label() + newchar);
          }
        }

        if (!m_checkbutton_can_propagate_up.get_active())
          return true; // Don't propagate
      }
      return false;
    }

    bool ExampleWindow::box_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
    {
      std::cout << "Box,    " << phase << " phase" << std::endl;

      // Let it propagate
      return false;
    }

    // This will set the second label's text in the first label every time a key is pressed.
    bool ExampleWindow::window_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
    {
      if (phase == "capture")
        std::cout << std::endl;
      std::cout << "Window, " << phase << " phase";

      // Checking if the second label is on focus, otherwise the label would get
      // changed by pressing keys on the window (when the label is not on focus),
      // even if m_checkbutton_can_propagate_up wasn't active.
      if (phase == "bubble" && m_label2.has_focus())
      {
        m_label1.set_label(m_label2.get_label());
        std::cout << ", " << m_label2.get_label();
      }
      std::cout << std::endl;

      if (phase == "capture" && !m_checkbutton_can_propagate_down.get_active())
        return true; // Don't propagate
      return false;
    }

    ExampleWindow::~ExampleWindow()
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 超时，I/O和空闲函数

### 超时

你可能想知道如何使gtkmm在空闲的时候做一些有用的工作。幸运的是你有几种选择。你可以创建一个每几毫秒就调用的超时方法。

    sigc::connection Glib::SignalTimeout::connect(const sigc::slot<bool()>& slot,
                                          unsigned int interval, int priority = Glib::PRIORITY_DEFAULT);

第一个参数是超时时你希望被调用的slot。第二个参数是调用该方法的时间间隔（多少毫秒）。你将收到一个sigc::connection对象，可以在该对象上调用disconnect()方法断开连接。

    my_connection.disconnect();

破坏连接的另一种方法是通过你的信号处理函数的返回值。其类型必须是sigc::slot<bool()>。从定义可以看出信号处理函数必须返回一个`bool`类型的值。示例方法的定义可能如下所示：

    bool MyCallback() { std::cout << "Hello World!\n" << std::endl; return true; }

你可以通过让你的信号处理函数返回`false`以停止此超时方法。因此，如果你还想重复调用此方法，则你需要让信号处理函数返回`true`。

这是此技术的示例：

Source Code

File: `timerexample.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_TIMEREXAMPLE_H
    #define GTKMM_EXAMPLE_TIMEREXAMPLE_H

    #include <gtkmm.h>
    #include <iostream>
    #include <map>

    class TimerExample : public Gtk::Window
    {
    public:
      TimerExample();

    protected:
      // signal handlers
      void on_button_add_timer();
      void on_button_delete_timer();
      void on_button_quit();

      // This is the callback function the timeout will call
      bool on_timeout(int timer_number);

      // Member data:

      Gtk::Box m_Box;
      Gtk::Button m_ButtonAddTimer, m_ButtonDeleteTimer, m_ButtonQuit;

      // Keep track of the timers being added:
      int m_timer_number;

      // These two constants are initialized in the constructor's member initializer:
      const int count_value;
      const int timeout_value;

      // STL map for storing our connections
      std::map<int, sigc::connection> m_timers;

      // STL map for storing our timer values.
      // Each timer counts back from COUNT_VALUE to 0 and is removed when it reaches 0
      std::map<int, int> m_counters;
    };

    #endif // GTKMM_EXAMPLE_TIMEREXAMPLE_H

File: `main.cc` (For use with gtkmm 4)

    #include "timerexample.h"
    #include <gtkmm/application.h>

    int main (int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<TimerExample>(argc, argv);
    }

File: `timerexample.cc` (For use with gtkmm 4)

    #include "timerexample.h"

    TimerExample::TimerExample() :
      m_Box(Gtk::Orientation::HORIZONTAL, 10),
      m_ButtonAddTimer("_Add", true),
      m_ButtonDeleteTimer("_Remove", true),
      m_ButtonQuit("_Quit", true),
      m_timer_number(0), // start numbering the timers at 0
      count_value(5), // each timer will count down 5 times before disconnecting
      timeout_value(1500) // 1500 ms = 1.5 seconds
    {
      m_Box.set_margin(10);
      set_child(m_Box);
      m_Box.append(m_ButtonAddTimer);
      m_Box.append(m_ButtonDeleteTimer);
      m_Box.append(m_ButtonQuit);
      m_ButtonAddTimer.set_expand();
      m_ButtonDeleteTimer.set_expand();
      m_ButtonQuit.set_expand();

      // Connect the three buttons:
      m_ButtonQuit.signal_clicked().connect(sigc::mem_fun(*this,
                  &TimerExample::on_button_quit));
      m_ButtonAddTimer.signal_clicked().connect(sigc::mem_fun(*this,
                  &TimerExample::on_button_add_timer));
      m_ButtonDeleteTimer.signal_clicked().connect(sigc::mem_fun(*this,
                  &TimerExample::on_button_delete_timer));
    }

    void TimerExample::on_button_quit()
    {
      set_visible(false);
    }

    void TimerExample::on_button_add_timer()
    {
      // Creation of a new object prevents long lines and shows us a little
      // how slots work.  We have 0 parameters and bool as a return value
      // after calling sigc::bind.
      sigc::slot<bool()> my_slot = sigc::bind(sigc::mem_fun(*this,
                  &TimerExample::on_timeout), m_timer_number);

      // This is where we connect the slot to the Glib::signal_timeout()
      auto conn = Glib::signal_timeout().connect(my_slot,
              timeout_value);

      // Remember the connection:
      m_timers[m_timer_number] = conn;

      // Initialize timer count:
      m_counters[m_timer_number] = count_value + 1;

      // Print some info to the console for the user:
      std::cout << "added timeout " << m_timer_number++ << std::endl;
    }

    void TimerExample::on_button_delete_timer()
    {
      // any timers?
      if(m_timers.empty())
      {
        // no timers left
        std::cout << "Sorry, there are no timers left." << std::endl;
      }
      else
      {
        // get the number of the first timer
        int timer_number = m_timers.begin()->first;

        // Give some info to the user:
        std::cout << "manually disconnecting timer " << timer_number
            << std::endl;

        // Remove the entry in the counter values
        m_counters.erase(timer_number);

        // Diconnect the signal handler:
        m_timers[timer_number].disconnect();

        // Forget the connection:
        m_timers.erase(timer_number);
      }
    }

    bool TimerExample::on_timeout(int timer_number)
    {
      // Print the timer:
      std::cout << "This is timer " << timer_number;

      // decrement and check counter value
      if (--m_counters[timer_number] == 0)
      {
        std::cout << " being disconnected" <<  std::endl;

        // delete the counter entry in the STL MAP
        m_counters.erase(timer_number);

        // delete the connection entry in the STL MAP
        m_timers.erase(timer_number);

        // Note that we do not have to explicitly call disconnect() on the
        // connection since Gtk::Main does this for us when we return false.
        return false;
      }

      // Print the timer value
      std::cout << " - " << m_counters[timer_number] << "/"
          << count_value << std::endl;

     // Keep going (do not disconnect yet):
      return true;
    }

### 监视I/O

Glib（gtkmm的底层库之一）有一个很好的特性是它具有帮你检查文件描述符中的数据的能力。这对于需要联网的应用程序十分的有用。使用以下方法来执行此操作：

    sigc::connection Glib::SignalIO::connect(const sigc::slot<bool(Glib::IOCondition)>& slot,
                                     Glib::PollFD::fd_t fd, Glib::IOCondition condition,
                                     int priority = Glib::PRIORITY_DEFAULT);

第一个参数是你希望在第二个参数指定的文件描述符上发生指定事件（见第三个参数的描述）时被调用的槽。第三个参数可能是一下的一项或多项（若想是多项则对以下多个使用`|`，例如：`Glib::IO_IN | Glib::IO_OUT | Glib::IO_PRI`）：

- Glib::IOCondition::IO_IN - Call your method when there is data ready for reading on your file descriptor.

- Glib::IOCondition::IO_OUT - Call your method when the file descriptor is ready for writing.

- Glib::IOCondition::IO_PRI - Call your method when the file descriptor has urgent data to be read.

- Glib::IOCondition::IO_ERR - Call your method when an error has occurred on the file descriptor.

- Glib::IOCondition::IO_HUP - Call your method when hung up (the connection has been broken usually for pipes and sockets).

返回值是一个sigc::connection对象，你可以在其上调用disconnect()方法以停止对该文件描述符的监视。信号处理函数slot应声明如下：

    bool input_callback(Glib::IOCondition condition);

where condition is as specified above. As usual the slot is created with `sigc::mem_fun()` (for a member method of an object), or `sigc::ptr_fun()` (for a function). A lambda expression can be used, if you don't need the automatic disconnection that `sigc::mem_fun()` provides when the object goes out of scope.

下面是个示例。要使用该示例，只需要在终端上执行它即可；它不会创建一个窗口。它将在当前目录创建一个名为`testfifo`的具名管道。然后启动另一个shell并执行`echo "Hello" > testfifo`。该示例将打印你输入的每一行直到你执行`echo "Q" > testfifo`

Source Code

File: `main.cc` (For use with gtkmm 4)

    #include <gtkmm/application.h>
    #include <glibmm/main.h>
    #include <glibmm/iochannel.h>
    #include <fcntl.h>
    #include <iostream>

    #include <unistd.h> //The SUN Forte compiler puts F_OK here.

    //The SUN Forte compiler needs these for mkfifo:
    #include <sys/types.h>
    #include <sys/stat.h>

    Glib::RefPtr<Gtk::Application> app;

    int read_fd;
    Glib::RefPtr<Glib::IOChannel> iochannel;

    /*
      send to the fifo with:
      echo "Hello" > testfifo

      quit the program with:
      echo "Q" > testfifo
    */

    // this will be our signal handler for read operations
    // it will print out the message sent to the fifo
    // and quit the program if the message was 'Q'.
    bool MyCallback(Glib::IOCondition io_condition)
    {
      if ((io_condition & Glib::IOCondition::IO_IN) != Glib::IOCondition::IO_IN) {
        std::cerr << "Invalid fifo response" << std::endl;
      }
      else {
       Glib::ustring buf;

       iochannel->read_line(buf);
       std::cout << buf;
       if (buf == "Q\n")
         app->quit();

      }
      return true;
    }

    int main(int argc, char *argv[])
    {
      app = Gtk::Application::create("org.gtkmm.example");

      if (access("testfifo", F_OK) == -1) {
        // fifo doesn't exist - create it
        #ifndef DONT_HAVE_MKFIFO
        if (mkfifo("testfifo", 0666) != 0) {
          std::cerr << "error creating fifo" << std::endl;
          return -1;
        }
        #else
          std::cerr << "error creating fifo: This platform does not have mkfifo()"
              << std::endl;
        #endif //DONT_HAVE_MKFIFO
      }

      // Although we will only read from the fifo, we open it in read/write mode.
      // Due to a peculiarity with the poll() system call, used deep down in glib,
      // this small program will use all available CPU time, if the fifo is opened
      // as O_RDONLY. See a discussion on the gtkmm-list, e.g.
      // https://mail.gnome.org/archives/gtkmm-list/2015-September/msg00034.html
      // and the link from there to stackoverflow.
      read_fd = open("testfifo", O_RDWR);
      if (read_fd == -1)
      {
        std::cerr << "error opening fifo" << std::endl;
        return -1;
      }

      // connect the signal handler
      Glib::signal_io().connect(sigc::ptr_fun(MyCallback), read_fd, Glib::IOCondition::IO_IN);

      // Creates a iochannel from the file descriptor
      iochannel = Glib::IOChannel::create_from_fd(read_fd);

      // and last but not least - run the application main loop
      app->hold(); // keep the application running without a window
      app->run(argc, argv);

      // now remove the temporary fifo
      if(unlink("testfifo"))
        std::cerr << "error removing fifo" << std::endl;

      return 0;
    }

### 空闲函数

如果你想要指定一个在无事发生时调用的方法，请使用以下函数：

    sigc::connection  Glib::SignalIdle::connect(const sigc::slot<bool()>& slot,
                                        int priority = Glib::PRIORITY_DEFAULT_IDLE);

这会导致gtkmm在无事发生时调用指定的方法。你可以为其添加优先级（数字越小优先级越高）。删除该信号处理程序的方法有两个：在sigc::connection对象上调用disconnect()、在信号处理程序中返回`false`，信号处理程序的声明应如下所示：

    bool idleFunc();

这与前一小节的方法极其类似，所以在此不再进行过多的解释。以下是个小例子：

Source Code

File: `idleexample.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_IDLEEXAMPLE_H
    #define GTKMM_EXAMPLE_IDLEEXAMPLE_H

    #include <gtkmm.h>
    #include <iostream>

    class IdleExample : public Gtk::Window
    {
    public:
      IdleExample();

    protected:
      // Signal Handlers:
      bool on_timer();
      bool on_idle();
      void on_button_clicked();

      // Member data:
      Gtk::Box m_Box;
      Gtk::Button m_ButtonQuit;
      Gtk::ProgressBar m_ProgressBar_c;
      Gtk::ProgressBar m_ProgressBar_d;
    };

    #endif // GTKMM_EXAMPLE_IDLEEXAMPLE_H

File: `idleexample.cc` (For use with gtkmm 4)

    #include "idleexample.h"

    IdleExample::IdleExample() :
      m_Box(Gtk::Orientation::VERTICAL, 5),
      m_ButtonQuit("_Quit", true)
    {
      m_Box.set_margin(5);

      // Put buttons into container

      // Adding a few widgets:
      set_child(m_Box);
      m_Box.append(*Gtk::make_managed<Gtk::Label>("Formatting Windows drive C:"));
      m_Box.append(*Gtk::make_managed<Gtk::Label>("100 MB"));
      m_Box.append(m_ProgressBar_c);
      m_ProgressBar_c.set_expand();

      m_Box.append(*Gtk::make_managed<Gtk::Label>(""));

      m_Box.append(*Gtk::make_managed<Gtk::Label>("Formatting Windows drive D:"));
      m_Box.append(*Gtk::make_managed<Gtk::Label>("5000 MB"));
      m_Box.append(m_ProgressBar_d);
      m_ProgressBar_d.set_expand();

      auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,10);
      m_Box.append(*hbox);
      hbox->append(m_ButtonQuit);
      m_ButtonQuit.set_expand();
      m_ButtonQuit.set_halign(Gtk::Align::END);
      m_ButtonQuit.set_valign(Gtk::Align::END);

      // Connect the signal handlers:
      m_ButtonQuit.signal_clicked().connect( sigc::mem_fun(*this,
                  &IdleExample::on_button_clicked) );

      // formatting drive c in timeout signal handler - called once every 50ms
      Glib::signal_timeout().connect( sigc::mem_fun(*this, &IdleExample::on_timer),
              50 );

      // formatting drive d in idle signal handler - called as quickly as possible
      Glib::signal_idle().connect( sigc::mem_fun(*this, &IdleExample::on_idle) );
    }

    void IdleExample::on_button_clicked()
    {
      set_visible(false);
    }

    // this timer callback function is executed once every 50ms (set in connection
    // above).  Use timeouts when speed is not critical. (ie periodically updating
    // something).
    bool IdleExample::on_timer()
    {
      double value = m_ProgressBar_c.get_fraction();

      // Update progressbar 1/500th each time:
      m_ProgressBar_c.set_fraction(value + 0.002);

      return value < 0.99;  // return false when done
    }

    // This idle callback function is executed as often as possible, hence it is
    // ideal for processing intensive tasks.
    bool IdleExample::on_idle()
    {
      double value = m_ProgressBar_d.get_fraction();

      // Update progressbar 1/5000th each time:
      m_ProgressBar_d.set_fraction(value + 0.0002);

      return value < 0.99;  // return false when done
    }

File: `main.cc` (For use with gtkmm 4)

    #include "idleexample.h"
    #include <gtkmm/application.h>

    int main (int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      return app->make_window_and_run<IdleExample>(argc, argv);
    }

这个例子指出了空闲与超时方法的微小区别。如果你需要定期调用，并且速度并不重要则你应该使用超时方法。如果你需要尽可能多的调用方法（例如在后台计算分形）那你应该使用空闲方法。

尝试执行此示例并逐渐增加系统的负载。上方的进度条将稳定的逐渐增加；下方的则会增加的越来越慢。

## 内存管理

### 部件

#### 标准C++内存管理

gtkmm允许程序员像管理任意其他C++对象一样对任意部件的生命周期（即构造和析构）进行控制。这种灵活性使你可以用`new`和`delete`动态创建和销毁对象，或使用常规的类成员（销毁类时部件将被自动销毁），或使用局部实例（当实例超出其作用域时被销毁）。这种灵活性在某些C++ GUI工具包中不存在，这些工具包限制程序员只能使用C++内存管理特性中的一部分。

以下是一些使用标准C++进行内存管理的示例：

##### 类作用域部件

如果程序员不需要动态分配内存，则可以在类作用域中使用具有自动储存期的部件。在类作用域中使用自动储存期的部件的优点之一是内存管理被集中在一个地方。程序员不需要承担因`delete`部件出现差错而导致内存泄漏的风险。

使用类作用域部件的主要缺点是在包含类声明的头文件中展示了类的实现而不只是类的接口。

    #include <gtkmm/button.h>
    #include <gtkmm/window.h>
    class Foo : public Gtk::Window
    {
    private:
      Gtk::Button theButton;
      // will be destroyed when the Foo object is destroyed
    };

##### 函数作用域部件

如果程序员不需要类作用域部件，那么也可以使用函数作用域部件。函数作用域相比于类作用域的优点是增加了数据的隐蔽性和隐藏了依赖。

    {
      Gtk::Button aButton;
      aButton.set_visible(true);
      ...
      app->run();
    }

However, this technique is rarely useful. Most widgets can't safely be created before the application has been registered or activated. They can't safely be deleted after Gtk::Application::run() or Gtk::Application::make_window_and_run() returns.

##### 使用new和delete进行动态分配

Usually, the programmer will prefer to allow containers to automatically destroy their children by creating them using `Gtk::make_managed()` (see below). This is not strictly required, as the `new` and `delete` operators may also be used, but modern C++ style discourages those in favor of safer models of memory management, so it is better to create widgets using `Gtk::make_managed()` and let their parent destroy them, than to manually perform dynamic allocation.

    auto pButton = new Gtk::Button("Test");

    // do something useful with pButton

    delete pButton;

Here, the programmer deletes `pButton` to prevent a memory leak.

#### 托管部件

另外，你可以让部件的容器控制部件被销毁的时机。多数情况下你会希望部件的生存周期与包含它的容器的生命周期一样长。要将部件的生命周期管理委托给其容器，请使用`Gtk::make_managed()`创建部件，然后使用诸如Gtk::Box::append()这类的方法将其装入容器中。这样部件就会随着容器一并销毁。

##### 使用make_managed()和append()进行动态分配

gtkmm提供了包括`make_managed()`函数和Gtk::Box::append()方法在内的用于简化创建和销毁部件的很多方法。这些部件的生命周期可以由其父部件进行管理。

除了顶级窗口以外的任何部件都需要添加到一个父容器中才能够被显示。`manage()`函数对部件进行标记，以便将该部件添加到父容器时，由其父容器负责删除该部件，这意味着用户不需要如此做。创建生命周期由其父部件进行管理的部件的原始方法是调用`manage()`，并将向其传入使用`new`表达式创建的动态分配的部件。

However, usually, when you create such a widget, you will already know that its parent container should be responsible for destroying it. In addition, modern C++ style discourages use of the `new` operator, which was required when passing a newly created widget to `manage()`. Therefore, gtkmm has added `make_managed()`, which combines creation and marking with `manage()` into a single step. This avoids you having to write `new`, which is discouraged in modern C++ style, and more clearly expresses intent to create a managed widget.

    MyContainer::MyContainer()
    {
      auto pButton = Gtk::make_managed<Gtk::Button>("Test");
      append(*pButton); //add *pButton to MyContainer
    }

现在，当你销毁MyContainer类型对象时，该按钮也被销毁。不再需要主动`delete` `pButton`以释放其内存；对其的删除操作委托给了MyContainer对象。

If you never added the widget to any parent container, it's your responsibility to delete it. If you add it to a container widget, and later remove it (for instance with Gtk::Box::remove()), it's deleted by the container.

Of course, a top-level container will not be added to another container. The programmer is responsible for destroying the top-level container using one of the traditional C++ techniques. Or you can let Gtk::Application::make_window_and_run() create a top-level window and delete it when it's hidden.

### 共享资源

一些对象是从共享储存中获取的，例如Gdk::Pixbuf和Pango::Font。因此你无法实例化自己的实例。这些类通常继承自Glib::Object。gtkmm使用智能指针Glib::RefPtr<>而不是要求你引用和解引用这些对象。开罗有自己的智能指针：Cairo::RefPtr<>。

诸如Gdk::Pixbuf之类的对象只能通过create()函数进行实例化。例如：

    auto pixbuf = Gdk::Pixbuf::create_from_file(filename);

你无法暴露Gdk::Pixbuf对象。在示例中，`pixbuf`是一个智能指针，因此可以像正常指针一样执行此操作：

    auto width = 0;
    if(pixbuf)
    {
      width = pixbuf->get_width();
    }

当`pixbuf`超出作用域时，将在后台调用unref()，你无需为它担心。没有进行`new`因此也不需要进行`delete`。

如果你复制一个RefPtr，例如：

    auto pixbuf2 = pixbuf;

，或者你将其作为一个方法的参数或返回类型进行传递，RefPtr将做必要的引用以确保在最后一个RefPtr超出作用域之前该实例不会被销毁。

更多与RefPtr有关的信息，请参见附录。

如果你想了解与智能指针更多的有关信息，请阅读以下书籍：

- Bjarne Stroustrup, "The C++ Programming Language" Fourth Edition - section 34.3

- Nicolai M. Josuttis,《C++标准库》第4章第2节

## Glade和Gtk::Builder

尽管你可以使用C++代码对部件进行实例化和排列，但是这很快就会变得枯燥且重复。并且需要重新编译才能显示更改。Glade应用程序允许你在屏幕上布局部件并然后将布局保存为XML描述。你的应用程序可以在之后使用Gtk::BuilderAPI在运行时加载该XML文件，并获取特定名称部件实例的指针。

这具有以下优点：

1. 需要的C++代码更少。

2. 可以更快的看到UI的更改，更便于改进UI。

3. 没有编程技能的设计人员可以创建和编辑UI。

你仍然需要编写C++代码以处理由用户操作触发的UI更改，但是将Gtk::Builder用于部件布局允许你更专注的实现该功能。

### 加载.glade文件

Gtk::Builder必须通过Glib::RefPtr来使用。与所有此类类一样，你需要通过使用create()方法实例化它。例如：

    auto builder = Gtk::Builder::create_from_file("basic.glade");

这将实例化`.glade`文件中定义的窗口。

如果只想实例化一个窗口或实例化一个子部件，你可以将部件的名称指定为第二个参数。例如：

    auto builder = Gtk::Builder::create_from_file("basic.glade", "treeview_products");

### 访问部件

To access a widget, for instance to show a dialog, use the get_widget() method, providing the widget's name. This name should be specified in the Glade Properties window. If the widget could not be found, or is of the wrong type, then the pointer will be set to nullptr.

The dialogs in this chapter are derived from Gtk::Window because Gtk::Dialog is deprecated since gtkmm 4.10.

    auto pDialog = builder->get_widget<Gtk::Window>("DialogBasic");

Gtk::Builder checks for a null pointer, and checks that the widget is of the expected type, and will show warnings on the command line about these.

请注意，你并没有使用get_widget()实例化部件，你只是获得了指向已经存在的部件的指针。当你在同一个Gtk::Builder上用同一个名称调用get_widget()你总是会得到指向同一实例的指针。部件在函数Gtk::Builder::create_from_file()执行期间被实例化。

get_widget() returns child widgets that are `manage()`ed (see the Memory Management chapter), so they will be deleted when their parent container is deleted. Windows (such as Dialogs) cannot be managed because they have no parent container, so you must delete them at some point. The documentation of Gtk::Builder has more to say about the memory management of different kinds of objects.

Reference

#### 示例

这个简单示例展示了如何在运行时加载Glade文件并使用Gtk::Builder访问部件。

Source Code

File: `main.cc` (For use with gtkmm 4)

    #include <gtkmm.h>
    #include <iostream>

    namespace
    {
    Gtk::Window* pDialog = nullptr;
    Glib::RefPtr<Gtk::Application> app;

    void on_button_clicked()
    {
      if (pDialog)
        pDialog->set_visible(false); // set_visible(false) will cause Gtk::Application::run() to end.
    }

    void on_app_activate()
    {
      // Load the GtkBuilder file and instantiate its widgets:
      auto refBuilder = Gtk::Builder::create();
      try
      {
        refBuilder->add_from_file("basic.glade");
      }
      catch(const Glib::FileError& ex)
      {
        std::cerr << "FileError: " << ex.what() << std::endl;
        return;
      }
      catch(const Glib::MarkupError& ex)
      {
        std::cerr << "MarkupError: " << ex.what() << std::endl;
        return;
      }
      catch(const Gtk::BuilderError& ex)
      {
        std::cerr << "BuilderError: " << ex.what() << std::endl;
        return;
      }

      // Get the GtkBuilder-instantiated dialog:
      pDialog = refBuilder->get_widget<Gtk::Window>("DialogBasic");
      if (!pDialog)
      {
        std::cerr << "Could not get the dialog" << std::endl;
        return;
      }

      // Get the GtkBuilder-instantiated button, and connect a signal handler:
      auto pButton = refBuilder->get_widget<Gtk::Button>("quit_button");
      if (pButton)
        pButton->signal_clicked().connect([] () { on_button_clicked(); });

      // It's not possible to delete widgets after app->run() has returned.
      // Delete the dialog with its child widgets before app->run() returns.
      pDialog->signal_hide().connect([] () { delete pDialog; });

      app->add_window(*pDialog);
      pDialog->set_visible(true);
    }
    } // anonymous namespace

    int main(int argc, char** argv)
    {
      app = Gtk::Application::create("org.gtkmm.example");

      // Instantiate a dialog when the application has been activated.
      // This can only be done after the application has been registered.
      // It's possible to call app->register_application() explicitly, but
      // usually it's easier to let app->run() do it for you.
      app->signal_activate().connect([] () { on_app_activate(); });

      return app->run(argc, argv);
    }

### 使用派生部件

你可以使用Gtk::Builder和Glade布局你从gtkmm部件类派生的自定义部件。这使得你的代码保持有序和封装性，将声明式表述与业务逻辑分离开来，避免你的大多数源代码都是在设置属性和装填容器。

像这样用Gtk::Builder::get_widget_derived()：

    auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived");

Your derived class must have a constructor that takes a pointer to the underlying C type, and the Gtk::Builder instance. All relevant classes of gtkmm typedef their underlying C type as BaseObjectType (Gtk::Window typedefs BaseObjectType as GtkWindow, for instance).

你必须在初始化列表中调用基类的构造函数，并提供C指针。例如：

    DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
    : Gtk::Window(cobject)
    {
    }

然后你可以将对子部件的操作封装于派生类的构造函数中，可以在构造函数中再次使用get_widget()或get_widget_derived()。例如：

    DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
    : Gtk::Window(cobject),
      m_builder(builder),
      //Get the Glade-instantiated Button, and connect a signal handler:
      m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
    {
      if (m_pButton)
      {
        m_pButton->signal_clicked().connect( sigc::mem_fun(*this, &DerivedDialog::on_button_quit) );
      }
    }

It's possible to pass additional arguments from get_widget_derived() to the constructor of the derived widget. For instance, this call to get_widget_derived()

    auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived", true);

can invoke this constructor

    DerivedDialog::DerivedDialog(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& builder, bool warning)
    : Gtk::Window(cobject),
      m_builder(builder),
      m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
    {
      // ....
    }

#### Gtk::Builder和Glib::Property

如果你派生的部件使用了Glib::Property，它将变得更复杂了一些。包涵Glib::Property成员的派生部件必须在GType系统中用自己的名称进行注册。必须在调用任何create_from_*()或add_from_*()方法之前进行注册，这意味着你可能需要完成对该类的注册才能创建派生部件的实例。你派生的部件必须有一个具有get_widget_derived()所需的所有参数以及调用Glib::ObjectBase构造函数以注册GType的构造函数。

    DerivedButton::DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
    : Glib::ObjectBase("MyButton"), // The GType name will be gtkmm__CustomObject_MyButton.
      Gtk::Button(cobject),
      prop_ustring(*this, "button-ustring"),
      prop_int(*this, "button-int", 10)
    {
      // ....
    }

When using gtkmm with a version of glibmm from 2.62 onwards, it is possible also to specify properties of derived widgets, declared in C++ using gtkmm, within `.glade` files and load/set these using Gtk::Builder. See the documentation of Gtk::Builder for more details on how to achieve this. Glade won’t recognize such properties as-is, but it should be able to through use of property class definitions and a catalog declaring those new properties.

#### 示例

本示例展示了如何在运行时加载Glade文件以及如何通过派生类访问部件。

Source Code

File: `derivedbutton.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_DERIVED_BUTTON_H
    #define GTKMM_EXAMPLE_DERIVED_BUTTON_H

    #include <gtkmm.h>

    class DerivedButton : public Gtk::Button
    {
    public:
      DerivedButton();
      DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade);
      virtual ~DerivedButton();

      // Provide proxies for the properties. The proxy allows you to connect to
      // the 'changed' signal, etc.
      Glib::PropertyProxy<Glib::ustring> property_ustring() { return prop_ustring.get_proxy(); }
      Glib::PropertyProxy<int> property_int() { return prop_int.get_proxy(); }

    private:
      Glib::Property<Glib::ustring> prop_ustring;
      Glib::Property<int> prop_int;
    };

    #endif //GTKMM_EXAMPLE_DERIVED_BUTTON_H

File: `deriveddialog.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_DERIVED_DIALOG_H
    #define GTKMM_EXAMPLE_DERIVED_DIALOG_H

    #include <gtkmm.h>
    #include "derivedbutton.h"

    class DerivedDialog : public Gtk::Window
    {
    public:
      DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder);
      DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder,
        bool is_glad);
      ~DerivedDialog() override;

    protected:
      //Signal handlers:
      void on_button_quit();

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      DerivedButton* m_pButton;
    };

    #endif //GTKMM_EXAMPLE_DERIVED_DIALOG_H

File: `derivedbutton.cc` (For use with gtkmm 4)

    #include "derivedbutton.h"
    #include <iostream>

    namespace
    {
    void on_ustring_changed()
    {
      std::cout << "- ustring property changed!" << std::endl;
    }

    void on_int_changed()
    {
      std::cout << "- int property changed!" << std::endl;
    }
    } // anonymous namespace

    // For creating a dummy object in main.cc.
    DerivedButton::DerivedButton()
    : Glib::ObjectBase("MyButton"),
      prop_ustring(*this, "button-ustring"),
      prop_int(*this, "button-int", 10)
    {
    }

    DerivedButton::DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& /* refGlade */)
    : // To register custom properties, you must register a custom GType.  If
      // you don't know what that means, don't worry, just remember to add
      // this Glib::ObjectBase constructor call to your class' constructor.
      // The GType name will be gtkmm__CustomObject_MyButton.
      Glib::ObjectBase("MyButton"),
      Gtk::Button(cobject),
      // register the properties with the object and give them names
      prop_ustring(*this, "button-ustring"),
      // this one has a default value
      prop_int(*this, "button-int", 10)
    {
      // Register some handlers that will be called when the values of the
      // specified parameters are changed.
      property_ustring().signal_changed().connect(sigc::ptr_fun(&on_ustring_changed));
      property_int().signal_changed().connect(sigc::ptr_fun(&on_int_changed));
    }

    DerivedButton::~DerivedButton()
    {
    }

File: `deriveddialog.cc` (For use with gtkmm 4)

    #include "deriveddialog.h"
    #include <iostream>

    DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::Window(cobject),
      m_refBuilder(refBuilder),
      m_pButton(nullptr)
    {
      // Get the Glade-instantiated Button, and connect a signal handler:
      m_pButton = Gtk::Builder::get_widget_derived<DerivedButton>(m_refBuilder, "quit_button");
      if (m_pButton)
      {
        m_pButton->signal_clicked().connect( sigc::mem_fun(*this, &DerivedDialog::on_button_quit) );
        std::cout << "ustring, int: " << m_pButton->property_ustring()
                  << ", " << m_pButton->property_int() << std::endl;
        m_pButton->property_int() = 99;
        std::cout << "ustring, int: " << m_pButton->property_ustring()
                  << ", " << m_pButton->property_int() << std::endl;
      }
    }

    // The first two parameters are mandatory in a constructor that will be called
    // from Gtk::Builder::get_widget_derived().
    // Additional parameters, if any, correspond to additional arguments in the call
    // to Gtk::Builder::get_widget_derived().
    DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder,
      bool is_glad)
    : DerivedDialog(cobject, refBuilder) // Delegate to the other constructor
    {
      // Show an icon.
      auto content_area = refBuilder->get_widget<Gtk::Box>("dialog-content_area");
      if (!content_area)
      {
        std::cerr << "Could not get the content area" << std::endl;
        return;
      }
      auto pImage = Gtk::make_managed<Gtk::Image>();
      pImage->set_from_icon_name(is_glad ? "face-smile" : "face-sad");
      pImage->set_icon_size(Gtk::IconSize::LARGE);
      pImage->set_expand();
      content_area->append(*pImage);
    }

    DerivedDialog::~DerivedDialog()
    {
    }

    void DerivedDialog::on_button_quit()
    {
      set_visible(false); // set_visible(false) will cause Gtk::Application::run() to end.
    }

File: `main.cc` (For use with gtkmm 4)

    #include "deriveddialog.h"
    #include <iostream>
    #include <cstring>

    namespace
    {
    bool show_icon = false;
    bool is_glad = true;

    DerivedDialog* pDialog = nullptr;
    Glib::RefPtr<Gtk::Application> app;

    void on_app_activate()
    {
      // Create a dummy instance before the call to refBuilder->add_from_file().
      // This creation registers DerivedButton's class in the GType system.
      // This is necessary because DerivedButton contains user-defined properties
      // (Glib::Property) and is created by Gtk::Builder.
      static_cast<void>(DerivedButton());

      // Load the GtkBuilder file and instantiate its widgets:
      auto refBuilder = Gtk::Builder::create();
      try
      {
        refBuilder->add_from_file("derived.glade");
      }
      catch(const Glib::FileError& ex)
      {
        std::cerr << "FileError: " << ex.what() << std::endl;
        return;
      }
      catch(const Glib::MarkupError& ex)
      {
        std::cerr << "MarkupError: " << ex.what() << std::endl;
        return;
      }
      catch(const Gtk::BuilderError& ex)
      {
        std::cerr << "BuilderError: " << ex.what() << std::endl;
        return;
      }

      // Get the GtkBuilder-instantiated dialog:
      if (show_icon)
        pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(refBuilder, "DialogDerived", is_glad);
      else
        pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(refBuilder, "DialogDerived");

      if (!pDialog)
      {
        std::cerr << "Could not get the dialog" << std::endl;
        return;
      }

      // It's not possible to delete widgets after app->run() has returned.
      // Delete the dialog with its child widgets before app->run() returns.
      pDialog->signal_hide().connect([] () { delete pDialog; });

      app->add_window(*pDialog);
      pDialog->set_visible(true);
    }
    } // anonymous namespace

    int main(int argc, char** argv)
    {
      int argc1 = argc;
      if (argc > 1)
      {
        if (std::strcmp(argv[1], "--glad") == 0)
        {
          show_icon = true;
          is_glad = true;
          argc1 = 1; // Don't give the command line arguments to Gtk::Application.
        }
        else if (std::strcmp(argv[1], "--sad") == 0)
        {
          show_icon = true;
          is_glad = false;
          argc1 = 1; // Don't give the command line arguments to Gtk::Application.
        }
      }

      app = Gtk::Application::create("org.gtkmm.example");

      // Instantiate a dialog when the application has been activated.
      // This can only be done after the application has been registered.
      // It's possible to call app->register_application() explicitly, but
      // usually it's easier to let app->run() do it for you.
      app->signal_activate().connect([] () { on_app_activate(); });

      return app->run(argc1, argv);
    }

## 国际化和本地化

gtkmm应用程序可以很轻松的支持包括非欧洲语言（如中文）和从右向左语言（如阿拉伯语）在内的多种语言。正确编写且进行翻译的gtkmm应用程序可以在运行时根据用户环境使用合适的语言。

你可能没有预料到需要支持其他语言，但你永远不能排除这种可能性。而且事先准备好支持多语言比后来改进要容易的多。

编写允许进行翻译的源代码的过程被称为`internationalization`，通常被缩写为`i18n`。而根据该源代码提供其他语言的翻译文本的过程`Localization`通常被简写为`l10n`。

国际化过程中的主要活动是查找用户能看到的字符串并将其标记为需要可翻译。你不需要一次性完成所有的操作 - 只要你的项目准备好了进行国际化无论你有多少个字符串被标记为可翻译都不会影响程序的正常运行。

源代码中的字符串字面量应该以英文键入，但要使用宏包围。gettext（或intltool）实用程序可以提取被标记的字符串进行翻译，并在运行时将其替换为翻译后的文本。

### 准备你的项目

在下面的说明中我们假定你不直接使用gettext，而是使用专门为`GNOME`编写的intltool。intltool使用`gettext()`从源代码中提取字符串，但是intltool也从其他文件（例如桌面菜单详情、glade的GUI资源文件）中提取字符串写入到标准的gettext`.pot/.po`文件中。

We also assume that you are using autotools (automake and autoconf) to build your project (although autotools is not recommended for new applications), and that you are using `./autogen.sh` from gnome-common or a similar `autogen.sh` file, which, among other things, takes care of some intltool initialization.

If you are using meson (recommended), see the Localisation chapter in Meson's manual. You can then skip this section.

gnome-common的`autogen.sh`的替代方案可能如下所示：

    #! /bin/sh -e
    test -n "$srcdir" || srcdir=`dirname "$0"`
    test -n "$srcdir" || srcdir=.

    autoreconf --force --install --verbose --warnings=all "$srcdir"
    echo "Running intltoolize --copy --force --automake"
    intltoolize --copy --force --automake
    test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"

在你项目的根目录下创建一个名称为`po`的子文件夹。该目录将包含你的所有翻译。在其中创建一个名为`LINGUAS`和一个名为`POTFILES.in`的文件。通常做法是在`po`文件夹再创建一个`ChangeLog`文件以便翻译人员跟踪翻译更改。

`LINGUAS`包含按字母顺序排列的代码列表，这些代码标识程序已翻译的语言（以`#`开头的行是注释将被忽略）。`LINGUAS`文件中每个位于代码列表中语言都需要有与之对应的`.po`文件。因此如果你的程序有德语和日语翻译，你的`LINGUAS`应如下所示：

    # keep this file sorted alphabetically, one language code per line
    de
    ja

(In addition, you'd have the files `de.po` and `ja.po` in your `po` directory which contain the German and Japanese translations, respectively.)

`POTFILES.in`是一个开始于项目根目录，包含所有被标记为需要翻译字符串的文件路径列表。例如你的项目源代码位于`src`子目录中，其中有两个文件包含有需要被翻译的字符串，则你的`POTFILES.in`可能如下所示：

    src/main.cc
    src/other.cc

If you are using gettext directly, you can only mark strings for translation if they are in source code file. However, if you use intltool, you can mark strings for translation in a variety of other file formats, including Glade UI files, xml, .desktop files and several more. So, if you have designed some of the application UI in Glade then also add your `.glade` files to the list in `POTFILES.in`.

现在已经有了放置翻译的地方，你需要初始化intltool和gettext。将一下代码添加到你的`configure.ac`，将"programname"替换为你的程序的名称：

    IT_PROG_INTLTOOL([0.35.0])

    GETTEXT_PACKAGE=programname
    AC_SUBST(GETTEXT_PACKAGE)
    AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"],
                       [The domain to use with gettext])
    AM_GNU_GETTEXT([external])
    AM_GNU_GETTEXT_VERSION([0.17])

    PROGRAMNAME_LOCALEDIR=[${datadir}/locale]
    AC_SUBST(PROGRAMNAME_LOCALEDIR)

`PROGRAMNAME_LOCALEDIR`将在稍后被`Makefile.am`文件使用。以定义在源代码中初始化gettext时将用到的宏。

`AM_GLIB_GNU_GETTEXT`已由`AM_GNU_GETTEXT`和`AM_GNU_GETTEXT_VERSION`替代，且`AM_GLIB_GNU_GETTEXT`已被弃用，不应在任何新代码中使用。

在顶层`Makefile.am`中：

- 将`po`添加到`SUBDIRS`变量中。如果没有这个，在构建程序时不会生成和安装翻译。

- 定义`INTLTOOL_FILES`为：

      INTLTOOL_FILES = intltool-extract.in \
                       intltool-merge.in \
                       intltool-update.in

- 将`INTLTOOL_FILES`添加到`EXTRA_DIST`文件列表。这样可以确保执行`make dist`时这些文件包含在源压缩包中。

- 更新你的`DISTCLEANFILES`：

      DISTCLEANFILES = ... intltool-extract \
                       intltool-merge \
                       intltool-update \
                       po/.intltool-merge-cache

- 根据包含可翻译字符串的文件类型，添加以下代码：

      desktopdir = $(datadir)/applications
      desktop_in_files = programname.desktop.in
      desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
      @INTLTOOL_DESKTOP_RULE@

在你的`src/Makefile.am`中，更新你的`AM_CPPFLAGS`以添加以下预处理宏定义：

    AM_CPPFLAGS = ... -DPROGRAMNAME_LOCALEDIR=\"${PROGRAMNAME_LOCALEDIR}\"

在你的源代码中初始化`gettext`时将使用这个宏。

### 标记要翻译的字符串

源代码中的字符串字面量应该以英文键入，但应将其包含在对`gettext()`函数的调用中。这些字符串将被提取以进行翻译，并且在运行时翻译可以替代原始的英文字符串。

GNU gettext允许你标记源代码中的字符串，提取这些字符串进行翻译，并在你的应用程序中使用翻译好的字符串。

Glib定义了`gettext()`支持宏，这些宏更为简单易用，要使用这些宏请`#include <glibmm/i18n.h>`然后将：

    display_message("Getting ready for i18n.");

替换为：

    display_message(_("Getting ready for i18n."));

可以生成一个包含代码中出现的所有字符串（即使没有被标记为需要翻译）以及其文件名与行号引用的文件作为参考。要生成名为`my-strings`的文件，请在源代码目录执行以下命令：

    xgettext -a -o my-strings --omit-header *.cc *.h

最后，要让你的程序使用当前环境的翻译，请将以下代码添加到你的`main.cc`文件的开头，以初始化`gettext()`。

    bindtextdomain(GETTEXT_PACKAGE, PROGRAMNAME_LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    textdomain(GETTEXT_PACKAGE);

#### gettext如何工作

intltool-update或xgettext脚本提取字符串并将其放入`mypackage.pot`文件中。应用程序的翻译人员首先将此`.pot`文件复制到`localename.po`中以创建翻译。语言环境标识一种语言和该语言使用的编码，包括日期和数字使用的格式。在之后，当源代码中的文本更改时，使用msgmerge或intltool-update脚本重新生成的`.pot`文件更新`localename.po`文件。

在安装时，`.po`将被转换为二进制格式（扩展名为`.mo`），并将其放置于系统所有的区域设置文件夹中，例如`/usr/share/locale/`。

当应用程序运行时，gettext将检查系统目录是否存在与用户语言环境对应的`.mo`文件（你可以使用以下命令指定语言环境，例如：在bash控制台上输入`export LANG=de_DE.UTF-8`）。稍后在程序到达`gettext`调用时，它将从中寻找特定字符串的翻译，如果未找到则使用原始字符串。

#### 测试和添加翻译

为了说服自己做得很好，你可能希望为新的语言环境添加翻译。为此请转到项目的`po`子文件夹并执行以下命令：

    intltool-update --pot

这将创建一个名为`programname.pot`的文件。现在将该文件复制到`languagecode.po`，例如`de.po`或`hu.po`。接着将该语言代码添加到`LINGUAS`文件中。`.po`文件包含标头和英语字符串列表，并为翻译文本预留了空间。请确保将`.po`文件的编码设置（在标头中指定，但也指定其内容的编码）为了`UTF-8`。

It's possible that certain strings will be marked as `fuzzy` in the `.po` file. These translations will not substitute the original string. To make them appear, simply remove the `fuzzy` tag. A `fuzzy` tag appears if a string content changed, but the location is still the same.

#### 资源

更多与国际化与本地化有关的信息，请参阅以下内容：

- L10N Guidelines for Developers

- Intltool README

- How to use Git for GNOME translators

- gettext manual

- `gtkmm_hello` example package

- `gnomemm_hello` example package

### 期望使用UTF8

好的国际化应用程序不会假设字符中的字节数。这意味着你不应该使用指针算数遍历字符串中的字符，也意味着你不应该使用std::string以及像`strlen()`这样的与字符串有关的标准C函数，因为他们都对字符的字节数做了假设。

你可能已经通过使用std::string避免了使用原始的`char*`和`char[]`进行指针算数。那么你只需要换成使用Glib::ustring即可。更多详情请参阅Glib::ustring基础章节。

#### Glib::ustring和std::iostreams

不幸的是，与标准iostreams的集成并非绝对安全的。如果你将Glib::ustring使用`operator<<`输出到ostream中，则gtkmm会将Glib::ustring转换为使用特定语言编码（通常不会是UTF-8编码）。而使用`operator>>`将istream输入到Glib::ustring会发生相反的转换。但是如果你通过std::string进行则该方案会失效，例如，从文本输入流转换成std::string再隐式转换为Glib::ustring。如果该字符串中包含非ASCII字符且当前的语言环境不使用UTF-8编码，则得到的结果是一个损坏的Glib::ustring。你可以通过手动转换编码解决此问题。例如，若要从ostringstream中接受std::string：

    std::locale::global(std::locale("")); // Set the global locale to the user's preferred locale.
                                          // Usually unnecessary here, because Glib::init()
                                          // or Gtk::Application::create() does it for you.
    std::ostringstream output;
    output << percentage << " % done";
    label->set_text(Glib::locale_to_utf8(output.str()));

### 陷阱

有几个你最终会自己发现的错误。但本小节可能能帮助你避免它们。

#### 不同语义的同一字符串。

有时，两个英文字符串是相同的但是在不同的上下文中有不同的含义，因此他们的译文不一定相同。此时用英文字符串作为查找键会引起问题。

在这个情况下，你应该在字符串中添加额外的字符。例如，使用`"jumps[noun]"`和`"jumps[verb]"`而不是用`"jumps"`并在调用`gettext`后将它们剥离。如果你添加了额外的字符，你还应该在调用`gettext`之前为译者添加注释。这样的注释将被显示于`.po`中。例如：

    // note to translators: don't translate the "[noun]" part - it is
    // just here to distinguish the string from another "jumps" string
    text = strip(gettext("jumps[noun]"), "[noun]");

如果你使用Glib宏，将会更为方便。你只需要使用`C_()`替换`_()`。例如：

    GLib::ustring text(C_("noun", "jumps"));

#### 合成字符串

C programmers use `sprintf()` to compose and concatenate strings. C++ favors streams, but unfortunately, this approach makes translation difficult, because each fragment of text is translated separately, without allowing the translators to rearrange them according to the grammar of the language.

例如，此代码将会出现问题：

    std::cout << _("Current amount: ") << amount
              << _(" Future: ") << future << std::endl;

    label.set_text(_("Really delete ") + filename + _(" now?"));

So you should either avoid this situation or use `Glib::ustring::compose()` which supports syntax such as:

    std::cout << Glib::ustring::compose(
                 _("Current amount: %1 Future: %2"), amount, future) << std::endl;

    label.set_text(Glib::ustring::compose(_("Really delete %1 now?"), filename));

#### 假设显示字符串所需的空间

你永远不知道一个字符串被翻译后要在屏幕上显示它需要消耗多少空间。它可能是原始英文字符串的两倍甚至更多。幸运的是 gtkmm的多数部件都能在运行时自动扩展到需要的尺寸。

#### "黑话"

你应该避免使用不常见的用于例如缩写、俚语、行话。通常它们对于译者来说很难翻译，甚至将该语言作为母语的译者也无法理解。例如，最好使用"application"而不是"app"

#### 在字符串中使用非ASCII字符

当前，gettext并不支持在源代码中使用非ASCII字符。例如，你不能使用版权标志（©）

要解决这个问题你可以使用注释提醒译者可以使用他们语言中可用的特殊字符。对于英语用户你可以在美国英语`en_US.po`翻译中使用特殊字符。

### 获取翻译帮助

If your program is free software, there is a whole `GNOME` subproject devoted to helping you make translations, the `GNOME` Translation Project.

The way it works is that you upload your source code to a git repository where translators can access it, then contact the gnome-i18n mailing list and ask to have your program added to the list of modules to translate.

然后请确保更新`po/`子目录下的`POTFILES.in`（`intltool-update -m`可以提供帮助），以便译者始终可以访问更新好的`myprogram.pot`文件，并在发布新版本之前的两三天在gnome-i18n邮件列表上宣布冻结字符串更改。根据程序所包含的字符串数数量以及程序的受欢迎程度，译者将逐渐向你提交`languagename.po`文件。

请注意，大多数语言的翻译团队仅有1-3位成员，所以如果你的程序包含了很多的字符串，则可能需要很长时间才有译者查看它。此外，大多数译者都不想浪费时间，因此如果他们认为你的项目质量很低，他们可能不会想为你提供翻译。

## 自定义部件

gtkmm makes it very easy to derive new widgets by inheriting from an existing widget class, either by deriving from a container and adding child widgets, or by deriving from a single-item widget, and changing its behavior. But you might occasionally find that no suitable starting point already exists. In this case, you can implement a widget from scratch.

### 自定义容器

直接从Gtk::Widget派生自定义容器部件时，应重写一下虚方法：

- get_request_mode_vfunc()：返回容器的`Gtk::SizeRequestMode`偏好。

- measure_vfunc()：返回容器的最小自然宽度或高度。

- size_allocate_vfunc(): Position the child widgets, given the height and width that the container has actually been given.

The get_request_mode_vfunc(), measure_vfunc(), and size_allocate_vfunc() virtual methods control the layout of the child widgets. For instance, if your container has 2 child widgets, with one below the other, your get_request_mode_vfunc() might request height-for-width layout. Then your measure_vfunc() might report the maximum of the widths of the child widgets when asked to report width, and it might report the sum of their heights when asked to report height. If you want padding between the child widgets then you would add that to the width and height too. Your widget's container will use this result to ensure that your widget gets enough space, and not less. By examining each widget's parent, and its parent, this logic will eventually decide the size of the top-level window.

你无法确保得到你请求的`Gtk::SizeRequestMode`。因此，measure_vfunc()必须为其所有合理的输入参数值返回合适的值。有关measure_vfunc()参数的描述，查看Gtk::Widget::measure()的描述可能比查看measure_vfunc()的文档更好。

size_allocate_vfunc() receives the actual height and width that the parent container has decided to give to your widget. This might be more than the minimum, or even more than the natural size, for instance if the top-level window has been expanded. You might choose to ignore the extra space and leave a blank area, or you might choose to expand your child widgets to fill the space, or you might choose to expand the padding between your widgets. It's your container, so you decide.

Your container must unparent its children before the underlying C object (a gtkmm__GtkWidget) is finalized. If your container is used as a managed widget, it shall unparent its children in a Gtk::Widget::signal_destroy() handler (available since gtkmm 4.8). If your container is not managed, that signal handler is not called. Instead the children shall be unparented in the C++ destructor. If you want your container to be useful both ways, unparent the children both in the destructor and in a signal handler. See the example code.

#### 示例

This example implements a container with child widgets, one above the other. Of course, in this case it would be far simpler just to use a vertical Gtk::Box or Gtk::Grid.

自定义容器

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "mycontainer.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      //Signal handlers:
      void on_button_quit();

      //Child widgets:
      Gtk::Box m_VBox;
      MyContainer m_MyContainer;
      Gtk::Button m_Button_Child;
      Gtk::Label m_Label_Child;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `mycontainer.h` (For use with gtkmm 4)

    #ifndef GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H
    #define GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H

    #include <gtkmm/widget.h>
    #include <gtkmm/version.h>

    #define HAS_SIGNAL_DESTROY GTKMM_CHECK_VERSION(4,7,1)

    class MyContainer : public Gtk::Widget
    {
    public:
      MyContainer();
      ~MyContainer() override;

      void append(Gtk::Widget& child);
      void prepend(Gtk::Widget& child);
      void remove(Gtk::Widget& child);

    protected:
      int get_nvis_children() const;

      //Overrides:
      Gtk::SizeRequestMode get_request_mode_vfunc() const override;
      void measure_vfunc(Gtk::Orientation orientation, int for_size, int& minimum, int& natural,
        int& minimum_baseline, int& natural_baseline) const override;
      void size_allocate_vfunc(int width, int height, int baseline) override;

    #if HAS_SIGNAL_DESTROY
      // Signal handler:
      void on_container_destroy();
    #endif
    };

    #endif //GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Child("Child button"),
      m_Label_Child("Child label", Gtk::Align::END, Gtk::Align::CENTER),
      m_Button_Quit("Quit")
    {
      set_title("Custom Container example");
      set_default_size(400, 200);

      m_VBox.set_margin(6);
      set_child(m_VBox);

      //Add the child widgets to the custom container:
      m_MyContainer.append(m_Button_Child);
      m_MyContainer.append(m_Label_Child);
      m_MyContainer.prepend(*Gtk::make_managed<Gtk::Label>(
        "First line\nSecond line\nThird line"));
      m_MyContainer.set_expand();
      m_VBox.append(m_MyContainer);

    #if HAS_SIGNAL_DESTROY
      // A managed custom container.
      auto container = Gtk::make_managed<MyContainer>();
      container->prepend(*Gtk::make_managed<Gtk::Label>("Second custom container"));
      container->set_expand();
      m_VBox.append(*container);
    #endif
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(6);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `mycontainer.cc` (For use with gtkmm 4)

    #include "mycontainer.h"

    // This example container is a simplified vertical Box.
    //
    // It can't be used as a managed widget, managed by another container
    // unless Gtk::Widget::signal_destroy() exists.
    // It would cause an error like
    // Gtk-WARNING **: 08:31:48.137: Finalizing gtkmm__GtkWidget 0x561b777462c0, but it still has children left:

    MyContainer::MyContainer()
    {
    #if HAS_SIGNAL_DESTROY
      signal_destroy().connect(sigc::mem_fun(*this, &MyContainer::on_container_destroy));
    #endif
    }

    MyContainer::~MyContainer()
    {
      // If MyContainer is a managed widget, the underlying C object is destructed
      // before this C++ destructor is executed.
      if (!gobj())
        return;

      // If MyContainer is not a managed widget, unparent all children here.
      while (Widget* child = get_first_child())
        child->unparent();
    }

    #if HAS_SIGNAL_DESTROY
    // This signal handler is called only if MyContainer is a managed widget.
    void MyContainer::on_container_destroy()
    {
      while (Widget* child = get_first_child())
        child->unparent();
    }
    #endif

    // Get number of visible children.
    int MyContainer::get_nvis_children() const
    {
      int nvis_children = 0;
      for (const Widget* child = get_first_child(); child; child = child->get_next_sibling())
        if (child->get_visible())
          ++nvis_children;

      return nvis_children;
    }

    Gtk::SizeRequestMode MyContainer::get_request_mode_vfunc() const
    {
      return Gtk::SizeRequestMode::HEIGHT_FOR_WIDTH;
    }

    // Discover the total amount of minimum space and natural space needed by
    // this container and its children.
    void MyContainer::measure_vfunc(Gtk::Orientation orientation, int for_size,
      int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const
    {
      // Don't use baseline alignment.
      minimum_baseline = -1;
      natural_baseline = -1;

      minimum = 0;
      natural = 0;

      int dummy_minimum_baseline = 0;
      int dummy_natural_baseline = 0;

      if (orientation == Gtk::Orientation::HORIZONTAL)
      {
        int height_per_child = for_size;

        if (for_size >= 0)
        {
          // Number of visible children.
          const int nvis_children = get_nvis_children();

          // Divide the height equally among the visible children.
          if (nvis_children > 0)
            height_per_child = for_size / nvis_children;
        }

        // Request a width equal to the width of the widest visible child.
        for (const Widget* child = get_first_child(); child; child = child->get_next_sibling())
          if (child->get_visible())
          {
            int child_minimum_width = 0;
            int child_natural_width = 0;
            child->measure(orientation, height_per_child, child_minimum_width,
              child_natural_width, dummy_minimum_baseline, dummy_natural_baseline);
            if (child_minimum_width > minimum)
              minimum = child_minimum_width;
            if (child_natural_width > natural)
              natural = child_natural_width;
          }
      }
      else // Gtk::Orientation::VERTICAL
      {
        // The allocated height will be divided equally among the visible children.
        // Request a height equal to the number of visible children times the height
        // of the highest child.
        int nvis_children = 0;
        for (const Widget* child = get_first_child(); child; child = child->get_next_sibling())
          if (child->get_visible())
          {
            ++nvis_children;
            int child_minimum_height = 0;
            int child_natural_height = 0;
            child->measure(orientation, for_size, child_minimum_height,
              child_natural_height, dummy_minimum_baseline, dummy_natural_baseline);
            if (child_minimum_height > minimum)
              minimum = child_minimum_height;
            if (child_natural_height > natural)
              natural = child_natural_height;
          }
        minimum *= nvis_children;
        natural *= nvis_children;
      }
    }

    void MyContainer::size_allocate_vfunc(int width, int height, int baseline)
    {
      //Do something with the space that we have actually been given:
      //(We will not be given heights or widths less than we have requested, though
      //we might get more.)

      // Number of visible children.
      const int nvis_children = get_nvis_children();

      if (nvis_children <= 0)
      {
        // No visible child.
        return;
      }

      //Assign space to the children:
      Gtk::Allocation child_allocation;
      const int height_per_child = height / nvis_children;

      //Place the first visible child at the top-left:
      child_allocation.set_x(0);
      child_allocation.set_y(0);

      //Make it take up the full width available:
      child_allocation.set_width(width);
      child_allocation.set_height(height_per_child);

      //Divide the height equally among the visible children.
      for (Widget* child = get_first_child(); child; child = child->get_next_sibling())
        if (child->get_visible())
        {
          child->size_allocate(child_allocation, baseline);
          child_allocation.set_y(child_allocation.get_y() + height_per_child);
        }
    }

    void MyContainer::append(Gtk::Widget& child)
    {
       child.insert_at_end(*this);
    }

    void MyContainer::prepend(Gtk::Widget& child)
    {
       child.insert_at_start(*this);
    }

    void MyContainer::remove(Gtk::Widget& child)
    {
      child.unparent();
    }

### 自定义部件

通过从Gtk::Widget派生部件你可以为部件绘制所有的图形，而不仅仅是排列子部件。例如：Gtk::Label不需要使用其他部件即可绘制标签文本。

当你从Gtk::Widget派生部件时，你应该覆写以下虚函数。其中标记为可选的的虚函数不必在所有自定义部件中都进行覆写。它们的基类方法通常情况下已经很合适。

- get_request_mode_vfunc()：覆写此虚函数是可选的，返回此部件的`Gtk::SizeRequestMode`偏好。

- measure_vfunc()：计算此部件的最小自然高度或宽度。

- size_allocate_vfunc(): (optional) Position the widget, given the height and width that it has actually been given.

- on_realize(): (optional)

- on_unrealize(): (optional)

- on_map()：覆写此虚函数是可选的。

- on_unmap()：覆写此虚函数是可选的。

- snapshot_vfunc()：创建一个渲染节点（例如：一个Cairo::Context节点），并在其上进行绘制。

上述前三个函数在自定义容器中也被覆写。它们在自定义容器小节做了简要说明。

#### 类初始化和实例初始化函数

Some GTK functions, if called at all, must be called from the class init function. Some other GTK functions, if called, must be called from the instance init function. If your custom widget must call any of those functions, you can derive a class from Glib::ExtraClassInit and derive your custom class from that class. The custom CSS name example shows how that's done.

#### 示例

本示例实现了绘制彭罗斯三角形的部件

自定义部件

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "mywidget.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();

      //Child widgets:
      Gtk::Grid m_Grid;
      MyWidget m_MyWidgetS1;
      MyWidget m_MyWidgetS2;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `mywidget.h` (For use with gtkmm 4)

    #ifndef GTKMM_CUSTOM_WIDGET_MYWIDGET_H
    #define GTKMM_CUSTOM_WIDGET_MYWIDGET_H

    #include <gtkmm/widget.h>
    #include <gtkmm/border.h>
    #include <gdkmm/rgba.h>

    class MyWidget : public Gtk::Widget
    {
    public:
      MyWidget();
      ~MyWidget() override;

    protected:
      // Overrides:
      Gtk::SizeRequestMode get_request_mode_vfunc() const override;
      void measure_vfunc(Gtk::Orientation orientation, int for_size, int& minimum, int& natural,
        int& minimum_baseline, int& natural_baseline) const override;
      void on_map() override;
      void on_unmap() override;
      void on_realize() override;
      void on_unrealize() override;
      void snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot) override;

      Gtk::Border m_padding;
      Gdk::RGBA m_foreground{0.0, 0.0, 1.0};
      Gdk::RGBA m_background{1.0, 0.0, 0.0};
    };

    #endif //GTKMM_CUSTOM_WIDGET_MYWIDGET_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_Button_Quit("Quit")
    {
      set_title("Custom Widget example");
      set_default_size(600, 400);

      m_Grid.set_margin(6);
      m_Grid.set_row_spacing(10);
      m_Grid.set_column_spacing(10);

      set_child(m_Grid);

      m_Grid.attach(m_MyWidgetS1, 0, 0);
      m_Grid.attach(m_MyWidgetS2, 1, 1);

      m_Grid.attach(m_ButtonBox, 0, 2, 2, 1);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(6);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ExampleWindow::on_button_quit) );
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `mywidget.cc` (For use with gtkmm 4)

    #include "mywidget.h"
    #include <gdkmm/general.h>  // for Gdk::Cairo:: functions
    #include <gtkmm/snapshot.h>

    MyWidget::MyWidget()
    {
      // Expand, if there is extra space.
      set_expand(true);

      m_padding.set_left(5);
      m_padding.set_right(15);
      m_padding.set_top(10);
      m_padding.set_bottom(20);
    }

    MyWidget::~MyWidget()
    {
    }

    Gtk::SizeRequestMode MyWidget::get_request_mode_vfunc() const
    {
      //Accept the default value supplied by the base class.
      return Gtk::Widget::get_request_mode_vfunc();
    }

    //Discover the total amount of minimum space and natural space needed by
    //this widget.
    //Let's make this simple example widget always need minimum 60 by 50 and
    //natural 100 by 70.
    void MyWidget::measure_vfunc(Gtk::Orientation orientation, int /* for_size */,
      int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const
    {
      if (orientation == Gtk::Orientation::HORIZONTAL)
      {
        minimum = 60;
        natural = 100;
      }
      else
      {
        minimum = 50;
        natural = 70;
      }

      // Don't use baseline alignment.
      minimum_baseline = -1;
      natural_baseline = -1;
    }

    void MyWidget::on_map()
    {
      //Call base class:
      Gtk::Widget::on_map();
    }

    void MyWidget::on_unmap()
    {
      //Call base class:
      Gtk::Widget::on_unmap();
    }

    void MyWidget::on_realize()
    {
      //Call base class:
      Gtk::Widget::on_realize();
    }

    void MyWidget::on_unrealize()
    {
      //Call base class:
      Gtk::Widget::on_unrealize();
    }

    void MyWidget::snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot)
    {
      const auto allocation = get_allocation();
      const Gdk::Rectangle rect(0, 0, allocation.get_width(), allocation.get_height());

      // Create a cairo context to draw on.
      auto cr = snapshot->append_cairo(rect);

      // paint the background
      Gdk::Cairo::set_source_rgba(cr, m_background);
      Gdk::Cairo::add_rectangle_to_path(cr, rect);
      cr->fill();

      // draw the foreground
      const double scale_x = 0.001 * (allocation.get_width() - m_padding.get_left() - m_padding.get_right());
      const double scale_y = 0.001 * (allocation.get_height() - m_padding.get_top() - m_padding.get_bottom());
      Gdk::Cairo::set_source_rgba(cr, m_foreground);
      cr->translate(m_padding.get_left(), m_padding.get_top());
      cr->rectangle(0.0, 0.0, 1000.0*scale_x, 1000.0*scale_y);
      cr->move_to(155.*scale_x, 165.*scale_y);
      cr->line_to(155.*scale_x, 838.*scale_y);
      cr->line_to(265.*scale_x, 900.*scale_y);
      cr->line_to(849.*scale_x, 564.*scale_y);
      cr->line_to(849.*scale_x, 438.*scale_y);
      cr->line_to(265.*scale_x, 100.*scale_y);
      cr->line_to(155.*scale_x, 165.*scale_y);
      cr->move_to(265.*scale_x, 100.*scale_y);
      cr->line_to(265.*scale_x, 652.*scale_y);
      cr->line_to(526.*scale_x, 502.*scale_y);
      cr->move_to(369.*scale_x, 411.*scale_y);
      cr->line_to(633.*scale_x, 564.*scale_y);
      cr->move_to(369.*scale_x, 286.*scale_y);
      cr->line_to(369.*scale_x, 592.*scale_y);
      cr->move_to(369.*scale_x, 286.*scale_y);
      cr->line_to(849.*scale_x, 564.*scale_y);
      cr->move_to(633.*scale_x, 564.*scale_y);
      cr->line_to(155.*scale_x, 838.*scale_y);
      cr->stroke();
    }

### Custom CSS Names

Many aspects of the look of widgets are controlled by CSS (Cascading Style Sheet) files. With CSS files you can choose color, font, line thickness, etc. If you give some widgets their own names or their own CSS classes, you can define CSS rules that apply only to those widgets, for instance certain buttons, without affecting other similar widgets.

#### CSS Node Name, Widget Name, CSS Class Name

There are three ways of referring from a widget to data in a CSS file:

- gtk_widget_class_set_css_name() can only be called from the class init function. It sets the CSS node name of all instances of a class (a GType). See the Class Init and Instance Init Functions section.

- Gtk::Widget::set_name() can be called from a C++ constructor. It sets the name of a widget instance.

- Gtk::Widget::add_class_name() can be called from a C++ constructor. It adds the name of a CSS class, used by a widget instance.

The following example shows a button with its own CSS node name, a label with a widget name and a label that uses its own CSS class.

#### 自定义样式信息

To add a style sheet to an application, use one of the Gtk::CssProvider::load_from_*() methods. Then add it with Gtk::StyleProvider::add_provider_for_display() (available since gtkmm 4.10) or Gtk::StyleContext::add_provider_for_display(). Gtk::StyleContext also contains methods to read some style information, but this class is deprecated since gtkmm 4.10.

CSS files are described in the documentation of GTK.

#### 示例

This example implements a button and two labels with custom style information.

Custom CSS Name

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "mybutton.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

    protected:
      // Signal handlers:
      void on_button_quit();
      void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
        const Glib::Error& error);

      // Child widgets:
      Gtk::Box m_VBox;
      MyButton m_Button_Child;
      Gtk::Label m_Label_Child1;
      Gtk::Label m_Label_Child2;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_Button_Quit;

      Glib::RefPtr<Gtk::CssProvider> m_refCssProvider;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `mybutton.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYBUTTON_H
    #define GTKMM_EXAMPLE_MYBUTTON_H

    #include <gtkmm/button.h>
    #include "myextrainit.h"

    class MyButton : public MyExtraInit, public Gtk::Button
    {
    public:
      MyButton(const Glib::ustring& label, bool mnemonic=false);
      ~MyButton() override;
    };

    #endif //GTKMM_EXAMPLE_MYBUTTON_H

File: `myextrainit.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_MYEXTRAINIT_H
    #define GTKMM_EXAMPLE_MYEXTRAINIT_H

    #include <glibmm/extraclassinit.h>
    #include <glibmm/ustring.h>

    // Calls gtk_widget_class_set_css_name() in the class init function.
    class MyExtraInit : public Glib::ExtraClassInit
    {
    public:
      MyExtraInit(const Glib::ustring& css_name);

    private:
      Glib::ustring m_css_name;
    };

    #endif //GTKMM_EXAMPLE_MYEXTRAINIT_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>
    #include <gtkmm/version.h>

    #define HAS_STYLE_PROVIDER_ADD_PROVIDER_FOR_DISPLAY GTKMM_CHECK_VERSION(4,9,1)

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_Button_Child("Button with custom CSS node name"),
      m_Label_Child1("Label with custom name"),
      m_Label_Child2("Label with custom CSS class"),
      m_Button_Quit("Quit")
    {
      set_title("Custom CSS name example");
      set_default_size(400, 200);

      m_VBox.set_margin(10);
      m_VBox.set_spacing(5);
      set_child(m_VBox);

      // There are 3 ways to set custom CSS data for a widget:
      // 1. The CSS name can be set for a GType with gtk_widget_class_set_css_name().
      // 2. The CSS name can be set for a widget instance with gtk_widget_set_name()
      //    (Gtk::Widget::set_name()).
      // 3. A CSS class can be set for a widget instance with
      //    gtk_widget_add_css_class() (Gtk::Widget::add_css_class()).
      //
      // gtk_widget_class_set_css_name(), if used, must be called in the class init
      // function. It has not been wrapped in a C++ function.
      // Gtk::Widget::set_name() and Gtk::Widget::add_css_class() can be called in
      // a C++ constructor.

      // CSS widget name, which must be used in the CSS file, preceded by "#".
      m_Label_Child1.set_name("my-label1");

      // CSS class name, which must be used in the CSS file, preceded by ".".
      m_Label_Child2.add_css_class("my-label2");

      // Add the child widgets to m_VBox:
      m_VBox.append(*Gtk::make_managed<Gtk::Label>("Ordinary label"));
      m_VBox.append(m_Button_Child);
      m_VBox.append(m_Label_Child1);
      m_VBox.append(m_Label_Child2);
      m_VBox.set_expand();
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_Button_Quit);
      m_ButtonBox.set_margin(6);
      m_Button_Quit.set_hexpand(true);
      m_Button_Quit.set_halign(Gtk::Align::END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
                  &ExampleWindow::on_button_quit) );

      // Load extra CSS file.
      m_refCssProvider = Gtk::CssProvider::create();
    #if HAS_STYLE_PROVIDER_ADD_PROVIDER_FOR_DISPLAY
      Gtk::StyleProvider::add_provider_for_display(get_display(), m_refCssProvider,
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    #else
      Gtk::StyleContext::add_provider_for_display(get_display(), m_refCssProvider,
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    #endif

      m_refCssProvider->signal_parsing_error().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_parsing_error));
      m_refCssProvider->load_from_path("custom_gtkmm.css");
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      set_visible(false);
    }

    void ExampleWindow::on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
      const Glib::Error& error)
    {
      std::cerr << "on_parsing_error(): " << error.what() << std::endl;
      if (section)
      {
        const auto file = section->get_file();
        if (file)
        {
          std::cerr << "  URI = " << file->get_uri() << std::endl;
        }

        auto start_location = section->get_start_location();
        auto end_location = section->get_end_location();
        std::cerr << "  start_line = " << start_location.get_lines()+1
                  << ", end_line = " << end_location.get_lines()+1 << std::endl;
        std::cerr << "  start_position = " << start_location.get_line_chars()
                  << ", end_position = " << end_location.get_line_chars() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

File: `mybutton.cc` (For use with gtkmm 4)

    #include "mybutton.h"
    #include <gtk/gtk.h> // For GTK_IS_BUTTON()
    #include <iostream>

    MyButton::MyButton(const Glib::ustring& label, bool mnemonic)
      :
      // The GType name will actually be gtkmm__CustomObject_MyButton
      Glib::ObjectBase("MyButton"),
      MyExtraInit("my-button"), // CSS node name, which must be used in the CSS file.
      Gtk::Button(label, mnemonic)
    {
      // This shows the GType name:
      std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl;

      // This shows that the GType still derives from GtkButton:
      std::cout << "Gtype is a GtkButton?: " << GTK_IS_BUTTON(gobj()) << std::endl;

      // The CSS name can be set either
      // - for a GType (in this case for your custom class) with gtk_widget_class_set_css_name(), or
      // - for a widget instance with gtk_widget_set_name() (Gtk::Widget::set_name()).
      //
      // gtk_widget_class_set_css_name(), if used, must be called in the class init function.
      // It has not been wrapped in a C++ function.
      // Gtk::Widget::set_name() can be called in a C++ constructor.
      //
      // Another alternative: The custom button inherits the CSS name "button" from
      // GtkButton. That name can be used in the CSS file.
    }

    MyButton::~MyButton()
    {
    }

File: `myextrainit.cc` (For use with gtkmm 4)

    #include "myextrainit.h"
    #include <gtkmm/widget.h>
    #include <gtk/gtk.h>

    namespace
    {
    using BaseObjectType = GtkWidget;
    using BaseClassType = GtkWidgetClass;

    // Extra class init function.
    void class_init_function(void* g_class, void* class_data)
    {
      g_return_if_fail(GTK_IS_WIDGET_CLASS(g_class));

      const auto klass = static_cast<BaseClassType*>(g_class);
      const auto css_name = static_cast<Glib::ustring*>(class_data);

      gtk_widget_class_set_css_name(klass, css_name->c_str());
    }

    // Extra instance init function.
    void instance_init_function(GTypeInstance* instance, void* /* g_class */)
    {
      g_return_if_fail(GTK_IS_WIDGET(instance));

      // Nothing to do here.
      // This extra instance init function just shows how such a function can
      // be added to a custom widget, if necessary.
    }

    } // anonymous namespace

    MyExtraInit::MyExtraInit(const Glib::ustring& css_name)
    :
    Glib::ExtraClassInit(class_init_function, &m_css_name, instance_init_function),
    m_css_name(css_name)
    {
    }

File: `custom_gtkmm.css` (For use with gtkmm 4)

    /* Example of a CSS style sheet. */

    my-button {
      background-color: red;
      color:            blue;
      padding:          10px 15px 20px 5px; /* top right bottom left */
      border:           solid black 2px;
      border-radius:    8px;
    }
    my-button:active {
      background-color: rgb(0,255,0);
    }
    #my-label1 {
      background-color: rgb(180,180,180);
      color:            rgb(0,255,255);
      padding:          10px 15px 10px 25px; /* top right bottom left */
    }
    #my-label1:hover {
      background-color: rgb(220,220,220);
    }
    .my-label2 {
      background-color: rgb(180,180,100);
      color:            black;
      padding:          5px 15px 5px 25px; /* top right bottom left */
    }
    .my-label2:hover {
      background-color: rgb(220,220,140);
    }

## 多线程程序

### 约束条件

由于libsigc++（尤其是sigc::trackable）不是线程安全的，所以在编写基于gtkmm的多线程程序时需要格外小心。这是因为使用libsigc++时在幕后发生的复杂交互不受互斥量或其他同步方式的保护。

#### 规则

在使用gtkmm编写多线程程序时需要遵守许多规则。这些规则下下面列出，但要注意的一点是从sigc::trackable派生类时需要格外小心，因为效果很不直观（特别是下述4、5条）。

1. 使用Glib::Dispatcher在工作线程中调用gtkmm函数（这点将在下一节详细介绍）

2. sigc::signal对象被创建其的线程所拥有。只有该线程应该将sigc::slot对象连接到信号对象，并且只有该线程应该在emit()信号或在信号上调用operator()()或是将已连接的sigc::slot对象置空。因此任何由gtkmm部件提供的信号对象都只应该在主GUI线程进行操作，以及任何从sigc::trackable派生的对象（其非静态方法由连接到信号对象的槽引用）都应该在该线程中销毁。

3. 任何sigc::connection对象都应视线程的拥有者在此线程调用了返回sigc::connection对象的方法。只有该线程才应在此对象上调用sigc::connection方法。

4. 通过调用`sigc::mem_fun()`函数创建的sigc::slot对象具有由sigc::trackable所派生类的成员函数的引用，请勿将其复制到另一个线程中，也不要让除创建线程以外的线程销毁它。

5. 如果从sigc::trackable派生一个特定的类对象，只应有一个线程通过调用`sigc::mem_fun()`创建代表该类任意非静态成员函数的sigc::slot对象。第一个创建这样的槽的线程被视为拥有相关对象的线程。这个线程可以对该类的任意非静态成员函数使用该函数以创建槽，或者在sigc::trackable对象断开连接和被销毁时将那些槽无效化。

6. 尽管glib自身是线程安全的，但是任何使用了libsigc++的glibmm封装都不会是线程安全的。因此只有在运行主循环的线程的主循环中才能调用Glib::SignalIdle::connect()、Glib::SignalIO::connect()、Glib::SignalTimeout::connect()、Glib::SignalTimeout::connect_seconds或是处理由它们返回的sigc::connection对象。

   对于Glib::SignalIdle::connect_once()、Glib::SignalTimeout::connect_once()、Glib::SignalTimeout::connect_seconds_once()等connect*_once()变体函数而言，只要它们使用的槽不是通过对sigc::trackable派生类的成员函数调用`sigc::mem_fun()`创建的，那么它们在任何情况下都是线程安全的。

### 使用Glib::Dispatcher

连接到sigc::signal对象的槽将会在调用信号emit()、operator()()方法的线程被调用。而Glib::Dispatcher的行为与此相反：与其连接的槽将在构造Glib::Dispatcher对象的线程执行（该线程必须正在运行Glib主循环）。如果在GUI主线程上构造了Glib::Dispatcher，任何工作线程都可以在其上调用emit()并让槽安全的执行连接的gtkmm函数。

一些线程安全规则对于Glib::Dispatcher依旧适用。如前所述，必须在接收器线程（将在该线程的主循环中执行连接的槽）构造Glib::Dispatcher对象。默认情况下接收器线程应该是程序的主进程，尽管Glib::Dispatcher有一个可以接受Glib::MainContext对象为参数并且可以在任何运行Glib主循环的线程中调用的构造函数。只有接受者线程才应该在Glib::Dispatcher对象上调用connect()或操纵相关的sigc::connection对象，除非你为此提供了额外的同步措施。一旦接收器线程连接了槽，任何工作现场都可以不加锁直接在Glib::Dispatcher对象上调用emit()，但前提是该对象是在工作线程启动前构造的，如果不是则需要进行额外的同步以确保可见性。

除了连接的槽在接收者线程执行以外，Glib::Dispatcher对象的行为与sigc::signal<void()>对象十分相似。因此它们不能传递未绑定的参数，也不能返回值。要传递未绑定参数最好的方法是使用线程安全的（同步）队列。尽管大多数编写多线程代码的人都会有一个可用的多线程代码，但是编写glibmm时没有（尽管线程安全与强异常安全结合有些微妙，但编写它们相对容易）。

接收者线程和工作线程都可以调用Glib::Dispatcher的emit()方法，虽然此操作需要在合理的范围内进行。在类Unix系统上Glib::Dispatcher共享同一个公共管道，从理论上讲在一个负载很大的系统上运行使用很大数量Dispatcher对象的程序至少可以填满系统。如果在接收者线程的主循环有机会读取并清空管道之前，管道已填满且接收器线程在此时尝试对Dispatcher调用emit()并因此对管道进行写入，这样会导致死锁。当然在接收者线程想要发出信号可以使用普通的sigc::signal<void()>对象。

### 示例

这是一个具有两个线程的示例程序，一个与所有gtkmm程序一样的GUI线程，和一个工作线程。当你按下`Start work`按钮时将创建工作线程，当工作完成或你按下`Stop work`按钮或是你按下`Quit`按钮时该线程将被删除。

Glib::Dispatcher被用于工作线程向GUI线程发送通知。ExampleWorker包含两个线程都可以访问的数据。该数据被一个std::mutex保护。只有GUI线程对GUI进行更新。

编译和链接多线程程序可能需要特殊的编译和链接参数。如果你使用g++编译器你需要添加`-pthread`选项。其他编译器可能会要求使用其他选项。如果你使用meson构建程序，如果你在构建脚本中添加了`dependency('threads')`，它会帮你处理多线程的复杂性。

多线程程序

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "exampleworker.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();

      // Called from the worker thread.
      void notify();

    private:
      // Signal handlers.
      void on_start_button_clicked();
      void on_stop_button_clicked();
      void on_quit_button_clicked();

      void update_start_stop_buttons();
      void update_widgets();

      // Dispatcher handler.
      void on_notification_from_worker_thread();

      // Member data.
      Gtk::Box m_VBox;
      Gtk::Box m_ButtonBox;
      Gtk::Button m_ButtonStart;
      Gtk::Button m_ButtonStop;
      Gtk::Button m_ButtonQuit;
      Gtk::ProgressBar m_ProgressBar;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::Dispatcher m_Dispatcher;
      ExampleWorker m_Worker;
      std::thread* m_WorkerThread;
    };

    #endif // GTKMM_EXAMPLEWINDOW_H

File: `exampleworker.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWORKER_H
    #define GTKMM_EXAMPLEWORKER_H

    #include <gtkmm.h>
    #include <thread>
    #include <mutex>

    class ExampleWindow;

    class ExampleWorker
    {
    public:
      ExampleWorker();

      // Thread function.
      void do_work(ExampleWindow* caller);

      void get_data(double* fraction_done, Glib::ustring* message) const;
      void stop_work();
      bool has_stopped() const;

    private:
      // Synchronizes access to member data.
      mutable std::mutex m_Mutex;

      // Data used by both GUI thread and worker thread.
      bool m_shall_stop;
      bool m_has_stopped;
      double m_fraction_done;
      Glib::ustring m_message;
    };

    #endif // GTKMM_EXAMPLEWORKER_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <iostream>

    ExampleWindow::ExampleWindow() :
      m_VBox(Gtk::Orientation::VERTICAL, 5),
      m_ButtonBox(Gtk::Orientation::HORIZONTAL),
      m_ButtonStart("Start work"),
      m_ButtonStop("Stop work"),
      m_ButtonQuit("_Quit", /* mnemonic= */ true),
      m_ProgressBar(),
      m_ScrolledWindow(),
      m_TextView(),
      m_Dispatcher(),
      m_Worker(),
      m_WorkerThread(nullptr)
    {
      set_title("Multi-threaded example");
      set_default_size(300, 300);

      m_VBox.set_margin(5);
      set_child(m_VBox);

      // Add the ProgressBar.
      m_VBox.append(m_ProgressBar);

      m_ProgressBar.set_text("Fraction done");
      m_ProgressBar.set_show_text();

      // Add the TextView, inside a ScrolledWindow.
      m_ScrolledWindow.set_child(m_TextView);

      // Only show the scrollbars when they are necessary.
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
      m_ScrolledWindow.set_expand();

      m_VBox.append(m_ScrolledWindow);

      m_TextView.set_editable(false);

      // Add the buttons to the ButtonBox.
      m_VBox.append(m_ButtonBox);

      m_ButtonBox.append(m_ButtonStart);
      m_ButtonBox.append(m_ButtonStop);
      m_ButtonBox.append(m_ButtonQuit);
      m_ButtonBox.set_margin(5);
      m_ButtonBox.set_spacing(5);
      m_ButtonStart.set_hexpand(true);
      m_ButtonStart.set_halign(Gtk::Align::END);

      // Connect the signal handlers to the buttons.
      m_ButtonStart.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_start_button_clicked));
      m_ButtonStop.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_stop_button_clicked));
      m_ButtonQuit.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_quit_button_clicked));

      // Connect the handler to the dispatcher.
      m_Dispatcher.connect(sigc::mem_fun(*this, &ExampleWindow::on_notification_from_worker_thread));

      // Create a text buffer mark for use in update_widgets().
      auto buffer = m_TextView.get_buffer();
      buffer->create_mark("last_line", buffer->end(), /* left_gravity= */ true);

      update_start_stop_buttons();
    }

    void ExampleWindow::on_start_button_clicked()
    {
      if (m_WorkerThread)
      {
        std::cout << "Can't start a worker thread while another one is running." << std::endl;
      }
      else
      {
        // Start a new worker thread.
        m_WorkerThread = new std::thread(
          [this]
          {
            m_Worker.do_work(this);
          });
      }
      update_start_stop_buttons();
    }

    void ExampleWindow::on_stop_button_clicked()
    {
      if (!m_WorkerThread)
      {
        std::cout << "Can't stop a worker thread. None is running." << std::endl;
      }
      else
      {
       // Order the worker thread to stop.
        m_Worker.stop_work();
        m_ButtonStop.set_sensitive(false);
      }
    }

    void ExampleWindow::update_start_stop_buttons()
    {
      const bool thread_is_running = m_WorkerThread != nullptr;

      m_ButtonStart.set_sensitive(!thread_is_running);
      m_ButtonStop.set_sensitive(thread_is_running);
    }

    void ExampleWindow::update_widgets()
    {
      double fraction_done;
      Glib::ustring message_from_worker_thread;
      m_Worker.get_data(&fraction_done, &message_from_worker_thread);

      m_ProgressBar.set_fraction(fraction_done);

      if (message_from_worker_thread != m_TextView.get_buffer()->get_text())
      {
        auto buffer = m_TextView.get_buffer();
        buffer->set_text(message_from_worker_thread);

        // Scroll the last inserted line into view. That's somewhat complicated.
        auto iter = buffer->end();
        iter.set_line_offset(0); // Beginning of last line
        auto mark = buffer->get_mark("last_line");
        buffer->move_mark(mark, iter);
        m_TextView.scroll_to(mark);
        // TextView::scroll_to(iter) is not perfect.
        // We do need a TextMark to always get the last line into view.
      }
    }

    void ExampleWindow::on_quit_button_clicked()
    {
      if (m_WorkerThread)
      {
        // Order the worker thread to stop and wait for it to stop.
        m_Worker.stop_work();
        if (m_WorkerThread->joinable())
          m_WorkerThread->join();
      }
      set_visible(false);
    }

    // notify() is called from ExampleWorker::do_work(). It is executed in the worker
    // thread. It triggers a call to on_notification_from_worker_thread(), which is
    // executed in the GUI thread.
    void ExampleWindow::notify()
    {
      m_Dispatcher.emit();
    }

    void ExampleWindow::on_notification_from_worker_thread()
    {
      if (m_WorkerThread && m_Worker.has_stopped())
      {
        // Work is done.
        if (m_WorkerThread->joinable())
          m_WorkerThread->join();
        delete m_WorkerThread;
        m_WorkerThread = nullptr;
        update_start_stop_buttons();
      }
      update_widgets();
    }

File: `exampleworker.cc` (For use with gtkmm 4)

    #include "exampleworker.h"
    #include "examplewindow.h"
    #include <sstream>
    #include <chrono>

    ExampleWorker::ExampleWorker() :
      m_Mutex(),
      m_shall_stop(false),
      m_has_stopped(false),
      m_fraction_done(0.0),
      m_message()
    {
    }

    // Accesses to these data are synchronized by a mutex.
    // Some microseconds can be saved by getting all data at once, instead of having
    // separate get_fraction_done() and get_message() methods.
    void ExampleWorker::get_data(double* fraction_done, Glib::ustring* message) const
    {
      std::lock_guard<std::mutex> lock(m_Mutex);

      if (fraction_done)
        *fraction_done = m_fraction_done;

      if (message)
        *message = m_message;
    }

    void ExampleWorker::stop_work()
    {
      std::lock_guard<std::mutex> lock(m_Mutex);
      m_shall_stop = true;
    }

    bool ExampleWorker::has_stopped() const
    {
      std::lock_guard<std::mutex> lock(m_Mutex);
      return m_has_stopped;
    }

    void ExampleWorker::do_work(ExampleWindow* caller)
    {
      {
        std::lock_guard<std::mutex> lock(m_Mutex);
        m_has_stopped = false;
        m_fraction_done = 0.0;
        m_message = "";
      } // The mutex is unlocked here by lock's destructor.

      // Simulate a long calculation.
      for (int i = 0; ; ++i) // do until break
      {
        std::this_thread::sleep_for(std::chrono::milliseconds(250));

        {
          std::lock_guard<std::mutex> lock(m_Mutex);

          m_fraction_done += 0.01;

          if (i % 4 == 3)
          {
            std::ostringstream ostr;
            ostr << (m_fraction_done * 100.0) << "% done\n";
            m_message += ostr.str();
          }

          if (m_fraction_done >= 1.0)
          {
            m_message += "Finished";
            break;
          }
          if (m_shall_stop)
          {
            m_message += "Stopped";
            break;
          }
        }

        caller->notify();
      }

      {
        std::lock_guard<std::mutex> lock(m_Mutex);
        m_shall_stop = false;
        m_has_stopped = true;
      }

      caller->notify();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      //Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

## 推荐技术

本小节将向你展示一些创建gtkmm应用程序的智慧、风格指南和提示。

Use Meson! It is your friend :) It examines C and C++ files, determines how they depend on each other, and generates `build.ninja` or an equivalent file so the files can be compiled in the correct order. Meson permits automatic configuration of software installation, handling a large number of system quirks to increase portability.

子类部件可以更好的组织你的代码。至少你的主窗口类应该继承Window。这样你可以使主窗口的子部件和信号处理函数成为该类的成员。

Create your own signals instead of passing pointers around. Objects can communicate with each other via signals and signal handlers. This is much simpler than objects holding pointers to each other and calling each other's methods. gtkmm's classes use special versions of sigc::signal, but you should use normal sigc::signals, as described in the libsigc++ documentation.

### 应用程序生命周期

Most applications will have only one Window, or only one main window. These applications can use Gtk::Application::make_window_and_run(int argc, char** argv, T_Args&&... args). It creates and shows a window. When the window is hidden, make_window_and_run() deletes the window and returns to the caller. This might happen when the user closes the window, or when your code decides to hide the window with set_visible(false). You can prevent the user from closing the window (for instance, if there are unsaved changes) by overriding Gtk::Window::on_close_request().

我们的大多数示例都是用这种技术。

### 使用gtkmm部件

我们的示例都倾向于使用相同的结构。它们按以下步骤使用Widget：

1. Declare a variable of the type of Widget you wish to use, generally as member variable of a derived container class. You could also declare a pointer to the widget type, and then create it with `new` or `Gtk::make_managed()` in your code.

2. Set the attributes of the widget. If the widget has no default constructor, then you will need to initialize the widget in the initializer list of your container class's constructor.

3. 为所以你需要使用的信号连接合适的信号处理函数。

4. 调用适当的函数将部件装入容器中，例如：Gtk::Box::append()。

If you don't want all widgets to be shown, call Gtk::Widget::set_visible(false) on the widgets that you don't want to show. If a container widget is hidden, all of its child widgets are also hidden, even if set_visible(false) is not called on the child widgets.

## 构建应用程序

This chapter is similar to Building applications and following sections in the Getting Started chapter in the GTK documentation. The same application is built, but gtkmm is used instead of GTK.

一个应用程序由许多文件组成：

二进制文件

  这将被安装在`/usr/bin`。

桌面文件

  桌面文件为桌面shell提供了于应用程序有关的重要信息，例如应用程序的名称、图标D-Bus名、启动时的命令行等。此文件将安装在`/usr/share/applications`。

图标

  图标将被安装在`/usr/share/icons/hicolor/48x48/apps`，无论当前主题是什么你都可以在其中找到图标。

设置模式

  如果应用程序使用了Gio::Settings，它需要将它的模式安装到`/usr/share/glib-2.0/schemas`，以便dconf-editor之类的工具可以找到它。

其他资源

  像Gtk::BuilderUI文件这样的其他文件，最好从储存应用程序自身的二进制中加载。这样就不需要按照传统将大多数文件安装在`/usr/share`中特定应用程序的位置。

gtkmm包括构建在Gio::Application上的应用程序支持。在本章中我们将从头开始构建一个简单的应用程序。然后逐渐向其中添加更多内容。在此过程中我们将学习Gtk::Application、Gtk::Builder、资源、菜单、设置、Gtk::HeaderBar、Gtk::Stack、Gtk::SearchBar、Gtk::ListBox等内容。

The full, buildable sources for these examples can be found in the `examples/book/buildapp` directory of the gtkmm-documentation source distribution, or online in the gtkmm-documentation git repository. You can build each example separately by using `meson` and `ninja` with the `meson.build` file or by using `make` with the `Makefile.example` file. For more information, see the `README` included in the `buildapp` directory.

### 简单的应用程序

使用Gtk::Application时，`main()`函数可以非常简单。我们只是在应用程序类实例上调用Gtk::Application::run()。

应用程序的所有逻辑都在应用程序类中，该类是Gtk::Application的子类。现在在我们的示例中还没有任何很有趣的功能。它要做的事就是在没有参数的情况下打开一个窗口，或在有参数的启动状态下打开给定的文件（准确的说我们的应用程序类尝试打开文件，但是应用程序的窗口子类并没有执行应执行的操作）。

为了处理这两种情况，我们覆写了signal_activate()和signal_open()的默认处理函数，在没有命令行参数时前者的处理函数被调用，有命令行参数时后者的处理函数被调用。

Gio::Application Reference

Gtk::Application Reference

作为gtkmm应用程序支持的另一个重要的类是Gtk::ApplicationWindow。通常我们也会将其子类化。此时我们的子类还没有做任何事，所以我们只会得到一个空白的窗口。

作为应用程序初始设置的一部分，我们还为其创建了一个图标和桌面文件。请注意在使用这个桌面文件之前你还需要将文件中的@bindir@替换为应用程序二进制的实际路径。

到目前为止我们做到了：

简单的应用程序

这看起来还无法让人印象深刻，但是我们的应用程序已经在会话总线上展示了自己。现在它具有单实例语义，并且接受命令行参数作为文件路径。

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPLICATION_H
    #define GTKMM_EXAMPLEAPPLICATION_H

    #include <gtkmm.h>

    class ExampleAppWindow;

    class ExampleApplication: public Gtk::Application
    {
    protected:
      ExampleApplication();

    public:
      static Glib::RefPtr<ExampleApplication> create();

    protected:
      // Override default signal handlers:
      void on_activate() override;
      void on_open(const Gio::Application::type_vec_files& files,
        const Glib::ustring& hint) override;

    private:
      ExampleAppWindow* create_appwindow();
      void on_hide_window(Gtk::Window* window);
    };

    #endif /* GTKMM_EXAMPLEAPPLICATION_H */

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"
    #include "exampleappwindow.h"

    ExampleApplication::ExampleApplication()
    : Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
    {
    }

    Glib::RefPtr<ExampleApplication> ExampleApplication::create()
    {
      return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
    }

    ExampleAppWindow* ExampleApplication::create_appwindow()
    {
      auto appwindow = new ExampleAppWindow();

      // Make sure that the application runs for as long this window is still open.
      add_window(*appwindow);

      // A window can be added to an application with Gtk::Application::add_window()
      // or Gtk::Window::set_application(). When all added windows have been hidden
      // or removed, the application stops running (Gtk::Application::run() returns()),
      // unless Gio::Application::hold() has been called.

      // Delete the window when it is hidden.
      appwindow->signal_hide().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleApplication::on_hide_window), appwindow));

      return appwindow;
    }

    void ExampleApplication::on_activate()
    {
      // The application has been started, so let's show a window.
      auto appwindow = create_appwindow();
      appwindow->present();
    }

    void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
      const Glib::ustring& /* hint */)
    {
      // The application has been asked to open some files,
      // so let's open a new view for each one.
      ExampleAppWindow* appwindow = nullptr;
      auto windows = get_windows();
      if (windows.size() > 0)
        appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);

      if (!appwindow)
        appwindow = create_appwindow();

      for (const auto& file : files)
        appwindow->open_file_view(file);

      appwindow->present();
    }

    void ExampleApplication::on_hide_window(Gtk::Window* window)
    {
      delete window;
    }

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"

    ExampleAppWindow::ExampleAppWindow()
    : Gtk::ApplicationWindow()
    {
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& /* file */)
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"

    int main(int argc, char* argv[])
    {
      auto application = ExampleApplication::create();

      // Start the application, showing the initial window,
      // and opening extra views for any files that it is asked to open,
      // for instance as a command-line parameter.
      // run() will return when the last window has been closed.
      return application->run(argc, argv);
    }

File: `exampleapp.desktop.in` (For use with gtkmm 4)

    [Desktop Entry]
    Type=Application
    Name=Gtkmm example
    GenericName=Example
    Comment=From the "Programming with gtkmm 4" tutorial
    Icon=exampleapp
    StartupNotify=true
    Exec=@bindir@/exampleapp %U
    Categories=GNOME;GTK;Utility

### 填充窗口

再次步骤中，我们使用Gtk::Builder实例将Gtk::Builder UI文件和我们的应用程序窗口类相关联。

我们的简单UI文件为窗口设定了一个标题，并将一个Gtk::Stack文件放入了窗口中。

为了在我们的应用程序中使用这个文件，我们重访Gtk::ApplicationWindow子类，并在ExampleAppWindow::create()成员函数中调用Gtk::Builder::create_from_resource()和Gtk::Builder::get_widget_derived()以获取我们的Gtk::ApplicationWindow子类的实例。更多有关的get_widget_derived()的信息，请参见使用派生部件小节。

You may have noticed that we use the _from_resource() variant of the method that reads the ui file. Now we need to use GLib's resource functionality to include the ui file in the binary. This is commonly done by listing all resources in a .gresource.xml file. This file has to be converted into a C source file that will be compiled and linked into the application together with the other source files. To do so, we use the glib-compile-resources utility:

    $ glib-compile-resources --target=resources.c --generate-source exampleapp.gresource.xml

The Gio::Resource and glib-compile-resources section contains more information about resource files. If you build with Meson, use the `compile_resources()` function in Meson's GNOME module.

我们的应用程序现在看起来像这样：

填充窗口

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step1/exampleapplication.h"
    // Equal to the corresponding file in step1

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"
    #include "exampleappwindow.h"
    #include <iostream>
    #include <exception>

    ExampleApplication::ExampleApplication()
    : Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
    {
    }

    Glib::RefPtr<ExampleApplication> ExampleApplication::create()
    {
      return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
    }

    ExampleAppWindow* ExampleApplication::create_appwindow()
    {
      auto appwindow = ExampleAppWindow::create();

      // Make sure that the application runs for as long this window is still open.
      add_window(*appwindow);

      // A window can be added to an application with Gtk::Application::add_window()
      // or Gtk::Window::set_application(). When all added windows have been hidden
      // or removed, the application stops running (Gtk::Application::run() returns()),
      // unless Gio::Application::hold() has been called.

      // Delete the window when it is hidden.
      appwindow->signal_hide().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleApplication::on_hide_window), appwindow));

      return appwindow;
    }

    void ExampleApplication::on_activate()
    {
      try
      {
        // The application has been started, so let's show a window.
        auto appwindow = create_appwindow();
        appwindow->present();
      }
      // If create_appwindow() throws an exception (perhaps from Gtk::Builder),
      // no window has been created, no window has been added to the application,
      // and therefore the application will stop running.
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
      const Glib::ustring& /* hint */)
    {
      // The application has been asked to open some files,
      // so let's open a new view for each one.
      ExampleAppWindow* appwindow = nullptr;
      auto windows = get_windows();
      if (windows.size() > 0)
        appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);

      try
      {
        if (!appwindow)
          appwindow = create_appwindow();

        for (const auto& file : files)
          appwindow->open_file_view(file);

        appwindow->present();
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_hide_window(Gtk::Window* window)
    {
      delete window;
    }

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <stdexcept>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& /* file */)
    {
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step1/main.cc"
    // Equal to the corresponding file in step1

File: `exampleapp.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/org/gtkmm/exampleapp">
        <file preprocess="xml-stripblanks">window.ui</file>
      </gresource>
    </gresources>

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkStack" id="stack"/>
            </child>
          </object>
        </child>
      </object>
    </interface>

### 打开文件

在这一步中，我们使我们的应用程序显示由命令行参数给定的文件的所有内容。

为此，我们向我们的应用程序窗口中添加一个数据成员，此保持一个指向Gtk::Stack的指针。我们通过在应用程序窗口的构造函数中调用Gtk::Builder::get_widget()获得指向该子部件的指针。

现在我们重访对每个命令行参数都会调用的ExampleAppWindow::open_file_view()成员函数，并构造一个Gtk::TextView然后将其作为页面添加到Gtk::Stack中。

最后，我们将Gtk::StackSwitcher添加到UI文件的标题栏区域，并告诉它如何显示Gtk::Stack的相关信息。

堆切换器(StackSwitcher)从其所属的堆(Stack)中获取其所需的关于显示选项卡的相关信息。在这，作为Gtk::Stack::add()方法的最后一个参数，我们为每个显示的文件传递一个标签。

我们的应用程序开始成形：

打开文件

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step1/exampleapplication.h"
    // Equal to the corresponding file in step1

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Gtk::Stack* m_stack {nullptr};
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "../step2/exampleapplication.cc"
    // Equal to the corresponding file in step2

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        view->get_buffer()->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step1/main.cc"
    // Equal to the corresponding file in step1

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkStack" id="stack"/>
            </child>
          </object>
        </child>
      </object>
    </interface>

### 菜单

菜单显示在标题栏的右侧。它旨在放置所有应用程序不常用的动作。

就像应用程序窗口一样，我们在UI文件中指定一个菜单，并将其作为资源添加到二进制中。

为了使菜单出现，我们必须加载UI文件，并将生成的菜单模型与添加到标题栏右侧的菜单按钮相关联。由于菜单通过激活Gio::Action工作，我们还需要向应用程序中添加一组合适的动作。

最好在on_startup()的默认信号处理函数中完成添加动作，并确保每个应用程序实例都只调用一次。

现在我们的首选项菜单还什么都不做，但是退出菜单项是有效的。也可以用常用的Ctrl-Q快捷键激活它。快捷键通过Gtk::Application::set_accel_for_action()添加。

菜单如下所示

菜单

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPLICATION_H
    #define GTKMM_EXAMPLEAPPLICATION_H

    #include <gtkmm.h>

    class ExampleAppWindow;

    class ExampleApplication: public Gtk::Application
    {
    protected:
      ExampleApplication();

    public:
      static Glib::RefPtr<ExampleApplication> create();

    protected:
      // Override default signal handlers:
      void on_startup() override;
      void on_activate() override;
      void on_open(const Gio::Application::type_vec_files& files,
        const Glib::ustring& hint) override;

    private:
      ExampleAppWindow* create_appwindow();
      void on_hide_window(Gtk::Window* window);
      void on_action_preferences();
      void on_action_quit();
    };

    #endif /* GTKMM_EXAMPLEAPPLICATION_H */

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Gtk::Stack* m_stack {nullptr};
      Gtk::MenuButton* m_gears {nullptr};
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"
    #include "exampleappwindow.h"
    #include <iostream>
    #include <exception>

    ExampleApplication::ExampleApplication()
    : Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
    {
    }

    Glib::RefPtr<ExampleApplication> ExampleApplication::create()
    {
      return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
    }

    ExampleAppWindow* ExampleApplication::create_appwindow()
    {
      auto appwindow = ExampleAppWindow::create();

      // Make sure that the application runs for as long this window is still open.
      add_window(*appwindow);

      // A window can be added to an application with Gtk::Application::add_window()
      // or Gtk::Window::set_application(). When all added windows have been hidden
      // or removed, the application stops running (Gtk::Application::run() returns()),
      // unless Gio::Application::hold() has been called.

      // Delete the window when it is hidden.
      appwindow->signal_hide().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleApplication::on_hide_window), appwindow));

      return appwindow;
    }

    void ExampleApplication::on_startup()
    {
      // Call the base class's implementation.
      Gtk::Application::on_startup();

      // Add actions and keyboard accelerators for the menu.
      add_action("preferences", sigc::mem_fun(*this, &ExampleApplication::on_action_preferences));
      add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_action_quit));
      set_accel_for_action("app.quit", "<Ctrl>Q");
    }

    void ExampleApplication::on_activate()
    {
      try
      {
        // The application has been started, so let's show a window.
        auto appwindow = create_appwindow();
        appwindow->present();
      }
      // If create_appwindow() throws an exception (perhaps from Gtk::Builder),
      // no window has been created, no window has been added to the application,
      // and therefore the application will stop running.
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
      const Glib::ustring& /* hint */)
    {
      // The application has been asked to open some files,
      // so let's open a new view for each one.
      ExampleAppWindow* appwindow = nullptr;
      auto windows = get_windows();
      if (windows.size() > 0)
        appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);

      try
      {
        if (!appwindow)
          appwindow = create_appwindow();

        for (const auto& file : files)
          appwindow->open_file_view(file);

        appwindow->present();
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_hide_window(Gtk::Window* window)
    {
      delete window;
    }

    void ExampleApplication::on_action_preferences()
    {

    }

    void ExampleApplication::on_action_quit()
    {
      // Gio::Application::quit() will make Gio::Application::run() return,
      // but it's a crude way of ending the program. The window is not removed
      // from the application. Neither the window's nor the application's
      // destructors will be called, because there will be remaining reference
      // counts in both of them. If we want the destructors to be called, we
      // must remove the window from the application. One way of doing this
      // is to hide the window. See comment in create_appwindow().
      auto windows = get_windows();
      for (auto window : windows)
        window->set_visible(false);

      // Not really necessary, when Gtk::Widget::set_visible(false) is called,
      // unless Gio::Application::hold() has been called without a corresponding
      // call to Gio::Application::release().
      quit();
    }

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      // Connect the menu to the MenuButton m_gears.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        view->get_buffer()->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step1/main.cc"
    // Equal to the corresponding file in step1

File: `exampleapp.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/org/gtkmm/exampleapp">
        <file preprocess="xml-stripblanks">window.ui</file>
        <file preprocess="xml-stripblanks">gears_menu.ui</file>
      </gresource>
    </gresources>

File: `gears_menu.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <menu id="menu">
        <section>
          <item>
            <attribute name="label" translatable="yes">_Preferences</attribute>
            <attribute name="action">app.preferences</attribute>
          </item>
        </section>
        <section>
          <item>
            <attribute name="label" translatable="yes">_Quit</attribute>
            <attribute name="action">app.quit</attribute>
          </item>
        </section>
      </menu>
    </interface>

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkStack" id="stack"/>
            </child>
          </object>
        </child>
      </object>
    </interface>

### 首选项对话框

经典的应用程序会有一些首选项，它们应该被记住而不需要在下次运行时在进行设置。即使对于我们这个简单的应用程序示例，我们可能也想改变用于文本内容的字体。

我们将使用Gio::Settings来储存我们的首选项。Gio::Settings需要一个用于描述我们的设置的模式，在本例中为`org.gtkmm.exampleapp.gschema.xml`文件。

Before we can make use of this schema in our application, we need to compile it into the binary form that Gio::Settings expects. GIO provides macros to do this in autotools-based projects. See the description of GSettings. Meson provides the `compile_schemas()` function in the GNOME module.

接下来，我们需要将设置连接到应该被控制的部件上。一种很方便的方法是使用Gio::Settings::bind()将对象的属性和设置键相互绑定，就像我们在ExampleAppWindow的构造函数中做的过渡设置一样。

    m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
    m_settings->bind("transition", m_stack->property_transition_type());

字体设置的连接要麻烦的多，因为我们需要先创建对应的对象属性的Gtk::TextTag。以下代码在ExampleAppWindow::open_file_view()函数中。

    auto tag = buffer->create_tag();
    m_settings->bind("font", tag->property_font());
    buffer->apply_tag(tag, buffer->begin(), buffer->end());

At this point, the application will already react if you change one of the settings, e.g. using the `gsettings` commandline tool. Of course, we expect the application to provide a preference dialog for these. So lets do that now. Our preference dialog will be a subclass of Gtk::Window, and we'll use the same techniques that we've already seen in ExampleAppWindow: a Gtk::Builder ui file and settings bindings. In this case the bindings are more involved, though. We use Gtk::FontDialogButton and Gtk::DropDown in the preference dialog. The types of the properties in these classes can't be automatically converted to the string type that Gio::Settings requires.

我们创建`prefs.ui`文件和ExampleAppPrefs子类之后，我们重访ExampleApplication::on_action_preferences()成员函数，并使其打开一个新的首选项对话框。

    auto prefs_dialog = ExampleAppPrefs::create(*get_active_window());
    prefs_dialog->present();

完成所有工作后，我们的应用程序现在可以显示如下所示的首选项对话框：

首选项对话框

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleappprefs.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPPREFS_H_
    #define GTKMM_EXAMPLEAPPPREFS_H_

    #include <gtkmm.h>

    #ifdef GLIBMM_CHECK_VERSION
    #define HAS_GIO_SETTINGS_BIND_WITH_MAPPING GLIBMM_CHECK_VERSION(2,75,0)
    #else
    #define HAS_GIO_SETTINGS_BIND_WITH_MAPPING 0
    #endif

    class ExampleAppPrefs : public Gtk::Window
    {
    public:
      ExampleAppPrefs(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppPrefs* create(Gtk::Window& parent);

    protected:
    #if HAS_GIO_SETTINGS_BIND_WITH_MAPPING
      // Mappings from Gio::Settings to properties
      std::optional<Pango::FontDescription> map_from_ustring_to_fontdesc(const Glib::ustring& font);
      std::optional<Glib::ustring> map_from_fontdesc_to_ustring(const Pango::FontDescription& fontdesc);
      std::optional<unsigned int> map_from_ustring_to_int(const Glib::ustring& transition);
      std::optional<Glib::ustring> map_from_int_to_ustring(const unsigned int& pos);
    #else
      // Signal handlers
      void on_font_setting_changed(const Glib::ustring& key);
      void on_font_selection_changed();
      void on_transition_setting_changed(const Glib::ustring& key);
      void on_transition_selection_changed();
    #endif
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::FontDialogButton* m_font {nullptr};
      Gtk::DropDown* m_transition {nullptr};
    };

    #endif /* GTKMM_EXAMPLEAPPPREFS_H_ */

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::Stack* m_stack {nullptr};
      Gtk::MenuButton* m_gears {nullptr};
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"
    #include "exampleappwindow.h"
    #include "exampleappprefs.h"
    #include <iostream>
    #include <exception>

    ExampleApplication::ExampleApplication()
    : Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
    {
    }

    Glib::RefPtr<ExampleApplication> ExampleApplication::create()
    {
      return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
    }

    ExampleAppWindow* ExampleApplication::create_appwindow()
    {
      auto appwindow = ExampleAppWindow::create();

      // Make sure that the application runs for as long this window is still open.
      add_window(*appwindow);

      // A window can be added to an application with Gtk::Application::add_window()
      // or Gtk::Window::set_application(). When all added windows have been hidden
      // or removed, the application stops running (Gtk::Application::run() returns()),
      // unless Gio::Application::hold() has been called.

      // Delete the window when it is hidden.
      appwindow->signal_hide().connect(sigc::bind(sigc::mem_fun(*this,
        &ExampleApplication::on_hide_window), appwindow));

      return appwindow;
    }

    void ExampleApplication::on_startup()
    {
      // Call the base class's implementation.
      Gtk::Application::on_startup();

      // Add actions and keyboard accelerators for the menu.
      add_action("preferences", sigc::mem_fun(*this, &ExampleApplication::on_action_preferences));
      add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_action_quit));
      set_accel_for_action("app.quit", "<Ctrl>Q");
    }

    void ExampleApplication::on_activate()
    {
      try
      {
        // The application has been started, so let's show a window.
        auto appwindow = create_appwindow();
        appwindow->present();
      }
      // If create_appwindow() throws an exception (perhaps from Gtk::Builder),
      // no window has been created, no window has been added to the application,
      // and therefore the application will stop running.
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
      const Glib::ustring& /* hint */)
    {
      // The application has been asked to open some files,
      // so let's open a new view for each one.
      ExampleAppWindow* appwindow = nullptr;
      auto windows = get_windows();
      if (windows.size() > 0)
        appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);

      try
      {
        if (!appwindow)
          appwindow = create_appwindow();

        for (const auto& file : files)
          appwindow->open_file_view(file);

        appwindow->present();
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_hide_window(Gtk::Window* window)
    {
      delete window;
    }

    void ExampleApplication::on_action_preferences()
    {
      try
      {
        auto prefs_dialog = ExampleAppPrefs::create(*get_active_window());
        prefs_dialog->present();

        // Delete the dialog when it is hidden.
        prefs_dialog->signal_hide().connect(sigc::bind(sigc::mem_fun(*this,
          &ExampleApplication::on_hide_window), prefs_dialog));
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_action_preferences(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_action_preferences(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_action_quit()
    {
      // Gio::Application::quit() will make Gio::Application::run() return,
      // but it's a crude way of ending the program. The window is not removed
      // from the application. Neither the window's nor the application's
      // destructors will be called, because there will be remaining reference
      // counts in both of them. If we want the destructors to be called, we
      // must remove the window from the application. One way of doing this
      // is to hide the window. See comment in create_appwindow().
      auto windows = get_windows();
      for (auto window : windows)
        window->set_visible(false);

      // Not really necessary, when Gtk::Widget::set_visible(false) is called,
      // unless Gio::Application::hold() has been called without a corresponding
      // call to Gio::Application::release().
      quit();
    }

File: `exampleappprefs.cc` (For use with gtkmm 4)

    #include "exampleappprefs.h"
    #include "exampleappwindow.h"
    #include <array>
    #include <stdexcept>

    namespace
    {
    struct TransitionTypeStruct
    {
      Glib::ustring id;   // Value of "transition" key in Gio::Settings
      Glib::ustring text; // Text in the DropDown list
    };

    const std::array<TransitionTypeStruct, 3> transitionTypes =
    {
      TransitionTypeStruct{"none",             "None"},
      TransitionTypeStruct{"crossfade",        "Fade"},
      TransitionTypeStruct{"slide-left-right", "Slide"}
    };

    } // anonymous namespace

    ExampleAppPrefs::ExampleAppPrefs(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::Window(cobject),
      m_refBuilder(refBuilder)
    {
      m_font = m_refBuilder->get_widget<Gtk::FontDialogButton>("font");
      if (!m_font)
        throw std::runtime_error("No \"font\" object in prefs.ui");

      m_transition = m_refBuilder->get_widget<Gtk::DropDown>("transition");
      if (!m_transition)
        throw std::runtime_error("No \"transition\" object in prefs.ui");

      // DropDown for transition type.
      auto string_list = Gtk::StringList::create();
      for (const auto& transitionType : transitionTypes)
        string_list->append(transitionType.text);

      m_transition->set_model(string_list);

      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");

      // Connect preference properties to the Gio::Settings.
    #if HAS_GIO_SETTINGS_BIND_WITH_MAPPING
      m_settings->bind<Glib::ustring, Pango::FontDescription>("font",
        m_font->property_font_desc(), Gio::Settings::BindFlags::DEFAULT,
        sigc::mem_fun(*this, &ExampleAppPrefs::map_from_ustring_to_fontdesc),
        sigc::mem_fun(*this, &ExampleAppPrefs::map_from_fontdesc_to_ustring));
      m_settings->bind<Glib::ustring, unsigned int>("transition",
        m_transition->property_selected(), Gio::Settings::BindFlags::DEFAULT,
        sigc::mem_fun(*this, &ExampleAppPrefs::map_from_ustring_to_int),
        sigc::mem_fun(*this, &ExampleAppPrefs::map_from_int_to_ustring));
    #else
      // This is easier when g_settings_bind_with_mapping() is
      // wrapped in a Gio::Settings method.
      m_settings->signal_changed("font").connect(
        sigc::mem_fun(*this, &ExampleAppPrefs::on_font_setting_changed));
      m_font->property_font_desc().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppPrefs::on_font_selection_changed));

      m_settings->signal_changed("transition").connect(
        sigc::mem_fun(*this, &ExampleAppPrefs::on_transition_setting_changed));
      m_transition->property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppPrefs::on_transition_selection_changed));

      // Synchronize the preferences dialog with m_settings.
      on_font_setting_changed("font");
      on_transition_setting_changed("transition");
    #endif
    }

    //static
    ExampleAppPrefs* ExampleAppPrefs::create(Gtk::Window& parent)
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/prefs.ui");

      auto dialog = Gtk::Builder::get_widget_derived<ExampleAppPrefs>(refBuilder, "prefs_dialog");
      if (!dialog)
        throw std::runtime_error("No \"prefs_dialog\" object in prefs.ui");

      dialog->set_transient_for(parent);

      return dialog;
    }

    #if HAS_GIO_SETTINGS_BIND_WITH_MAPPING
    std::optional<Pango::FontDescription> ExampleAppPrefs::map_from_ustring_to_fontdesc(const Glib::ustring& font)
    {
      return Pango::FontDescription(font);
    }

    std::optional<Glib::ustring> ExampleAppPrefs::map_from_fontdesc_to_ustring(const Pango::FontDescription& fontdesc)
    {
      return fontdesc.to_string();
    }

    std::optional<unsigned int> ExampleAppPrefs::map_from_ustring_to_int(const Glib::ustring& transition)
    {
      for (std::size_t i = 0; i < transitionTypes.size(); ++i)
      {
        if (transitionTypes[i].id == transition)
          return i;
      }
      return std::nullopt;
    }

    std::optional<Glib::ustring> ExampleAppPrefs::map_from_int_to_ustring(const unsigned int& pos)
    {
      if (pos >= transitionTypes.size())
        return std::nullopt;
      return transitionTypes[pos].id;
    }
    #else
    void ExampleAppPrefs::on_font_setting_changed(const Glib::ustring& /* key */)
    {
      const auto font_setting = m_settings->get_string("font");
      const auto font_button = m_font->get_font_desc().to_string();
      if (font_setting != font_button)
        m_font->set_font_desc(Pango::FontDescription(font_setting));
    }

    void ExampleAppPrefs::on_font_selection_changed()
    {
      const auto font_setting = m_settings->get_string("font");
      const auto font_button = m_font->get_font_desc().to_string();
      if (font_setting != font_button)
        m_settings->set_string("font", font_button);
    }

    void ExampleAppPrefs::on_transition_setting_changed(const Glib::ustring& /* key */)
    {
      const auto transition_setting = m_settings->get_string("transition");
      const auto transition_button = transitionTypes[m_transition->get_selected()].id;
      if (transition_setting != transition_button)
      {
        for (std::size_t i = 0; i < transitionTypes.size(); ++i)
        {
          if (transitionTypes[i].id == transition_setting)
          {
            m_transition->set_selected(i);
            break;
          }
        }
      }
    }

    void ExampleAppPrefs::on_transition_selection_changed()
    {
      const auto pos = m_transition->get_selected();
      if (pos >= transitionTypes.size())
        return;
      const auto transition_setting = m_settings->get_string("transition");
      const auto transition_button = transitionTypes[pos].id;
      if (transition_setting != transition_button)
        m_settings->set_string("transition", transition_button);
    }
    #endif

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      // Bind settings.
      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());

      // Connect the menu to the MenuButton m_gears.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());
    }

File: `main.cc` (For use with gtkmm 4)

    #include "exampleapplication.h"

    int main(int argc, char* argv[])
    {
      // Since this example is running uninstalled, we have to help it find its
      // schema. This is *not* necessary in a properly installed application.
      Glib::setenv ("GSETTINGS_SCHEMA_DIR", ".", false);

      auto application = ExampleApplication::create();

      // Start the application, showing the initial window,
      // and opening extra views for any files that it is asked to open,
      // for instance as a command-line parameter.
      // run() will return when the last window has been closed.
      return application->run(argc, argv);
    }

File: `exampleapp.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/org/gtkmm/exampleapp">
        <file preprocess="xml-stripblanks">window.ui</file>
        <file preprocess="xml-stripblanks">gears_menu.ui</file>
        <file preprocess="xml-stripblanks">prefs.ui</file>
      </gresource>
    </gresources>

File: `prefs.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkWindow" id="prefs_dialog">
        <property name="title" translatable="yes">Preferences</property>
        <property name="resizable">False</property>
        <property name="modal">True</property>
        <property name="hide_on_close">True</property>
        <child>
          <object class="GtkGrid" id="grid">
            <property name="margin-start">12</property>
            <property name="margin-end">12</property>
            <property name="margin-top">12</property>
            <property name="margin-bottom">12</property>
            <property name="row-spacing">12</property>
            <property name="column-spacing">12</property>
            <child>
              <object class="GtkLabel" id="fontlabel">
                <property name="label">_Font:</property>
                <property name="use-underline">True</property>
                <property name="mnemonic-widget">font</property>
                <property name="xalign">1</property>
                <layout>
                  <property name="column">0</property>
                  <property name="row">0</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="GtkFontDialogButton" id="font">
                <property name="dialog">
                  <object class="GtkFontDialog"/>
                </property>
                <layout>
                  <property name="column">1</property>
                  <property name="row">0</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="transitionlabel">
                <property name="label">_Transition:</property>
                <property name="use-underline">True</property>
                <property name="mnemonic-widget">transition</property>
                <property name="xalign">1</property>
                <layout>
                  <property name="column">0</property>
                  <property name="row">1</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="GtkDropDown" id="transition">
                <layout>
                  <property name="column">1</property>
                  <property name="row">1</property>
                </layout>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

File: `org.gtkmm.exampleapp.gschema.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <schemalist>
      <schema path="/org/gtkmm/exampleapp/" id="org.gtkmm.exampleapp">
        <key name="font" type="s">
          <default>'Monospace 12'</default>
          <summary>Font</summary>
          <description>The font to be used for content.</description>
        </key>
        <key name="transition" type="s">
          <choices>
            <choice value='none'/>
            <choice value='crossfade'/>
            <choice value='slide-left-right'/>
          </choices>
          <default>'none'</default>
          <summary>Transition</summary>
          <description>The transition to use when switching tabs.</description>
        </key>
      </schema>
    </schemalist>

### 添加搜索栏

我们将继续充实我们的应用程序的功能。现在，我们向其中添加搜索功能。gtkmm通过Gtk::SearchEntry和Gtk::SearchBar对此提供了支持。搜索栏是一个部件，可以从从顶部滑入以显示搜索条目。

我们向标题栏添加了一个开关按钮，可以用于滑出位于标题栏下方的搜索栏。新的部件将添加到`window.ui`文件中。

实现搜索需要进行大量的代码更改，在此我们不对其进行详细的介绍。实现搜索的核心部分是一个信号处理函数，它监听搜索条目的变更，此处显示的示例没有进行错误处理。

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());

      // Very simple-minded search implementation.
      auto buffer = view->get_buffer();
      Gtk::TextIter match_start;
      Gtk::TextIter match_end;
      if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
          match_start, match_end))
      {
        buffer->select_range(match_start, match_end);
        view->scroll_to(match_start);
      }
    }

有了搜索栏后，我们的应用程序看起来像这样：

添加搜索栏

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleappprefs.h` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.h"
    // Equal to the corresponding file in step5

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      // Signal handlers
      void on_search_text_changed();
      void on_visible_child_changed();

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::Stack* m_stack {nullptr};
      Gtk::ToggleButton* m_search {nullptr};
      Gtk::SearchBar* m_searchbar {nullptr};
      Gtk::SearchEntry* m_searchentry {nullptr};
      Gtk::MenuButton* m_gears {nullptr};
      Glib::RefPtr<Glib::Binding> m_prop_binding;
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "../step5/exampleapplication.cc"
    // Equal to the corresponding file in step5

File: `exampleappprefs.cc` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.cc"
    // Equal to the corresponding file in step5

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
      if (!m_search)
        throw std::runtime_error("No \"search\" object in window.ui");

      m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
      if (!m_searchbar)
        throw std::runtime_error("No \"searchbar\" object in window.ui");

      m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
      if (!m_searchentry)
        throw std::runtime_error("No \"searchentry\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      // Bind settings.
      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());

      // Bind properties.
      m_prop_binding = Glib::Binding::bind_property(m_search->property_active(),
        m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);

      // Connect signal handlers.
      m_searchentry->signal_search_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
      m_stack->property_visible_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));

      // Connect the menu to the MenuButton m_gears.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
        return;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());

      m_search->set_sensitive(true);
    }

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      if (text.empty())
        return;

      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
        return;
      }

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
        return;
      }

      // Very simple-minded search implementation.
      auto buffer = view->get_buffer();
      Gtk::TextIter match_start;
      Gtk::TextIter match_end;
      if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
          match_start, match_end))
      {
        buffer->select_range(match_start, match_end);
        view->scroll_to(match_start);
      }
    }

    void ExampleAppWindow::on_visible_child_changed()
    {
      m_searchbar->set_search_mode(false);
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step5/main.cc"
    // Equal to the corresponding file in step5

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkToggleButton" id="search">
                <property name="sensitive">False</property>
                <property name="icon-name">edit-find-symbolic</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkSearchBar" id="searchbar">
                <child>
                  <object class="GtkSearchEntry" id="searchentry">
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkStack" id="stack">
                <property name="transition-duration">500</property>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

### 添加侧边栏

作为另一项功能，我们向应用程序中添加了侧边栏，用于演示Gtk::Revealer和Gtk::ListBox。新的部件将被添加到`window.ui`文件中。

在每个文件中用找到的单词生成按钮填充侧边栏的代码很复杂，这里我们不进行介绍。我们将对为菜单中添加新功能的多选按钮的代码进行介绍。菜单项将被添加到`gears_menu.ui` UI文件中。

我们使用Gio::Action与给定的Gio::Settings键相关联，以使菜单项与新的`show-words`设置向连接。在ExampleAppWindow构造函数中：

    // Connect the menu to the MenuButton m_gears, and bind the show-words setting
    // to the win.show-words action and the "Words" menu item.
    // (The connection between action and menu item is specified in gears_menu.ui.)
    auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
    auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
    m_gears->set_menu_model(menu);
    add_action(m_settings->create_action("show-words"));

现在我们的应用程序看起来像这样：

添加侧边栏

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleappprefs.h` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.h"
    // Equal to the corresponding file in step5

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      // Signal handlers
      void on_search_text_changed();
      void on_visible_child_changed();
      void on_find_word(const Gtk::Button* button);
      void on_reveal_child_changed();

      void update_words();

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::Stack* m_stack {nullptr};
      Gtk::ToggleButton* m_search {nullptr};
      Gtk::SearchBar* m_searchbar {nullptr};
      Gtk::SearchEntry* m_searchentry {nullptr};
      Gtk::MenuButton* m_gears {nullptr};
      Gtk::Revealer* m_sidebar {nullptr};
      Gtk::ListBox* m_words {nullptr};
      Glib::RefPtr<Glib::Binding> m_prop_binding;
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "../step5/exampleapplication.cc"
    // Equal to the corresponding file in step5

File: `exampleappprefs.cc` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.cc"
    // Equal to the corresponding file in step5

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>
    #include <set>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
      if (!m_search)
        throw std::runtime_error("No \"search\" object in window.ui");

      m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
      if (!m_searchbar)
        throw std::runtime_error("No \"searchbar\" object in window.ui");

      m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
      if (!m_searchentry)
        throw std::runtime_error("No \"searchentry\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
      if (!m_sidebar)
        throw std::runtime_error("No \"sidebar\" object in window.ui");

      m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
      if (!m_words)
        throw std::runtime_error("No \"words\" object in window.ui");

      // Bind settings.
      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());
      m_settings->bind("show-words", m_sidebar->property_reveal_child());

      // Bind properties.
      m_prop_binding = Glib::Binding::bind_property(m_search->property_active(),
        m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);

      // Connect signal handlers.
      m_searchentry->signal_search_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
      m_stack->property_visible_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
      m_sidebar->property_reveal_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));

      // Connect the menu to the MenuButton m_gears, and bind the show-words setting
      // to the win.show-words action and the "Words" menu item.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
      add_action(m_settings->create_action("show-words"));
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
        return;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());

      m_search->set_sensitive(true);
      update_words();
    }

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      if (text.empty())
        return;

      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
        return;
      }

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
        return;
      }

      // Very simple-minded search implementation.
      auto buffer = view->get_buffer();
      Gtk::TextIter match_start;
      Gtk::TextIter match_end;
      if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
          match_start, match_end))
      {
        buffer->select_range(match_start, match_end);
        view->scroll_to(match_start);
      }
    }

    void ExampleAppWindow::on_visible_child_changed()
    {
      m_searchbar->set_search_mode(false);
      update_words();
    }

    void ExampleAppWindow::on_find_word(const Gtk::Button* button)
    {
      m_searchentry->set_text(button->get_label());
    }

    void ExampleAppWindow::on_reveal_child_changed()
    {
      update_words();
    }

    void ExampleAppWindow::update_words()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      // Find all words in the text buffer.
      std::set<Glib::ustring> words;
      auto start = buffer->begin();
      Gtk::TextIter end;
      while (start)
      {
        while (start && !start.starts_word())
          ++start;

        if (!start)
          break;

        end = start;
        end.forward_word_end();
        if (start == end)
          break;

        auto word = buffer->get_text(start, end, false);
        words.insert(word.lowercase());
        start = end;
      }

      // Remove old children from the ListBox.
      while (auto child = m_words->get_first_child())
        m_words->remove(*child);

      // Add new child buttons, one per unique word.
      for (const auto& word : words)
      {
        auto row = Gtk::make_managed<Gtk::Button>(word);
        row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
          &ExampleAppWindow::on_find_word), row));
        m_words->append(*row);
      }
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step5/main.cc"
    // Equal to the corresponding file in step5

File: `gears_menu.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <menu id="menu">
        <section>
          <item>
            <attribute name="label" translatable="yes">_Words</attribute>
            <attribute name="action">win.show-words</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">_Preferences</attribute>
            <attribute name="action">app.preferences</attribute>
          </item>
        </section>
        <section>
          <item>
            <attribute name="label" translatable="yes">_Quit</attribute>
            <attribute name="action">app.quit</attribute>
          </item>
        </section>
      </menu>
    </interface>

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkToggleButton" id="search">
                <property name="sensitive">False</property>
                <property name="icon-name">edit-find-symbolic</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkSearchBar" id="searchbar">
                <child>
                  <object class="GtkSearchEntry" id="searchentry">
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkBox" id="hbox">
                <child>
                  <object class="GtkRevealer" id="sidebar">
                    <property name="transition-type">slide-right</property>
                    <child>
                     <object class="GtkScrolledWindow" id="sidebar-sw">
                       <property name="hscrollbar-policy">never</property>
                       <child>
                         <object class="GtkListBox" id="words">
                           <property name="selection-mode">none</property>
                         </object>
                       </child>
                     </object>
                    </child>
                  </object>
                </child>
                <child>
                  <object class="GtkStack" id="stack">
                    <property name="transition-duration">500</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

File: `org.gtkmm.exampleapp.gschema.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <schemalist>
      <schema path="/org/gtkmm/exampleapp/" id="org.gtkmm.exampleapp">
        <key name="font" type="s">
          <default>'Monospace 12'</default>
          <summary>Font</summary>
          <description>The font to be used for content.</description>
        </key>
        <key name="transition" type="s">
          <choices>
            <choice value='none'/>
            <choice value='crossfade'/>
            <choice value='slide-left-right'/>
          </choices>
          <default>'none'</default>
          <summary>Transition</summary>
          <description>The transition to use when switching tabs.</description>
        </key>
        <key name="show-words" type="b">
          <default>false</default>
          <summary>Show words</summary>
          <description>Whether to show a word list in the sidebar.</description>
        </key>
      </schema>
    </schemalist>

### 属性

部件和其他对象有很多有用的属性。在这里，我们通过一些新颖的使用方法展示了使用它们的方法。例如，将它们包装在Gio::PropertyAction动作中或将它们与Glib::Binding绑定。

我们在`window.ui`文件中的标题栏中添加了两个标签，分别命名为`lines_label`和`lines`，并在应用程序窗口的构造函数中获取指向它们的指针。接着我们向齿轮菜单中添加了一个新的"Lines"菜单项，该菜单项会触发`show-lines`动作。

为了使此菜单项能执行某些操作，我们为`lines`标签的`visible`属性创建了一个属性动作，并将其添加到窗口动作中。这样做的效果是，每当动作被激活，标签的可见性就会改变。由于我们希望两个标签的可见性一致，因此我们将`lines_label`部件的`visible`属性绑定到`lines`部件的相同属性上。在ExampleAppWindow的构造函数中：

    add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
    m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
      m_lines_label->property_visible());

我们还需要一个函数对当前标签页的行进行计数，并更新`lines`标签。如果你对此感兴趣请阅读完整源代码。

现在我们的示例应用程序看起来如下：

属性

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleappprefs.h` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.h"
    // Equal to the corresponding file in step5

File: `exampleappwindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      // Signal handlers
      void on_search_text_changed();
      void on_visible_child_changed();
      void on_find_word(const Gtk::Button* button);
      void on_reveal_child_changed();

      void update_words();
      void update_lines();

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::Stack* m_stack {nullptr};
      Gtk::ToggleButton* m_search {nullptr};
      Gtk::SearchBar* m_searchbar {nullptr};
      Gtk::SearchEntry* m_searchentry {nullptr};
      Gtk::MenuButton* m_gears {nullptr};
      Gtk::Revealer* m_sidebar {nullptr};
      Gtk::ListBox* m_words {nullptr};
      Gtk::Label* m_lines {nullptr};
      Gtk::Label* m_lines_label {nullptr};
      Glib::RefPtr<Glib::Binding> m_binding_search_enabled;
      Glib::RefPtr<Glib::Binding> m_binding_lines_visible;
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "../step5/exampleapplication.cc"
    // Equal to the corresponding file in step5

File: `exampleappprefs.cc` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.cc"
    // Equal to the corresponding file in step5

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>
    #include <set>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
      if (!m_search)
        throw std::runtime_error("No \"search\" object in window.ui");

      m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
      if (!m_searchbar)
        throw std::runtime_error("No \"searchbar\" object in window.ui");

      m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
      if (!m_searchentry)
        throw std::runtime_error("No \"searchentry\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
      if (!m_sidebar)
        throw std::runtime_error("No \"sidebar\" object in window.ui");

      m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
      if (!m_words)
        throw std::runtime_error("No \"words\" object in window.ui");

      m_lines = m_refBuilder->get_widget<Gtk::Label>("lines");
      if (!m_lines)
        throw std::runtime_error("No \"lines\" object in window.ui");

      m_lines_label = m_refBuilder->get_widget<Gtk::Label>("lines_label");
      if (!m_lines_label)
        throw std::runtime_error("No \"lines_label\" object in window.ui");

      // Bind settings.
      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());
      m_settings->bind("show-words", m_sidebar->property_reveal_child());

      // Bind properties of the search button to the search bar.
      m_binding_search_enabled = Glib::Binding::bind_property(m_search->property_active(),
        m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);

      // Connect signal handlers.
      m_searchentry->signal_search_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
      m_stack->property_visible_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
      m_sidebar->property_reveal_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));

      // Connect the menu to the MenuButton m_gears, and bind the show-words setting
      // to the win.show-words action and the "Words" menu item.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
      add_action(m_settings->create_action("show-words"));

      // Bind the "visible" property of m_lines to the win.show-lines action, to
      // the "Lines" menu item and to the "visible" property of m_lines_label.
      add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
      m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
        m_lines_label->property_visible());
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
        return;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());

      m_search->set_sensitive(true);
      update_words();
      update_lines();
    }

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      if (text.empty())
        return;

      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
        return;
      }

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
        return;
      }

      // Very simple-minded search implementation.
      auto buffer = view->get_buffer();
      Gtk::TextIter match_start;
      Gtk::TextIter match_end;
      if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
          match_start, match_end))
      {
        buffer->select_range(match_start, match_end);
        view->scroll_to(match_start);
      }
    }

    void ExampleAppWindow::on_visible_child_changed()
    {
      m_searchbar->set_search_mode(false);
      update_words();
      update_lines();
    }

    void ExampleAppWindow::on_find_word(const Gtk::Button* button)
    {
      m_searchentry->set_text(button->get_label());
    }

    void ExampleAppWindow::on_reveal_child_changed()
    {
      update_words();
    }

    void ExampleAppWindow::update_words()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      // Find all words in the text buffer.
      std::set<Glib::ustring> words;
      auto start = buffer->begin();
      Gtk::TextIter end;
      while (start)
      {
        while (start && !start.starts_word())
          ++start;

        if (!start)
          break;

        end = start;
        end.forward_word_end();
        if (start == end)
          break;

        auto word = buffer->get_text(start, end, false);
        words.insert(word.lowercase());
        start = end;
      }

      // Remove old children from the ListBox.
      while (auto child = m_words->get_first_child())
        m_words->remove(*child);

      // Add new child buttons, one per unique word.
      for (const auto& word : words)
      {
        auto row = Gtk::make_managed<Gtk::Button>(word);
        row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
          &ExampleAppWindow::on_find_word), row));
        m_words->append(*row);
      }
    }

    void ExampleAppWindow::update_lines()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_lines(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      int count = 0;
      auto iter = buffer->begin();
      while (iter)
      {
        ++count;
        if (!iter.forward_line())
          break;
      }
      m_lines->set_text(Glib::ustring::format(count));
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step5/main.cc"
    // Equal to the corresponding file in step5

File: `gears_menu.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <menu id="menu">
        <section>
          <item>
            <attribute name="label" translatable="yes">_Words</attribute>
            <attribute name="action">win.show-words</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">_Lines</attribute>
            <attribute name="action">win.show-lines</attribute>
          </item>
          <item>
            <attribute name="label" translatable="yes">_Preferences</attribute>
            <attribute name="action">app.preferences</attribute>
          </item>
        </section>
        <section>
          <item>
            <attribute name="label" translatable="yes">_Quit</attribute>
            <attribute name="action">app.quit</attribute>
          </item>
        </section>
      </menu>
    </interface>

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <child>
              <object class="GtkLabel" id="lines_label">
                <property name="visible">False</property>
                <property name="label" translatable="yes">Lines:</property>
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="lines">
                <property name="visible">False</property>
              </object>
            </child>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkToggleButton" id="search">
                <property name="sensitive">False</property>
                <property name="icon-name">edit-find-symbolic</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkSearchBar" id="searchbar">
                <child>
                  <object class="GtkSearchEntry" id="searchentry">
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkBox" id="hbox">
                <child>
                  <object class="GtkRevealer" id="sidebar">
                    <property name="transition-type">slide-right</property>
                    <child>
                      <object class="GtkScrolledWindow" id="sidebar-sw">
                        <property name="hscrollbar-policy">never</property>
                        <child>
                          <object class="GtkListBox" id="words">
                            <property name="selection-mode">none</property>
                          </object>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
                <child>
                  <object class="GtkStack" id="stack">
                    <property name="transition-duration">500</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

### 标题栏

我们的应用程序使用了Gtk::HeaderBar而不是普通的窗口标题栏。标题栏是窗口的直接子项，其类型为`titlebar`。这些由`window.ui`文件进行设置。

在这里我们对标题栏进行了两个小的更改。`decoration-layout`属性在`window.ui`文件中设置，使标题栏只显示关闭按钮，并隐藏最小化和最大化按钮。我们还在资源文件中包含了一个图标，并将其设置为窗口图标。在ExampleAppWindow的构造函数中：

    Gtk::IconTheme::get_for_display(get_display())->add_resource_path("/org/gtkmm/exampleapp");
    set_icon_name("exampleapp");

现在应用程序看起来如下所示：

标题栏

`window.ui`文件设置了标题栏标题，但是此标题未显示。这是因为堆切换器是`title`的子类型。此时堆切换器成为了自定义标题并将正常的标题标签隐藏。

Source Code

File: `exampleapplication.h` (For use with gtkmm 4)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleappprefs.h` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.h"
    // Equal to the corresponding file in step5

File: `exampleappwindow.h` (For use with gtkmm 4)

    #include "../step8/exampleappwindow.h"
    // Equal to the corresponding file in step8

File: `exampleapplication.cc` (For use with gtkmm 4)

    #include "../step5/exampleapplication.cc"
    // Equal to the corresponding file in step5

File: `exampleappprefs.cc` (For use with gtkmm 4)

    #include "../step5/exampleappprefs.cc"
    // Equal to the corresponding file in step5

File: `exampleappwindow.cc` (For use with gtkmm 4)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>
    #include <set>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder)
    {
      // Get widgets from the Gtk::Builder file.
      m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
      if (!m_search)
        throw std::runtime_error("No \"search\" object in window.ui");

      m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
      if (!m_searchbar)
        throw std::runtime_error("No \"searchbar\" object in window.ui");

      m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
      if (!m_searchentry)
        throw std::runtime_error("No \"searchentry\" object in window.ui");

      m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
      if (!m_gears)
        throw std::runtime_error("No \"gears\" object in window.ui");

      m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
      if (!m_sidebar)
        throw std::runtime_error("No \"sidebar\" object in window.ui");

      m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
      if (!m_words)
        throw std::runtime_error("No \"words\" object in window.ui");

      m_lines = m_refBuilder->get_widget<Gtk::Label>("lines");
      if (!m_lines)
        throw std::runtime_error("No \"lines\" object in window.ui");

      m_lines_label = m_refBuilder->get_widget<Gtk::Label>("lines_label");
      if (!m_lines_label)
        throw std::runtime_error("No \"lines_label\" object in window.ui");

      // Bind settings.
      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());
      m_settings->bind("show-words", m_sidebar->property_reveal_child());

      // Bind properties of the search button to the search bar.
      m_binding_search_enabled = Glib::Binding::bind_property(m_search->property_active(),
        m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);

      // Connect signal handlers.
      m_searchentry->signal_search_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
      m_stack->property_visible_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
      m_sidebar->property_reveal_child().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));

      // Connect the menu to the MenuButton m_gears, and bind the show-words setting
      // to the win.show-words action and the "Words" menu item.
      // (The connection between action and menu item is specified in gears_menu.ui.)
      auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
      auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
      if (!menu)
        throw std::runtime_error("No \"menu\" object in gears_menu.ui");

      m_gears->set_menu_model(menu);
      add_action(m_settings->create_action("show-words"));

      // Bind the "visible" property of m_lines to the win.show-lines action, to
      // the "Lines" menu item and to the "visible" property of m_lines_label.
      add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
      m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
        m_lines_label->property_visible());

      // Set the window icon.
      Gtk::IconTheme::get_for_display(get_display())->add_resource_path("/org/gtkmm/exampleapp");
      set_icon_name("exampleapp");
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const Glib::ustring basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_expand(true);
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      scrolled->set_child(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
        return;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());

      m_search->set_sensitive(true);
      update_words();
      update_lines();
    }

    void ExampleAppWindow::on_search_text_changed()
    {
      const auto text = m_searchentry->get_text();
      if (text.empty())
        return;

      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
        return;
      }

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
        return;
      }

      // Very simple-minded search implementation.
      auto buffer = view->get_buffer();
      Gtk::TextIter match_start;
      Gtk::TextIter match_end;
      if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
          match_start, match_end))
      {
        buffer->select_range(match_start, match_end);
        view->scroll_to(match_start);
      }
    }

    void ExampleAppWindow::on_visible_child_changed()
    {
      m_searchbar->set_search_mode(false);
      update_words();
      update_lines();
    }

    void ExampleAppWindow::on_find_word(const Gtk::Button* button)
    {
      m_searchentry->set_text(button->get_label());
    }

    void ExampleAppWindow::on_reveal_child_changed()
    {
      update_words();
    }

    void ExampleAppWindow::update_words()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      // Find all words in the text buffer.
      std::set<Glib::ustring> words;
      auto start = buffer->begin();
      Gtk::TextIter end;
      while (start)
      {
        while (start && !start.starts_word())
          ++start;

        if (!start)
          break;

        end = start;
        end.forward_word_end();
        if (start == end)
          break;

        auto word = buffer->get_text(start, end, false);
        words.insert(word.lowercase());
        start = end;
      }

      // Remove old children from the ListBox.
      while (auto child = m_words->get_first_child())
        m_words->remove(*child);

      // Add new child buttons, one per unique word.
      for (const auto& word : words)
      {
        auto row = Gtk::make_managed<Gtk::Button>(word);
        row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
          &ExampleAppWindow::on_find_word), row));
        m_words->append(*row);
      }
    }

    void ExampleAppWindow::update_lines()
    {
      auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
      if (!tab)
        return;

      auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
      if (!view)
      {
        std::cout << "ExampleAppWindow::update_lines(): No visible text view." << std::endl;
        return;
      }
      auto buffer = view->get_buffer();

      int count = 0;
      auto iter = buffer->begin();
      while (iter)
      {
        ++count;
        if (!iter.forward_line())
          break;
      }
      m_lines->set_text(Glib::ustring::format(count));
    }

File: `main.cc` (For use with gtkmm 4)

    #include "../step5/main.cc"
    // Equal to the corresponding file in step5

File: `exampleapp.gresource.xml` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/org/gtkmm/exampleapp">
        <file preprocess="xml-stripblanks">window.ui</file>
        <file preprocess="xml-stripblanks">gears_menu.ui</file>
        <file preprocess="xml-stripblanks">prefs.ui</file>
        <file>exampleapp.png</file>
      </gresource>
    </gresources>

File: `window.ui` (For use with gtkmm 4)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object class="GtkApplicationWindow" id="app_window">
        <property name="title" translatable="yes">Example Application</property>
        <property name="default-width">600</property>
        <property name="default-height">400</property>
        <property name="hide-on-close">True</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <property name="decoration-layout">icon:close</property>
            <child>
              <object class="GtkLabel" id="lines_label">
                <property name="visible">False</property>
                <property name="label" translatable="yes">Lines:</property>
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="lines">
                <property name="visible">False</property>
              </object>
            </child>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="stack">stack</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkToggleButton" id="search">
                <property name="sensitive">False</property>
                <property name="icon-name">edit-find-symbolic</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton" id="gears">
                <property name="direction">none</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="content_box">
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkSearchBar" id="searchbar">
                <child>
                  <object class="GtkSearchEntry" id="searchentry">
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkBox" id="hbox">
                <child>
                  <object class="GtkRevealer" id="sidebar">
                    <property name="transition-type">slide-right</property>
                    <child>
                      <object class="GtkScrolledWindow" id="sidebar-sw">
                        <property name="hscrollbar-policy">never</property>
                        <child>
                          <object class="GtkListBox" id="words">
                            <property name="selection-mode">none</property>
                          </object>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
                <child>
                  <object class="GtkStack" id="stack">
                    <property name="transition-duration">500</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

## 贡献

本文档和其他出色的软件一样，是由志愿者免费创建的。如果你对任何一个gtkmm没有文档的部分有所了解，请考虑帮助编写/翻译本文档。

Ideally, we would like you to provide a merge request to the `docs/tutorial/C/index-in.docbook` file. This file is in the `gtkmm-documentation` module in GNOME git.

If you do decide to contribute, please post your contribution as an issue or merge request to GitLab. You can also discuss your ideas on GNOME's Discourse instance, under the Platform/Language bindings category with a `cplusplus` tag. Also, be aware that the entirety of this document is free, and any addition you provide must also be free. That is, people must be able to use any portion of your examples in their programs, and copies of this document (including your contribution) may be distributed freely.

## RefPtr智能指针

Glib::RefPtr是一个智能指针。准确的说，它是一个引用计数智能指针。你可能对std::unique_ptr<>和std::shared_ptr<>十分熟悉，它们也是智能指针。在gtkmm-4.0中Glib::RefPtr<>是std::shared_ptr<>的别名。Glib::RefPtr<>在C++标准库还没有添加引用计数智能指针的时候就已经存在于glibmm了（译注：事实上Glib::RefPtr的行为与std::shared_ptr的行为并不完全一致，且因glibmm等库的文档是由C文档直接生成而来，在一些C库中可以接受NULL作为参数的函数在对应的C++绑定库中的文档中都是可以接受nullptr作为参数，但实际上并不一定能接受nullptr作为参数，有时候需要传递Glib::RefPtr<FOO>()、Gdk::Event()、空的字符串等。参见：问题24）。

If you make your own Glib::ObjectBase-derived classes with create() methods that return a Glib::RefPtr, you must use `Glib::make_refptr_for_instance()` in your create() methods. This function creates a std::shared_ptr with a special deleter, which handles the reference-count for the wrapped C object.

Reference

智能指针的行为很像普通指针。这里有几个例子。

### 复制

你可以像复制普通指针一样复制RefPtr，你不需要担心删除底层实例。

    auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
    auto refPixbuf2 = refPixbuf;

这意味着你可以将RefPtr储存于标准容器中，例如：std::vector或std::list。

    std::list<Glib::RefPtr<Gdk::Pixbuf>> listPixbufs;
    auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
    listPixbufs.push_back(refPixbuf);

### 解引用

你可以使用->操作符解引用智能指针，就像使用普通指针访问底层实例的方法一样。

    auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
    auto width = refPixbuf->get_width();

你还可以使用*操作符和get()方法访问底层实例，不过通常这不是一个好主意。除非你非常的谨慎没有犯错，否则你将得到一个不存在于引用计数中的指向底层实例的指针或底层实例的引用。

    auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
    auto& underlying = *refPixbuf; // Possible, but not recommended

### 类型转换

你可以像对正常指针进行类型转换一样，将RefPtr转换到基础类型。

    auto refStore = Gtk::TreeStore::create(columns);
    Glib::RefPtr<Gtk::TreeModel> refModel = refStore;

这意味着任何接受const Glib::RefPtr<BaseType>&参数的函数也能接受const Glib::RefPtr<DerivedType>&对象。此转换和普通指针一样是隐式的。

你还可以将其转换为派生类型，但是语法和普通指针的语法略有不同。

    auto refStore = std::dynamic_pointer_cast<Gtk::TreeStore>(refModel);
    auto refStore2 = std::static_pointer_cast<Gtk::TreeStore>(refModel);

### nullptr检查

就像普通指针一样，你可以检查RefPtr是否指向了任何东西。

    auto refModel = m_TreeView.get_model();
    if (refModel)
    {
      auto cols_count = refModel->get_n_columns();
      ...
    }

和普通指针不一样的是，RefPtr将会自动初始化为`nullptr`，不需要你自己进行置空。

### 常数

The use of the `const` keyword in C++ is not always clear. You might not realize that const Something* declares a pointer to a const Something. The pointer can be changed, but not the Something that it points to.

因此，在方法参数中与Something*等效的RefPtr是const Glib::RefPtr<Something>&，而与const Something*等效的是const Glib::RefPtr<const Something>&。

用`const ... &`包围是处于效率考虑，就像在方法参数中使用const std::string&而不是用std::string一样，这是为了避免不必要的复制。

## 信号

### 连接到信号处理函数

gtkmm的部件类拥有信号访问器方法，例如:Gtk::Button::signal_clicked()。这些方法允许你将你的信号处理函数与信号相连接。gtkmm所使用的的libsigc++回调库提供了非常强的灵活性，它几乎允许你使用任何函数作为信号的处理程序，不过通常你会希望使用类的成员函数。在GTK的C代码中，这些信号处理程序被称为回调。

这是将信号处理函数与信号相连接的示例：

    #include <gtkmm/button.h>

    void on_button_clicked()
    {
      std::cout << "Hello World" << std::endl;
    }

    class some_class
    {
    public:
      some_class
      {
        button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
      }
    private:
      Gtk::Button button {"Hello World"};
    };

在此代码（非功能性）中有很多事情要考虑，首先让我们确定代码的行为：

- 信号处理函数是on_button_clicked()。

- 我们将其连接到一个名为`button`的Gtk::Button对象。

- 当`button`发出`clicked`信号时，on_button_clicked()将被调用。

现在，我们再次看一下连接：

        ...
        button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
        ...

请注意，我们不会直接将一个指向on_button_clicked()的指针传递给信号的connect()方法。我们将调用`sigc::ptr_fun()`并将其返回值传递给connect()。

`sigc::ptr_fun()`生成一个sigc::slot。槽对象看起来很像一个函数，但实际上是一个对象。这类对象也被称为函数对象或是函子。`sigc::ptr_fun()`为独立函数或静态成员函数生成槽。`sigc::mem_fun()`为特定实例的成员函数生成槽。

A C++ lambda expression is a functor which can be implicitly converted to a sigc::slot in the call to connect(). A lambda expression can be used instead of `sigc::ptr_fun()`. It's also possible to use a lambda expression instead of `sigc::mem_fun()`, but then you won't get automatic disconnection of the signal handler when a sigc::trackable-derived object goes out of scope.

这是一个在动作中使用槽的较大示例：

    #include <gtkmm/button.h>

    void on_button_clicked()
    {
      std::cout << "Hello World" << std::endl;
    }

    class some_class
    {
    public:
      some_class
      {
        button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
        button.signal_clicked().connect(sigc::mem_fun(*this, &some_class::on_button_clicked));
      }
      void on_button_clicked();
    private:
      Gtk::Button button {"Hello World"};
    };

第一个connect()调用与我们之前看到的没有什么区别。

The next is more interesting. `sigc::mem_fun()` is called with two arguments. The first argument is *this, which is the object that our new slot will be pointing at. The second argument is a pointer to one of its methods. This particular version of `sigc::mem_fun()` creates a slot which will, when "called", call the pointed-to method of the specified object.

另一个与此示例有关的注意事项是，我们对同一个信号对象调用了两次connect()。所以当该按钮被点击时，这两个信号处理函数都将被调用。

你应该已经注意到了，按钮的`clicked`信号期望调用一个不接受任何参数的函数。事实上所有的信号都有这样的要求。所以你不能将一个接受两个参数的函数与期望所调用函数不接受参数的信号相关联，当然你可以通过使用`sigc::bind()`之类的适配器在一定程度上绕过这个限制。

### 编写信号处理函数

要找到你可以连接到信号的信号处理函数类型，你可以查看它的参考文档或者是头文件。这是一个你可能在gtkmm头文件中看到的信号声明示例：

    Glib::SignalProxy<bool(Gtk::DirectionType)> signal_focus()

除了信号名为`focus`之外，这里的膜拜参数很重要。第一个参数bool，表示信号处理函数的返回值应该是个bool类型。而括号中的Gtk::DirectionType表示信号所期望调用的函数的第一个也是唯一一个参数的类型。通过查看参考文档你还可以看到参数的名称。

对于接受期望调用的函数有更多参数的信号也是一样的阅读规则。以下声明取自`<gtkmm/textbuffer.h>`：

    Glib::SignalProxy<void(TextBuffer::iterator&, const Glib::ustrin&, int)> signal_insert();

以一样的阅读规则阅读后可知，该信号所期望调用的函数的返回值类型为void，括号内三种类型为函数的三个参数类型。所以我们的信号处理函数原型可能如下所示：

    void on_insert(TextBuffer::iterator& pos, const Glib::ustring& text, int bytes)

### 断开信号处理函数

让我们再来看看信号的`connect`成员函数：

    sigc::connection signal<void(int)>::connect(const sigc::slot<void(int)>&);

该函数返回的sigc::connection对象可以控制连接。通过保存连接对象就可以断开与其相关联的信号处理函数的连接。若你想如此做，只需对对象调用sigc::connection::disconnect()成员函数即可。

### 覆写默认信号处理函数

到现在为止，你已经学会了通过将自定义的信号处理函数连接到信号来响应按钮按下等动作。这是一种好方法，但不是唯一的方法。

你还可以创建一个从部件继承而来的新类，然后覆盖默认的信号处理函数（例如，Button::on_clicked()），这样你就可以省去将信号处理函数连接到信号的步骤。

子类化并不是银弹。它只在你只希望由部件自身来处理自己的信号时才好用。如果你需要用其他的类来处理信号，你还是需要为此将函数连接到目标信号上。这在你需要用多个对象处理同一个信号或者需要一个信号处理函数处理不同的对象的信号的时候会很不方便。

gtkmm类在设计的时候已经考虑到了覆写；它们包含了专门用于被覆写的虚成员函数。

让我们看一个覆写的例子：

    #include <gtkmm/button.h>

    class OverriddenButton : public Gtk::Button
    {
    protected:
      void on_clicked() override;
    }

    void OverriddenButton::on_clicked()
    {
      std::cout << "Hello World" << std::endl;

      // call the base class's version of the method:
      Gtk::Button::on_clicked();
    }

在这我们定义了一个名为OverriddenButton的新类，该类继承自Gtk::Button。我们对其唯一的更改是on_clicked()成员函数，只要Gtk::Button发出`clicked`信号该成员函数就会被调用。这个成员函数将字符串"Hello World"打印到`stdout`，然后调用Gtk::Button已被覆写本该被调用的成员函数。

调用父方法不是必须的，你可以在不想做的时候不调用它。请注意在此我们在做完了想做的事情之后调用了父方法，而你当然可以在此函数的中间或是任何你想调用父方法的位置调用它。这对于通过连接到信号调用的信号处理函数是无法做到的。

### 绑定额外参数

如果你想使用一个信号处理函数从多个部件捕获同一个信号，你可能希望信号处理函数能收到一些额外的信息。例如，你可能想知道那个按钮被点击了。你可以使用`sigc::bind()`。这是helloworld2示例的部分代码：

    m_button1.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1"));

这表示我们希望信号向信号处理函数发送一个额外的Glib::ustring参数，并且该参数的值为"button 1"。当然，我们将会需要向信号处理函数的声明中添加额外的参数:

    void on_button_clicked(const Glib::ustring& data);

。当然，正常的"clicked"信号处理函数是没有参数的。

`sigc::bind()`并不常用，不过偶尔会对你有所帮助（译注：很多时候直接使用lambda比调用sigc::bind要好）。如果你对使用GTK编程很熟悉，就会注意到，这和GTK中所有的回调函数都具有一个额外的`gpointer data`参数十分的类似。因为在C语言中要派生一个部件十分的困难，通过让GTK中所有回调函数都具有一个这样的额外参数用于传递本该储存于派生部件成员中的数据可以有效降低代码复杂度。而在gtkmm中派生部件类十分的简单，所以这种强侵入式的写法不再需要了。

### Event signals

Event signals are emitted as a result of some user input, for instance a key press or a mouse motion. Usually you don't handle these events directly. Instead, you use a subclass of Gtk::EventController, such as Gtk::EventControllerKey or Gtk::GestureClick. Event controllers can be added to a widget with Gtk::Widget::add_controller().

You might occasionally find it useful to handle events when there's something you can't accomplish with normal signals. Gtk::Button, for example, does not send mouse-pointer coordinates with its `clicked` signal, but you could handle Gtk::GestureClick::signal_pressed() if you needed this information. Gtk::EventControllerKey::signal_key_pressed() is often used to handle key-presses.

Some event controller signals behave slightly differently. The value returned from the signal handler indicates whether it has fully "handled" the event. If the value is `false` then gtkmm will pass the event on to the next signal handler. If the value is `true` then no other signal handlers will need to be called.

Handling an event doesn't affect the Widget's other signals. If you handle Gtk::GestureClick::signal_pressed() for Gtk::Button, you'll still be able to get the `clicked` signal. They are emitted at (nearly) the same time.

这是一个简单的例子：

    void on_button_press(int n_press, double x, double y);
    Gtk::Button button("label");
    auto controller = Gtk::GestureClick::create();
    controller->signal_pressed().connect(sigc::ptr_fun(&on_button_press));
    button.add_controller(controller);

当鼠标悬停在鼠标上方并按下鼠标按钮时，on_button_press()将被调用。

#### 信号处理函数序列

By default, signal handlers that return void are called after any previously-connected signal handlers. However, this can be a problem with event signals that can stop event propagation by returning `true`. For instance, the existing signal handlers, or the default signal handler, might return `true` to stop other signal handlers from being called. To specify that your signal handler should be called before the other signal handlers, you can specify `false` for the `after` parameter. This connect() parameter is optional, if the signal handler returns void. For instance,

    key_controller->signal_key_pressed().connect(sigc::ptr_fun(&on_mywindow_key_pressed), false);

The event is propagated between widgets in 3 phases.

1. Capture phase - runs from the toplevel down to the event widget.

2. Target phase - runs only on the event widget.

3. Bubble phase - runs from the event widget up to the toplevel.

The Input Handling chapter in the GTK documentation describes user input handling in more detail.

### 信号处理函数中的异常

当程序因未处理C++异常而终止时，有时候可以使用调试器查找抛出异常的位置。但是如果异常是在信号处理函数中抛出的，这将会比平常困难的多。

This section describes primarily what you can expect on a Linux system, when you use the gdb debugger.

首先让我们看一个简单的示例，其中的一个普通函数（非信号处理函数）抛出异常：

    // without_signal.cc
    #include <gtkmm.h>

    bool throwSomething()
    {
      throw "Something";
      return true;
    }

    int main(int argc, char** argv)
    {
      throwSomething();
      auto app = Gtk::Application::create("org.gtkmm.without_signal");
      return app->run();
    }

这是gdb会话的摘要。只显示输出中最有趣的部分：

    > gdb without_signal
    (gdb) run
    terminate called after throwing an instance of 'char const*'

    Program received signal SIGABRT, Aborted.
    (gdb) backtrace
    #7  0x08048864 in throwSomething () at without_signal.cc:6
    #8  0x0804887d in main (argc=1, argv=0xbfffecd4) at without_signal.cc:12

你可以看到在`without_signal.cc`的第六行抛出了异常(`throw "Something";`)。

现在，让我们看看从信号处理函数中抛出异常会发生什么。这是源代码：

    // with_signal.cc
    #include <gtkmm.h>

    bool throwSomething()
    {
      throw "Something";
      return true;
    }

    int main(int argc, char** argv)
    {
      Glib::signal_timeout().connect(sigc::ptr_fun(throwSomething), 500);
      auto app = Gtk::Application::create("org.gtkmm.with_signal");
      app->hold();
      return app->run();
    }

And here's an excerpt from a gdb session.

    > gdb with_signal
    (gdb) run
    (with_signal:2703): glibmm-ERROR **:
    unhandled exception (type unknown) in signal handler

    Program received signal SIGTRAP, Trace/breakpoint trap.
    (gdb) backtrace
    #2  0x0063c6ab in glibmm_unexpected_exception () at exceptionhandler.cc:77
    #3  Glib::exception_handlers_invoke () at exceptionhandler.cc:150
    #4  0x0063d370 in glibmm_source_callback (data=0x804d620) at main.cc:212
    #13 0x002e1b31 in Gtk::Application::run (this=0x804f300) at application.cc:178
    #14 0x08048ccc in main (argc=1, argv=0xbfffecd4) at with_signal.cc:16

The exception is caught in glibmm, and the program ends with a call to `g_error()`. Other exceptions may result in different behavior, but in any case the exception from a signal handler is caught in glibmm or gtkmm, and gdb can't see where it was thrown.

要查看哪里抛出了异常，你可以使用gdb的`catch throw`命令。

    > gdb with_signal
    (gdb) catch throw
    Catchpoint 1 (throw)
    (gdb) run
    Catchpoint 1 (exception thrown), 0x00714ff0 in __cxa_throw ()
    (gdb) backtrace
    #0  0x00714ff0 in __cxa_throw () from /usr/lib/i386-linux-gnu/libstdc++.so.6
    #1  0x08048bd4 in throwSomething () at with_signal.cc:6
    (gdb) continue
    Continuing.
    (with_signal:2375): glibmm-ERROR **
    unhandled exception (type unknown) in signal handler

    Program received signal SIGTRAP, Trace/breakpoint trap.

如果在感兴趣的未捕获异常之前有多个捕获异常，这个方法将会非常的枯燥。可以使用以下gdb命令自动化进行这个工作。

    (gdb) catch throw
    (gdb) commands
    (gdb)   backtrace
    (gdb)   continue
    (gdb)   end
    (gdb) set pagination off
    (gdb) run

这些命令将在每次抛出时打印回溯信息并继续。通常最后一个或第一个`throw`是你所需要的。

## 创建属于你的信号

现在你已经看到了gtkmm中的信号于信号处理函数，你可能希望你自己的类使用相同的技术进行交互。你可以很轻松的使用libsigc++库实现这个需求。

这不是单纯的gtkmm或GUI问题。gtkmm使用libsigc++实现对GTK信号系统的代理封装。而对于新的非GTK信号，你可以使用sigc::signal<>模版创建属于自己的C++信号。

例如，要创建一个发送参数类型分别为bool和int的信号，只需要按如下所示声明一个sigc::signal即可：

    sigc::signal<void(bool, int)> signal_something;

你可以简单的将信号声明为公有成员变量，但是有些人讨厌这种声明，他们更喜欢如下所示的访问器方法：

    class Server
    {
    public:
      //signal accessor:
      using type_signal_something = sigc::signal<void(bool, int)>;
      type_signal_something signal_something();

    protected:
      type_signal_something m_signal_something;
    };

    Server::type_signal_something Server::signal_something()
    {
      return m_signal_something;
    }

然后你可以使用与连接gtkmm信号一样的语法连接到这个信号。例如：

    server.signal_something().connect(
      sigc::mem_fun(client, &Client::on_server_something) );

### 示例

这是一个完整的定义了并使用了自定义信号的示例。

Source Code

File: `client.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_CLIENT_H
    #define GTKMM_EXAMPLE_CLIENT_H

    #include <sigc++/sigc++.h>

    //Client must inherit from sigc::trackable.
    //because libsigc++ needs to keep track of the lifetime of signal handlers.
    class Client : public sigc::trackable
    {
    public:
      Client();
      virtual ~Client();

      //Signal handler:
      void on_server_something(bool a, int b);
    };

    #endif //GTKMM_EXAMPLE_CLIENT_H

File: `server.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLE_SERVER_H
    #define GTKMM_EXAMPLE_SERVER_H

    #include <sigc++/sigc++.h>

    class Server
    {
    public:
      Server();
      virtual ~Server();

      void do_something();

      //signal accessor:
      using type_signal_something = sigc::signal<void(bool, int)>;
      type_signal_something signal_something();

    protected:
      type_signal_something m_signal_something;
    };

    #endif //GTKMM_EXAMPLE_SERVER_H

File: `client.cc` (For use with gtkmm 4)

    #include "client.h"
    #include <iostream>

    Client::Client()
    {
    }

    Client::~Client()
    {
    }

    void Client::on_server_something(bool a, int b)
    {
      std::cout << "Client::on_server_something() called with these parameters: "
          << a << ", " << b << std::endl;
    }

File: `main.cc` (For use with gtkmm 4)

    #include "server.h"
    #include "client.h"
    #include <iostream>

    int main(int, char**)
    {
      Server server;
      Client client;

      //Connect a Server signal to the signal handler in Client.
      server.signal_something().connect(sigc::mem_fun(client,
                  &Client::on_server_something) );

      std::cout << "Before Server::do_something()" << std::endl;

      //Tell the server to do something that will eventually cause it to emit the
      //"something" signal.
      server.do_something();    // Client::on_server_something() will run before
                                // Server::do_something() has completed.

      std::cout << "After Server::do_something()" << std::endl;

      return 0;
    }

File: `server.cc` (For use with gtkmm 4)

    #include "server.h"
    #include <iostream>

    Server::Server()
    {
    }

    Server::~Server()
    {
    }

    Server::type_signal_something Server::signal_something()
    {
      return m_signal_something;
    }

    void Server::do_something()
    {
      m_signal_something.emit(false, 5);
    }

## 与其他信号系统的比较

GTK将自己的信号系统方案称为"信号传递"；有使用其他GUI工具包经验的读者可能会注意到，这种设计经常会被称作"广播者-监听者"。它的工作方式大致为：先设置`广播者(broadcasters)`，然后将`监听者(listeners)`与它连接。广播者会保留一个监听者对象列表，当有某人向广播者发送消息时，它将向列表中所有对象发送消息。在gtkmm中，信号对象扮演广播者的角色，而槽扮演监听者的角色。

gtkmm的信号处理函数是强类型的，而gtk的C代码啊运行你连接到一个使用错误参数数量和类型的回调函数，这会导致运行时出现段错误。并且和Qt不同，gtkmm不需要对C++语言进行修改即可达到此目的。

虽然你也能在C语言中覆写信号处理函数（这是GTK使用对象系统的目的），但是由于语言对于高级特性的支持特别弱，你需要编写一系列复杂的代码才能做到继承和重载。而在C++中，因为语言本身就支持这些特性，所以你可以让编译器代为完成肮脏的工作。

这是真正体现C++魅力的地方之一。没有人会只为了覆写GTK部件的成员函数就将其子类化，这过于麻烦。除非是为了编写新的部件否则基本上都是利用信号完成任务。而在C++中覆写成员函数非常简单，因此为了修改部件的某个行为而将其子类化是完全可行且明智的。

## gtkmm与Win32

gtkmm的主要优点之一是它是跨平台的。在其他平台编写的gtkmm程序只需要修改很少一部分的源代码就可以从GNU/Linux移植到Windows，当然反之亦然。

gtkmm currently works with the MinGW/GCC compiler with a compiler version that supports C++17, such as gcc 7 or 8. It also works with Microsoft Visual C++ 2017 15.7.x or later (including the freely available express/community editions) on the Windows platform. There is an installer available for gtkmm on Microsoft Windows, but as of this writing (October 2020) it has not been updated for a long time. Please be aware that although normally it is fine to mix builds done with Visual Studio 2017 and 2019, please do not do so when building gtkmm with its -mm dependencies.

Refer to the README.win32, as well as the README files in the gtkmm, pangomm and glibmm for instructions on how to build gtkmm on Windows.

## 使用gtkmm源代码

如果你有兴趣协助开发gtkmm或修复gtkmm中的bug，你可能需要构建gtkmm的开发版。但是你不应该将开发版本覆盖稳定版。你应该选择已存在gtkmm版本的目录外的路径安装开发版。

The easiest way to do this is using jhbuild. jhbuild is a program that makes building GNOME software much easier by calculating dependencies and building things in the correct order. This section will give a brief explanation of how to set up jhbuild to build and install gtkmm from the source repository (git). For up-to-date information on jhbuild, please refer to the jhbuild manual.

请注意，要从git构建gtkmm，通常你还需要从git构建许多的依赖项。jhbuild使此操作比平常更容易，但是构建和安装这些依赖依旧需要相当长的时间。在这过程中你可能会遇到构建问题，因为master分支并不总是能构建成功的，当然构建问题通常很快就会被修复。

gnome-build-meta is an alternative to jhbuild. It is described at the Building system components wiki page, but here we concentrate on jhbuild.

### 设置jhbuild

To set up jhbuild, follow the basic installation instructions from the jhbuild manual. After you have installed jhbuild, you should copy the sample jhbuild configuration file into your home directory by executing the following command from the jhbuild directory:

    $ cp examples/sample.jhbuildrc ~/.config/jhbuildrc

gtkmm模块在文件`gnome-suites-core-deps-latest.modules`中的模块组(moduleset)定义。因此你需要按以下方式编辑你的`jhbuildrc`文件并设置你的模块组：

    moduleset = 'gnome-suites-core-deps-latest'

正确设置模块组之后，你需要告诉jhbuild哪些模块需要构建。想要构建gtkmm以及所有相关的依赖项，请按如下所示设置`modules`：

    modules = [ 'gtkmm' ]

你可以通过设置`modules`为元包(meta-package)来构建多个模块。当你未在命令行上指定任何内容的时候，`modules`变量将指定构建哪些模块。你始终可以用命令行指定其他模块组来构建其他模块组（例如：`jhbuild build gtkmm`）。

设置前缀

在默认情况下，jhbuild被配置为在`~/jhbuild/install`前缀安装所有由jhbuild构建的软件。你可以选择其他的前缀，但建议你选择的新前缀与旧前缀保持不同，且不将前缀设置为`/usr`。如果你遵循jhbuild的说明进行操作，该前缀属于你的用户，你将无需以`root`用户权限运行jhbuild。

You should also set `buildroot` in `jhbuildrc`. jhbuild builds gtkmm and many of its dependencies with Meson. Meson does not allow building in the source tree. jhbuild's default action is to build in a `build` directory directly below the source root directory. Some modules have a `build` directory with files used when building with Autotools. Those files can be destroyed if you let jhbuild build in that directory.

When you downloaded jhbuild from the git repository, you got a number of `.modules` files, specifying dependencies between modules. By default jhbuild does not use the downloaded versions of these files, but reads the latest versions in the git repository. This is usually what you want. If you don't want it, use the `use_local_modulesets` variable in `jhbuildrc`.

### 安装和使用gtkmm的git版本

如上所述，配置好jhbuild后，构建gtkmm会非常简单。首次运行jhbuild时，你应运行以下命令序列，以确保jhbuild拥有所有所需的工具以及验证设置是否正确：

    $ jhbuild bootstrap
    $ jhbuild sanitycheck

#### 用jhbuild安装gtkmm

如果一切正常，你应该已经可以通过执行`jhbuild build`（如果你没有在`modules`变量中指定gtkmm，那也可以使用命令`jhbuild build gtkmm`来进行构建）来从git构建gtkmm以及所有有关的依赖项。

此命令将构建并安装一系列模块，第一次运行可能需要很长的时间。但是之后运行就会快很多，因为之后他只会重新编译自上次构建依赖变更过的文件。在第一次构建并安装gtkmm后你可以使用命令`jhbuild buildone gtkmm`重新构建gtkmm（不需要重新构建相关依赖）。

#### 使用git版本的gtkmm

安装git版本的gtkmm后，你就可以开始使用并进行试验了。为了使用刚安装的新版本gtkmm，你需要设置一些环境变量以便你的`configure`或`meson.build`脚本可以找到新的库。jhbuild为此提供了一个简单的解决方案。执行命令`jhbuild shell`将启动一个设置好了所有环境变量的shell。现在如果你和往常一样重新配置并构建你的项目，它应该链接到新的库。要返回之前的环境你只需要jhbuild shell即可。

构建完你的软件后，你还需要在jhbuild环境中运行你的程序。为此，你可以再次使用`jhbuild shell`命令在设置了jhbuild环境的情况下启动新的shell。或者你可以用以下命令在jhbuild环境下执行单次命令：`jhbuild run command-name`。在这种情况下，命令将在设置了正确的环境变量的环境中运行，命令退出后，环境将恢复到之前的状态。

## 使用gmmproc封装C库

gtkmm的大部分源代码是使用`gmmproc`工具生成的，它们使用.defs文件定义基于GObject的库的API。因此，可以很容易为其他基于glib/Gobject的库创建类似于gtkmm风格的封装。

这涉及到很多工具，其中有一些很笨重，不过还算能用，并且已经被多个项目使用。

### 构建结构

要生成gtkmm风格的API封装源代码需要使用包含在glibmm的工具，例如`gmmproc`和`generate_wrap_init.pl`。理论上来说你可以自己编写构建文本以更好的使用它们，不过更好的选择是利用mm-common模块提供的构建基础设施。选择一个已经存在绑定的模块作为示例对于开始使用gmmproc很有帮助。

假设，我们封装了一个名为libsomething的C库。它提供了基于GObject API的具名类型，例如类型名为：SomeWidget和SomeStuff。

#### 复制skeleton项目

Typically our wrapper library would be called libsomethingmm. We can start by copying the skeleton source tree from the mm-common module. Starting with mm-common 1.0.0 this skeleton application is built with the Meson build system.

      $ git clone https://gitlab.gnome.org/GNOME/mm-common.git
      $ cp -a mm-common/skeletonmm libsomethingmm

这为源.hg和.ccg文件、手写的.h和.cc文件提供了目录结构，`meson.build`文件可以通过各种变量指定Meson使用哪些文件进行编译。适当的重命名目录后，目录结构将如下所示：

- `libsomethingmm`：顶级目录。

  - `libsomething`：包含主要的被包含文件(被#include的文件)和由pkg-config所需的.pc文件。

    - `src`：包含.hg和.ccg源文件。

    - `libsomethingmm`：包含手写的.h和.cc文件。

除了重命名目录外，我们还应该重命名一些源文件。例如：

    $ for f in $(find libsomethingmm -depth -name '*skeleton*'); do \
        d="${f%/*}"; b="${f##*/}"; mv "$f" "$d/${b//skeleton/libsomething}"; \
      done

然后，还有些skeleton文件需要在特定项目内容完成后填写。

请注意，在配置阶段中某些变量将会替换为实际值，这将会生成与`.in`相同名称但不带`.in`后缀的文件。

生成的文件被保存在构建树中，当使用`meson`和`ninja`时构建树会与源树分离。

#### 修改构建文件

现在我们修改构建文件以适应我们的需求。在这个时候你可能喜欢使用类似于`regexxer`这样的多文件搜索替换工具。请注意，skeleton源代码树提供的所有文件几乎都包含了占位符。因此，应该进行全局替换而不是仅对Meson文件替换。

所有提到`skeleton`的地方都应该替换成你想要封装的C库的名称，例如替换成"something"或"libsomething"。同样的，所有的`SKELETON`都应该被替换成"SOMETHING"或"LIBSOMETHING"，以及所有的`Skeleton`都应该被替换为"Something"。

同样的，将所有的`Joe Hacker`替换为预期的版权所有者的名称，也就是你的名称。然后对`joe@example.com`邮件地址进行同样的操作。

##### 顶级目录中的meson.build

- 使绑定模块的版本跟踪被封装的库的版本很常见。例如，C库版本是1.23.4，则绑定模块的初始版本为1.23.0。但是请尽量避免以偶数次要版本开始，因为这通常意味着该版本是稳定版本。

- 如果有必要，请在`project()`函数中修改许可证和C++版本。

- 你可能需要添加除glibmm和skeleton(libsomething)以外的必需模块。

##### 其他meson.build文件

接下来我们必须对其他的`meson.build`文件进行修改：

- `skeleton/meson.build`：在这除了全局替换以外基本上不需要做什么。

- `skeleton/skeletonmm/meson.build`

  `defs_basefiles`

    在这添加额外的.defs和docs.xml文件

  `hg_ccg_basenames`

    必须在这里提及所有的`.hg`和`.ccg`。

  `extra_cc_files, extra_h_files`

    所有手写的`.h`和`.cc`源文件都需要写在这。

##### 创建.hg个.ccg文件

现在我们应该创建第一个`.hg`和`.ccg`，用以对一个C库中的对象进行封装。存在一对可供参考的示例源文件：`skeleton.ccg`和`skeleton.hg`。请根据需要创建这些文件的副本。

在.hg和.ccg文件小节你可以学到这些文件使用的语法。

### 生成.defs文件

`.defs`是一种使用lisp格式的文本文件，它描述了C库的API，其中包括了以下内容：

- 对象(GObjects、部件、接口、装箱类型、普通结构体)

- 函数

- 枚举

- 信号

- 属性

- 虚函数(vfuncs)

目前我们有一些单独的工具用于生成`.defs`文件的一部分，因此我们将它们拆分为单独的文件，例如在gtkmm源的`gtk/src`目录中你将看到以下文件：

`gtk.defs`

  包含其他文件。

`gtk_methods.defs`

  对象和函数。

`gtk_enums.defs`

  枚举。

`gtk_signals.defs`

  信号和属性。

`gtk_vfuncs.defs`

  虚函数(结构体中的函数指针字段)，手动编写。

`skeletonmm/tools/generate_defs_and_docs.sh`将生成所有的这些`.defs`以及`*_docs.xml`文件，如该小节的文档所述。

#### 生成_methods.defs文件

这个`.defs`文件描述了对象及其函数。它是由`h2def.py`脚本生成的，你可以在glibmm的`tools/defs_gen`文件夹中找到该脚本。例如：

    $ ./h2def.py /usr/include/gtk-4.0/gtk/*.h > gtk_methods.defs

#### 生成_enums.defs文件

This `.defs` file describes enum types and their possible values. It is generated by the `enumextract.py` script which you can also find in glibmm's `tools/defs_gen` directory. For instance,

    $ ./enumextract.py /usr/include/gtk-4.0/gtk/*.h > gtk_enums.defs

#### 生成_signals.defs文件

这个`.defs`文件描述了信号和属性。它是由封装项目自有的`generate_extra_defs`实用程序生成的，你可以在类似于`gtkmm/tools/extra_defs_gen/`的目录下找到它。例如：

    $ cd tools/extra_defs_gen
    $ ./generate_extra_defs > gtk_signals.defs

你必须编辑自己的`generate_extra_defs`工具的源代码才能为需要封装的GObject C类型生成`.defs`文件。在skeleton源代码树中，源文件名为`tools/extra_defs_gen/generate_defs_skeleton.cc`。你需要将文件重命名为正确的文件名（按前一小节所述），然后在`tools/extra_defs_gen/meson.build`文件中提到新的源文件名。

然后编辑`.cc`以指定正确的类型。例如，你的`main()`函数可能如下所示：

    #include <glibmm_generate_extra_defs/generate_extra_defs.h>
    #include <libsomething.h>
    #include <iostream>

    int main(int, char**)
    {
      something_init();

      std::cout << get_defs(SOME_TYPE_WIDGET)
                << get_defs(SOME_TYPE_STUFF);
      return 0;
    }

#### 编写_vfunc.defs文件

`.defs`文件描述了虚函数（vfuncs）。它必须手写。你可以从`skeleton/src/skeleton_vfunc.defs`文件开始进行修改。你也可以从gtkmm的`gtk/src/gtk_vfuncs.defs`文件开始。

### .hg和.ccg文件

.hg和.ccg源文件与C++的.h和.cc源文件非常相似，但是它们包含额外的宏，例如`_CLASS_GOBJECT()`和`_WRAP_METHOD()`，`gmmproc`会使用这些宏在头文件中的相同位置生成合适的C++源代码。而所有其他的C++源代码会按原样复制到相应的.h或.cc文件中。

A .hg file will typically include some headers and then declare a class, using some macros to add API or behavior to this class. For instance, gtkmm's `button.hg` looks roughly like this:

    #include <gtkmm/widget.h>
    #include <gtkmm/actionable.h>
    _DEFS(gtkmm,gtk)
    _PINCLUDE(gtkmm/private/widget_p.h)

    namespace Gtk
    {

    class Button
      : public Widget,
        public Actionable
    {
      _CLASS_GTKOBJECT(Button, GtkButton, GTK_BUTTON, Gtk::Widget, GtkWidget)
      _IMPLEMENTS_INTERFACE(Actionable)
    public:

      _CTOR_DEFAULT
      explicit Button(const Glib::ustring& label, bool mnemonic = false);

      _WRAP_METHOD(void set_label(const Glib::ustring& label), gtk_button_set_label)

      ...

      _WRAP_SIGNAL(void clicked(), "clicked")

      ...

      _WRAP_PROPERTY("label", Glib::ustring)
    };

    } // namespace Gtk

本示例中的宏执行下述操作：

`_DEFS()`

  指定所生成源代码的目标目录，以及`gmmproc`应该解析的主要.defs文件的名称。

`_PINCLUDE()`

  告诉`gmmproc`在生成的文件中包含`private/button_p.h`头文件。

`_CLASS_GTKOBJECT()`

  告诉`gmmproc`在封装部件类时在类中添加一些类型定义(typedef)、构造函数、标准方法。

`_IMPLEMENTS_INTERFACE()`

  告诉`gmmproc`为接口添加初始化代码。

`_CTOR_DEFAULT`

  添加默认构造函数。

`_WRAP_METHOD()`、`_WRAP_SIGNAL()`和`_WRAP_PROPERTY()`

  添加用于封装C API的一部分的方法。

通过这样使用`gmmproc`处理.hg和.ccg文件，可以从.hg和.ccg文件生成.h和.cc文件，尽管这在使用上述的构建结构时会自动进行：

    $ cd gtk/src
    $ /usr/lib/glibmm-2.68/proc/gmmproc -I ../../tools/m4 --defs . button . ./../gtkmm

请注意我们为`gmmproc`提供了.m4转换文件、.defs文件路径、.hg文件名、源代码目录、目标目录。

你应该避免在C++头文件中包含C头文件，以免污染全局名称空间，以免导出不必要的公共API。但是你需要在你的.ccg文件中包含必要的C头文件。

以下各节将对宏进行详细说明。

#### m4转换

The macros that you use in the .hg and .ccg files often need to know how to convert a C++ type to a C type, or vice-versa. `gmmproc` takes this information from an .m4 file in your `tools/m4/` or `codegen/m4/` directory. This allows it to call a C function in the implementation of your C++ method, passing the appropriate parameters to that C function. For instance, this tells `gmmproc` how to convert a GtkTreeView pointer to a Gtk::TreeView pointer:

    _CONVERSION(`GtkTreeView*',`TreeView*',`Glib::wrap($3)')

当此转换被`gmmproc`使用时，`$3`将会被参数名称替换。

使用一些额外的宏可以使其变得更简单和一致。例如gtkmm中的.m4文件：

    _CONVERSION(`PrintSettings&',`GtkPrintSettings*',__FR2P)
    _CONVERSION(`const PrintSettings&',`GtkPrintSettings*',__FCR2P)
    _CONVERSION(`const Glib::RefPtr<Printer>&',`GtkPrinter*',__CONVERT_REFPTR_TO_P($3))

#### 安装m4

在封装方法时，通常最好将C函数的返回值储存在被称为输出参数的参数中。在本例中，C++方法返回void但是有一个用于储存C函数值的输出参数。`gmmproc`支持此功能，但是必须使用适当的初始化宏来告诉`gmmproc`如何从C函数的返回值初始化C++参数。

例如，如果一个C函数返回了一个GtkWidget*，并且由于某种原因，不想让C++方法返回此部件，而是希望C++方法将此部件放入指定的输出参数中，则如下所示的初始化宏是必须的：

    _INITIALIZATION(`Gtk::Widget&',`GtkWidget*',`$3 = Glib::wrap($4)')

`$3`将被C++方法的输出参数名替换，`$4`将在此初始化被`gmmproc`使用时被C函数的返回值替代。为了方便起见，`$1`也将被不带`&`符的C++类型替换，而`$2`将被替换为C类型。

#### 类宏

类宏声明了类本身与其底层C类型的关系。它会生成一些内部构造函数、`gobject_`成员、类型定义(typedef)、`gobj()`访问器、`Glib::wrap()`成员函数、并进行类型注册等。

其他像`_WRAP_METHOD()`和`_WRAP_SIGNAL()`这样的宏只能在调用`_CLASS_*`宏之后使用。

##### _CLASS_GOBJECT

这个宏为从GObject派生的类型声明了一个类型包装器，但是此包装器并不会派生自Gtk::Object。

`_CLASS_GOBJECT( C++类, C类, C转换宏, C++基类, C基类 )`

示例来自于`entry.hg`：

    _CLASS_GOBJECT(Adjustment, GtkAdjustment, GTK_ADJUSTMENT, Glib::Object, GObject)

##### _CLASS_GTKOBJECT

这个宏为类型声明了一个类型派生包装器，此包装器将派生自Gtk::Object，例如部件(Widget)和对话框(Dialog)。

`_CLASS_GTKOBJECT( C++类, C类, C转换宏, C++基类, C基类 )`

来自`button.hg`文件中的例子：

    _CLASS_GTKOBJECT(Button, GtkButton, GTK_BUTTON, Gtk::Widget, GtkWidget)

当类已从Gtk::Object派生时，你通常会使用此宏。例如：你将在封装GTK部件时使用它，因为Gtk::Widget派生自Gtk::Object。

你还可以从Gtk::Object派生非部件类，这样你就可以在不通过Glib::RefPtr直接使用它们。例如：它们将可以使用`Gtk::make_managed()`实例化也可以作为成员变量在栈上实例化。这样你可以自行决定是否对它们使用引用计数。我们认为对部件使用引用计数很有用。

##### _CLASS_BOXEDTYPE

这个宏声明了一个非GObject结构体类型的包装器，并使用`g_boxed_type_register_static()`对该类型进行注册。

`_CLASS_BOXEDTYPE( C++类, C类, new函数, copy函数, free函数 )`

示例来自Gdk::RGBA：

    _CLASS_BOXEDTYPE(RGBA, GdkRGBA, NONE, gdk_rgba_copy, gdk_rgba_free)

##### _CLASS_BOXEDTYPE_STATIC

这个宏为类似于GdkRectangle这样的简单可赋值结构体声明了一个包装器。它和`_CLASS_BOXEDTYPE`类似，不过其包装的C结构体不是动态分配的。

`_CLASS_BOXEDTYPE_STATIC( C++类, C类 )`

示例来自Gdk::Rectangle：

    _CLASS_BOXEDTYPE_STATIC(Rectangle, GdkRectangle)

##### _CLASS_OPAQUE_COPYABLE

这个宏为具有复制和释放函数的不透明结构体声明了一个包装器。新建(new)、复制(copy)、释放(free)函数将用于实例化默认构造函数、复制构造函数、析构函数。

`_CLASS_OPAQUE_COPYABLE( C++类, C类, new函数, copy函数, free函数 )`

示例来自Glib::Checksum：

    _CLASS_OPAQUE_COPYABLE(VariantType, GVariantType, NONE, g_variant_type_copy, g_variant_type_free)

##### _CLASS_OPAQUE_REFCOUNTED

这个宏为使用引用计数的不透明结构体声明了一个包装器。C++包装器将不能直接实例化，需要与Glib::RefPtr一起使用。

`_CLASS_OPAQUE_REFCOUNTED( C++函数, C函数, new函数, ref函数, unref函数 )`

示例来自Gdk::Rectangle：

    _CLASS_OPAQUE_REFCOUNTED(CssSection, GtkCssSection, NONE, gtk_css_section_ref, gtk_css_section_unref)

##### _CLASS_GENERIC

这个宏用于包装任何不属于专用包装宏的结构体。

`_CLASS_GENERIC( C++类, C类 )`

示例来自Gdk::Rectangle：

    _CLASS_GENERIC(TimeCoord, GdkTimeCoord)

##### _CLASS_INTERFACE

这个宏为派生自GTypeInterface的类型声明了一个包装器。

`_CLASS_INTERFACE( C++类, C类, C转换宏, C接口结构体, C++基类(可选的), C基类(可选的) )`

示例来自与`celleditable.hg`：

    _CLASS_INTERFACE(CellEditable, GtkCellEditable, GTK_CELL_EDITABLE, GtkCellEditableIface)

对于接口是从其他接口派生的情况，两个附加参数是可选的，GInterface有另一个GInterface作为先决条件时属于此情况。示例来自于`loadableicon.hg`：

    _CLASS_INTERFACE(LoadableIcon, GLoadableIcon, G_LOADABLE_ICON, GLoadableIconIface, Icon, GIcon)

#### 构造函数宏

`_CTOR_DEFAULT()`和`_WRAP_CTOR()`宏封装指定的`*_new()` C函数并添加构造函数。这些宏假定C对象具有与函数参数名一样的属性名，这样它就可以直接向`g_object_new()`调用提供参数。这些构造函数实际上从不会调用`*_new()` C函数，因为gtkmm必须实例化派生的GType，而`*_new()` C函数只是用于给C程序员提供便利。

在使用`_CLASS_GOBJECT()`时，其构造函数应该是保护(protected)的而不是公有(public)。并且每个公有(public)的构造函数都应该有`_WRAP_CREATE()`。这样可以防止在不使用RefPtr的情况下直接实例化这个类。例如：

    class TextMark : public Glib::Object
    {
      _CLASS_GOBJECT(TextMark, GtkTextMark, GTK_TEXT_MARK, Glib::Object, GObject)

    protected:
      _WRAP_CTOR(TextMark(const Glib::ustring& name, bool left_gravity = true), gtk_text_mark_new)

    public:
      _WRAP_CREATE(const Glib::ustring& name, bool left_gravity = true)

##### _CTOR_DEFAULT

这个宏创建一个不接受参数的默认构造函数

##### _WRAP_CTOR

这个宏创建一个接受参数的构造函数，与`*_new()` C函数等效。但是他不会调用`*_new()`函数，只会创建有相同参数类型的构造函数。它需要一个C++构造函数签名和一个C函数名。

他还有需要额外的可选参数：

errthrow

  这告诉`gmmproc`，`*_new()`的最后一个 GError**是否应该被忽略。

##### 手工编码构造函数

有时因为`*_new()` C函数的参数不与对象属性直接对应，或是`*_new()` C函数不只是简单的调用`g_object_new()`，你将必须手写一部分构造函数。在此时你可以在》ccg文件中使用`_CONSTRUCT()`宏来保存一些工作。`_CONSTRUCT`宏接受一系列属性名与值作为参数。示例来自`button.ccg`：

    Button::Button(const Glib::ustring& label, bool mnemonic)
    :
      _CONSTRUCT("label", label.c_str(), "use_underline", gboolean(mnemonic))
    {}

#### 抑制某些代码生成的宏

在某些宏在`_CLASS_*`宏之后使用时，它们会抑制某些代码的生成。一些抑制在.cc文件中生成定义，另一些抑制在.h文件中生成的声明和.cc文件中生成的定义。

##### _CUSTOM_DEFAULT_CTOR

抑制`_CLASS_BOXEDTYPE`、`_CLASS_BOXEDTYPE_STATIC`、`_CLASS_OPAQUE_COPYABLE`生成默认构造函数的声明和定义。

##### _CUSTOM_CTOR_CAST

抑制`_CLASS_BOXEDTYPE`和`_CLASS_BOXEDTYPE_STATIC`生成接受指向其所包装的C类型的指针的构造函数的声明和定义。

抑制`_CLASS_INTERFACE`和`_CLASS_OPAQUE_COPYABLE`生成接受指向其所包装的C类型的指针的构造函数的定义。

抑制`_CLASS_GOBJECT`和`_CLASS_GTKOBJECT`生成接受指向其所包装的C类型的指针且接受其他构造参数的构造函数的定义。

##### _CUSTOM_DTOR

抑制`_CLASS_GOBJECT`和`_CLASS_GTKOBJECT`生成析构函数的定义

##### _CUSTOM_MOVE_OPERATIONS

抑制`_CLASS_GOBJECT` `_CLASS_GTKOBJECT`生成移动构造函数和移动赋值运算符的声明和定义。

例如：

    class Derived : public Glib::Object
    {
      _CLASS_GOBJECT(Derived, GDerived, G_DERIVED, Glib::Object, GObject)

      _CUSTOM_MOVE_OPERATIONS

    public:
      Derived(Derived&& src) noexcept;
      Derived& operator=(Derived&& src) noexcept;
      // ...
    };

##### _CUSTOM_WRAP_NEW

抑制`_CLASS_GOBJECT`生成`Glib::wrap_new()`的定义。

##### _CUSTOM_WRAP_FUNCTION

抑制`_CLASS_GOBJECT`和`_CLASS_GTKOBJECT`生成`Glib::wrap()`函数的定义。

##### _NO_WRAP_FUNCTION

抑制`_CLASS_GOBJECT`、`_CLASS_BOXEDTYPE`、`_CLASS_BOXEDTYPE_STATIC`、`_CLASS_OPAQUE_COPYABLE`、`_CLASS_INTERFACE`和`_CLASS_GTKOBJECT`生成`Glib::wrap()`函数的声明和定义。

#### 方法宏

##### _WRAP_METHOD

这个宏用于生成封装C函数的C++方法。

`_WRAP_METHOD( C++方法签名, C函数名)`

示例来自于`entry.hg`：

    _WRAP_METHOD(void set_text(const Glib::ustring& text), gtk_entry_set_text)

在.defs文件中有对C函数更全面的描述，`convert*.m4`文件中包含了从C++参数类型到C参数类型的必要转换。这个宏还基于`*_docs.xml`和`*_docs_override.xml`文件生成doxygen文档注释。

有一些可选的额外参数：

refreturn

  在C函数不提供引用时，对其返回值额外调用一次`reference()`。

errthrow ["<exceptions>"]

  使用C函数的最后一个GError**参数抛出异常，可选的"<exceptions>"是一个以逗号分隔的可抛出异常列表。它决定了哪些@throws Doxygen命令将被加入文档。默认抛出的是Glib::Error异常。如果你需要在异常描述中使用逗号，请使用反斜杠对其进行转义。例如：`errthrow "Glib::OptionError Hello\, world, Glib::ConvertError"`

deprecated ["<text>"]

  将生成的代码放入#ifdef块中。text是可选参数，用于提供与弃用有关的文本信息。

ignore_deprecations

  Puts the generated code in the .cc file in a G_GNUC_BEGIN_IGNORE_DEPRECATIONS / G_GNUC_END_IGNORE_DEPRECATIONS block. (Only in glibmm >= 2.70.1)

constversion

  直接调用同一个函数的非const版本，而不是生成几乎相同的代码。

newin "<version>"

  在文档中添加@newin Doxygen命令，或替换从C文档中生成的@newin命令。

ifdef <identifier>

  将生成的代码放入ifdef <identifier>块中。

slot_name <parameter_name>

  如果方法存在一个槽参数，指定该槽参数的名称。这使得`gmmproc`可以生成用于复制槽并将其复制传递给C函数的`gpointer user_data`参数的代码。`slot_callback`参数需要使用此选项指定的名称。

slot_callback <function_name>

  与`slot_name`选项一起使用。用`slot_name`所指定的胶水回调函数处理提取槽然后调用它。这个回调函数的函数指针也将被传递给封装此C函数的方法。

no_slot_copy

  告知`gmmproc`如果此方法有槽，不要将槽的副本传递给C函数。而是直接传递槽本身。槽的参数名和胶水回调函数必须分别使用`slot_name`和`slot_callback`选项指定。

封装C API时，选择对应的C++类型也很重要。尽管多数时候应该在C++方法中使用什么C++类型是很明显的，但我们还是想给你一些提示：

- 通过RefPtr使用的对象：传递RefPtr的const引用。例如：`const Glib::RefPtr<Gtk::FileFilter>& filter`。

- 通过RefPtr使用的const对象：如果函数不应该修改此对象，即使对象已经是const的，也应该确保对象的RefPtr是const的。例如：`const Glib::RefPtr<const Gtk::FileFilter>& filter`。

- 包装GList*和GSList*参数：首先你需要阅读C函数的文档，确定列表中每项的数据字段包含了那些对象。然后确定如何用std::vector进行包装。例如包装为`std::vector<Glib::RefPtr<Gdk::Pixbuf>>`。你可能需要定义一个Traits类型来指示C类型和C++类型如何相互转换。

- 包装GList*和GSList*返回值：你需要在此阅读C函数文档，确定调用方是否需要负责释放列表和列表中的项目。然后根据这些信息，选择m4转换规则的所有权（无(none)、浅(shallow)、深(deep)），你可能应该将其直接放在.hg文件中，因为所有权取决于函数本身而不是其类型。例如：

      #m4 _CONVERSION(`GSList*',`std::vector<Widget*>',`Glib::SListHandler<Widget*>::slist_to_vector($3, Glib::OWNERSHIP_SHALLOW)')

##### _WRAP_METHOD_DOCS_ONLY

这个宏类似于`_WRAP_METHOD()`，但是它只生成封装C函数的C++方法的文档。当你需要手动编写该方法又不想手动编写文档时，请使用这个宏。

`_WRAP_METHOD_DOCS_ONLY(C function name)`

示例来自于`container.hg`：

    _WRAP_METHOD_DOCS_ONLY(gtk_recent_info_get_applications)

有一些可选的额外参数：

errthrow ["<exceptions>"]

  不要在文档中包含C函数的最后一个GError**参数的文档。可选的 "<exceptions>"是一个以逗号分隔的可抛出异常列表。它决定了哪些@throws Doxygen命令将被加入文档。默认抛出的是Glib::Error异常。如果你需要在异常描述中使用逗号，请使用反斜杠对其进行转义。例如：`errthrow "Glib::OptionError Hello\, world, Glib::ConvertError"`

newin "<version>"

  在文档中添加@newin Doxygen命令，或替换从C文档中生成的@newin命令。

voidreturn

  不要再文档中包含@return Doxygen命令。如果被封装的C函数有返回而封装它的C++方法返回void，这将很有用。

##### _IGNORE, _IGNORE_SIGNAL, _IGNORE_PROPERTY

`gmmproc`会在stdout上警告你忘了封装相关的函数、信号、属性、子属性，这有助于确保API被完整的封装。但有时候你会不想对某些函数、信号、属性、子属性封装，或者你想要手动编写一些函数，那么你可以使用_IGNORE()、_IGNORE_SIGNAL()、_IGNORE_PROPERTY()宏使`gmmproc`停止发出部分警告。

    _IGNORE(C属性名1, C属性名2, ...)
    _IGNORE_SIGNAL(C属性名1, C属性名2, ...)
    _IGNORE_PROPERTY(C属性名1, C属性名2, ...)

示例来自`flowbox.hg`：

    _IGNORE(gtk_flow_box_set_filter_func, gtk_flow_box_set_sort_func)
    _IGNORE_SIGNAL(activate-cursor-child, toggle-cursor-child, move-cursor)

##### _WRAP_SIGNAL

这个宏生成用来封装C GObject信号的C++ libsigc++风格的信号。实际上它会生成一个公有访问器方法，例如`signal_clicked()`，它返回一个代理对象。`gmmproc`使用.defs文件寻找C参数类型并使用.m4转换文件查找如何将其转换为适当的类型。

`_WRAP_SIGNAL( C++信号处理函数签名, C信号名)`

来自`button.hg`文件中的例子：

    _WRAP_SIGNAL(void clicked(),"clicked")

信号通常在GTK结构体中具有函数指针，在.c文件中有对应的枚举值和`g_signal_new()`函数。

有一些可选的额外参数：

no_default_handler

  不要生成`on_something()`虚成员函数以允许简单的覆写默认信号处理函数。可以在添加一个具有默认信号处理函数的信号会使虚函数表尺寸变大以至于破坏ABI的时候，或是在需要添加一个非公共C默认处理函数的时候，使用此可选参数。

custom_default_handler

  在`.h`文件中生成`on_something()`虚成员函数的声明，但不在`.cc`文件中生成该函数的定义。请在你需要手动编写函数定义的时候使用此可选参数。

custom_c_callback

  不要为信号生成C回调函数。请在你需要手动编写回调函数的时候使用此可选参数。

refreturn

  当C函数没有提供引用的时候，在`on_something()`虚成员函数的返回值上额外调用一次`reference()`。

deprecated ["<text>"]

  将生成的代码放入#ifdef块中。text是可选参数，用于提供与弃用有关的文本信息。

newin "<version>"

  在文档中添加@newin Doxygen命令，或替换从C文档中生成的@newin命令。

ifdef <identifier>

  将生成的代码放入ifdef <identifier>块中。

exception_handler <method_name>

  允许使用自定义异常处理函数而不是使用默认异常处理函数。如果异常被用户自定义的处理函数程序抛出，则该异常将被默认异常处理函数捕获。

detail_name <parameter_name>

  向signal_something()成员函数添加一个const Glib::ustring&参数。如果信号接受详细信号名，也就是说底层C代码使用了`G_SIGNAL_DETAILED`标志注册这个信号，则使用该参数作为详细信号名。

two_signal_methods

  需与`detail_name`选项一起使用。生成两个signal_something()成员函数，其中一个不带任何参数，而另一个接受一个参数但该参数没有默认值。如果只使用`detail_name`选项，则只生成一个成员函数，此成员函数将接受一个参数且此参数有默认值。请再需要保持ABI不变的情况下使用此选项。

##### _WRAP_PROPERTY

这个宏生成用于包装C GObject属性的C++方法。你需要为该属性指定属性名以及其值所需的C++类型。`gmmproc`使用.defs文件寻找C参数类型并使用.m4转换文件查找如何将其转换为适当的类型。

`_WRAP_PROPERTY(C属性名, C++类型)`

来自`button.hg`文件中的例子：

    _WRAP_PROPERTY("label", Glib::ustring)

有一些可选的额外参数：

deprecated ["<text>"]

  将生成的代码放入#ifdef块中。text是可选参数，用于提供与弃用有关的文本信息。

newin "<version>"

  在文档中添加@newin Doxygen命令，或替换从C文档中生成的@newin命令。

##### _WRAP_VFUNC

这个宏生成用于封装C虚函数的C++成员函数。

`_WRAP_VFUNC( C++成员函数签名, C函数名)`

示例来自于`widget.hg`：

    _WRAP_VFUNC(SizeRequestMode get_request_mode() const, get_request_mode)

在`*_vfuncs.defs`文件中有对C函数更全面的描述，`convert*.m4`文件中包含了从C++参数类型到C参数类型的必要转换。转换也可以写入.hg文件。虚函数通常需要额外的转换，这些转换最好保存在使用这些虚函数的.hg文件文件中。

有一些可选的额外参数：

refreturn

  当C虚函数没有提供引用的时候，在`something_vfunc()`函数的返回值上额外调用一次`reference()`。

refreturn_ctype

  当被调用的C函数期望其提供一个引用的时候，在C回调函数当中，对被覆写的`something_vfunc()`函数的返回值额外调用一次`reference()`。

keep_return

  当被调用的C函数不想要得到属于它的引用的时候，在C回调函数中保留一份返回值的副本。

errthrow

  如果C虚函数的参数中的最后有一个GError**参数，则使用此参数抛出异常。

custom_vfunc

  不在`.cc`文件中生成虚函数的定义。请在你需要手写虚函数的时候使用此可选参数。

custom_vfunc_callback

  不为虚函数生成C回调函数。请在你需要手写回调函数时使用此可选参数。

ifdef <identifier>

  将生成的代码放入ifdef <identifier>块中。

slot_name <parameter_name>

  如果方法存在一个槽参数，指定该槽参数的名称。这使得`gmmproc`可以生成用于复制槽并将其复制传递给C函数的`gpointer user_data`参数的代码。`slot_callback`参数需要使用此选项指定的名称。

slot_callback <function_name>

  与`slot_name`选项一起使用。用`slot_name`所指定的胶水回调函数处理提取槽然后调用它。这个回调函数的函数指针也将被传递给封装此C函数的方法。

no_slot_copy

  告知`gmmproc`如果此方法有槽，不要将槽的副本传递给C函数。而是直接传递槽本身。槽的参数名和胶水回调函数必须分别使用`slot_name`和`slot_callback`选项指定。

return_value <value>

  定义非默认的返回值。

err_return_value <value>

  定义一个只在C++ `something_vfunc()`函数抛出异常，且传播到了C回调函数中的时候才使用的非默认返回值。如果指定了return_value但没有指定err_return_value，则将err_return_value的值视作return_value的值。

exception_handler <method_name>

  允许使用自定义异常处理函数而不是使用默认异常处理函数。如果异常被用户自定义的处理函数程序抛出，则该异常将被默认异常处理函数捕获。

一个可能有例外的规则：如果一个C虚函数返回了指向派生自GObject的对象的指针，也即一个引用计数对象的指针，那么C++虚函数应该返回Glib::RefPtr<>对象。且需要refreturn或refreturn_ctype额外参数。

#### 其他宏

##### _IMPLEMENTS_INTERFACE

这个宏为接口生成初始化代码

`_IMPLEMENTS_INTERFACE(C++接口名)`

示例来自于`grid.hg`：

    _IMPLEMENTS_INTERFACE(Orientable)

有一个可选的额外参数：

ifdef <identifier>

  将生成的代码放入ifdef <identifier>块中。

##### _WRAP_ENUM

这个宏生成一个C++枚举以包装C枚举。你必须指定所需的C++名称和底层C枚举的名称。

示例来自于`enums.hg`：

    _WRAP_ENUM(Orientation, GtkOrientation)

有一些可选的额外参数：

NO_GTYPE

  如果枚举不是一个GType，则需使用此选项。在这个情况下C枚举没有`*_get_type()`函数，你不需要为该函数额外包含头文件。但你应该此为对C API提交一个bug，因为所有的枚举都应该被注册为GTypes。

  如果你指定了`NO_GTYPE`，不要再_WRAP_PROPERTY中使用此枚举类型。这会导致生成的property_*()成员函数被调用时出现运行时错误。

  示例来自于`icontheme.hg`：

      _WRAP_ENUM(IconLookupFlags, GtkIconLookupFlags, NO_GTYPE)

gtype_func <function_name>

  指定C枚举的`*_get_type()`函数名。如果`gmmproc`无法从C枚举类型的名称推断出正确的函数名，请使用此额外参数。

  示例来自于glibmm的`dbusproxy.hg`：

      _WRAP_ENUM(ProxyFlags, GDBusProxyFlags, gtype_func g_dbus_proxy_flags_get_type)

CONV_TO_INT

  在一个类中生成普通枚举而不是生成枚举类。这将允许该枚举类型的值可以直接转换为int类型的值，且这样的枚举还拥有与枚举类类似的作用域。

  示例来自于`dialog.hg`：

      _WRAP_ENUM(ResponseType, GtkResponseType, CONV_TO_INT)

s#<from>#<to>#

  替换一个或多个枚举常量名称的全部或一部分。你可以添加任意数量的替换。

  示例来自于glibmm中的`iochannel.hg`：

      _WRAP_ENUM(SeekType, GSeekType, NO_GTYPE, s#^SEEK_#SEEK_TYPE_#)

deprecated ["<text>"]

  将生成的代码放入#ifdef块中。text是可选参数，用于提供与弃用有关的文本信息。

newin "<version>"

  在文档中添加@newin Doxygen命令，或替换从C文档中生成的@newin命令。

##### _WRAP_ENUM_DOCS_ONLY

This macro just generates a Doxygen documentation block for the enum. This is useful for enums that can't be wrapped with `_WRAP_ENUM()` because they are complexly defined (maybe using C macros) but including the generated enum documentation is still desired. It is used with the same syntax as `_WRAP_ENUM()` and also processes the same options (though NO_GTYPE, gtype_func <function_name> and CONV_TO_INT are ignored because they make no difference when just generating the enum's documentation).

##### _WRAP_GERROR

这个宏会生成一个C++异常类，生成的类派生自Glib::Error，并具有一个Code枚举成员变量和一个code()成员函数。你必须指定所需的C++名称、以及与其对应的C枚举名、C枚举值前缀。

然后可以由带有errthrow选项的_WRAP_METHOD()生成的成员函数抛出此异常。

示例来自于`pixbuf.hg`：

    _WRAP_GERROR(PixbufError, GdkPixbufError, GDK_PIXBUF_ERROR)

_WRAP_GERROR()接受与_WRAP_ENUM()一样的可选参数（尽管CONV_TO_INT参数因为没有作用而被忽略）。

##### _MEMBER_GET / _MEMBER_SET

如果要包装可以被直接访问其数据成员的简单结构体或装箱类型，请使用这些宏。这些宏会为其数据成员创建访问器成员函数（getter和setter）。

`_MEMBER_GET(C++名, C名, C++类型, C类型)`

`_MEMBER_SET(C++名, C名, C++类型, C类型)`

示例来自于`rectangle.hg`：

    _MEMBER_GET(x, x, int, int)

##### _MEMBER_GET_PTR / _MEMBER_SET_PTR

使用这些宏可以自动为指针类型的数据成员提供访问器（getter和setter）。对于getter函数，将创建两个版本，一个const版本一个非const版本。

`_MEMBER_GET_PTR(C++名, C名, C++类型, C类型)`

`_MEMBER_GET_PTR(C++名, C名, C++类型, C类型)`

示例来自于`item.hg`文件中的Pango::Analysis：

    // _MEMBER_GET_PTR(engine_lang, lang_engine, EngineLang*, PangoEngineLang*)
    // It's just a comment. It's difficult to find a real-world example.

##### _MEMBER_GET_GOBJECT / _MEMBER_SET_GOBJECT

使用这些宏可以为返回之前必须被引用的GObject类型数据成员提供访问器（getters和setters）。

`_MEMBER_GET_GOBJECT(C++名, C名, C++类型, C类型)`

`_MEMBER_SET_GOBJECT(C++名, C名, C++类型, C类型)`

示例来自于Pangomm中的`layoutline.hg`：

    _MEMBER_GET_GOBJECT(layout, layout, Pango::Layout, PangoLayout*)

#### gmmproc参数处理

`gmmproc`允许以各种方式为处理成员函数签名的宏（如`_WRAP_METHOD()`、`_WRAP_CTOR()`、`_WRAP_CREATE()`）处理成员函数签名中的参数：

##### 对参数重新排序

对于所有处理成员函数签名的宏，可以为C++参数指定一个与C函数、虚函数、信号中现有顺序不同的顺序。例如，假设如下所示的C函数将被封装为Gtk::Widget类的C++成员函数：

    void gtk_widget_set_device_events(GtkWidget* widget, GdkDevice* device,
      GdkEventMask events);

但是必须改变C++成员函数中两个参数的顺序。如下所示的代码会将函数封装为C++成员函数但两个参数的顺序会有所不同：

    _WRAP_METHOD(void set_device_events(Gdk::EventMask events{events},
      const Glib::RefPtr<const Gdk::Device>& device{device}),
      gtk_widget_set_device_events)

成员函数参数后的`{c_param_name}`告知`gmmproc`将C++参数映射到`{}`内的C参数。由于C++参数名与C参数名相对应，所以上述代码可以被重写为：

    _WRAP_METHOD(void set_device_events(Gdk::EventMask events{.},
      const Glib::RefPtr<const Gdk::Device>& device{.}),
      gtk_widget_set_device_events)

请注意，当为使用`_WRAP_SIGNAL()`的成员函数签名重排参数时，C参数名总是`p0`、`p1`等。因为这些参数名由`generate_extra_defs`实用工具使用，而该实用工具不在乎C API中的参数名是什么。

##### 可选参数处理

对于除了`_WRAP_SIGNAL()`和`_WRAP_VFUNC()`以外的所有处理成员函数签名的宏，都可以讲参数设为可选的，这样就可以在没有指定可选参数的情况下额外生成C++成员函数。例如，假设以下所示的`*_new()`将被封装为Gtk::ToolButton类的构造函数：

    GtkToolItem* gtk_tool_button_new(GtkWidget* icon_widget, const gchar* label);

另外，假设C API允许函数的label参数为NULL，这样该参数就是可选的。通过在参数名后附加一个`{?}`可以使`gmmproc`生成具有所有参数的原始构造函数和一个带有可选参数的构造函数。在如下所示的情况下：

    _WRAP_CTOR(ToolButton(Widget& icon_widget, const Glib::ustring& label{?}),
      gtk_tool_button_new)

将生成两个构造函数：一个带可选参数，另一个不带可选参数。

##### 输出参数处理

使用`_WRAP_METHOD()`也可以将封装好的C函数的返回值放到C++成员函数的输出参数中，而不是让C++成员函数和C函数一样返回一个值。要做到这个，只需要在C++成员函数参数列表中的输出参数名后附加`{OUT}`即可。例如，如果`gtk_widget_get_request_mode()`的声明如下所示：

    GtkSizeRequestMode gtk_widget_get_request_mode(GtkWidget* widget);

想要让C++成员函数使用输出参数而不是返回一个SizeRequestMode。可以用如下所示的方法：

    _WRAP_METHOD(void get_request_mode(SizeRequestMode& mode{OUT}) const,
      gtk_widget_get_request_mode)

附加在名为mode的输出参数之后的`{OUT}`，告知`gmmproc`将C函数的返回值放在输出参数中。但是在这种情况下还必须指定必要的初始化宏。如下所示：

    _INITIALIZATION(`SizeRequestMode&',`GtkSizeRequestMode',`$3 = (SizeRequestMode)($4)')

也可以写成：

    _INITIALIZATION(`SizeRequestMode&',`GtkSizeRequestMode',`$3 = ($1)($4)')

`_WRAP_METHOD()` also supports setting C++ output parameters from C output parameters if the C function being wrapped has any. Suppose, for example, that we want to wrap the following C function that returns a value in its C output parameter out_mime_type:

    GInputStream* gdk_clipboard_read_finish(GdkClipboard* clipboard,
      GAsyncResult* result, const char** out_mime_type, GError** error)

To have `gmmproc` place the value returned in the C++ out_mime_type output parameter, something like the following `_WRAP_METHOD()` macro could be used:

    _WRAP_METHOD(Glib::RefPtr<Gio::InputStream> read_finish(
      const Glib::RefPtr<Gio::AsyncResult>& result,
      Glib::ustring& out_mime_type{>>}), gdk_clipboard_read_finish, errthrow)

The `{>>}` following the out_mime_type parameter name indicates that the C++ output parameter should be set from the value returned in the C parameter from the C function. `gmmproc` will generate a declaration of a temporary variable in which to store the value of the C output parameter and a statement that sets the C++ output parameter from the temporary variable. In this case it may be necessary to have an `_INITIALIZATION()` describing how to set a Glib::ustring& from a const char** such as the following:

    _INITIALIZATION(`Glib::ustring&',`const char*',`$3 = Glib::convert_const_gchar_ptr_to_ustring($4)')

##### 处理字符串参数

在C++成员函数中的字符串值的输入参数通常是const Glib::ustring&或者是const std::string&。而在C代码中，它是const gchar*。在将空字符串转换为const gchar*的时候，可以将其转换为`nullptr`或者是一个指向空字符串的指针（用c_str()）。一些C函数的一些参数接受`nullptr`，并以特殊方式解释它。其他参数不能为`nullptr`。

`_WRAP_METHOD()`和类似的宏的默认转换是：

- 对于带有或不带有默认值的强制性参数：空字符串到空字符串

- 对于附加了`{?}`的可选参数：空字符串到`nullptr`。

如果默认转换不够好，可以将`{NULL}`附加到强制性参数之后，或是将`{?!NULL}`附加到可选参数之后（`!NULL`意为非`NULL`）。如果想同时添加C参数名和`NULL`，则用空格对它们进行分隔：`{c_param_name NULL}`。

#### 基本类型

C API中使用的一些基本类型在C++中有更好的替代类型。例如，C++中有bool类型，所以不需要gboolean类型。以下列表显示了C API中的一些常用类型，以及你可以在C++封装库中将它们转换为哪些类型。

C 类型 C++类型
- gboolean bool
- gint int
- guint guint
- gdouble double
- gunichar gunichar
- gchar*Glib::ustring(或std::string)

### 手工编码源文件

你可能想包括`gmmproc`不会从`.hg`和`.ccg`文件中生成的额外源文件。你可以将它们放到你的`libsomething/libsomethingmm`文件夹中，并在`meson.build`的`extra_h_files`和`extra_cc_files`变量提到它们。

### 初始化

你必须初始化你的库，以便注册库提供给的新类型，这样你才能使用它。另外你所封装的C库可能有自己需要调用的初始化函数。你可以将其放在手动编码的`init.h`和`init.cc`文件中的`init()`函数中完成此操作。这个函数应该初始化你的依赖项和调用你生成的`wrap_init()`函数。例如：

    void init()
    {
      Gtk::init_gtkmm_internals(); //Sets up the g type system and the Glib::wrap() table.
      wrap_init(); //Tells the Glib::wrap() table about the libsomethingmm classes.
    }

`wrap_init.cc`文件中的`wrap_init()`方法是由`generate_wrap_init.pl`生成的，但是它的声明所在的`wrap_init.h`文件需要手动编码。因此你需要调整`wrap_init.h`文件以使`wrap_init()`函数出现在正确的C++命名空间中。

### C API中的问题

你可能会在封装库的过程中遇到一些问题。特别是它是一个新项目的时候。以下是一些常见问题以及解决方案。

#### 无法提前声明的结构体

按照约定，glib/GTK风格的头文件中结构体声明应如下所示：

    typedef struct _ExampleWidget ExampleWidget;

    struct _ExampleWidget
    {
      ...
    };

额外的typedef允许在头文件中使用该结构体而不需要包含其完整定义，只需要通过重复这个typedef预先声明它即可。这意味着你不需要在你的C++头文件中包含C库的头文件。，从而将其排除在你的公共API之外。`gmmproc`假定使用了这个技术，因此如果没使用这种技术就会遇到编译错误。

编译错误看起来可能如下所示：

    example-widget.h:56: error: using typedef-name 'ExampleWidget' after 'struct'
    ../../libexample/libexamplemm/example-widget.h:34: error: 'ExampleWidget' has a previous declaration here
    make[4]: *** [example-widget.lo] Error 1

或如下所示：

    example-widget.h:60: error: '_ExampleWidget ExampleWidget' redeclared as different kind of symbol
    ../../libexample/libexamplemm/example-widget.h:34: error: previous declaration of 'typedef struct _ExampleWidget ExampleWidget'

这很容易在C库中修复，你可以向相关库的维护者发送补丁。

#### 缺少属性

按照约定，glib/GTK风格对象应具有`*_new()`函数，例如`example_widget_new()`仅调用`g_object_new()`并返回其结果。输入参数为`g_object_new()`函数提供属性名和属性值。例如：

    GtkWidget* example_widget_new(int something, const char* thing)
    {
            return g_object_new (EXAMPLE_TYPE_WIDGET, "something", something, "thing", thing, NULL);
    }

这允许语言绑定实现自己的等效项（例如C++构造函数），而不需要使用`*_new()`函数。这通常是必须的，这是为了让它们可以实例化派生的GType，这样就可以为信号处理函数和虚函数添加自己的钩子。

`_new()`函数不应该使用任何只在.c文件中的私有API。即使没有对应的函数，有时我们也可以在`_new()`函数中重新实现2到3行代码，只要这些行使用的API是可用的。

另一个解决方法是添加一个C++构造函数可以在实例化自身类型后调用的`*_construct()`函数。例如：

    GtkWidget* example_widget_new(int something, const char* thing)
    {
            ExampleWidget* widget;
            widget = g_object_new (EXAMPLE_TYPE_WIDGET, NULL);
            example_widget_construct(widget, "something", something, "thing", thing);
    }

    void example_widget_construct(ExampleWidget* widget, int something, const char* thing)
    {
            //Do stuff that uses private API:
            widget->priv->thing = thing;
            do_something(something);
    }

在C库中，添加属性并确保它们之间正确的进行交互相对来说比较困难。但还是能做到的。若你发现了一个bug请联系相关的维护人员并向其提供相关信息以便尽快修复该bug。

### 文档

通常，gtkmm风格项目使用Doxygen生成文档，它会读取特殊格式的C++注释并生成HTML文档。你可以直接将这些doxygen可读的注释写到头文件中。

#### 重用C文档

You might wish to reuse documentation that exists for the C library that you are wrapping. GTK-style C libraries typically use gtk-doc or gi-docgen and therefore have source code comments formatted for gtk-doc or gi-docgen and some extra documentation in .sgml and .xml files. The docextract_to_xml.py script, from glibmm's `tools/defs_gen` directory, can read these files and generate an .xml file that `gmmproc` can use to generate doxygen comments. `gmmproc` will even try to transform the documentation to make it more appropriate for a C++ API.

例如：

    ./docextract_to_xml.py -s ~/checkout/gnome/gtk/gtk/ > gtk_docs.xml

由于此自动转换并不总是合适的，所以你可能希望对特定的方法提供手写的文本。为此可以将函数的XML节点从`something_docs.xml`文件复制到`something_docs_override.xml`文件并更改其内容。你也可以在`.hg`文件中编写自己的文档。

#### 文档构建结构

如果你已经从mm-common中复制skeleton源代码树并替换占位文本，那么在`doc/reference/`目录中已经拥有了合适的`meson.build`和`Doxyfile.in`文件。你可能需要修改`meson.build`文件中的`tag_file_modules`变量，虽然在mm-common构建设置中，Doxygen输入文件不是通过Doxygen配置文件定义的，而是由`meson/ninja`传递文件内容到`doxygen`的标准输入。