I am trying to learn how to use gtkmm having got a basic grasp of C++ (I like a challenge!). I have been working my way through the tutorials (as well as other reading). I am trying to use the approach of using glade to design the UI and then write the code to do the work.
So I have built a very simple UI (window and button at the moment!). I am using the GTK::Builder to load the UI from file. I am dividing the code into classes and a main caller.
Here is the main.cpp
#include "hellowindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[]) {
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example"); //creates a Gtk::Application object, stored in a Glib::RefPtr smartpointer, create() method for this object initializes gtkmm.
HelloWindow hw; // Create a HelloWindow object
return app->run(hw, argc, argv); // shows the HelloWindow object and enter the gtkmm main processing loop, will then return with an appropriate success or error code
}
here is the header for the HelloWindow class
#ifndef HELLOWINDOW_H
#define HELLOWINDOW_H
#include <gtkmm/application.h>
#include <gtkmm/applicationwindow.h>
#include <gtkmm/button.h>
#include <gtkmm/box.h>
#include <gtkmm/builder.h>
#include <glibmm/fileutils.h>
/* derive the class from Gtk::ApplicationWindow base class */
class HelloWindow : public Gtk::ApplicationWindow {
public:
/* Conctructor */
HelloWindow();
/* Destructor */
~HelloWindow() override;
protected:
/* Signal handlers: */
void on_button_clicked();
/* Member widgets: */
Gtk::Box *cont; // Container
Gtk::Button *pButton; // Pointer to a Button
Glib::RefPtr<Gtk::Button> display_btn; // Smart pointer to a Button
Glib::RefPtr<Gtk::Builder> builder; // Builder
};
#endif // HELLOWINDOW_H
and here is the class code:
#include "hellowindow.h"
#include <iostream>
HelloWindow::HelloWindow() : builder(Gtk::Builder::create()){
try {
/* load window from glade file */
builder->add_from_file("glade/simple.glade");
}
catch(const Glib::FileError& ex) {
/* catch file errors */
std::cerr << "FileError: " << ex.what() << std::endl;
return;
}
/* ui builder created successfully from file */
/* add a container to the builder */
builder->get_widget<Gtk::Box>("cont", cont);
builder->get_widget<Gtk::Button>("display_button", pButton);
pButton->signal_clicked().connect(
sigc::mem_fun(*this, &HelloWindow::on_button_clicked)
);
/* add the container to the application window */
add(*cont);
/* set some parameters for the window */
set_title("Simple Gtk::Builder Demo"); // set the window title
set_default_size(500, 500); // set the window size
show_all(); // show the window and all of the enclosed widgets
}
HelloWindow::~HelloWindow(){
}
void HelloWindow::on_button_clicked(){
std::cout << "Hello World" << std::endl;
}
This all works fine and I think I understand what is happening. However, I have seen a different approach to adding widgets at runtime (https://sodocumentation.net/gtk3/topic/5579/using-glade-with-builder-api). The difference is how the button object is declared. In the code above it is declared as a pointer to a button object in the line:
builder->get_widget<Gtk::Button>("display_button", pButton);
However, the website above uses an approach of a smartpointer to the button object:
display_btn = Glib::RefPtr<Gtk::Button>::cast_dynamic(builder->get_object("display_button"));
The second way of doing it seems less clear, specifically the cast_dynamic aspect, would someone please explain the difference between the two approaches?
I hope that I have included enough information.
Thank you
Martyn
First of all, to understand the difference, and since you are new to C++, I would recommend reading on the following topics:
get_widget
With this approach, you are getting a raw pointer (as opposed to a smart pointer) to a widget defined from the ui file. Example:
Gtk::Grid* pGrid = nullptr;
refXml->get_widget("mygrid", pGrid);
After this, pGrid
points to a Gtk::Grid
widget. Notice that no
casting is required, you immediately get a Gtk::Grid
. This is because
the Gtk::Builder::get_widget
method is a template method:
// T_Widget is like a placeholder for some widget type,
// like Gtk::Grid for example.
template <class T_Widget >
void Gtk::Builder::get_widget(const Glib::ustring& name,
T_Widget*& widget
)
Usually in C++, such
raw pointers may be dangerous because if they point to an object
allocated on the heap (usually using new
), one must remember to use
delete
on them when done, otherwise memory leaks will occur. In this
case, pGrid
is indeed a raw pointer to an object allocated on the
heap, but the documentation states:
Note that you are responsible for deleting top-level widgets (windows and dialogs) instantiated by the Builder object. Other widgets are instantiated as managed so they will be deleted automatically if you add them to a container widget.
so most of the time (i.e. if not a toplevel widget), you do not have
to call delete
, but sometimes you do. This is because Gtkmm has facilities
to automatically delete
destroyed objects. See Gtk::manage
for
more information on this.
When or not to use delete
may become hard as code grows, especially since
you do not have to always do it (it can become easy to forget).
get_object
With this approach, you are getting a smart pointer (a Glib::RefPtr
) to
an object and casting to the right type is required, hence the
cast_dynamic
// get_object returns a Glib::RefPtr<Glib::Object>, which is not a Glib::RefPtr<Gtk::Button>
// so a cast is performed. This works because Gtk::Button is a child class of
// Glib::Object.
display_btn = Glib::RefPtr<Gtk::Button>::cast_dynamic(builder->get_object("display_button"));
The advantage of this is that once the cast is performed,
you do not have to manage the object's memory. It is managed by the
smart pointer (i.e. the smart pointer will automatically call delete
for you. This is true even for "top level" widgets.
Note: the cast used here, cast_dynamic
, is a Gtkmm specific cast which wraps a dynamic_cast
.
I would personally go with the second approach because you get automatic memory management (even for top level widgets), and hence no memory leaks. However, the code gets harder to read as you already noticed.