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.
- get_preferred_width_vfunc(): Calculate the minimum and natural width of the container.
- get_preferred_height_vfunc(): Calculate the minimum and natural height of the container.
- get_preferred_width_for_height_vfunc(): Calculate the minimum and natural width of the container, if it would be given the specified height.
- get_preferred_height_for_width_vfunc(): Calculate the minimum and natural height of the container, if it would be given the specified width.
- 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.
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: 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: 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.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; } }