自定义容器
直接从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. 示例
26.1.1. 示例
本示例实现了一个具有两个子部件的容器,一个在另一个上面。当然在这种情况下直接使用垂直Gtk::Box或Gtk::Grid会更简单。
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