Hlavní navigace

Signály a dialogy v Gtkmm

25. 11. 2010
Doba čtení: 7 minut

Sdílet

V minulém článku o knihovně Gtkmm jsme započali programovat kalkulačku a zadal jsem vám menší domácí úkol. Dnes si ukážeme řešení tohoto úkolu a poté si předvedeme práci se signály, opět změníme vzhled klávesnice, tentokrát s pomocí tabulky, a naučíme se, jak uživateli něco sdělit prostřednictví dialogů.

Řešení DÚ

Minule jsem zadal úkol: naprogramovat klávesnici tak, aby vypadala takto:

Já bych pozměnil třídu Kalkulacka následovně:

class Kalkulacka : public Gtk::Window
{
    public:
        Kalkulacka();

    protected:
        Gtk::Button cisla[10]; // Pole s číselnými tlačítky
        Gtk::Button operace[4]; // Pole s tlačítky operací
        Gtk::Button vysledek; // Tlačítko "="
        Gtk::Button smazat; // Tlačítko na smazání
        Gtk::Entry displej; // Vstupní pole (displej)
        Gtk::Frame popis; // Informační text
        Gtk::Image panel; // Obrázek se solárním panelem
        Gtk::LinkButton odkaz; // Odkazové tlačítko na článek
        Gtk::VBox VRamStroj;
        Gtk::HBox HRamCisla;
        Gtk::VButtonBox VRam789;
        Gtk::VButtonBox VRam456;
        Gtk::VButtonBox VRam123;
        Gtk::VButtonBox VRam0;
};

// ZAČÁTEK KONSTRUKTORU ZŮSTÁVÁ TAK, JAK JE

    add(popis);
    popis.add(VRamStroj);
        VRamStroj.pack_start(panel);
        VRamStroj.pack_start(displej);
        VRamStroj.pack_start(HRamCisla);
            HRamCisla.pack_start(VRam789);
                VRam789.pack_start(cisla[9]);
                VRam789.pack_start(cisla[8]);
                VRam789.pack_start(cisla[7]);
                VRam789.pack_start(operace[0]);
            HRamCisla.pack_start(VRam456);
                VRam456.pack_start(cisla[6]);
                VRam456.pack_start(cisla[5]);
                VRam456.pack_start(cisla[4]);
                VRam456.pack_start(operace[1]);
            HRamCisla.pack_start(VRam123);
                VRam123.pack_start(cisla[3]);
                VRam123.pack_start(cisla[2]);
                VRam123.pack_start(cisla[1]);
                VRam123.pack_start(operace[2]);
            HRamCisla.pack_start(VRam0);
                VRam0.pack_start(cisla[0]);
                VRam0.pack_start(smazat);
                VRam0.pack_start(vysledek);
                VRam0.pack_start(operace[3]);
        VRamStroj.pack_start(odkaz);
    show_all_children(); // Zobrazí všechny widgety
}

Bylo potřeba změnit dvě věci. Za prvé na klávesnici použít VBottonBox a HButtonBox místo VBox a HBox, aby byla všechna tlačítka zarovnaná. Za druhé dát všechny sloupce s tlačítky (HRam*) do jediného kontejneru, aby měla všechna tlačítka nebo mezery mezi nimi stejnou velikost, tedy tak, jak to je na schématu níže. Světle modrou je znázorněn HBox, zelenou VButtonBox a tmavě modře jsou opět tlačítka. Dále je samozřejmě nutné dát do kontejnerů správná tlačítka, ne tak, jako jsem to omylem udělal já.

Tabulka

Že se vám ještě ta klávesnice nějak nelíbí? Dobrá, existuje ještě jeden způsob. Tím je kontejner Gtk:Table. Tlačítka v něm se přizpůsobují velikosti tabulky a nejsou mezi nimi (velké) mezery. Do tabulky další widgety nedáváme metodou pack_start(), ale attach(). Prvním argumentem je widget, který má být do tabulky přidán. Následují čtyři čísla, které určují polohu a velikost widgetu. Představte si tabulku 4×4. Každá její hrana (celá hranice mezi řádky) má své pořadové číslo. První shora má číslo nula, druhá jedna atd. Stejně je to se svislými čarami. Ty se číslují zleva.

Pokud si přeju mít widget přes celý dolní řádek, napíšu tabulka.attach(widget, 0, 4, 3, 4);.

Samotný konstruktor tabulky potřebuje tři argumenty. První dva jsou (přirozená, typu guint) čísla, která udávají počet řádků a sloupců. Třetím je příznak, zda je tabulka stejnorodá.

Dialogy

Pokud chceme uživateli něco sdělit, nebo se ho na něco zeptat, použijeme dialog. Jeden z typů dialogu je Gtk::MessageDialog. Jeho konstruktor bude požadovat případný ukazatel na rodiče – okno, kterému patří. Dále bude chtít řetězec s textem, který má zobrazit jako nadpis. Pokud jako třetí argument předáme true, budeme moct v nadpisu použít některé HTML formátovací tagy, v příkladu je použit < sup >. Dále musíme určit typ dialogu – informativní, varovací, tázací, dialog oznamující chybu, nebo nespecifikovaný. Zde je seznam:

  • MESSAGE_INFO
  • MESSAGE_WARNING
  • MESSAGE_QUESTION
  • MESSAGE_ERROR
  • MESSAGE_OTHER

