gtkmm/{,check,radio}button.h

Wuttup toons!

In this session, we'll round up we've learned about button types by learning about the Gtk::CheckButton and the Gtk::RadioButton. I will briefly cover different ways to initialize buttons and configure components. For example, you can get a simple vertical layout container by setting a box's orientation to vertical in its constructor.

Setup

Generate a standard main.cpp file like so:

#include "buttonWindow.h"
#include <gtkmm/application.h>

int main(int argc, char** argv) {  
  auto app = Gtk::Application::
    create(
      argc, argv,
      "org.animatedlew.buttons"
    );
  ButtonWindow window;
  return app->run(window);
}

This file will instantiate a window that will contain all the controls for this exercise.

Reskinned toggle buttons

Let's declare ButtonWindow over in the buttonWindow.h header file.

#pragma once

#include <gtkmm/window.h>
#include <gtkmm/checkbutton.h>
#include <gtkmm/radiobutton.h>
#include <gtkmm/separator.h>
#include <gtkmm/box.h>

class ButtonWindow : public Gtk::Window {  
public:  
  ButtonWindow();
  virtual ~ButtonWindow();

protected:  
  void onClicked();
  void onToggle();
  void onClose();

  Gtk::Button togglerBtn;
  Gtk::CheckButton toggleeBtn;
  Gtk::RadioButton radioBtn00, radioBtn01, radioBtn02;

  Gtk::Separator separatorH, separatorV;
  Gtk::Button closeBtn;

  Gtk::Box box00, box01, box02;
};

We'll need to individually include checkbutton.h, radiobutton.h, box.h, and separator.h. We'll also need to declare three callbacks for each of the signals we will be connecting to.

Our toggleeBtn code is nearly identical from the last time you saw a Gtk::ToggleButton. The only difference here is the component's skin: it appears like a checkbox yet behaves identically to a toggle button.

Gtk::Separators made it in this tutorial to showcase visual organization. They are UI components that exist merely to provide a better experience to your users.

I brought in three Gtk::Box's to show that layouts can be composed by nesting them.

Radio buttons and groups

Create buttonWindow.cpp and write in the following:

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

ButtonWindow::ButtonWindow():  
    radioBtn00("Apples"),
    radioBtn01("Oranges"),
    radioBtn02("Grapes"),
    closeBtn("Close"),
    box00(Gtk::ORIENTATION_HORIZONTAL),
    box01(Gtk::ORIENTATION_VERTICAL, 10),
    box02(Gtk::ORIENTATION_VERTICAL, 10) {

  // style the window a bit...
  set_title("Button Window");
  set_border_width(0);
  set_default_size(250, 200);
  set_resizable(false);

  radioBtn01.join_group(radioBtn00);
  radioBtn02.join_group(radioBtn00);

  // hook up the onClicked handler to the signal
  togglerBtn
    .signal_clicked()
    .connect(sigc::mem_fun(*this,
      &ButtonWindow::onClicked));

  toggleeBtn
    .signal_toggled()
    .connect(sigc::mem_fun(*this,
      &ButtonWindow::onToggle));

  closeBtn
    .signal_clicked()
    .connect(sigc::mem_fun(*this,
      &ButtonWindow::onClose));

  togglerBtn.set_label("toggler");
  toggleeBtn.set_label("togglee");

  // add the button to the window container
  add(box00);
  box01.set_border_width(40);
  box02.set_border_width(40);

  box00.add(box01);
  box01.pack_start(togglerBtn);
  box01.pack_start(toggleeBtn);

  box00.pack_start(separatorV);

  box00.add(box02);
  box02.pack_start(radioBtn00);
  box02.pack_start(radioBtn01);
  box02.pack_start(radioBtn02);
  box02.pack_start(separatorH);
  box02.pack_start(closeBtn);

  // initialize radio buttons
  radioBtn00.set_active();

  closeBtn.set_can_default();
  closeBtn.grab_default();

  // saves us from calling show on all widgets
  show_all_children();
}

ButtonWindow::~ButtonWindow() {}

void ButtonWindow::onToggle() {  
    std::cout << "isActive: " << toggleeBtn.get_active() << std::endl;
}

void ButtonWindow::onClicked() {  
    toggleeBtn.set_active(!toggleeBtn.get_active());
}

void ButtonWindow::onClose() {  
    hide();
}

The first thing you'll notice is that I specified labels for the close and radio buttons in the constructor's initializer list. I also passed in some orientation and spacing configurations to the box containers. Note that most of these options are completely optional and many choices that you'll make, may be arbitrary. In this case, I want you to be aware that they exist.

The following options were set simply to style the window. I removed the border width, set a default size, and turned off the ability to resize the window.

set_title("Button Window");  
set_border_width(0);  
set_default_size(250, 200);  
set_resizable(false);  

Only one radio button can be active at once and this is managed by a radio button group. Here I add two of the radio buttons to the group of the third. I also make sure to set one of them active because none of them will be active by default.

radioBtn01.join_group(radioBtn00);  
radioBtn02.join_group(radioBtn00);  
/* ... */
radioBtn00.set_active();  

There is also a section in the constructor where I setup all the signal slots. In this example, I am connecting to two signal_clicked() signals and one to signal_toggled(). For more detail on the code inside these handlers, please see my previous posts.

In the section immediately after the signal connections, I set button labels. Then, I began to specify my layout by adding box00 to the window. I soon after added box01 and box02 to box00.

I set default focus on the close button by enabling defaulting and grabbing the default.

closeBtn.set_can_default();  
closeBtn.grab_default();  

To finish off the constructor, I use show_all_children() to display all the nested controls when the window is shown.

One final important note: To close the app, call hide() on the window just like we're doing in our onClose handler.

void ButtonWindow::onClose() {  
    hide();
}

In my opinion, some of these gtkmm method calls could have better names but you quickly get used to them. I personally prefer camel cased names because snake cased things looks noisy.

Final thoughts

Because of the gtkmm hierarchy, our journey so far has been painless. I encourage you to install Glade and explore all the options that can be set on these controls. Damage can be done with what you already know. I encourage you to experiment.

In a future session, I will show you how to take Glade's XML output and load it directly into your app using Gtk::Builder. This will save you countless hours when designing a more intricate interface.