有没有一个看起来像标签的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 将恢复为之前的值。

一种可能的解决方案是对 QWidgetQLabelQLineEdit 进行子分类。 Qt 中还有其他可用的解决方案吗?

其中一个解决方案是拥有一个 QLineEdit 并将其设置为 read-only 并将其设置为看起来像标签的样式。我个人不喜欢这个解决方案,因为它更像是一种黑客方法。我想出了一些我认为非常酷的东西,其中包括 sub-classing QWidgetQLabelQLineEdit:

让我们首先介绍一个模型,它将在我们 QWidget 的 sub-classed 版本中创建,这个模型将被传递给它的 child 小部件, sub-classed QLabelQLineEdit 的版本:

型号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);
    }
}

正如我们所见,模型有文本,这在 QLabelQLineEdit 中都很常见,并且它有一个模式,可以是只读模式或编辑模式。

标签实现是 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 方法

以下版本也实现了与您的答案相同的功能,但不是子类化 QLineEditQLabel 只使用 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);
      }
}