How to disconnect a signal

899 views Asked by At

I made a program to calculate the area and volume of shapes from a window. Everything works, except one thing regarding the use of Gtk::Entry::signal_activate(). In the following code:

  sigc::connection c = elongueur.signal_activate().connect([this]() { calcul(); });
  switch (forme)
  {
  case 1: //carré
    cacheEntry();
    c.connected();
    labelForme.set_label(listeDeroulante.get_active_text());
    labelLongueur.set_label("longueur");
    break;
  case 2: //rectangle
    c.disconnect();
    elargeur.set_sensitive(true);
    labelForme.set_label(listeDeroulante.get_active_text());
    labelLongueur.set_label("longueur");
    labelLargeur.set_label("largeur");
    elongueur.signal_activate().connect([this]() { elargeur.grab_focus(); });
    elargeur.signal_activate().connect([this]() { calcul(); });
    break;

 // ...

if I start with option case 2 everything works fine. If I go through case 1 and then case 2, pressing the enter key shifts the focus to the next Gtk::Entry but also launches the calculation() function, which I'm trying to avoid. I think the Gtk::Entry::signal_activate() handler is not disconnected after passing through case 1, but I don't know why since I am calling sigc::connection::disconnect() when entering case 2.

How could I make this work?

2

There are 2 answers

0
kramer On BEST ANSWER

First add this header. See documentation from sigc++

#include <sigc++/connection.h>

We have to disconnect signal and stop it

Declare in your own class

sigc::connection c;

then in your main programm we can do something like that

  switch (forme)
  {
  case 1: // carré
    cacheEntry();
    labelForme.set_label(listeDeroulante.get_active_text());
    labelLongueur.set_label("longueur");
    if (c.connected() == false)
    {
      c = elongueur.signal_activate().connect([this]() { calcul(); });
    }
    break;
  case 2: // rectangle
    c.disconnect();//disconnected signal
    elargeur.set_sensitive(true);
    labelForme.set_label(listeDeroulante.get_active_text());
    labelLongueur.set_label("longueur");
    labelLargeur.set_label("largeur");
    elongueur.signal_activate().connect([this]() { elargeur.grab_focus(); });
    elargeur.signal_activate().connect([this]() { calcul(); elargeur.signal_activate().emission_stop();/*stop signal*/ });
    break;
4
BobMorane On

I wrote a small program to replicate your issue:

#include <iostream>
#include <gtkmm.h>

class MyWindow : public Gtk::ApplicationWindow
{

public:

    MyWindow()
    {
        m_btn1.signal_clicked().connect([this](){PerformAction1();});
        m_btn2.signal_clicked().connect([this](){PerformAction2();});
        m_btn1And2.signal_clicked().connect([this](){PerformActions1And2();});

        m_entryA.set_text("Entry A");
        m_entryB.set_text("Entry B");

        m_layout.attach(m_entryA, 0, 0, 1, 1);
        m_layout.attach(m_entryB, 1, 0, 1, 1);
        m_layout.attach(m_btn1, 0, 1, 2, 1);
        m_layout.attach(m_btn2, 0, 2, 2, 1);
        m_layout.attach(m_btn1And2, 0, 3, 2, 1);

        add(m_layout);
    }

    void Calcul()
    {
        std::cout << "calcul() called" << std::endl;
    }

    void PerformAction1()
    {
        // Add another handler here to amplify the problem:
        // m_signalEntryA = m_entryA.signal_activate().connect([this](){std::cout << "Action 0 : "; Calcul();});
        m_signalEntryA = m_entryA.signal_activate().connect([this](){std::cout << "Action 1 : "; Calcul();});

        // Works if you uncomment here:
        // m_signalEntryA.disconnect();
    }

    void PerformAction2()
    {
        m_signalEntryA = m_entryA.signal_activate().connect([this](){std::cout << "Action 2 : "; Calcul();});
        m_signalEntryA.disconnect();

        m_signalEntryA = m_entryA.signal_activate().connect([this](){m_entryB.grab_focus();});
        m_entryB.signal_activate().connect([this](){Calcul();});
    }

    void PerformActions1And2()
    {
        PerformAction1();
        PerformAction2();
    }

private:

    Gtk::Grid m_layout;
    Gtk::Entry m_entryA;
    Gtk::Entry m_entryB;
    Gtk::Button m_btn1{"Action 1"};
    Gtk::Button m_btn2{"Action 2"};
    Gtk::Button m_btn1And2{"Actions 1 and 2"};

    sigc::connection m_signalEntryA;

};

int main(int argc, char* argv[]) 
{
    auto app = Gtk::Application::create(argc, argv, "so.question.q66320704");

    MyWindow window;
    window.show_all();

    return app->run(window);
}

This program has two cases, like yours that do the same fundamental operations on signals. You can activate the different scenarios using the buttons:

enter image description here

Running the program and clicking the buttons, then the Enter key, yields the following output:

  • Action 1: Action 1 : calcul() called
  • Action 2: Nothing
  • Actions 1 and 2: Action 1 : calcul() called

From what I can see in this program, it seems that there is some sort of "stacking" in the signal connections. Uncommenting this line:

m_signalEntryA = m_entryA.signal_activate().connect([this](){std::cout << "Action 0 : "; Calcul();});

yield the following output:

Action 0 : calcul() called
Action 1 : calcul() called

This was very surprising as this behavior is not documented in the official documentation. Anyway, to solve your issue, it seems you have to call disconnect early, just before break.

sigc::connection c = elongueur.signal_activate().connect([this]() { calcul(); });
  switch (forme)
  {
  case 1: //carré
    cacheEntry();
    c.connected();
    labelForme.set_label(listeDeroulante.get_active_text());
    labelLongueur.set_label("longueur");
    c.disconnect(); // <-- Add this. The connection is no more needed.
    break;
  case 2: //rectangle
    c.disconnect();
    elargeur.set_sensitive(true);
    labelForme.set_label(listeDeroulante.get_active_text());
    labelLongueur.set_label("longueur");
    labelLargeur.set_label("largeur");
    elongueur.signal_activate().connect([this]() { elargeur.grab_focus(); });
    elargeur.signal_activate().connect([this]() { calcul(); });
    break;

Note: My Gtkmm version is 3.22.