Můžeme také přidat tlačítko. Zde je výčet druhů:

  • BUTTONS_NONE /*NO NE, nic tam nebude!*/
  • BUTTONS_OK /*Jen tlačítko s nápisem „OK“*/
  • BUTTONS_CLOSE /*„Zavřít“*/
  • BUTTONS_CANCEL /*„Zrušit“*/
  • BUTTONS_YES_NO /*Toto má na starosti rovnou dvě tlačítka, „Ano“ a „Ne“*/
  • BUTTONS_OK_CANCEL /*Je opět kombinací předešlých tlačítek.*/

Ač se obrázek v dialogu mění podle typu dialogu, někdy je vhodné mít v dialogu obrázek vlastní. K tomu slouží metoda set_image(Widget& obrazek). Třídu Gtk::Image najdete v minulém díle.

Akorát nadpis v dialogu nestačí. Text pod ním se nastavuje metodou set_secondary_tex­t(). Prvním argumentem je samotný text a druhým je příznak, zda je text formátovaný.

Tou nejdůležitější metodou je však run(). Jak z názvu vyplývá, zobrazuje dialog. Její návratovou hodnotou je číslo, které určuje, na které tlačítko uživatel klikl.

Signály

Jak jsem již napsal, aby se mohlo něco stát, když uživatel stiskne tlačítko, musí to být naprogramované. Dalo by se říct, že v tuto chvíli máme pouze obal kalkulačky, ale chybí jí ty správné součástky.

Gtkmm signál (zprávu, událost) vygeneruje vždy, když se něco stane – když uživatel stiskne tlačítko, zatrhne nějakou volbu nebo jen stiskne tlačítko myši na daném widgetu, pustí tlačítko myši. V tu chvíli se zavolá funkce (metoda), která má reakci na danou událost na starosti. Taková funkce se označuje jako handler. O přiřazení funkce k dané události daného widgetu se stará metoda connect(). To ale není všechno. U obyčejného tlačítka máme na výběr mezi signálem ke kliknutí, stisknutí tlačítka myši, puštění tlačítka myši, stisknutí enteru atd. Podle toho, na co chceme, aby program reagoval, použijeme jednu z následujících metod:

  • signal_pressed()
  • signal_released()
  • signal_clicked()
  • signal_enter()
  • signal_leave()
  • signal_activate()
V praxi to celé vypadá například takto:
#include <gtkmm.h>

class Okno : public Gtk::Window
{
    public:
        Okno();

    protected:
        //Handlery:
        void Kliknuto();
        void Stisknuto();
        void Pusteno();
        void Odentrovano();
        void Opusteno();
        void Aktivovano();

        //Widgety:
        Gtk::Button tlacitko;
        Gtk::Label udalost;
        Gtk::VBox VRam;
};

Okno::Okno()
:tlacitko("Já jsem tlačítko a vím, co se mnou děláš!"),
udalost("Ještě jsi se mnou neudělal(a) nic!")
{
    // Spojování událostí tlačítka s příslušnými metodami
    tlacitko.signal_clicked().connect(sigc::mem_fun(*this, &Okno::Kliknuto));
    tlacitko.signal_pressed().connect(sigc::mem_fun(*this, &Okno::Stisknuto));
    tlacitko.signal_released().connect(sigc::mem_fun(*this, &Okno::Pusteno));
    tlacitko.signal_enter().connect(sigc::mem_fun(*this, &Okno::Odentrovano));
    tlacitko.signal_leave().connect(sigc::mem_fun(*this, &Okno::Opusteno));
    tlacitko.signal_activate().connect(sigc::mem_fun(*this, &Okno::Aktivovano));

    add(VRam);
    VRam.pack_start(tlacitko);
    VRam.pack_start(udalost);
    show_all_children();
}

void Okno::Kliknuto() {
    udalost.set_label("Klikl(a) jsi na mě!");
}

void Okno::Stisknuto() {
    udalost.set_label("Stiskl jsi nade mnou tlačítko myši!");
}

void Okno::Pusteno() {
    udalost.set_label("Pustil jsi nade mnou tlačítko myši!");
}

void Okno::Odentrovano() {
    udalost.set_label("Najel jsi na mě!");
}

void Okno::Opusteno() {
    udalost.set_label("Opustil jsi mě :(");
}

void Okno::Aktivovano() {
    udalost.set_label("Aktivoval jsi mě!");
}

int main(int argc, char *argv[]) {
    Gtk::Main  kit(argc, argv);
    Okno okno;
    Gtk::Main::run(okno);
    return 0;
}

