Qt Implementation of selecting items from list into other list (Dual List, Accumulator, List builder, TwoListSelection ...)
Asked Answered
W

1

6

I want to select an arbitrary amount of items from a list of arbitrary length. A dropdown (QComboBox) doesn't allow checkable items. A list of checkable items would become clumsy with a lot of items.

I found this question in the User Experience SE subsite and this answer seems to be the solution that best suits my needs. It has many names, as a comment in said answer remarks: Dual List, Accumulator, List builder, TwoListSelection ...

The version from OpenFaces.org shown in the answer linked above:

a

I couldn't find an implementation in Qt, though. Should I implement it myself or is there an implementation available in Qt? Is there a recommended approach?

Welloiled answered 18/1, 2018 at 17:56 Comment(7)
do a survey to Qt examples ... I have seen this in one example .. really can't recall which!Heelandtoe
Done it before asking this question. Couldn't find anything similar. Do you remember something about the example? Maybe I missed it.Welloiled
you can take a Current item from a left side QListWidget and add Right Side using addItem(new QListWidgetItem()), you can create QListWidgetItem from other QListWidgetItem. If you want take all item from a left side, you can use while(ui->leftList->count() > 0) {and take a item from a index}, what is problem?Lecherous
@Taz742, it seems like a common pattern and didn't want to reinvent the wheel. I'll implement it myself and add it as an answer for future reference from me and others. Thanks!Welloiled
If you have any problem, I'm here. I have already done the exact thing for myself.Lecherous
@Taz742, I'm having problems when disabling the buttons if their respective lists are empty. Is there an easy way to add a signal to QListWidget that is emitted when it changes from empty to populated and viceversa?Welloiled
Yes, you can use eventFilter. https://mcmap.net/q/1771015/-qt-mouse-event-filterLecherous
A
12

This widget does not come by default in Qt, but it is not complicated to build it, the first thing you should do is list the requirements:

  • The button with text >> is enabled if the list on the left is not empty, if pressed it must move all the items.

  • The button with text << similar to the previous one but with the list on the right

  • The button with text > is enabled if an item in the list on the left is selected, if pressed, the selected item should be moved to the right one.

  • The button with text < the same but for the right side.

  • The button with "up" text is enabled if an item is selected and this is not the first item in the list. when pressed you must move the current item to a higher position.

  • The button with "down" text is enabled if an item is selected and this is not the last item in the list. when pressed you must move the current item to a lower position.

As we see most of the logic depends on the selection of items, for this we connect the signal itemSelectionChanged with the slot that decides whether the buttons are enabled or not.

To move items we must use takeItem() and addItem(), the first removes the item and the second adds it.

Moving up or down is equivalent to removing the item and then inserting it, for this we use takeItem() again with insertItem()

All the above is implemented in the following widget:

#ifndef TWOLISTSELECTION_H
#define TWOLISTSELECTION_H

#include <QHBoxLayout>
#include <QListWidget>
#include <QPushButton>
#include <QWidget>

class TwoListSelection : public QWidget
{
    Q_OBJECT
public:
    explicit TwoListSelection(QWidget *parent = nullptr):QWidget{parent}{
        init();
        connections();
    }

    void addAvailableItems(const QStringList &items){
        mInput->addItems(items);
    }

    QStringList seletedItems(){
        QStringList selected;
        for(int i=0; i<mOuput->count(); i++)
            selected<< mOuput->item(i)->text();
        return selected;
    }
private:
    void init(){
        QHBoxLayout *layout = new QHBoxLayout(this);
        mInput = new QListWidget;
        mOuput = new QListWidget;

        mButtonToSelected = new QPushButton(">>");
        mBtnMoveToAvailable= new QPushButton(">");
        mBtnMoveToSelected= new QPushButton("<");
        mButtonToAvailable = new QPushButton("<<");

        layout->addWidget(mInput);

        QVBoxLayout *layoutm = new QVBoxLayout;
        layoutm->addItem(new QSpacerItem(10, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));
        layoutm->addWidget(mButtonToSelected);
        layoutm->addWidget(mBtnMoveToAvailable);
        layoutm->addWidget(mBtnMoveToSelected);
        layoutm->addWidget(mButtonToAvailable);
        layoutm->addItem(new QSpacerItem(10, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));

        layout->addLayout(layoutm);
        layout->addWidget(mOuput);

        mBtnUp = new QPushButton("Up");
        mBtnDown = new QPushButton("Down");

        QVBoxLayout *layoutl =  new QVBoxLayout;
        layoutl->addItem(new QSpacerItem(10, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));
        layoutl->addWidget(mBtnUp);
        layoutl->addWidget(mBtnDown);
        layoutl->addItem(new QSpacerItem(10, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));

        layout->addLayout(layoutl);
        setStatusButton();
    }

