Chapter 21. Custom Widgets
Prev |  | Next

## Chapter 21. Custom Widgets

`gtkmm` makes it very easy to derive new widgets by inheriting from an existing widget class, either by deriving from a container and adding child widgets, or by deriving from a single-item widget, and changing its behaviour. But you might occasionally find that no suitable starting point already exists. In this case, you can implement a widget from scratch.

## Custom Containers

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

- `on_size_request()`: Calculate the minimum height and width needed by 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()`:

- `on_remove()`:

- `child_type_vfunc()`: Return what type of child can be added.

The `on_size_request()` 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 `on_size_request()` might report the maximum of their widths and the sum of their heights. 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.

`on_size_allocate()`, however, receives the actual height and width that the parent container has decided to give to your widget. This might be more than the minimum, 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. Its your container, so you decide. Don't forget to call `set_allocation` inside your `on_size_allocate()` implementation to actually use the allocated space that has been offered by the parent container.

Unless your container is a top-level window that derives from Gtk::Window, you should also call `Gtk::Container::set_flags(Gtk::NO_WINDOW)` in your constructor. Otherwise, your container will appear in its own window, regardless of what container you put it in. And unless your container draws directly onto the underlying Gdk::Window, you should probably call `set_redraw_on_allocate(false)` to improve performance.

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

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.

Your implementation of the `child_type_vfunc()` method should report the type of widget that may be added to your container, if it is not yet full. This is usually Gtk::Widget::get_type() to indicate that the container may contain any class derived from Gtk::Widget. If the container may not contain any more widgets, then this method should return G_TYPE_NONE.

### Example

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 Gtk::VBox.

Figure 21.1. Custom Container

Source Code

File: examplewindow.h

    #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:
      virtual void on_button_quit();

      //Child widgets:
      Gtk::VBox m_VBox;
      MyContainer m_MyContainer;
      Gtk::Button m_Button_One, m_Button_Two;
      Gtk::HButtonBox m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: mycontainer.h

    #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();

      virtual void set_child_widgets(Gtk::Widget& child_one, Gtk::Widget& child_two);

    protected:

      //Overrides:
      virtual void on_size_request(Gtk::Requisition* requisition);
      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 GtkType child_type_vfunc() const;

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

    #endif //GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H

File: examplewindow.cc

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

    ExampleWindow::ExampleWindow()
    : m_Button_One("Child One"),
      m_Button_Two("Child Two"),
      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_Button_Two);
      m_Button_One.show();
      m_Button_Two.show();
      m_VBox.pack_start(m_MyContainer, Gtk::PACK_EXPAND_WIDGET);
      m_MyContainer.show();

      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

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

    int main(int argc, char *argv[])
    {
      Gtk::Main kit(argc, argv);

      ExampleWindow window;
      Gtk::Main::run(window); //Shows the window and returns when it is closed.

      return 0;
    }

File: mycontainer.cc

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

    MyContainer::MyContainer()
    : m_child_one(0), m_child_two(0)
    {
      set_flags(Gtk::NO_WINDOW);
      set_redraw_on_allocate(false);
    }

    MyContainer::~MyContainer()
    {
    }

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

    void MyContainer::on_size_request(Gtk::Requisition* requisition)
    {
      //Initialize the output parameter:
      *requisition = Gtk::Requisition();

      //Discover the total amount of minimum space needed by this container widget, by examining its child widgets.
      //The layouts in this custom container will be arranged vertically, one above the other.

       Gtk::Requisition child_requisition_one, child_requisition_two;
       if(m_child_one && m_child_one->is_visible())
         child_requisition_one = m_child_one->size_request();

       if(m_child_two && m_child_two->is_visible())
         child_requisition_two = m_child_two->size_request();

      //See which one has the most width:
      int max_width = MAX(child_requisition_one.width, child_requisition_two.width);

      //Add the heights together:
      int total_height = child_requisition_one.height + child_requisition_two.height;

      //Request the width for this container based on the sizes requested by its child widgets:
      requisition->height = total_height;
      requisition->width = max_width;
    }

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

      //Assign sign space to the child:
      Gtk::Allocation child_allocation_one, 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() );

      //Make it take up half the height available:
      child_allocation_one.set_height( allocation.get_height() / 2);

      if(m_child_one && m_child_one->is_visible())
        m_child_one->size_allocate(child_allocation_one);

      //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 half the height available:
      child_allocation_two.set_height( allocation.get_height() - child_allocation_one.get_height());

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

    void MyContainer::forall_vfunc(gboolean /* include_internals */, 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->is_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();
        }
      }
    }

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

Prev | Up | Next
Getting help with translations | Home | Custom Widgets