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