Contenedores personalizados

Cuando derive de un Gtk::Container, debe reemplazar los siguientes métodos virtuales:

  • get_request_mode_vfunc(): devuelve qué Gtk::SizeRequestMode prefiere el contenedor.
  • get_preferred_width_vfunc(): calcula la anchura mínima y natural del contenedor.
  • get_preferred_height_vfunc(): calcula la altura mínima y natural del contenedor.
  • get_preferred_width_for_height_vfunc(): calcula la anchura mínima y natural del contenedor, si se le da la altura especificada.
  • get_preferred_height_for_width_vfunc(): calcula la altura mínima y natural del contenedor, si se le da la anchura especificada.
  • on_size_allocate(): posiciona a los widgets hijos de acuerdo a la altura y anchura que se le ha dado al contenedor.
  • forall_vfunc(): llama al mismo retorno de llamada para cada uno de los hijos.
  • on_add(): añade un widget hijo al contenedor.
  • on_remove(): elimina un widget hijo del contenedor.
  • child_type_vfunc(): devuelve qué tipo de hijo puede añadirse.

Los métodos virtuales get_request_mode_vfunc(), get_preferred_width_vfunc(), get_preferred_height_vfunc(), get_preferred_width_for_height_vfunc(), get_preferred_height_for_width_vfunc(), y on_size_allocate() controlan la distribución de los widgets hijos. Por ejemplo, si su contenedor tiene 2 widgets hijos, con uno debajo del otro, su get_request_mode_vfunc() puede pedir una distribución de altura por anchura. Después su get_preferred_width_vfunc() puede reportar el máximo de las anchuras de los widgets hijos, y get_preferred_height_for_width_vfunc() puede reportar la suma de sus anchuras. Si quiere espacios entre los widgets hijos, entonces añádaselos a la altura y a la anchura también. El contenedor de su widget usará este resultado para asegurarse de que obtenga espacio suficiente, y no menos. Examinando al padre de cada widget, y a su padre, esta lógica eventualmente decidirá el tamaño de la ventana de nivel superior.

No se le garantiza obtener el Gtk::SizeRequestMode que pida. Por lo tanto, los cuatro métodos get_preferred_xxx_vfunc() deben devolver valores razonables.

on_size_allocate() recibe la altura y anchura reales que el contenedor padre ha decidido darle a su widget. Esto podría ser más del mínimo, o aún más que el tamaño natural, por ejemplo si se ha expandido la ventana de nivel superior. Puede escoger ignorar el espacio adicional y dejar un área en blanco, expandir a sus widgets hijos para llenar el espacio, o bien expandir la separación entre sus widgets. Es su contenedor, por lo que es su decisión. No olvide llamar a set_allocation() dentro de su implementación de on_size_allocate() para usar realmente el espacio asignado que le ha ofrecido el contenedor padre.

A menos que su contenedor sea una ventana de nivel superior que derive de Gtk::Window, probablemente también deba llamar a Gtk::Widget::set_has_window(false) en su constructor. Esto significa que su contenedor no crea su propia Gdk::Window, sino que usa la de su padre (note la diferencia entre Gtk::Window y Gdk::Window). Si su contenedor no necesita su propia Gdk::Window, y no deriva de Gtk::Window, también debe reemplazar el método on_realize() como se describe en la sección Widgets personalizados. Y, a menos que su contenedor dibuje directamente sobre la Gdk::Window subyacente, probablemente deba llamar a set_redraw_on_allocate(false) para mejorar el rendimiento.

Reemplazar forall_vfunc() puede permitirle a las aplicaciones operar en todos los widgets hijos de los contenedores. Por ejemplo, show_all_children() usa esto para encontrar a todos los widgets hijos y mostrarlos.

A pesar de que su contenedor puede tener su propio método para establecer widgets hijos, aún así debe proporcionar una implementación para los métodos virtuales on_add() y on_remove() de la clase base, para que los métodos add() y remove() hagan algo apropiado si se llaman.

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.

28.1.1. Ejemplo

Este ejemplo implementa un contenedor con dos widgets hijos, uno encima del otro. Por supuesto, en este caso sería mucho más simple usar sólo una Gtk::Box vertical.

Figura 28-1Contenedor personalizado

Código fuente

File: mycontainer.h (For use with gtkmm 3, not gtkmm 2)

#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:
  virtual Gtk::SizeRequestMode get_request_mode_vfunc() const;
  virtual void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const;
  virtual void get_preferred_height_for_width_vfunc(int width, int& minimum_height, int& natural_height) const;
  virtual void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const;
  virtual void get_preferred_width_for_height_vfunc(int height, int& minimum_width, int& natural_width) const;
  virtual void on_size_allocate(Gtk::Allocation& allocation);

  virtual void forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data);

  virtual void on_add(Gtk::Widget* child);
  virtual void on_remove(Gtk::Widget* child);
  virtual GType child_type_vfunc() const;

  Gtk::Widget* m_child_one;
  Gtk::Widget* m_child_two;
};

#endif //GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H

File: examplewindow.h (For use with gtkmm 3, not gtkmm 2)

#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;
  MyContainer m_MyContainer;
  Gtk::Button m_Button_One;
  Gtk::Label m_Label_Two;
  Gtk::ButtonBox m_ButtonBox;
  Gtk::Button m_Button_Quit;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: examplewindow.cc (For use with gtkmm 3, not gtkmm 2)

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

