自定义容器

直接从Gtk::Widget派生自定义容器部件时,应重写一下虚方法:

  • get_request_mode_vfunc():返回容器的Gtk::SizeRequestMode偏好。
  • measure_vfunc():返回容器的最小自然宽度或高度。
  • on_size_allocate():根据容器实际宽度和高度定位子部件。

get_request_mode_vfunc()measure_vfunc()on_size_allocate()虚方法控制子部件的布局。例如,如果你的容器有两个子部件,一个在另一个的下方,则你的get_request_mode_vfunc()可能会请求布局高度适应宽度。然后你的measure_vfunc()可能会要求报告宽度的时候报告子部件的最大宽度,在要求报告高度的时候要求子部件的高度之和。如果你想要在子部件之间进行填充,则你应将填充部分算入宽高中。你的容器部件将用这个结果确保部件获得足够的空间。通过对每个部件的父部件进行检查最终确认顶级窗口的大小。

你无法确保得到你请求的Gtk::SizeRequestMode。因此,measure_vfunc()必须为其所有合理的输入参数值返回合适的值。有关measure_vfunc()参数的描述,查看Gtk::Widget::measure()的描述可能比查看measure_vfunc()的文档更好。

on_size_allocate()接收父容器决定给你的部件的实际高度和宽度。例如,如果顶层窗口被扩展,则该值将大于最小值,甚至大于自然大小。你可以选择忽略多余的空间留出一个空白区域,或者选择扩展子部件以填充该空间,或者是选择扩展部件之间的填充。

26.1.1. 示例

本示例实现了一个具有两个子部件的容器,一个在另一个上面。当然在这种情况下直接使用垂直Gtk::BoxGtk::Grid会更简单。

Figure 26-1自定义容器

源代码

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();
  virtual ~ExampleWindow();

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

  //Child widgets:
  Gtk::Box m_VBox;
  Gtk::Button m_Button_One;
  Gtk::Label m_Label_Two;
  // A restriction with MyContainer is that it must be deleted before
  // its children, meaning that it must be declared after its children.
  MyContainer m_MyContainer;
  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>

class MyContainer : public Gtk::Widget
{
public:
  MyContainer();
  virtual ~MyContainer();

  void set_child_widgets(Gtk::Widget& child_one, Gtk::Widget& child_two);

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 size_allocate_vfunc(int width, int height, int baseline) override;
#if 0
  void forall_vfunc(const ForeachSlot& slot) override;

  void on_add(Gtk::Widget* child) override;
  void on_remove(Gtk::Widget* child) override;
  GType child_type_vfunc() const override;
#endif
  Gtk::Widget* m_child_one;
  Gtk::Widget* m_child_two;
};

