有没有一个看起来像标签的Qt小部件来显示文本,但如果双击也可以编辑?
Is there a Qt widget that looks like a label to display text but can also be edited if double-clicked?
我想要 Qt 中的小部件,它可以像电子表格单元格一样工作。它可以显示文本,然后当用户双击它时,它就变成可编辑的。用户完成编辑并按下 Enter 后,文本将被保存并且控件不再可编辑。如果用户在编辑时点击 Escape,则控件 returns 将恢复为之前的值。
一种可能的解决方案是对 QWidget
、QLabel
和 QLineEdit
进行子分类。 Qt 中还有其他可用的解决方案吗?
其中一个解决方案是拥有一个 QLineEdit 并将其设置为 read-only 并将其设置为看起来像标签的样式。我个人不喜欢这个解决方案,因为它更像是一种黑客方法。我想出了一些我认为非常酷的东西,其中包括 sub-classing QWidget
、QLabel
和 QLineEdit
:
让我们首先介绍一个模型,它将在我们 QWidget
的 sub-classed 版本中创建,这个模型将被传递给它的 child 小部件, sub-classed QLabel
和 QLineEdit
的版本:
型号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
模型实施 - 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);
}
}
正如我们所见,模型有文本,这在 QLabel
和 QLineEdit
中都很常见,并且它有一个模式,可以是只读模式或编辑模式。
标签实现是 Label
的 sub-class。
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
实施 - 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);
}
因为我们的 class MyLabel 有一个 setModel() 方法,它将从其 parent 中获取模型。我们正在覆盖 mouseDoubleClickEvent()
,虽然我们将模型的文本设置为标签中的任何文本,并将模式设置为编辑,因为当 double-clicking 时我们想要编辑文本。
现在让我们来看看QLineEdit
。我们的 QLineEdit
版本称为 MyLineEdit
,它正在监听键盘事件,当按下 Enter 和 Esc 键时,它要么将文本保存到模型中,要么将其丢弃。然后它将模式更改为 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
这是实现 - 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);
}
所以现在我们有了模型,我们有了我们的 QLabel
版本和我们的 QLineEdit
版本。我们现在想要的是一个包含它们的 parent 小部件,监听来自模型的信号并根据信号改变它的外观。 class 派生自 QWidget
并称为 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:
使用它非常简单。如果您使用的是 Qt Creator
设计器,那么您想绘制一个 QWidget
并右键单击它并将其提升为 MyEditableLabel
即可完成。如果您不使用 Qt Creator
设计器,那么您只需要创建 MyEditableLabel
的实例即可。
Improvements:
最好不要在 MyEditableLabel
的构造函数中创建模型,而是在它之外并在 MyEditableLabel
.
中使用 setModel
方法
以下版本也实现了与您的答案相同的功能,但不是子类化 QLineEdit
和 QLabel
只使用 eventFilter()
而不是手动管理可见性让 QStackedWidget
去做。
#include <QApplication>
#include <QFormLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QStackedWidget>
#include <QVBoxLayout>
class MyEditableLabel: public QWidget{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
MyEditableLabel(QWidget *parent=nullptr):
QWidget(parent),
mLabel(new QLabel),
mLineEdit(new QLineEdit)
{
setLayout(new QVBoxLayout);
layout()->setMargin(0);
layout()->setSpacing(0);
layout()->addWidget(&stacked);
stacked.addWidget(mLabel);
stacked.addWidget(mLineEdit);
mLabel->installEventFilter(this);
mLineEdit->installEventFilter(this);
setSizePolicy(mLineEdit->sizePolicy());
connect(mLineEdit, &QLineEdit::textChanged, this, &MyEditableLabel::setText);
}
bool eventFilter(QObject *watched, QEvent *event){
if (watched == mLineEdit) {
if(event->type() == QEvent::KeyPress){
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Return ||
keyEvent->key() == Qt::Key_Escape ||
keyEvent->key() == Qt::Key_Enter)
{
mLabel->setText(mLineEdit->text());
stacked.setCurrentIndex(0);
}
}
else if (event->type() == QEvent::FocusOut) {
mLabel->setText(mLineEdit->text());
stacked.setCurrentIndex(0);
}
}
else if (watched == mLabel) {
if(event->type() == QEvent::MouseButtonDblClick){
stacked.setCurrentIndex(1);
mLineEdit->setText(mLabel->text());
mLineEdit->setFocus();
}
}
return QWidget::eventFilter(watched, event);
}
QString text() const{
return mText;
}
void setText(const QString &text){
if(text == mText)
return;
mText == text;
emit textChanged(mText);
}
signals:
void textChanged(const QString & text);
private:
QLabel *mLabel;
QLineEdit *mLineEdit;
QStackedWidget stacked;
QString mText;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QFormLayout *lay = new QFormLayout(&w);
MyEditableLabel el;
lay->addRow("MyEditableLabel: ", &el);
lay->addRow("QLineEdit: ", new QLineEdit);
w.show();
return a.exec();
}
#include "main.moc"
此解决方案不是那么吸引人,但可能是您可用的性能更高的解决方案之一是使用 QInputdialog 更改 QLabel 并覆盖 mouseDoubleClickEvent 以触发输入对话框。我和这里的一些人一样了解到,没有办法从 QLabel 中提取编辑过的文本。并非没有更改 QLabels 内部代码。这是一个使用 QInputDialog 作为手段的例子。
//intrlbl.h
#ifndef INTRLBL_H
#define INTRLBL_H
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
class intrLbl: public QLabel
{
Q_OBJECT
public:
intrLbl(QWidget *parent);
void mouseDoubleClickEvent(QMouseEvent *event) override;
QString text;
};
#endif // INTRLBL_H
//intrlbl.cpp file
#include "intrlbl.h"
#include <QDebug>
#include <QInputDialog>
intrLbl::intrLbl(QWidget *parent)
{
this->setText("Text Changeable Via Double Click QInput Dialog");
this->setFocusPolicy(Qt::ClickFocus);
this->setWordWrap(false);
}
void intrLbl::mouseDoubleClickEvent(QMouseEvent *event)
{
QString title
= QInputDialog::getText(this,
tr("Enter your Idea Title:"),
tr("Title:"), QLineEdit::Normal,
tr("enter your title here"));
if(!title.isEmpty())
{
qDebug() << "Title set to:" << title;
this->setText(title);
}
else
{
title = "Title";
this->setText(title);
}
}
我想要 Qt 中的小部件,它可以像电子表格单元格一样工作。它可以显示文本,然后当用户双击它时,它就变成可编辑的。用户完成编辑并按下 Enter 后,文本将被保存并且控件不再可编辑。如果用户在编辑时点击 Escape,则控件 returns 将恢复为之前的值。
一种可能的解决方案是对 QWidget
、QLabel
和 QLineEdit
进行子分类。 Qt 中还有其他可用的解决方案吗?
其中一个解决方案是拥有一个 QLineEdit 并将其设置为 read-only 并将其设置为看起来像标签的样式。我个人不喜欢这个解决方案,因为它更像是一种黑客方法。我想出了一些我认为非常酷的东西,其中包括 sub-classing QWidget
、QLabel
和 QLineEdit
:
让我们首先介绍一个模型,它将在我们 QWidget
的 sub-classed 版本中创建,这个模型将被传递给它的 child 小部件, sub-classed QLabel
和 QLineEdit
的版本:
型号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
模型实施 - 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);
}
}
正如我们所见,模型有文本,这在 QLabel
和 QLineEdit
中都很常见,并且它有一个模式,可以是只读模式或编辑模式。
标签实现是 Label
的 sub-class。
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
实施 - 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);
}
因为我们的 class MyLabel 有一个 setModel() 方法,它将从其 parent 中获取模型。我们正在覆盖 mouseDoubleClickEvent()
,虽然我们将模型的文本设置为标签中的任何文本,并将模式设置为编辑,因为当 double-clicking 时我们想要编辑文本。
现在让我们来看看QLineEdit
。我们的 QLineEdit
版本称为 MyLineEdit
,它正在监听键盘事件,当按下 Enter 和 Esc 键时,它要么将文本保存到模型中,要么将其丢弃。然后它将模式更改为 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
这是实现 - 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);
}
所以现在我们有了模型,我们有了我们的 QLabel
版本和我们的 QLineEdit
版本。我们现在想要的是一个包含它们的 parent 小部件,监听来自模型的信号并根据信号改变它的外观。 class 派生自 QWidget
并称为 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:
使用它非常简单。如果您使用的是 Qt Creator
设计器,那么您想绘制一个 QWidget
并右键单击它并将其提升为 MyEditableLabel
即可完成。如果您不使用 Qt Creator
设计器,那么您只需要创建 MyEditableLabel
的实例即可。
Improvements:
最好不要在 MyEditableLabel
的构造函数中创建模型,而是在它之外并在 MyEditableLabel
.
setModel
方法
以下版本也实现了与您的答案相同的功能,但不是子类化 QLineEdit
和 QLabel
只使用 eventFilter()
而不是手动管理可见性让 QStackedWidget
去做。
#include <QApplication>
#include <QFormLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QStackedWidget>
#include <QVBoxLayout>
class MyEditableLabel: public QWidget{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
MyEditableLabel(QWidget *parent=nullptr):
QWidget(parent),
mLabel(new QLabel),
mLineEdit(new QLineEdit)
{
setLayout(new QVBoxLayout);
layout()->setMargin(0);
layout()->setSpacing(0);
layout()->addWidget(&stacked);
stacked.addWidget(mLabel);
stacked.addWidget(mLineEdit);
mLabel->installEventFilter(this);
mLineEdit->installEventFilter(this);
setSizePolicy(mLineEdit->sizePolicy());
connect(mLineEdit, &QLineEdit::textChanged, this, &MyEditableLabel::setText);
}
bool eventFilter(QObject *watched, QEvent *event){
if (watched == mLineEdit) {
if(event->type() == QEvent::KeyPress){
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Return ||
keyEvent->key() == Qt::Key_Escape ||
keyEvent->key() == Qt::Key_Enter)
{
mLabel->setText(mLineEdit->text());
stacked.setCurrentIndex(0);
}
}
else if (event->type() == QEvent::FocusOut) {
mLabel->setText(mLineEdit->text());
stacked.setCurrentIndex(0);
}
}
else if (watched == mLabel) {
if(event->type() == QEvent::MouseButtonDblClick){
stacked.setCurrentIndex(1);
mLineEdit->setText(mLabel->text());
mLineEdit->setFocus();
}
}
return QWidget::eventFilter(watched, event);
}
QString text() const{
return mText;
}
void setText(const QString &text){
if(text == mText)
return;
mText == text;
emit textChanged(mText);
}
signals:
void textChanged(const QString & text);
private:
QLabel *mLabel;
QLineEdit *mLineEdit;
QStackedWidget stacked;
QString mText;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QFormLayout *lay = new QFormLayout(&w);
MyEditableLabel el;
lay->addRow("MyEditableLabel: ", &el);
lay->addRow("QLineEdit: ", new QLineEdit);
w.show();
return a.exec();
}
#include "main.moc"
此解决方案不是那么吸引人,但可能是您可用的性能更高的解决方案之一是使用 QInputdialog 更改 QLabel 并覆盖 mouseDoubleClickEvent 以触发输入对话框。我和这里的一些人一样了解到,没有办法从 QLabel 中提取编辑过的文本。并非没有更改 QLabels 内部代码。这是一个使用 QInputDialog 作为手段的例子。
//intrlbl.h
#ifndef INTRLBL_H
#define INTRLBL_H
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
class intrLbl: public QLabel
{
Q_OBJECT
public:
intrLbl(QWidget *parent);
void mouseDoubleClickEvent(QMouseEvent *event) override;
QString text;
};
#endif // INTRLBL_H
//intrlbl.cpp file
#include "intrlbl.h"
#include <QDebug>
#include <QInputDialog>
intrLbl::intrLbl(QWidget *parent)
{
this->setText("Text Changeable Via Double Click QInput Dialog");
this->setFocusPolicy(Qt::ClickFocus);
this->setWordWrap(false);
}
void intrLbl::mouseDoubleClickEvent(QMouseEvent *event)
{
QString title
= QInputDialog::getText(this,
tr("Enter your Idea Title:"),
tr("Title:"), QLineEdit::Normal,
tr("enter your title here"));
if(!title.isEmpty())
{
qDebug() << "Title set to:" << title;
this->setText(title);
}
else
{
title = "Title";
this->setText(title);
}
}