Pro názornost jsem do kódu vložil všechny možné funkce. Po kliknutí na tlačítko se však logicky nezobrazí text „Pustil jsi nade mnou tlačítko myši!“, protože bude „přebit“ textem „Klikl(a) jsi na mě!“. Pokud tedy chcete vidět i text „Pustil jsi nade mnou tlačítko myši!“, stačí zakomentovat metodu Okno::Kliknuto.

root_podpora

Ukázkový program

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

class Kalkulacka : public Gtk::Window
{
    public:
        Kalkulacka();

    protected:
        void VypisVysledek();
        Gtk::Button cisla[10]; // Pole s číselnými tlačítky
        Gtk::Button operace[4]; // Pole s tlačítky operací
        Gtk::Button vysledek; // Tlačítko "="
        Gtk::Button smazat; // Tlačítko na smazání
        Gtk::Entry displej; // Vstupní pole (displej)
        Gtk::VBox VRamStroj;
        Gtk::Table klavesnice; // Tabulka s tlačítky klávesnice
        Gtk::Label stav;
};

void Kalkulacka::VypisVysledek() {
    Gtk::MessageDialog dialog(*this, "Vyskytla se menší chyba :(", true, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
    dialog.set_secondary_text("Byl zjištěn problém v algoritmu programu. Algoritmus je příliš jednoduchý, proto ho bude z důvodu bezpečnosti Vašeho systému Windows potřeba zpomalit.");

    // Zobrazení dialogu
    int vysledek = dialog.run();

    // Zjistí, na které tlačítko uživatel klikl a podle toho zobrazí text
    switch(vysledek) {
        case(Gtk::RESPONSE_OK): {
            stav.set_label("OK, začínám počítat.");
            break;
        }
        case(Gtk::RESPONSE_CANCEL): {
            stav.set_label("V zájmu Vaší bezpečnosti byl požadavek na vypočítání zrušen.");
            break;
        }
    }
}

Kalkulacka::Kalkulacka():
klavesnice(4,4,false)
{
    set_title("Kalkulačka"); // Nastavení titulku okna
    set_border_width(10); // Odstup od okraje nastavíme na 10 pixelů
    cisla[0].set_label("0"); // Nastavení popisků tlačítek s čísly
    cisla[1].set_label("1"); // ---//---
    cisla[2].set_label("2"); // ---//---
    cisla[3].set_label("3"); // ---//---
    cisla[4].set_label("4"); // ---//---
    cisla[5].set_label("5"); // ---//---
    cisla[6].set_label("6"); // ---//---
    cisla[7].set_label("7"); // ---//---
    cisla[8].set_label("8"); // ---//---
    cisla[9].set_label("9"); // ---//---
    operace[0].set_label("+"); // Nastavení popisků tlačítek s operacemi
    operace[1].set_label("-"); // ---//---
    operace[2].set_label("×"); // ---//---
    operace[3].set_label("÷"); // ---//---
    vysledek.set_label("="); // Nastavení popisku tlačítka na zobrazení výsledku
    smazat.set_label("C"); // Nastavení popisku tlačítka na zobrazení výsledku

    vysledek.signal_clicked().connect( sigc::mem_fun(*this, &Kalkulacka::VypisVysledek) );

    add(VRamStroj);
        VRamStroj.pack_start(displej);
        VRamStroj.pack_start(klavesnice);
            klavesnice.attach(cisla[7], 0, 1, 0, 1);
            klavesnice.attach(cisla[8], 1, 2, 0, 1);
            klavesnice.attach(cisla[9], 2, 3, 0, 1);
            klavesnice.attach(operace[0], 3, 4, 0, 1);
            klavesnice.attach(cisla[4], 0, 1, 1, 2);
            klavesnice.attach(cisla[5], 1, 2, 1, 2);
            klavesnice.attach(cisla[6], 2, 3, 1, 2);
            klavesnice.attach(operace[1], 3, 4, 1, 2);
            klavesnice.attach(cisla[1], 0, 1, 2, 3);
            klavesnice.attach(cisla[2], 1, 2, 2, 3);
            klavesnice.attach(cisla[3], 2, 3, 2, 3);
            klavesnice.attach(operace[2], 3, 4, 2, 3);
            klavesnice.attach(cisla[0], 0, 1, 3, 4);
            klavesnice.attach(smazat, 1, 2, 3, 4);
            klavesnice.attach(vysledek, 2, 3, 3, 4);
            klavesnice.attach(operace[3], 3, 4, 3, 4);
        VRamStroj.pack_start(stav);
    show_all_children(); // Zobrazí všechny widgety
}

int main(int argc, char *argv[]){
    Gtk::Main kit(argc, argv);
    Kalkulacka okno;
    Gtk::Main::run(okno);
    return 0;
}

Pokračování

Všech widgetů, které nám gtkmm nabízí, je kolem devadesáti. Příště se podíváme na velkou část z widgetů, které se v tomto serálu objeví. Ty se předvedou v novém ukázkovém programu, textovém editoru, který bude již plně funkční.

Byl pro vás článek přínosný?