多项容器

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

9.2.1. 组装(Packing)

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

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

  • 在调整窗口大小的时候部件不会重新排布自己。窗口变小的时候一些部件会被隐藏窗口变大的时候会出现很多无用的空间。
  • 将文本翻译成其他语言或者用其他字体显示的时候,无法预测显示文本所需要的空间。而在Unix上不可能预料在每个主题和窗口管理器下的显示效果。
  • 即时更改窗口布局以显示额外的部件将会很复杂,这需要对每个部件的位置进行繁琐的重新计算。

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

gtkmm使用containers分层排布部件。容器部件包含其他部件。大多数的gtkmm部件都是容器部件。窗口(Windows)部件、笔记本选项卡(Notebook tabs)部件、按钮(Buttons)部件都是容器部件。容器有两种类型:单项容器和多项容器。gtkmm中包括Gtk::Window在内的多数容器部件都是单项部件。

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

  • Gtk::Grid将其子部件按行和列排列。使用attach()attach_next_to()方法向网格(Grid)部件中插入子部件。
  • Gtk::Box将其子部件水平或垂直排列。使用append()方法向盒(Box)部件插入子部件。

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

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

9.2.2. 改进后的Hello World

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

Figure 9-5Hello World 2

源代码

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: 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);
}

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::show() 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;
}

生成并运行这个程序后,请尝试调整窗口的大小查看其行为。此外在阅读盒子(Boxes)小节时请尝试使用set_expand()set_hexpand()set_vexpand()set_halign()set_valign()方法。

9.2.3. 盒子(Boxes)

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

9.2.3.1. 添加部件

9.2.3.1.1. 子部件装填选项

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。

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

Figure 9-6组装盒1

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

参考

9.2.3.1.2. 容器装填选项

这是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是每个部件之间要保留的最小像素数。

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

Figure 9-7组装盒2

9.2.3.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::ApplicationGtk::Application的基类)。

9.2.3.3. 示例

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

源代码

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: 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: 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()
{
  hide();
}

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);
  }
}

9.2.4. 网格(Grid)

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

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

你可以设置子部件的marginexpand属性用以控制调整网格大小时它们之间的间距。

参考

9.2.4.1. 示例

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

Figure 9-8网格(Grid)

源代码

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: 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: 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()
{
  hide();
}

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

9.2.5. 笔记本(Notebook)

Notebook有一组堆叠好的pages,每个页面(page)包含一个部件。带标签的tabs允许用户选择页面。Notebook一次只显示一页,这样可以将好几组部件放于很小的空间当中。例如他们经常在首选项对话框中使用。

使用append_page()prepend_page()insert_page()方法可以添加带标签的页面到Notebook中,你需要为这些方法提供子部件和标签名。

如果你想知道当前可见的页面是那一个,可以使用get_current_page()方法。该方法返回一个页码,你可以将页码传递给get_nth_page()获取指向该页当前子部件的指针。

要以编程方式改变所选的页面,请使用set_current_page()方法。

参考

9.2.5.1. 示例

Figure 9-9笔记本(Notebook)

源代码

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: 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: 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()
{
  hide();
}

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.
}

9.2.6. 助手(Assistant)

Assistant将一个复杂的操作切分为数个步骤。每个步骤都是一个页面,包含一个标题、一个子部件和一个可操作区域。助手的操作区域有一个导航按钮,这些按钮会根据页面的类型自动更新(使用set_page_type()设置页面类型)。

使用append_page()prepend_pageinsert_page()方法可以向Assistant添加页面,你需要每个页面提供子部件。

要确定当前可见的页面,请使用get_current_page()方法,然后将返回值传递给get_nth_page(),其会返回一个指向实际部件的指针。要想用编程的方式改变当前页,请使用set_current_page()方法。

要为页面设置标题,请使用set_page_title()方法。

要将部件添加到操作区域,请使用add_action_widget()方法。他们将与默认按钮一并被装填。使用remove_action_widget()方法可以删除部件。

参考

9.2.6.1. 示例

Figure 9-10助手(Assistant)

源代码

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.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: 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: 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.show();
}

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();
  hide();
}

void ExampleAssistant::on_assistant_close()
{
  std::cout << "Assistant was closed";
  print_status();
  hide();
}

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;
}

9.2.7. 其他多项容器

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

动作条(ActionBar)源代码

流式盒(FlowBox)源代码

图标视图(IconView)源代码