1

I'm creating a ToDo list app in c++ qt. When a plus button is pressed, it adds a QHBoxLayout containing a QLabel and a QToolButton to a vertical layout inside my gui, so I get new boxes with the 'ToDos' inside them, and buttons to remove them next to them. I have set up the various widgets inside my slot which is called when the add button is clicked. However, I need to pass them as arguments to the slot which is called when a remove button is pressed. I have researched already, and all I have found is QSignalMapper. However, I cannot find any cases close enough to mine to replicate, and I have read it only works with certain arguments, and not the three I need (QHBoxLayout, QLineEdit and QToolButton). Some of the code for the slot which is called when the 'add' button is pressed is:

//Creates a read only LineEdit which the user will add
   QLineEdit *toDoBox = new QLineEdit(this);
   toDoBox->setText(ui->lineEdit->text());
   toDoBox->setReadOnly(true);

   //Creates a new X button for removal of ToDo's
    QToolButton *removeButton = new QToolButton;
    removeButton->setText("X");

    //Adds a horizontal layout with the ToDo and the remove button in it, to keep them together
    QHBoxLayout *toDoLayout = new QHBoxLayout;
    toDoLayout->addWidget(toDoBox);
    toDoLayout->addWidget(removeButton);

    //Removes a ToDo when the remove button is clicked
    connect(removeButton, SIGNAL(clicked()), this, SLOT(on_removeButton_clicked()));

My code is hosted on GitHub if you want to see the whole project: https://github.com/DanWilkes02/ToDoList

Thanks for bearing with me- I struggle explaining things that are so clear in my head!

Daniel Wilkes
  • 79
  • 1
  • 11
  • Signals and slots or explained in the [QObject](http://doc.qt.io/qt-5/qobject.html) documentation. More details are in the Qt wiki: [New Signal Slot Syntax](https://wiki.qt.io/New_Signal_Slot_Syntax) (you should be using the new syntax, unless you have reasons not to). – IInspectable Aug 30 '16 at 18:52

2 Answers2

2

If I understand well your problem, you want to get the allocated objects which represent a todo in order to free them and to update your View.

You could achieve this by simply wrapping your QLineEdit, QToolButton and QHBoxLayout objects into a class, and use a container (a vector for instance) in your ToDoList class. That way, you push_back your "todo object" each time you press the on_toolButton_clicked method.

Then, you simply have to use a signal with an index triggering an on_delete_todo slot which deletes a "todo object" from your vector and update the view.

Also, take a look at this Qt Model-View Programming

Here is a sample (tested and working under QT5):

Your Todo Widget

#ifndef TODOVIEW_H
#define TODOVIEW_H

#include <QString>

class QLineEdit;
class QToolButton;
class QHBoxLayout;

#include <QWidget>

class TodoView : public QWidget
{
    Q_OBJECT

private:
    QLineEdit*      frame;
    QToolButton*    removeButton;
    QHBoxLayout*    toDoLayout;
    int             index;

public:
    TodoView(const QString& what, int index, QWidget* parent);
    ~TodoView();

    inline void setIndex(int i) { index = i; }
    inline int getIndex(){ return index; }

private slots:
    void emitIndex();

signals:
    void selectedIndex(int);

};

#endif // TODOVIEW_H

#include "todoview.h"

#include <QLineEdit>
#include <QToolButton>
#include <QHBoxLayout>

TodoView::TodoView(const QString& what, int index, QWidget* parent) : QWidget(parent), index(index)
{
    frame = new QLineEdit(this);
    frame->setText(what);
    frame->setReadOnly(true);

    removeButton = new QToolButton(this);
    removeButton->setText("X");

    toDoLayout = new QHBoxLayout(this);
    toDoLayout->addWidget(frame);
    toDoLayout->addWidget(removeButton);

    connect(removeButton, SIGNAL(clicked()), this, SLOT(emitIndex()));
}

TodoView::~TodoView() {}

void TodoView::emitIndex()
{
    emit selectedIndex(getIndex());
}

Your MainWindow

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <vector>

class TodoView;
class QVBoxLayout;

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void addTodo();
    void delTodo(int);

private:
    Ui::MainWindow*         ui;

    QVBoxLayout*            vBoxLayout;

    std::vector<TodoView*>  todoView;
    int                     max = -1;
};