#endif //GTKMM_CUSTOM_CONTAINER_MYCONTAINER_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_Button_One("Child One"),
  m_Label_Two("Child 2", 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.set_child_widgets(m_Button_One, m_Label_Two);
  m_MyContainer.set_expand();

  m_VBox.append(m_MyContainer);
  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()
{
  hide();
}

File: mycontainer.cc (For use with gtkmm 4)

#include <iostream>
#include <algorithm> // std::max
#include "mycontainer.h"

MyContainer::MyContainer()
: m_child_one(nullptr), m_child_two(nullptr)
{
}

MyContainer::~MyContainer()
{
  if (m_child_one)
    m_child_one->unparent();

  if (m_child_two)
    m_child_two->unparent();
}

void MyContainer::set_child_widgets(Gtk::Widget& child_one,
        Gtk::Widget& child_two)
{
  m_child_one = &child_one;
  m_child_two = &child_two;

  m_child_one->set_parent(*this);
  m_child_two->set_parent(*this);
}

// This example container is a simplified vertical Box with at most two 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;

  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)
    {
      int nvis_children = 0;

      // Get number of visible children.
      if (m_child_one && m_child_one->get_visible())
        ++nvis_children;
      if (m_child_two && m_child_two->get_visible())
        ++nvis_children;

      // Divide the height equally among the visible children.
      if (nvis_children > 0)
        height_per_child = for_size / nvis_children;
    }

    int child_minimum_width[2] = {0, 0};
    int child_natural_width[2] = {0, 0};

    if (m_child_one && m_child_one->get_visible())
      m_child_one->measure(orientation, height_per_child, child_minimum_width[0],
        child_natural_width[0], dummy_minimum_baseline, dummy_natural_baseline);

    if (m_child_two && m_child_two->get_visible())
      m_child_two->measure(orientation, height_per_child, child_minimum_width[1],
        child_natural_width[1], dummy_minimum_baseline, dummy_natural_baseline);

    // Request a width equal to the width of the widest visible child.
    minimum = std::max(child_minimum_width[0], child_minimum_width[1]);
    natural = std::max(child_natural_width[0], child_natural_width[1]);
  }
  else // Gtk::Orientation::VERTICAL
  {
    int child_minimum_height[2] = {0, 0};
    int child_natural_height[2] = {0, 0};
    int nvis_children = 0;

    if (m_child_one && m_child_one->get_visible())
    {
      ++nvis_children;
      m_child_one->measure(orientation, for_size, child_minimum_height[0],
        child_natural_height[0], dummy_minimum_baseline, dummy_natural_baseline);
    }

    if (m_child_two && m_child_two->get_visible())
    {
      ++nvis_children;
      m_child_two->measure(orientation, for_size, child_minimum_height[1],
        child_natural_height[1], dummy_minimum_baseline, dummy_natural_baseline);
    }

    // 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.
    minimum = nvis_children * std::max(child_minimum_height[0],
                                       child_minimum_height[1]);
    natural = nvis_children * std::max(child_natural_height[0],
                                       child_natural_height[1]);
  }
}

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

  //Get number of visible children.
  const bool visible_one = m_child_one && m_child_one->get_visible();
  const bool visible_two = m_child_two && m_child_two->get_visible();
  int nvis_children = 0;
  if (visible_one)
    ++nvis_children;
  if (visible_two)
    ++nvis_children;

  if (nvis_children <= 0)
  {
    // No visible child.
    return;
  }

  //Assign space to the children:
  Gtk::Allocation child_allocation_one;
  Gtk::Allocation child_allocation_two;

  //Place the first child at the top-left:
  child_allocation_one.set_x(0);
  child_allocation_one.set_y(0);

  //Make it take up the full width available:
  child_allocation_one.set_width(width);

  if (visible_one)
  {
    //Divide the height equally among the visible children.
    child_allocation_one.set_height(height / nvis_children);
    m_child_one->size_allocate(child_allocation_one, baseline);
  }
  else
    child_allocation_one.set_height(0);

  //Place the second child below the first child:
  child_allocation_two.set_x(0);
  child_allocation_two.set_y(child_allocation_one.get_height());

  //Make it take up the full width available:
  child_allocation_two.set_width(width);

  //Make it take up the remaining height:
  child_allocation_two.set_height(height - child_allocation_one.get_height());

  if (visible_two)
  {
    m_child_two->size_allocate(child_allocation_two, baseline);
  }
}
#if 0
void MyContainer::forall_vfunc(const ForeachSlot& slot)
{
  if (m_child_one)
    slot(*m_child_one);

  if (m_child_two)
    slot(*m_child_two);
}

void MyContainer::on_add(Gtk::Widget* child)
{
  if(!m_child_one)
  {
    m_child_one = child;
    m_child_one->set_parent(*this);
  }
  else if(!m_child_two)
  {
    m_child_two = child;
    m_child_two->set_parent(*this);
  }
}

void MyContainer::on_remove(Gtk::Widget* child)
{
  if(child)
  {
    const bool visible = child->get_visible();
    bool found = false;

    if(child == m_child_one)
    {
      m_child_one = nullptr;
      found = true;
    }
    else if(child == m_child_two)
    {
      m_child_two = nullptr;
      found = true;
    }

    if(found)
    {
      child->unparent();

      if(visible)
        queue_resize();
    }
  }
}

GType MyContainer::child_type_vfunc() const
{
  //If there is still space for one widget, then report the type of widget that
  //may be added.
  if(!m_child_one || !m_child_two)
    return Gtk::Widget::get_type();
  else
  {
    //No more widgets may be added.
    return G_TYPE_NONE;
  }
}
#endif