Pregunta

I have a number of actions put together in a QActionGroup, because I want them to be exclusive (if one is checked, all the others should be unchecked). However, I also want to be able to uncheck all of them. At the moment if I check action1, and then click the button again, it will not uncheck, the only way to uncheck it is to click on a different action. In short, QActionGroup ensures exactly 1 of the actions is checked - I want to ensure exactly 1 OR 0 actions are checked. Is there a workaround for this?

I'm using PySide but I'm fairly comfortable with QT in c++ as well, so a solution in c++ would be fine - I'm sure I could convert it.

Code (not very helpful, but I'm posting in case it is relevant)

def defineGroups(self):

    navigation = QActionGroup(self)
    navigation.addAction(self.action1)
    navigation.addAction(self.action2)
    navigation.addAction(self.action3)
    navigation.addAction(self.action4)
    navigation.addAction(self.action5)
    navigation.addAction(self.action6)
    navigation.addAction(self.action7)

All the actions are checkable (set in QT Designer).

Related: There is a bug report similar to this, but the designers have decided not to change anything: https://bugreports.qt-project.org/browse/QTBUG-3512

¿Fue útil?

Solución

I am using Qt in C++ instead of Python, but I hope these information could help.

Unfortunately there seems no direct solution to this problem at present. The "exclusive" property of QActionGroup limits the feature of selection, which ensures at least and only one action is selected at any one time (but at the begining of the program, you have to manually set one of actions to be "checked")

Here is part of the source code, you can see how it works on QAction if the "exclusive" property is installed by QActionGroup

{
    if ( !isExclusive() )
      return;
    QAction* s = (QAction*) sender();
    if ( b ) {
      if ( s != d->selected ) {
          d->selected = s;
          for ( QPtrListIterator<QAction> it( d->actions); it.current(); ++it ) {
            if ( it.current()->isToggleAction() && it.current() != s )
                it.current()->setOn( FALSE );
          }
          emit activated();
          emit selected( s );
      } else if ( !s->isToggleAction() ) {
          emit activated();
      }
    } else {
      if ( s == d->selected ) {
          // at least one has to be selected
          s->setOn( TRUE );
      }
    }
}

(Ref: http://qt-x11-free.sourcearchive.com/documentation/3.3./classQActionGroup_182fe3a5a859e6397a4ba05be346d894.html older version of Qt, but the concept is the same)

(see also: http://qt-x11-free.sourcearchive.com/documentation/3.3.6/classQActionGroup_032480c0c57abda9c1b4485ee9edbcc7.html#032480c0c57abda9c1b4485ee9edbcc7)

To solve the problem, at least you have two options:

  1. Still use QActionGroup and add another QAction "None is selected" (or something similar) in your list. This might be the most proper option since it considers "no selection" as a selection and can exploit the feature of QActionGroup .

  2. Not to use QActionGroup. In this case, you have to manually implement the exclusiveness. Once a QAction is selected, emit a signal that sends a pointer QAction* to a SLOT which compares the present selection with the previous one from buffer (default = 0), if difference occurs, deselect (use setCheck(false)) the previous one and assign the present selection to buffer.

It's just a rough idea, but still feasible if you really need the feature

[EDIT]: Though I am not sure about the exact functionality you want, I wrote some C++ code here FYR. Notice that this is just a demo of the "ostensible" feature of the exclusively checkable QActions and the ability of unchecking any checked one.The according responses of checked/unchecked state still need further implementation.

TheClass.h:

class TheClass 
{
    Q_OBJECT  
    ......

public slots:
    void action0Checked();
    void action1Checked();
    void action2Checked();
    void compareChecked(QAction *newChecked);

signals:
    void selectedID(QAction *newChecked);

private:        
    QAction *buffer;
    QList<QAction*> actionList;
}

TheClass.cpp:

TheClass::TheClass()
{
    QMenuBar *menuBar = new QMenuBar(this); 
    buffer = 0;

    QMenu *menu = menuBar->addMenu("ActionList");
    actionList.append(menu->addAction("Action0"));
    actionList.append(menu->addAction("Action1"));
    actionList.append(menu->addAction("Action2"));

    for (int i=0; i<3; i++) {
        actionList[i]->setCheckable(true);
    }

    connect(actionList[0], SIGNAL(triggered()), this, SLOT(action0Checked()));
    connect(actionList[1], SIGNAL(triggered()), this, SLOT(action1Checked()));
    connect(actionList[2], SIGNAL(triggered()), this, SLOT(action2Checked()));
    connect(this, SIGNAL(selectedID(QAction*)), this, SLOT(compareChecked(QAction*)));
}

void TheClass::action0Checked()
{
    if (actionList[0]->isChecked()) {
        // do sth.

        emit selectedID(actionList[0]);
    }
}

void TheClass::action1Checked()
{
    if (actionList[1]->isChecked()) {
        // do sth.

        emit selectedID(actionList[1]);
    }
}

void TheClass::action2Checked()
{
    if (actionList[2]->isChecked()) {
        // do sth.

        emit selectedID(actionList[2]);
    }
}

void TheClass::compareChecked(QAction *newChecked)
{
    if (newChecked != buffer) {
        if (buffer != 0)
            buffer->setChecked(false);

        buffer = newChecked;
    }
}

Otros consejos

I know it's been some years. But I came across the same issue and found a nicer solution in Qt forums. It essentially does this:

connect(action_group, &QActionGroup::triggered, [](QAction* action) {
    static QAction* lastAction = nullptr;
    if (action == lastAction)
    {
      action->setChecked(false);
      lastAction = nullptr;
    }
    else
      lastAction = action;
  });

Hopefully, it will be useful for the future search of other people.

EDIT: as @Joe suggested, this can be further improved with the following code:

connect(action_group, &QActionGroup::triggered, [lastAction = static_cast<QAction *>(nullptr)](QAction* action) mutable {
    if (action == lastAction)
    {
      action->setChecked(false);
      lastAction = nullptr;
    }
    else
      lastAction = action;
  });

As of Qt 5.14 there is the ExclusiveOptional policy:

setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional)

Source: https://doc.qt.io/qt-5/qactiongroup.html#ExclusionPolicy-enum

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top