#endif // MAINWINDOW_H

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "todoview.h"

#include <QVBoxLayout>
#include <QAction>

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    vBoxLayout = new QVBoxLayout(centralWidget());

    QAction* add = new QAction(ui->mainToolBar);
    ui->mainToolBar->addAction(add);

    connect(add, SIGNAL(triggered()), this, SLOT(addTodo()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::addTodo()
{
    if(max > 9)
    {
        // Error msg.
    }
    else
    {
        TodoView* tdV = new TodoView("Yolo", max, centralWidget());
        connect(tdV, SIGNAL(selectedIndex(int)), this, SLOT(delTodo(int)));

        vBoxLayout->addWidget(tdV);
        todoView.push_back(tdV);
        ++max;
    }
}


void MainWindow::delTodo(int i)
{
    // check if i < todoView.size().
    delete todoView.at(i);
    // update vector indexes !!!
    --max;
}

I have edited this piece of code rapidly, I may have made several mistakes, but you have an idea of at least one solution.

It is also possible to use a fixed size for the vector (better solution). Setting the TodoView deleted objects to nullptr in the vector and search for nullptr when you want to add new Todo view components:

In the MainWindow constructor

todoView.reserve(10);
for(std::size_t i = 0; i < 10; ++i)
{
    todoView[i] = nullptr;
}

In the addTodo Slot

// Do not use push back.
// retrieve the max index.
// if < 10
for(std::size_t i = 0; i < todoView.size(); ++i)
{
    if(todoView[i] == nullptr)
    {
        // allocate TodoView and affect it to the i° element
    }
}

In the delTodo slot

delete todoView[i];
todoView[i] = nullptr;

Using a vector of pair is also possible (a pair of int TodoView).

Papipone
  • 1,083
  • 3
  • 20
  • 39
  • This works, however every so often it crashes (usually at the removal of items, however I haven't tested it thoroughly enough). Is there anything I have done wrong? My most recent commit on github has the new code. I haven't used a fixed size vector yet though, so could that be causing it? – Daniel Wilkes Aug 31 '16 at 11:05
  • When you add a TodoView object, you are "pushing back" an item. This means that the vector is always growing. However, when you delete one or more item, the max member is decremented. When you call delete, this operation is realized on an already deleted object (=> segfault). You need to resize the vector after a delete. Also, the max member should be initialized to -1, and test the validity of the emited index when you delete an object from the vector. – Papipone Aug 31 '16 at 19:16
  • Basically, the indexes of your objects and the indexes of your vector are not corresponding. This cause a segfault when you delete. Each time you resize the vector, you have to update the indexes of the TodoView objects. The indexes are the same of those of the vector. – Papipone Aug 31 '16 at 19:35
  • I'm not sure how all I can think of is: `TodoVeiw* thisObject = todoVeiw[i]; thisObject->setIndex(i); delete todoVeiw.at(i); --max; ` – Daniel Wilkes Sep 18 '16 at 14:32
1

I can think of two approaches you can go with.

  1. Using QObject::sender().
  2. Using QSignalMapper.

QObject::sender()

This static method returns QObject * of the sender object who emitted the signal. In this case QToolButton, so you can use this reference to find its parent (i.e. QHBoxLayout) and its sibling (i.e. QLineEdit).


QSingalMapper

First define a list for saving references to all rows, then assign each row an unique identifier (e.g. row number), then according to the official documentation's example use this identifier as QSignalMapper key for each button. Now if user clicks a button, in your slot, you will be given the same identifier, use it and lookup the whole row in the list of rows.

frogatto
  • 28,539
  • 11
  • 83
  • 129
  • How do you get `QHBoxLayout` and `QLineEdi`t using `QObject::sender()`? `removeButton->parent()` doesn't work as that gets the central widget. I can use that to get the children- `QHBoxLayout` and `QLineEdit`, but when I remove that it removes the first set, not the ones attached to removeButton – Daniel Wilkes Sep 19 '16 at 18:02