GTKMM/C++11: How to create a custom composite widget out of other widgets?

2.1k views Asked by At

I want to derive my own widget class and add standard widgets to this class to create a composite widget. Does anybody have an examples or suggesions on how to do this? For example, Suppose I want to create custom composite widget of 4 buttons. I'm guessing its something like the code below:

//First Question:  Is this the best way to create composite widget? (see below)

//Second Question:  How do you make a widget container expand in 
// the horizontal direction while at the same time shrink in 
// the vertical direction?  because i wanted the boxes to expand horizontally
// to fill the window, and at the same time shrink to minimum width in the vertical
// direction


#include <iostream>

using namespace std;
#include <gtkmm.h>

class MyWidget : public Gtk::Frame {
  public:
    MyWidget() {
        add(m_hbox1);
        m_hbox1.pack_start  (m_vbox1,    Gtk::PackOptions::PACK_SHRINK);
        m_vbox1.pack_start  (m_hbox2,    Gtk::PackOptions::PACK_EXPAND_WIDGET);
        m_hbox2.pack_start  (m_btn_fwd,  Gtk::PackOptions::PACK_EXPAND_WIDGET);
        m_hbox2.pack_start  (m_btn_play, Gtk::PackOptions::PACK_EXPAND_WIDGET);
        m_hbox2.pack_start  (m_btn_stop, Gtk::PackOptions::PACK_EXPAND_WIDGET);
        m_hbox2.pack_start  (m_btn_back, Gtk::PackOptions::PACK_EXPAND_WIDGET);

        show_all_children();
    }
    ~MyWidget() {
    }

  private:

    Gtk::Box    m_vbox1    {Gtk::ORIENTATION_VERTICAL};
    Gtk::Box    m_hbox1    {Gtk::ORIENTATION_HORIZONTAL};
    Gtk::Box    m_hbox2    {Gtk::ORIENTATION_HORIZONTAL};
    Gtk::Button m_btn_fwd  {"Fwd"};
    Gtk::Button m_btn_back {"Back"};
    Gtk::Button m_btn_play {"Play"};
    Gtk::Button m_btn_stop {"Stop"};
};

class MyWindow : public Gtk::Window {
  public:
    MyWindow(string name) {
       set_title(name);
       add(m_vbox);

       // Shrink in Vertical Direction
       m_vbox.pack_start(m_mywidget, Gtk::PackOptions::PACK_SHRINK);

       show_all_children();
    }

  private:
    Gtk::Box    m_vbox      {Gtk::ORIENTATION_VERTICAL};
    MyWidget    m_mywidget;
};

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

  MyWindow window {"Testing Custom Composite Widget"};

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

There are 2 answers

0
Francesco Garbin On

it is actually possible to derive a custom C++ widget from Gtk::Widget in gtkmm although the library requires quite a bit of additional code, not only for compositing the composite oject but for adhering to the Gtk::Widget interface specification.

I am referring to a set of virtual methods which should be overridden in your implementation, in particular:

  • get_request_mode_vfunc(): (optional) Return what Gtk::SizeRequestMode is preferred by the widget.
  • get_preferred_width_vfunc(): Calculate the minimum and natural width of the widget.
  • get_preferred_height_vfunc(): Calculate the minimum and natural
    height of the widget.
  • get_preferred_width_for_height_vfunc(): Calculate the minimum and natural width of the widget, if it would be given the specified height.
  • get_preferred_height_for_width_vfunc(): Calculate the minimum and natural height of the widget, if it would be given the specified width.
  • on_size_allocate(): Position the widget, given the height and width that it has actually been given.
  • on_realize(): Associate a Gdk::Window with the widget.
  • on_unrealize(): (optional) Break the association with the Gdk::Window.
  • on_map(): (optional)
  • on_unmap(): (optional)
  • on_draw(): Draw on the supplied Cairo::Context.

The gtkmm documentation has a solid example in the gtkmm-tutorial section.

1
Bimo On

After a few weeks of experimentation, my conclusion is that its better to avoid using c++ to inherit from a Gtk::Widget in order to create a composite widget. Instead its better to make a composite widget in gktmm as a pure container class, ie. not derived from any classes, and to overload the C++11 Functor operator to return a Gtk::Box Widget that is pre-packed by the object's constructor with all the widgets needed to make the composite component. Example:

using namespace std;
#include <gtkmm.h>
#include <iostream>

//======================================================
// SearchBar: An Example GTKMM Composite Widget / wmoore
//======================================================
class SearchBar {
  public:
     SearchBar();
     Gtk::Widget& operator()();

  public:
     Gtk::Box    box {Gtk::ORIENTATION_HORIZONTAL};
     Gtk::Label  label {"search: "};
     Gtk::Entry  entry;
     Gtk::Button BtnOk{"find"};
     Gtk::Button BtnNext{">"};
     Gtk::Button BtnPrev{"<"};
};

inline SearchBar::SearchBar() {
  box.pack_start(label);
  box.pack_start(entry, Gtk::PACK_EXPAND_WIDGET);
  box.pack_end(BtnNext);
  box.pack_end(BtnPrev);
  box.pack_end(BtnOk);
}

inline Gtk::Widget& SearchBar::operator()() {
  return box;
}

class MyWindow : public Gtk::Window {
  public:
    MyWindow(string name) {
       set_title(name);
       add(m_vbox);

       // Shrink in Vertical Direction
       m_vbox.pack_start(m_searchbar(), Gtk::PackOptions::PACK_SHRINK);  
                        // ^^^ NOTE use of C++11 functor operator "()"
                        // added to end of object name
                        // that makes it easy to tell difference between 
                        // Gtk::Widget and Composite widget's built 
                       // from many Gtk::Widget's

      ////Example Connecting of Signals to composite widget:
      // m_searchbar.BtnOk.signal_clicked.connect([]() {
      //     cout << "clicked button!\n";})

       show_all_children();
    }

  private:
    Gtk::Box    m_vbox      {Gtk::ORIENTATION_VERTICAL};
    SearchBar   m_searchbar;
};

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

  MyWindow window {"Testing Custom Composite Widget"};

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

The reason for using pure container classes rather than inheritance from Gtk::Widget to create a composite widget are as follows:

(1) the Widget api's for gtkmm are very numerous and tend to hide any new public methods that you add to your composite widget in a mess of gtkmm widget method's that you probably won't use anyways. For this reason. its better to add all your container widget object into a public section of the class including the toplevel box widget. You will still have access to all the methods of each object widget without the gtkmm api clutter.

(2) Inheriting your class from a Gtk::Widget doesn't really get you anything useful in terms of polymorphic behavior since the widgets of the composite widget are already polymorphic objects derived from Gtk::Widget. When you add your toplevel widget box to a parent widget all of children widget of your composite widget, under box, are added to the children list of the parent widget. Thus, there's really no need to have polymorphic behavior for the composite widget container.

(3) If you need your composite widget derived from a Gtk:Widget its easy to put a class wrapper around it that converts it back into a class derived from Gtk::Widget. In fact this step is easily done with a oneline instantiation of a a template class wrapper. The only reason I could think of doing this is to insert a components back into glade (short of the Glade Gui recognizing the C++11 functor operator as a standard way to returning the toplevel widget of composite widget (possible for glade to support in future? wishlist...))