Frage

I've problem with qt signal-slot system.

First I've created a class which is called System in Singleton pattern, so I can access it's instance where I want. System has a signal SelectionChanged.

I've a list widget and I am connecting it's itemSelectionChanged signal to my custom slot which is called onSelectionChanged. In onSelectionChanged slot, I am emitting System's SelectionChanged signal. There is no problem yet.

In my software design, a selection of object(s) can be used by many GUI widgets or custom classes and System's SelectionChanged signal can be emited by widgets other then the list widget.

So I am creating a slot called OnSystemSelectionChanged in the list widget then connect it to the System's SelectionChanged signal. The OnSystemSelectionChangedSlot is like this.

void MyListWidget::OnSystemSelectionChanged(QObject *sender)
{
    if (sender == this) return;
    // Then I want to get a list of selected objects and set them as selection of this widget like this:
    this->SetSelection(System::Instance()->GetSelectedObjects());
}

But the problem is when I start to set the list widget's selected items, it is going to emit itemSelectionChanged signal and my onSelectionChanged slot will be called. Then the slot will emit System's SelectionChanged signal and then OnSystemSelectionChanged will be called too. It will stop through sender parameter but there is no method for setting list widget's selected items at once.

How can I figure this problem out.

I hope I did explain my problem well. Thanks in advance.

Edit: Spelling and grammer errors are corrected.

War es hilfreich?

Lösung

There are a few ways of dealing with this in Qt.

Idioms

  1. Use multiple views with one underlying model. This handles propagation of changes to multiple view controls automatically and you don't need to do anything extra. You can use QDataWidgetMapper to link "plain old" widgets to the data elements in a model. I'd say that this should be the preferred way of doing things. Having an underlying model for all of your UI is a step in the direction of good software design anyway.

  2. When propagating changes between data models, implement both a DisplayRole and an EditRole. The views will nominally modify the models using one of the roles (say, the EditRole), while you can, programmatically, modify the models using the other role (say, the DisplayRole). You handle the dataChanged signals from the model in your own slot, properly dealing with the roles, and call setData on the other models with the other role. This prevents the loops.

  3. For controls that are not QAbstractItemViews, implement two signals: one emitted on any change, another one emitted only on changes based on keyboard/mouse input. This is the interface exposed by QAbstractButton, for example: the toggled(bool) signal is the former, the clicked() is the latter. You then only connect to the input-based signals.

    Your own code must propagate programmatic changes to all the interlinked controls, since changing one control from your code won't modify the others. This should not be a problem, since well designed code should encapsulate the implementation details of UI controls from rest of the code. Your dialog/window class will thus expose its properties in a way that's not coupled to the number of controls showing a particular property.

Hackish Let's-Hope-They-Won't-Become Idioms

  1. Use a flag inhibiting signal emission (Bartosz's answer).

  2. Break the signal/slot connections for the duration of the change (Bartosz's answer).

  3. Use QObject::blockSignals().

Andere Tipps

There are two possible solutions I can think of:

  • add a flag which makes possible to ignore particular signals:

 

void MyListWidget::OnSystemSelectionChanged(QObject *sender)
{
    if (sender == this || inhibitSelectionChanged)
        return;

    this->inhibitSelectionChanged = true;
    this->SetSelection(System::Instance()->GetSelectedObjects());
    this->inhibitSelectionChanged = false;
}
  • disconnect the slot from the signal, and reconnect it after changing the selection:

 

void MyListWidget::OnSystemSelectionChanged(QObject *sender)
{
    if (sender == this)
        return;

    this->disconnect(SIGNAL(SelectionChanged()));

    this->SetSelection(System::Instance()->GetSelectedObjects());

    this->connect(
        this, SIGNAL(SelectionChanged()), 
        this, SLOT(OnSystemSelectionChanged(QObject*)));
}

I found my solution in QObject::blockSignals() method. It will prevent emitting signals from the list widget while I am setting selected items.

Thanks for all the answers and solutions especialy for BartoszKP's. This solution is looks like the official way of his first solution.

The problem: you've tried to cut corners and created a singleton. Not a classic case for singleton.

Signals and slots are used for notifications, each object notifies interested objects about what it did or to reflect its new state.

I'm suggesting changing the design as follows:

  • No singleton signal.
  • Each Object has its own signal and slot for a relevant event (e.g. selection change).
  • The application or a higher level object (that created the widgets/objects) performs the signal to slot connection. If those widgets are placed in a list, this is very simple.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top