Mapping binary buttons in the order they are placed on a Widget



  • Hi,
    I have a widget to print and memorized values of each button in a variable and each row below is a 4 bit binary number.
    alt text
    I'm trying to assign each button position to be "memorized" with a value (0 or 1) for each row in the order that they appear in the widget.

    When a user changes the value of several buttons and presses "calculate", a vector or a string should print out every value on each button in the text box.

    I tried with mapping but I can't get it to work.

    Right now all I can get are the values that were pressed but not all of them (eg. the "0"s) and they don't come out in order.

    The QTextEdit box also changes row spacing for unknown reasons.

    In the example above, it should print in the QTextEdit box:
    1011
    1100
    1000
    1111
    here's my widget.cpp:

    #include "widget.h"
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
    {
        // index_ corresponds to the index of each button (0 to 60) == 15 rows of 4 buttons
        index_ = 0;
    
        layout_ = new QGridLayout;
    
        for (unsigned i = 0; i < SIZE_LONG; ++i) {
            for (unsigned j = 0; j < SIZE_BITS; ++j) {
    
                digitButtons[index_] = createButton("0", SLOT(buttonClicked()));
    
                connect(digitButtons[index_], SIGNAL(clicked(bool)), &mapper, SLOT(map()));
    
                mapper.setMapping(digitButtons[index_], index_);
    
                layout_->addWidget(digitButtons[index_++], i, j, Qt::AlignHCenter);
    
                if (i == SIZE_LONG - 1 && j == SIZE_BITS - 1) {
                    setColumnNumber(j);
                    layout_->addWidget(createButton("Calculate", SLOT(calculateClicked())), i + 1, j, Qt::AlignRight);
                }
            }
        }
        connect(&mapper, SIGNAL(mapped(int)), this, SIGNAL(handleButton(int)));
        setLayout(layout_);
    }
    
    Widget::~Widget()
    {}
    
    QString Widget::getButtons() { return btns_; }
    
    // set & get column number for the calculateClicked() SLOT
    void Widget::setColumnNumber(unsigned j) { columnNumber_ = j; }
    
    unsigned Widget::getColumnNumber() { return columnNumber_; }
    
    // if a button is clicked()
    void Widget::buttonClicked()
    {
        QPushButton *clickedButton = qobject_cast<QPushButton *>(sender());
        if (isChecked(clickedButton) == true)
            clickedButton->setText("1");
        else
            clickedButton->setText("0");
    
        btns_ += clickedButton->text();
    }
    
    QGridLayout *Widget::getQGridLayout() { return layout_; }
    
    void Widget::calculateClicked()
    {
        clickedButtonResult_ = qobject_cast<QPushButton *>(sender());
        clickedButtonResult_->setText("Calculate");
        createTextEdit(getQGridLayout(), 0, getColumnNumber());
    }
    
    void Widget::createTextEdit(QGridLayout *layout, unsigned i, unsigned j)
    {
        textGroupBox = new QTextEdit;
    
        layout->addWidget(textGroupBox, i + 1, j + 1);
        textGroupBox->setPlainText(btns_);
    }
    
    QPushButton *Widget::createButton(const QString &text, const char *member)
    {
        QPushButton *button = new QPushButton(text);
        button->setCheckable(true);
        button->setChecked(false);
        connect(button, SIGNAL(toggled(bool)), this, member);
        return button;
    }
    
    bool Widget::isChecked(QPushButton *btn)
    {
        return btn->isChecked();
    }
    
    

    widget.h:

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    #include <QApplication>
    #include <QString>
    #include <QStringList>
    #include <QPushButton>
    #include <QGridLayout>
    #include <QVector>
    #include <QSignalMapper>
    #include <QGroupBox>
    #include <QFormLayout>
    #include <QLabel>
    #include <QLineEdit>
    #include <QTextBlock>
    #include <QTextEdit>
    
    static const unsigned SIZE_LONG = 15;
    static const unsigned SIZE_BITS = 4;
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        Widget(QWidget *parent = 0);
        ~Widget();
    
        QVector<int> getVector();
        QString getButtons();
    
        void setColumnNumber(unsigned j);
        unsigned getColumnNumber();
    
        QGridLayout *getQGridLayout();
    
    private slots:
        void buttonClicked();
        void calculateClicked();
    
    private:
    
        QString btns_;
    
        enum { NumDigitButtons = 60 };
        QPushButton *digitButtons[NumDigitButtons];
    
        QSignalMapper mapper;
        unsigned index_;
        QTextEdit *textGroupBox;
        unsigned columnNumber_;
        QPushButton *clickedButtonResult_;
    
        QGridLayout *layout_;
        void createTextEdit(QGridLayout *layout, unsigned i, unsigned j);
        QPushButton *createButton(const QString &text, const char *member);
        bool isChecked(QPushButton *btn);
    
    signals:
        void handleButton(int button);
    
    };
    
    #endif // WIDGET_H
    
    

    main.cpp:

    #include "widget.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Widget w;
        w.show();
        return a.exec();
    }
    

    I'm trying to do this without lambdas & c++11 for now as I'm fairly new to QT5.
    Thank you for your help.


  • Moderators

    @jensig What does handleButton(int) do?
    Why do you create a new QTextEdit every time instead of reusing the existing one?
    The spacing inside the text edit is wrong probably because you don't use a font with fix spacing.
    This

    bool Widget::isChecked(QPushButton *btn)
    {
        if (btn->isChecked())
            return true;
        return false;
    }
    

    can be simplified to

    bool Widget::isChecked(QPushButton *btn)
    {
        return btn->isChecked();
    }
    


  • @jsulm handleButton(int button) is supposed to be a signal but I have no idea how to implement it.

    I don't understand what you mean when you say to reuse the existing QTextEdit? Could you show me what to modify?

    Thank you


  • Moderators

    @jensig

    connect(&mapper, SIGNAL(mapped(int)), this, SIGNAL(handleButton(int)));
    

    So, you want to emit a signal when mapped(int) signal is emitted - is that correct?

    "I don't understand what you mean when you say to reuse the existing QTextEdit?" - do you want to create a text edit for each button row?



  • @jsulm
    In regards to mapping, all I want is to have all the values of each button (indexed 0 to 59) in a QVector<QString> or anything else so I would be able to retrieve them when a user presses on "calculate".
    When a user presses any of the buttons, it is able to modify the text on the QPushButton to "0" or "1", so I want their values in order or indexed in a QVector<QString> or anything else when we press on "calculate".

    "So, you want to emit a signal when mapped(int) signal is emitted - is that correct?"

    I am very confused about the utilization of signals so I really can't tell if to be able to retrieve the values I need to emit a signal.

    "do you want to create a text edit for each button row?"
    No, only one QTextEdit at the end to show all values on the buttons like so:
    1011
    1100
    1000
    1111

    Thank you


  • Moderators

    @jensig said in Mapping binary buttons in the order they are placed on a Widget:

    No, only one QTextEdit at the end to show all values on the buttons like so:

    Then do not create a new text edit each time a button is pressed.

    Change this line

    connect(&mapper, SIGNAL(mapped(int)), this, SIGNAL(handleButton(int)));
    

    to

    connect(&mapper, SIGNAL(mapped(int)), this, SLOT(mapped(int)));
    

    And implement mapped slot where you get the index of the button as parameter:

    void Widget::mapped(int index)
    {
    ...
    }
    


  • Hi @jensig

    are you sure, you want to do this without lambdas?

    Would make this so, so much easier:

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    #include <QVector>
    
    class QTextEdit;
    class QPushButton;
    class QGridLayout;
    class Widget : public QWidget
    {
        Q_OBJECT
    public:
        explicit Widget(QWidget *parent = nullptr);
    
    
    private:
        QVector<QVector<QPushButton*>> btns;
        QGridLayout *layout_;
        QTextEdit *textEdit;
    };
    
    #endif // WIDGET_H
    
    
    #include "widget.h"
    #include <QGridLayout>
    #include <QTextEdit>
    #include <QPushButton>
    
    static const unsigned SIZE_LONG = 15;
    static const unsigned SIZE_BITS = 4;
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
    {
    
        layout_ = new QGridLayout;
        textEdit = new QTextEdit();
        textEdit->resize(100,100);
        textEdit->show();
    
        for (int i = 0; i < SIZE_LONG; ++i) {
            QVector<QPushButton*> btnRow;
            for (int j = 0; j < SIZE_BITS; ++j) {
                QPushButton *btn = new QPushButton("0");
                btn->setCheckable(true);
                layout_->addWidget(btn,i,j,Qt::AlignHCenter);
    
                connect(btn, &QPushButton::toggled, this, [=](bool checked){
                    btn->setText(checked ? "1" : "0");
                    textEdit->clear();
                    for(int x(0); x < btns.size(); x++){
                        QString line;
                        for(int y(0); y < btns.at(x).size(); y++){
                            line.append(btns.at(x).at(y)->text());
                        }
                        textEdit->append(line);
                    }
                });
    
                btnRow.append(btn);
            }
            btns.append(btnRow);
        }
        setLayout(layout_);
    }
    


  • @J.Hilk
    Hi , thanks for the simplification.
    I changed your code a little but I can't figure out how to put a QTextEdit when a "Calculate" button is pressed like in the picture above.

    Is there any particular reason why you've written QTextEdit, QPushButton, QGridLayout in a forward declaration instead of includes?

    #include "widget.h"
    #include <QGridLayout>
    #include <QTextEdit>
    #include <QPushButton>
    
    static const unsigned SIZE_LONG = 15;
    static const unsigned SIZE_BITS = 4;
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
    {
    
        layout_ = new QGridLayout;
        textEdit_ = new QTextEdit();
        textEdit_->resize(100, 100);
        //textEdit_->show();
        QPushButton *btnCalculate = new QPushButton("Calculate");
        btnCalculate->setCheckable(true);
    
        for (unsigned i = 0; i < SIZE_LONG; ++i) {
            QVector<QPushButton*> btnRow;
            for (unsigned j = 0; j < SIZE_BITS; ++j) {
                QPushButton *btn = new QPushButton("0");
                btn->setCheckable(true);
                layout_->addWidget(btn, i, j, Qt::AlignHCenter);
    
                if (i == SIZE_LONG - 1 && j == SIZE_BITS - 1) {
    
                    layout_->addWidget(btnCalculate, i + 1, j, Qt::AlignRight);
                }
                    connect(btn, &QPushButton::toggled, this, [=](bool checked){
                        btn->setText(checked ? "1" : "0");
                        
    
                        textEdit_->clear();
                        for(int x = 0; x < btns_.size(); x++){
                            QString line;
                            for(int y = 0; y < btns_[x].size(); y++){
                                line.append(btns_[x][y]->text());
                            }
                            textEdit_->append(line);
                        }
                    });
                    connect(btnCalculate, &QPushButton::toggled, this, [=](bool calculate){
                           if (calculate == false)
                              layout_->addWidget(textEdit_, 0, SIZE_BITS + 1, Qt::AlignHCenter);
                    });
    
                btnRow.append(btn);
            }
            btns_.append(btnRow);
        }
        setLayout(layout_);
    }
    

    widget.h

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    #include <QVector>
    
    class QTextEdit;
    class QPushButton;
    class QGridLayout;
    class Widget : public QWidget
    {
        Q_OBJECT
    public:
        explicit Widget(QWidget *parent = nullptr);
    
    
    private:
        QVector<QVector<QPushButton*>> btns_;
        QGridLayout *layout_;
        QTextEdit *textEdit_;
    };
    
    #endif // WIDGET_H
    

    Thank you



  • @jensig
    can you clarify, what exactly is supposed to happen when caluclate is pressed? It's hard to guess from the code example.



  • @J.Hilk
    I couldn't prevent QTextEdit from changing the spacing between rows when it is called with the QPushButton "Calculate" (see image in my first post). Spacing between rows 1, 2 & 3 change when QTextEdit is placed there. I tried to follow @jsulm 's advice but I think he refers to spacing inside QTextEdit and not the spacing between the rows of buttons.
    I've also tried to follow the documentation without much result Qt5 Layouts.

    I tried this, but it crashes instantly:
    The rest of my code is identical to my previous post, where I changed your code.

                    connect(btnCalculate, &QPushButton::toggled, this, [=](bool calculate){
                           if (calculate) {
                                vLayout_->addWidget(textEdit_); // from QVBoxLayout *vLayout_;
                                groupBox_->setLayout(vLayout_); // from QGroupBox *groupBox_;
                                layout_->addWidget(groupBox_, 0, SIZE_BITS + 1, Qt::AlignRight); // from QGridLayout *layout_;
                               //layout_->setSpacing(0);
                           }
                    });
    
    The program has unexpectedly finished.
    The process was ended forcefully.
    

    Thank you



  • Ok @jensig ,
    as a general rule of thumb, don‘t dynamically create widgets in slots such as "button clicks", that‘s bad design and will end up slowing down your application pretty fast and you may loose oversight of what widget was created and still needs to be deleted. Etc.

    That said, you can create the QTextEdit during the Button setup and refere it later on.

    But, QPushButtons have by default a fixed height and placing the QTextEdit into the gridlayout will result in „streched „ rows like you have it in the op.

    Two solutions I can think of:

    • Nesting layouts

    Place your gridlayout and the QTextEdit as Widgets in a QHorizontalLayout. That should solve your issue

    • stretching the QTextEdit over all rows of the Gridlayout

    For that I‘ll adjust my example code:
    Right after the QPushbutton create loop:

    ...
    layout_->addWidget(TextEdit,0,SIZE_BITS,SIZE_LONG,1);
    

    addWidget has an overload that allows colmns and rows that it shall span:

    void QGridLayout::addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = ...)

    Edit: fixed obious spelling mistakes, creating posts at 1 am on an iPad is apparently not the best idea.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.