web-dev-qa-db-fra.com

Empêcher le déclenchement de signaux dans Qt

Nous avons un objet QCheckBox, lorsque l'utilisateur le vérifie ou supprime la vérification, nous voulons appeler une fonction afin que nous connections notre fonction au signal stateChanged ( int state ). D'un autre côté, selon certaines conditions, nous changeons également l'état de l'objet QCheckBox dans le code, ce qui provoque le signal indésirable.

Existe-t-il un moyen d'empêcher le déclenchement du signal dans certaines conditions?

51
metdos

Vous pouvez utiliser le signal clicked car il n'est émis que lorsque l'utilisateur a réellement cliqué sur la case à cocher, pas lorsque vous le cochez manuellement à l'aide de setChecked .

Si vous ne voulez tout simplement pas que le signal soit émis à un moment précis, vous pouvez utiliser QObject::blockSignals comme ceci:

bool oldState = checkBox->blockSignals(true);
checkBox->setChecked(true);
checkBox->blockSignals(oldState);

L'inconvénient de cette approche est que tous les signaux seront bloqués. Mais je suppose que cela n'a pas vraiment d'importance en cas de QCheckBox.

78
Job

Vous pouvez toujours bloquer l'émission de signal sur les QObjects en utilisant QObject::blockSignals() . Notez que pour être correct sur les choses, vous devez vous souvenir de l'ancien état (renvoyé par l'appel de fonction) et le restaurer lorsque vous avez terminé.

À mon travail, nous préférons RAII pour ce genre de choses. Une classe simple pour le faire pourrait ressembler à ceci:

class SignalBlocker
{
public:
    SignalBlocker( QObject *obj ) : m_obj( obj ), m_old( obj->blockSignals( true ) )
    {
    }

    ~SignalBlocker()
    {
        m_obj->blockSignals( m_old );
    }

private:
    QObject *m_obj;
    bool m_old;
};

Edit : À partir de Qt 5.3, voir QSignalBlocker (h/t à HappyCactus dans les commentaires)

35

Vous pouvez QObject::disconnect pour supprimer la connexion de signal-slot correspondante et peut QObject::connect à nouveau une fois que vous avez terminé ...

14
liaK

En apprenant Qt, j'ai rencontré ce problème avec un ensemble de widgets interconnectés que je voulais mettre à jour "atomiquement". J'ai aimé la solution de @ cjhuitt, mais j'ai trouvé qu'elle allait encore mieux avec un peu de sucre syntaxique basé sur objets proxy . Voici l'approche que j'ai utilisée ...

Tout d'abord, j'ai défini un modèle de classe pour un objet proxy bloqueur. Comme Caleb, cela bloque les signaux sur la construction, puis restaure leur état précédent lors de la destruction. Cependant, il surcharge également le -> opérateur pour renvoyer un pointeur sur l'objet bloqué:

template<class T> class Blocker {
    T *blocked;
    bool previous;
public:
    Blocker(T *blocked)
        : blocked(blocked),
          previous(blocked->blockSignals(true)) {}
    ~Blocker() { blocked->blockSignals(previous); }
    T *operator->() { return blocked; }
};

Ensuite, j'ai défini une petite fonction de modèle pour construire et renvoyer un bloqueur:

template<class T> inline Blocker<T> whileBlocking(T *blocked) {
    return Blocker<T>(blocked);
}

En mettant tout cela ensemble, je l'utiliserais comme ceci:

whileBlocking(checkBox)->setChecked(true);

ou

whileBlocking(xyzzySpin)->setValue(50);

Cela m'apporte tous les avantages de RAII, avec un blocage et une restauration automatiquement associés autour de l'appel de méthode, mais je n'ai pas besoin de nommer de wrapper ou d'indicateur d'état. C'est agréable, facile et sacrément infaillible.

12
Boojum

Dans les classes dérivées QObject, vous pouvez appeler blockSignals(bool) pour empêcher l'objet d'émettre des signaux. Ainsi, par exemple:

void customChangeState(bool checked)
{
    blockSignals(true);
    ui->checkBox->setCheckState(Qt::Checked);
    // other work
    blockSignals(false);
}

La méthode ci-dessus changerait l'état de vérification sans cliquer, stateChanged ou tout autre signal émis.

6
ZestyMeta

Qt5.3 a introduit la classe QSignalBlocker qui fait exactement ce dont il avait besoin d'une manière sûre d'exception.

if (something) {
   const QSignalBlocker blocker(someQObject);
   // no signals here
}
3
HappyCactus

Même en QT5, c'est un peu lourd quand il y a plusieurs/plusieurs choses à bloquer. Voici une version multi-objet qui est concise à utiliser:

class SignalBlocker
{
public:
  SignalBlocker(QObject *obj)
  {
    insert( QList<QObject*>()<<obj );
  }    
  SignalBlocker(QList<QObject*>  objects)
  {
    insert(objects);
  }    
  void insert(QList<QObject*>  objects)
  {
    for (auto obj : objects)
      m_objs.insert(obj, obj->signalsBlocked());
    blockAll();
  }    
  void blockAll() {
    for( auto m_obj : m_objs.keys() )
      m_obj->blockSignals(true);
  }    
  ~SignalBlocker()
  {
    for( auto m_obj : m_objs.keys() )
      m_obj->blockSignals( m_objs[m_obj] );
  }    
private:
  QMap<QObject*,bool> m_objs;      
};

usage:

void SomeType::myFunction()
{
    SignalBlocker tmp( QList<QObject*>() 
    << m_paramWidget->radioButton_View0
    << m_paramWidget->radioButton_View1
    << m_paramWidget->radioButton_View2
    );
    // Do more work, ... 
}
0
peter karasev