QWidget实现文本选中与复制功能
一、问题与思路
在Qt中,QWidget本身并不直接支持文本的选择和拷贝,因为QWidget是一个基础的窗口部件,通常用于自定义绘制和交互,只有QLabel、QLineEdit、QTextEdit等文本显示部件支持文本选择和拷贝。通常有以下几种方法实现文本的选择和拷贝:
1、使用QLabel并设置其文本交互属性,方法为:setTextInteractionFlags(Qt::TextSelectableByMouse);
2、使用QLineEdit、QPlainTextEdit 和 QTextEdit等组件,将其设置为只读 setReadOnly(true),并调整组件的样式,使其看起来像普通的QLabel;
3、在QWidget上自己实现文本的绘制和鼠标事件处理,以支持文本的选择和拷贝。这种方法较为复杂,需要处理鼠标事件、文本选择、绘制选中背景等,但它也更灵活,如可以方便设置选中文本的背景色。实现过程如下:
1)记录文本内容;
2)处理鼠标按下、移动和释放事件,以确定选择的范围;
3)在paintEvent中绘制文本和选中的背景;
4)实现拷贝功能(通过QClipboard)。
二、源代码
从Qt Creator新建一个对话框项目,代码如下所示:
1、SelectableTextWidget.h(从QWidget派生,实现文本的选择与拷贝)
#pragma once#include <qwidget.h>class SelectableTextWidget : public QWidget
{Q_OBJECT
public:SelectableTextWidget(QWidget *parent = nullptr);~SelectableTextWidget();private:// 根据位置获取行号int getLineForPosition(int position);// 根据行号获取该行起始位置int getPositionForLine(int line);// 根据位置获取X坐标int getXForPosition(int position);// 根据点获取文本位置int getPositionForPoint(const QPoint &point);protected:void paintEvent(QPaintEvent *event) override;void mousePressEvent(QMouseEvent *event) override;void mouseMoveEvent(QMouseEvent *event) override;void mouseReleaseEvent(QMouseEvent *event) override;void contextMenuEvent(QContextMenuEvent *event) override;void keyPressEvent(QKeyEvent *event) override;void focusInEvent(QFocusEvent *event) override;void focusOutEvent(QFocusEvent *event) override;private slots:void copySelectedText(); void selectAllText();private:QString m_text;int m_selectionStart = -1;int m_selectionEnd = -1;bool m_dragging = false;int m_lineHeight;
};
2、SelectableTextWidget.cpp
#include "SelectableTextWidget.h"
#include <qapplication.h>
#include <qclipboard.h>
#include <qevent.h>
#include <qmenu.h>
#include <qpainter.h>SelectableTextWidget::SelectableTextWidget(QWidget *parent): QWidget(parent)
{setMouseTracking(true);setFocusPolicy(Qt::StrongFocus);// 示例文本m_text = QString("这是一个可选中文本的示例。\n""您可以拖动鼠标来选择文本,\n""然后使用右键菜单或Ctrl+C复制。\n""支持多行文本选择和复制。\n""两个黄鹂鸣翠柳,\n一行白鹭上青天。\n""窗含西岭千秋雪,\n门泊东吴万里船。");// 计算文本行高QFontMetrics fm(font());m_lineHeight = fm.height();// 设置合适的大小setMinimumSize(400, 200);
}SelectableTextWidget::~SelectableTextWidget()
{
}int SelectableTextWidget::getLineForPosition(int position)
{int currentPos = 0;QStringList lines = m_text.split('\n');for (int i = 0; i < lines.size(); ++i) {// 加上换行符的长度int lineLength = lines[i].length() + (i < lines.size() - 1 ? 1 : 0);if (position >= currentPos && position < currentPos + lineLength) {return i;}currentPos += lineLength;}return lines.size() - 1;
}int SelectableTextWidget::getPositionForLine(int line)
{QStringList lines = m_text.split('\n');int position = 0;for (int i = 0; i < line && i < lines.size(); ++i) {position += lines[i].length() + 1; // +1 为换行符}return position;
}int SelectableTextWidget::getXForPosition(int position)
{int line = getLineForPosition(position);int lineStart = getPositionForLine(line);QStringList lines = m_text.split('\n');QString lineText = lines[line];QFontMetrics fm(font());QString textBefore = lineText.left(position - lineStart);return fm.horizontalAdvance(textBefore) + 10;
}int SelectableTextWidget::getPositionForPoint(const QPoint &point)
{QStringList lines = m_text.split('\n');int line = qBound(0, point.y() / m_lineHeight, lines.size() - 1);if (line >= lines.size()) {return m_text.length();}QString lineText = lines[line];QFontMetrics fm(font());// 查找最接近的字符位置int x = point.x() - 10;int pos = 0;int minDistance = std::numeric_limits<int>::max();for (int i = 0; i <= lineText.length(); ++i) {int textWidth = fm.horizontalAdvance(lineText.left(i));int distance = abs(textWidth - x);if (distance < minDistance) {minDistance = distance;pos = i;}}return getPositionForLine(line) + pos;
}void SelectableTextWidget::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);// 绘制背景painter.fillRect(rect(), QColor(240, 240, 240));// 绘制文本painter.setPen(Qt::black);// 绘制每一行文本QStringList lines = m_text.split('\n');for (int i = 0; i < lines.size(); ++i) {int y = (i + 1) * m_lineHeight;// 如果有选中文本,绘制选中背景if (m_selectionStart != -1 && m_selectionEnd != -1) {// 计算当前行是否在选中范围内int lineStartPos = getPositionForLine(i);int lineEndPos = getPositionForLine(i) + lines[i].length();// 如果当前行在选中范围内if (lineEndPos >= m_selectionStart && lineStartPos <= m_selectionEnd) {int startX = 0;int endX = width();// 如果是第一行选中if (i == getLineForPosition(m_selectionStart)) {startX = getXForPosition(m_selectionStart);}// 如果是最后一行选中if (i == getLineForPosition(m_selectionEnd)) {endX = getXForPosition(m_selectionEnd);}// 绘制选中背景painter.fillRect(startX, y - m_lineHeight, endX - startX, m_lineHeight, QColor(180, 200, 255));}}// 绘制文本painter.drawText(10, y, lines[i]);}// 如果有选中文本,绘制选中边框if (hasFocus() && m_selectionStart != -1 && m_selectionEnd != -1) {painter.setPen(QPen(QColor(100, 150, 255), 2, Qt::DotLine));painter.drawRect(rect().adjusted(1, 1, -1, -1));} else {painter.setPen(QPen(QColor(230, 150, 100), 1, Qt::SolidLine));painter.drawRect(rect().adjusted(1, 1, -1, -1));}
}void SelectableTextWidget::mousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {m_dragging = true;m_selectionStart = getPositionForPoint(event->pos());m_selectionEnd = m_selectionStart;update();}QWidget::mousePressEvent(event);
}void SelectableTextWidget::mouseMoveEvent(QMouseEvent *event)
{if (m_dragging) {m_selectionEnd = getPositionForPoint(event->pos());update();}QWidget::mouseMoveEvent(event);
}void SelectableTextWidget::mouseReleaseEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {m_dragging = false;}QWidget::mouseReleaseEvent(event);
}void SelectableTextWidget::contextMenuEvent(QContextMenuEvent *event)
{QMenu menu(this);QAction *copyAction = menu.addAction("复制");connect(copyAction, &QAction::triggered, this, &SelectableTextWidget::copySelectedText);menu.addSeparator();QAction *selectAllAction = menu.addAction("全选");connect(selectAllAction, &QAction::triggered, this, &SelectableTextWidget::selectAllText);menu.exec(event->globalPos());
}void SelectableTextWidget::keyPressEvent(QKeyEvent *event)
{// 支持Ctrl+A全选if (event->key() == Qt::Key_A && event->modifiers() & Qt::ControlModifier) {selectAllText();event->accept();return;}// 支持Ctrl+C复制if (event->key() == Qt::Key_C && event->modifiers() & Qt::ControlModifier) {copySelectedText();event->accept();return;}QWidget::keyPressEvent(event);
}void SelectableTextWidget::focusInEvent(QFocusEvent *event)
{update();QWidget::focusInEvent(event);
}void SelectableTextWidget::focusOutEvent(QFocusEvent *event)
{update();QWidget::focusOutEvent(event);
}void SelectableTextWidget::copySelectedText()
{if (m_selectionStart != -1 && m_selectionEnd != -1) {int start = qMin(m_selectionStart, m_selectionEnd);int end = qMax(m_selectionStart, m_selectionEnd);QString selectedText = m_text.mid(start, end - start);QApplication::clipboard()->setText(selectedText);}
}void SelectableTextWidget::selectAllText()
{m_selectionStart = 0;m_selectionEnd = m_text.length();update();
}
3、dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>class Dialog : public QDialog
{Q_OBJECT
public:Dialog(QWidget *parent = nullptr);~Dialog();private:void init();
};
#endif // DIALOG_H
4、dialog.cpp
#include "dialog.h"
#include <qlabel.h>
#include <qtextedit.h>
#include <qboxlayout.h>
#include <qapplication.h>
#include <qclipboard.h>
#include "SelectableTextWidget.h"Dialog::Dialog(QWidget *parent): QDialog(parent)
{init();
}Dialog::~Dialog() {}void Dialog::init()
{setWindowFlags(Qt::Dialog |Qt::WindowMinMaxButtonsHint |Qt::WindowCloseButtonHint);setWindowTitle("QWidget 文本选择与复制示例");setMinimumSize(500, 400);QVBoxLayout *layout = new QVBoxLayout(this);QLabel *titleLabel = new QLabel("自定义QWidget文本选择示例");titleLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // 设置通过鼠标选中文本内容titleLabel->setAlignment(Qt::AlignCenter);QFont titleFont = titleLabel->font();titleFont.setPointSize(16);titleFont.setBold(true);titleLabel->setFont(titleFont);QLabel *instructionLabel = new QLabel("使用说明:\n""1. 在下方自定义Widget中拖动鼠标选择文本\n""2. 右键点击显示上下文菜单\n""3. 使用Ctrl+C或右键菜单复制选中的文本\n""4. 使用Ctrl+A全选文本");instructionLabel->setWordWrap(true);SelectableTextWidget *selectableWidget = new SelectableTextWidget;QTextEdit *outputEdit = new QTextEdit;outputEdit->setPlaceholderText("复制的内容将显示在这里...");outputEdit->setMaximumHeight(100);// 监听剪贴板变化QObject::connect(QApplication::clipboard(), &QClipboard::dataChanged, this, [outputEdit]() {outputEdit->setPlainText(QApplication::clipboard()->text());});layout->addWidget(titleLabel);layout->addWidget(instructionLabel);layout->addWidget(selectableWidget, 1);layout->addWidget(new QLabel("剪贴板内容:"));layout->addWidget(outputEdit);
}
5、main.cpp
#include <QApplication>
#include "dialog.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);Dialog dlg;dlg.show();return app.exec();
}
三、运行结果
可以看出,QWidget中的文本可以选取和拷贝到剪切板。上面的代码只是基础功能,可以进行优化,如调整多行文本之间的段落间距、字体的样式和颜色等,有兴趣的小伙伴可以试试。