    void connections(){
        connect(mOuput, &QListWidget::itemSelectionChanged, this, &TwoListSelection::setStatusButton);
        connect(mInput, &QListWidget::itemSelectionChanged, this, &TwoListSelection::setStatusButton);
        connect(mBtnMoveToAvailable, &QPushButton::clicked, [=](){
           mOuput->addItem(mInput->takeItem(mInput->currentRow()));
        });

        connect(mBtnMoveToSelected, &QPushButton::clicked, [=](){
           mInput->addItem(mOuput->takeItem(mOuput->currentRow()));
        });

        connect(mButtonToAvailable, &QPushButton::clicked, [=](){
            while (mOuput->count()>0) {
                 mInput->addItem(mOuput->takeItem(0));
            }
        });

        connect(mButtonToSelected, &QPushButton::clicked, [=](){
            while (mInput->count()>0) {
                 mOuput->addItem(mInput->takeItem(0));
            }
        });

        connect(mBtnUp, &QPushButton::clicked, [=](){
            int row = mOuput->currentRow();
            QListWidgetItem *currentItem = mOuput->takeItem(row);
            mOuput->insertItem(row-1, currentItem);
            mOuput->setCurrentRow(row-1);
        });

        connect(mBtnDown, &QPushButton::clicked, [=](){
            int row = mOuput->currentRow();
            QListWidgetItem *currentItem = mOuput->takeItem(row);
            mOuput->insertItem(row+1, currentItem);
            mOuput->setCurrentRow(row+1);
        });
    }

    void setStatusButton(){
        mBtnUp->setDisabled(mOuput->selectedItems().isEmpty() || mOuput->currentRow() == 0);
        mBtnDown->setDisabled(mOuput->selectedItems().isEmpty() || mOuput->currentRow() == mOuput->count()-1);
        mBtnMoveToAvailable->setDisabled(mInput->selectedItems().isEmpty());
        mBtnMoveToSelected->setDisabled(mOuput->selectedItems().isEmpty());
    }

    QListWidget *mInput;
    QListWidget *mOuput;

    QPushButton *mButtonToAvailable;
    QPushButton *mButtonToSelected;

    QPushButton *mBtnMoveToAvailable;
    QPushButton *mBtnMoveToSelected;

    QPushButton *mBtnUp;
    QPushButton *mBtnDown;
};

#endif // TWOLISTSELECTION_H

enter image description here

In the following link you will find a complete example.

Acetum answered 19/1, 2018 at 0:34 Comment(7)
+ It is a good example but from the comments it seems that he himself wanted to do it.Lecherous
I tried compiling it (both in Qt Creator and by qmake && make) and it raised too many errors to fix myself. But in theory it's very similar to what I ended up doing, so +1 and accepted .Welloiled
@Welloiled What is your version of Qt?Acetum
@eyllanesc, QMake version 3.0. Using Qt version 5.2.1 in /usr/lib/x86_64-linux-gnuWelloiled
its version of Qt is very old, from 5.6 or 5.7 Qt supports C ++ 11 where you can use the new connection style:wiki.qt.io/New_Signal_Slot_Syntax, in addition to other features. That is why it is not compatible with its version. I have tested it with Qt 5.10, but I think it is compatible with Qt 5.7 forwards. If I have time I give support for previous versions.Acetum
I uploaded my version to github.com/Alechan/TwoListsExample. The main difference is that I check for empty list on operations instead of signals.Welloiled
Do not use MainWindow::movexxxx, you should simply use movexxxAcetum

© 2022 - 2024 — McMap. All rights reserved.