Contenedores personalizados

When deriving from Gtk::Container, you should override the following virtual methods:

  • get_request_mode_vfunc(): Return what Gtk::SizeRequestMode is preferred by the container.
  • measure_vfunc(): Calculate the minimum and natural width or height of the container.
  • on_size_allocate(): Position the child widgets, given the height and width that the container has actually been given.
  • forall_vfunc(): Call the same callback for each of the children.
  • on_add(): Add a child widget to the container.
  • on_remove(): Remove a child widget from the container.
  • child_type_vfunc(): Return what type of child can be added.

The get_request_mode_vfunc(), measure_vfunc(), and on_size_allocate() 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.

You are not guaranteed to get the Gtk::SizeRequestMode that you request. Therefore measure_vfunc() must return sensible values for all reasonable values of its input parameters. For a description of measure_vfunc()'s parameters see also the description of Gtk::Widget::measure(), which may be better documented than measure_vfunc().

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

Unless your container is a top-level window that derives from Gtk::Window, you should probably also call Gtk::Widget::set_has_surface(false) in your constructor. This means that your container does not create its own Gdk::Surface, but uses its parent's surface. If your container does need its own Gdk::Surface, and does not derive from Gtk::Window, you must also override the on_realize() method as described in the Custom Widgets section.

GTK+ requires that set_has_surface() is called in the instance init function, which is executed before your constructor. If you call it in the constructor, GTK+ may issue a warning message, but at the time of writing (2018-05-07) it works as intended. Adding code to the instance init function is more complicated for a gtkmm programmer. The custom widget example shows how it can be done.

By overriding forall_vfunc() you can allow applications to operate on all of the container's child widgets. For instance, get_children() uses this to find all the child widgets.

Although your container might have its own method to set the child widgets, you should still provide an implementation for the virtual on_add() and on_remove() methods from the base class, so that the add() and remove() methods will do something appropriate if they are called.

Su implementación del método child_type_vfunc() debe reportar el tipo de widget que puede añadírsele a su contenedor, si todavía no está lleno. Esto es generalmente Gtk::Widget::get_type() para indicar que el contenedor puede contener cualquier clase derivada de Gtk::Widget. Si el contenedor no puede contener más widgets, entonces este método debe devolver G_TYPE_NONE.

26.1.1. Ejemplo

This example implements a container with two 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.

Figura 26-1Contenedor personalizado

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();
  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/container.h>

class MyContainer : public Gtk::Container
{
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 on_size_allocate(int width, int height, int baseline) override;

  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;

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

  ExampleWindow window;

  //Shows the window and returns when it is closed.
  return app->run(window, 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);
  add(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.add(m_MyContainer);
  m_VBox.add(m_ButtonBox);

  m_ButtonBox.add(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::on_size_allocate(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);
  }
}

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