One of the solutions is to have a QLineEdit and set it to read-only and style it in a way that it will look like a label. I personally do not like this solution, because it's more of a hacking approach. I have come up with something that in my opinion is pretty cool, which includes sub-classing QWidget
, QLabel
and QLineEdit
:
Let's first introduce a model, which will be created in the sub-classed version of our QWidget
and this model will be passed to its child widgets, the sub-classed versions of QLabel
and QLineEdit
:
Model header - mymodel.h
:
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QObject>
class MyModel : public QObject {
Q_OBJECT
Q_PROPERTY(Mode mode READ getMode WRITE setMode NOTIFY modeChanged)
Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged)
public:
enum class Mode {
ReadOnly = 0,
Edit = 1,
};
explicit MyModel(QObject* parent = nullptr);
Mode getMode() const {
return _mode;
}
const QString& getText() const {
return _text;
}
signals:
void modeChanged(Mode mode);
void textChanged(const QString& text);
public slots:
void setMode(Mode mode);
void setText(const QString& text);
private:
Mode _mode;
QString _text;
};
#endif // MYMODEL_H
Model implementation - mymodel.cpp
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
: QObject(parent)
, _mode(MyModel::Mode::ReadOnly)
, _text(QString()) {
}
void MyModel::setMode(MyModel::Mode mode) {
if (_mode != mode) {
_mode = mode;
emit modeChanged(_mode);
}
}
void MyModel::setText(const QString &text) {
if (_text != text) {
_text = text;
emit textChanged(text);
}
}
As we see the model has the text, which is common for both the QLabel
and the QLineEdit
, and it has a mode, which can be either read only or edit mode.
The label implementation is a sub-class of Label
.
Header - mylabel.h
:
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
#include <QSharedPointer>
#include "mymodel.h"
class MyLabel : public QLabel {
Q_OBJECT
public:
explicit MyLabel(QWidget *parent = 0);
void setModel(QSharedPointer<MyModel> model);
protected:
void mouseDoubleClickEvent(QMouseEvent *) override;
private:
QSharedPointer<MyModel> _model;
};
#endif // MYLABEL_H
Implementation - mylabel.cpp
:
#include "mylabel.h"
#include <QMouseEvent>
MyLabel::MyLabel(QWidget *parent)
: QLabel(parent) {
}
void MyLabel::setModel(QSharedPointer<MyModel> model) {
_model = model;
}
void MyLabel::mouseDoubleClickEvent(QMouseEvent *) {
_model->setText(text());
_model->setMode(MyModel::Mode::Edit);
}
As we our class MyLabel has a setModel() method, which will take the model from its parent. We are overriding the mouseDoubleClickEvent()
, though which we are setting the text of the model to whatever text there is in the label, and setting the mode to edit, because when double-clicking we want to edit the text.
Now let's take a look at the QLineEdit
. Our version of QLineEdit
, called MyLineEdit
, is listening to keyboard events and when Enter and Esc keys are pressed it either saves the text to the model, or discards it. Then it changes the mode to read-only.
MyLineEdit.h
:
#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H
#include <QLineEdit>
#include <QSharedPointer>
#include "mymodel.h"
class MyLineEdit : public QLineEdit {
Q_OBJECT
public:
MyLineEdit(QWidget* parent = nullptr);
void setModel(QSharedPointer<MyModel> model);
protected:
void keyPressEvent(QKeyEvent* event) override;
void focusOutEvent(QFocusEvent*);
private:
QSharedPointer<MyModel> _model;
};
#endif // MYLINEEDIT_H
And here's the implementation - MyLineEdit.cpp
:
#include "mylineedit.h"
#include <QKeyEvent>
MyLineEdit::MyLineEdit(QWidget *parent)
: QLineEdit(parent) {
}
void MyLineEdit::setModel(QSharedPointer<MyModel> model) {
_model = model;
}
void MyLineEdit::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Enter) {
_model->setText(text());
_model->setMode(MyModel::Mode::ReadOnly);
} else if (event->key() == Qt::Key_Escape) {
_model->setMode(MyModel::Mode::ReadOnly);
} else {
QLineEdit::keyPressEvent(event);
}
}
void MyLineEdit::focusOutEvent(QFocusEvent *) {
_model->setText(text());
_model->setMode(MyModel::Mode::ReadOnly);
}
So now we have the model, we have our version of QLabel
and our version of QLineEdit
. What we want now is a parent widget that will contain both of them, listen to signals from the model and change its appearance based on the signals. That class is derived from QWidget
and is called MyEditableLabel
:
MyEditableLabel.h
:
#ifndef MYEDITABLELABEL_H
#define MYEDITABLELABEL_H
#include <QSharedPointer>
#include <QWidget>
#include "mylabel.h"
#include "mylineedit.h"
class MyEditableLabel : public QWidget {
Q_OBJECT
public:
explicit MyEditableLabel(QWidget *parent = nullptr);
QString getText() const {return _text;}
private:
MyLabel *_label;
MyLineEdit *_lineEdit;
QSharedPointer<MyModel> _model;
private slots:
void onModeChanged(MyModel::Mode mode);
void onTextChanged(const QString &text);
private:
QString _text;
};
#endif // MYEDITABLELABEL_H
MyEditableLabel.cpp
:
#include "myeditablelabel.h"
#include <QHBoxLayout>
MyEditableLabel::MyEditableLabel(QWidget *parent)
: QWidget(parent) {
_model = QSharedPointer<MyModel>(new MyModel());
_model->setText("Click me!");
_label = new MyLabel(this);
_label->setModel(_model);
_lineEdit = new MyLineEdit(this);
_lineEdit->setModel(_model);
_lineEdit->setReadOnly(false);
QHBoxLayout *mainLayout = new QHBoxLayout();
mainLayout->setMargin(0);
mainLayout->setSpacing(0);
mainLayout->addWidget(_label);
mainLayout->addWidget(_lineEdit);
setLayout(mainLayout);
connect(_model.data(), &MyModel::modeChanged, this, &MyEditableLabel::onModeChanged);
onModeChanged(_model->getMode());
connect(_model.data(), &MyModel::textChanged, this, &MyEditableLabel::onTextChanged);
onTextChanged(_model->getText());
}
void MyEditableLabel::onModeChanged(MyModel::Mode mode) {
_lineEdit->setVisible(mode == MyModel::Mode::Edit);
_lineEdit->selectAll();
_label->setVisible(mode == MyModel::Mode::ReadOnly);
}
void MyEditableLabel::onTextChanged(const QString &text) {
_lineEdit->setText(text);
_label->setText(text);
_text = text;
}
Usage:
Using this is pretty straightforward. If you're using the Qt Creator
designer, then you want to draw a QWidget
and the right click on it and promote it to MyEditableLabel
and you're done. If you're not using the Qt Creator
designer then you just have to create and instance of MyEditableLabel
and you're in business.
Improvements:
It probably is a better idea to not create the model in the constructor of MyEditableLabel
, but outside of it and have a setModel
method in MyEditableLabel
.