ExampleWindow::ExampleWindow()
: m_VBox(Gtk::ORIENTATION_VERTICAL),
  m_Button_One("Child One"),
  m_Label_Two("Child 2"),
  m_Button_Quit("Quit")
{
  set_title("Custom Container example");
  set_border_width(6);
  set_default_size(400, 200);

  add(m_VBox);

  //Add the child widgets to the custom container:
  m_MyContainer.set_child_widgets(m_Button_One, m_Label_Two);

  m_Label_Two.set_alignment(1.0, 0.5);

  m_VBox.pack_start(m_MyContainer, Gtk::PACK_EXPAND_WIDGET);
  m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK);

  m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK);
  m_ButtonBox.set_border_width(6);
  m_ButtonBox.set_layout(Gtk::BUTTONBOX_END);
  m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
              &ExampleWindow::on_button_quit) );

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_quit()
{
  hide();
}

File: main.cc (For use with gtkmm 3, not gtkmm 2)

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

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

  ExampleWindow window;

  //Shows the window and returns when it is closed.
  return app->run(window);
}

File: mycontainer.cc (For use with gtkmm 3, not gtkmm 2)

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

MyContainer::MyContainer()
: m_child_one(0), m_child_two(0)
{
  set_has_window(false);
  set_redraw_on_allocate(false);
}

MyContainer::~MyContainer()
{
/*
  // These calls to Gtk::Widget::unparent() are necessary if MyContainer is
  // deleted before its children. But if you use a version of gtkmm where bug
  // https://bugzilla.gnome.org/show_bug.cgi?id=605728
  // has not been fixed (gtkmm 3.7.10 or earlier) and the children are deleted
  // before the container, these calls can make the program crash.
  // That's because on_remove() is not called, when the children are deleted.
  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 VBox with at most two children.
Gtk::SizeRequestMode MyContainer::get_request_mode_vfunc() const
{
  return Gtk::SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

//Discover the total amount of minimum space and natural space needed by
//this container and its children.
void MyContainer::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const
{
  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->get_preferred_width(child_minimum_width[0], child_natural_width[0]);

  if(m_child_two && m_child_two->get_visible())
    m_child_two->get_preferred_width(child_minimum_width[1], child_natural_width[1]);

  //Request a width equal to the width of the widest visible child.
  minimum_width = std::max(child_minimum_width[0], child_minimum_width[1]);
  natural_width = std::max(child_natural_width[0], child_natural_width[1]);
}

void MyContainer::get_preferred_height_for_width_vfunc(int width,
   int& minimum_height, int& natural_height) const
{
  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->get_preferred_height_for_width(width, child_minimum_height[0],
                                                child_natural_height[0]);
  }

  if(m_child_two && m_child_two->get_visible())
  {
    ++nvis_children;
    m_child_two->get_preferred_height_for_width(width, child_minimum_height[1],
                                                child_natural_height[1]);
  }

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

void MyContainer::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const
{
  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->get_preferred_height(child_minimum_height[0], child_natural_height[0]);
  }

  if(m_child_two && m_child_two->get_visible())
  {
    ++nvis_children;
    m_child_two->get_preferred_height(child_minimum_height[1], child_natural_height[1]);
  }

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

void MyContainer::get_preferred_width_for_height_vfunc(int height,
   int& minimum_width, int& natural_width) const
{
  int child_minimum_width[2] = {0, 0};
  int child_natural_width[2] = {0, 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;

  if(nvis_children > 0)
  {
    //Divide the height equally among the visible children.
    const int height_per_child = height / nvis_children;

    if(m_child_one && m_child_one->get_visible())
      m_child_one->get_preferred_width_for_height(height_per_child,
                   child_minimum_width[0], child_natural_width[0]);

    if(m_child_two && m_child_two->get_visible())
      m_child_two->get_preferred_width_for_height(height_per_child,
                   child_minimum_width[1], child_natural_width[1]);
  }

  //Request a width equal to the width of the widest child.
  minimum_width = std::max(child_minimum_width[0], child_minimum_width[1]);
  natural_width = std::max(child_natural_width[0], child_natural_width[1]);
}

void MyContainer::on_size_allocate(Gtk::Allocation& allocation)
{
  //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.)

  //Use the offered allocation for this container:
  set_allocation(allocation);

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

  if(nvis_children <= 0)
    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( allocation.get_x() );
  child_allocation_one.set_y( allocation.get_y() );

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

  if(m_child_one && m_child_one->get_visible())
  {
    //Divide the height equally among the visible children.
    child_allocation_one.set_height( allocation.get_height() / nvis_children);
    m_child_one->size_allocate(child_allocation_one);
  }
  else
    child_allocation_one.set_height(0);

  //Place the second child below the first child:
  child_allocation_two.set_x( allocation.get_x() );
  child_allocation_two.set_y( allocation.get_y() +
          child_allocation_one.get_height());

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

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

  if(m_child_two && m_child_two->get_visible())
    m_child_two->size_allocate(child_allocation_two);
}

void MyContainer::forall_vfunc(gboolean, GtkCallback callback, gpointer callback_data)
{
  if(m_child_one)
    callback(m_child_one->gobj(), callback_data);

  if(m_child_two)
    callback(m_child_two->gobj(), callback_data);
}

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 = 0;
      found = true;
    }
    else if(child == m_child_two)
    {
      m_child_two = 0;
      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;
  }
}