1、菜单栏
添加菜单:
QMainWindow中的菜单栏是一个QMenuBar对象,可以通过设计模式下来给菜单栏添加菜单,菜单项也可以设置加速键,通过在给菜单文本添加(&加速键),而且子菜单上如果设置的加速键其实就快捷键,不用再使用alt键,直接输入对应键即为点击对应的菜单项。如下所示的文件菜单项的加速键为alt + f,菜单项新建的快捷键为n。
QMainWindow设计模式下还有一个动作编辑器和信号槽编辑器,如下所示,动作编辑器里每一行对应一个菜单项,双击动作编辑器里一行来设置对应菜单项的显示文本、对象名称、提示文本、图标、快捷键等。信号槽编辑器里可以添加菜单项的一些信号槽方法,比如菜单项点击信号triggered,也可以在动作编辑器里右键-转到槽来自动生成菜单项的信号槽方法。
添加资源文件:
上面在动作编辑器里设置菜单的图标是在当前项目的资源文件里寻找图标文件,添加图片资源文件的方法:右键项目添加QT资源文件,名称可以设置为myResource,路径选择当前项目所在,点击确定后会在选择的路径下生成qrc文件,将图片文件放到qrc文件所在目录或子目录下,在Qt Creator中打开qrc文档(右击qrc文件选择open in editor),点击添加按钮选择添加前缀,编辑前缀名如“/myImage”,再点击添加按钮选择添加文件,选择要添加的图片文件,最后ctrl+s保存即可。在项目中使用图片资源的时候使用路径":/myImage/imageFolder/imageName.png"即可。
使用代码来添加菜单:
在菜单的动作编辑器里有个Checkable选项,这是用来设置菜单项点击后显示一个对勾标志的,在代码中我们可以将菜单项添加到一个QActionGroup动作组中,使只有一个菜单项点击后显示对勾:
也可以向菜单栏添加一个其它类型的部件,通过继承QWidgetAction,然后重写createWidget方法,在其中返回要添加的部件,如下代码实现了一个包含一个标签和一个行编辑器的菜单项:
QWidget* MyAction::createWidget(QWidget* parent) { if(parent->inherits("QMenu")) //父部件不是菜单直接返回 return nullptr; QSplitter* splitter = new QSplitter(parent); QLabel* label = new QLabel; label->setText(QString::fromUtf8(u8"输入文本")); splitter->addWidget(label); QLineEdit* lineEidt = new QLineEdit; splitter->addWidget(lineEidt); return splitter; }
QObject::inherits(const char* className)方法用来测试当前对象是否是className类型对象或其派生类对象。
2、工具栏
QToolBar是工具栏类,在前面代码里可以看到往工具栏上添加项也是使用addAction()方法添加一个QAction。如果想要往工具栏上添加Button等类型的部件的话可以使用addWidget()方法。通过工具栏的属性可以对其进行设置:toolButtonStyle设置图标和文本显示(默认只显示图标)、movable设置状态栏是否可以移动、allowedArea设置允许停靠的位置、floatable设置是否可以悬浮。
3、状态栏
QStatusBar提供了一个水平条,用来显示状态信息,QMainWindow下默认有一个状态栏。状态信息分为3类,临时信息(超过一段时间后消失,出现在状态栏左边)、正常信息(如当前行号、页数,在状态栏左边显示,可能会被临时信息掩盖)、永久信息(如版本信息,在状态栏右边显示),调用showMessage方法来显示临时信息,一般使用addWidget方法添加一个QLabel到状态栏上显示正常信息,使用addPermanentWidget方法来添加一个QLabel来显示永久信息。
QMainWindow状态栏的最右端有一个QSizeGrip部件,它用来调整窗口的大小,可以使用状态栏的setSizeGripEnabled()方法来禁用它。
4、富文本编辑器
QTextEdit是一个富文本编辑器,QTextBrowser是其只读版本,QPlaintTextEdit是普通的纯文本编辑器。如下是对QTextEdit进行的操作和效果:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->textEdit->setText("test"); //设置文本后光标默认没有变化还是在首位 QTextCursor cursor = ui->textEdit->textCursor(); //获取光标 cursor.movePosition(QTextCursor::End); //移动光标到末尾 ui->textEdit->setTextCursor(cursor); //应用到当前编辑器 }
上面代码在使用setText设置编辑器文本后光标还是在首位,也可以直接使用光标的insertText()方法来插入文本,这样光标会直接改变:
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); QTextCursor cursr = ui->textEdit->textCursor(); cursr.insertText("test"); }
Qt对富文本的处理分为编辑操作和只读操作,编辑操作使用基于光标(QTextCursor)的一些接口函数,文档(QTextDocument)的读取使用只读的分层次接口函数。富文本文档中包含框架(QTextFrame)、文本块(QTextBlock)、表格(QTextTable)、列表(QTextList)这几种元素,每种元素的格式使用对应的format类来表示,如框架格式QTextFrameFormat、文本块格式QTextBlockFormat,一个空文档包含了一个根框架,这个根框架又包含了一个空的文本块,在根框架中还可以再添加文本块、子框架等。QTextEdit默认包含了一个QTextCursor光标对象和一个QTextDocument文档对象。如下图所示:
如下是对富文本编辑器重新设置了框架的格式,运行程序可以发现只能在红色边框的中进行输入:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QTextDocument* document = ui->textEdit->document(); //获取富文本编辑器的文档对象 QTextFrame* rootFrame = document->rootFrame(); //获取文档的根框架 QTextFrameFormat format; //创建框架格式 format.setBorderBrush(Qt::red); //边界颜色 format.setBorder(4); //边界宽度 //format.setWidth(100); //高度 rootFrame->setFrameFormat(format); //设置根框架格式 }
下面是在上面代码的基础上,使用光标对象,在根框架中再添加了一个子框架:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QTextDocument* document = ui->textEdit->document(); //获取富文本编辑器的文档对象 QTextFrame* rootFrame = document->rootFrame(); //获取文档的根框架 QTextFrameFormat format; //创建框架格式 format.setBorderBrush(Qt::red); //边界颜色 format.setBorder(4); //边界宽度 //format.setWidth(100); //高度 rootFrame->setFrameFormat(format); //设置根框架格式 QTextFrameFormat frameFormat; frameFormat.setBackground(Qt::lightGray); //背景颜色 frameFormat.setMargin(10); //边距 frameFormat.setPadding(5); //填衬(边框和文本的间距) frameFormat.setBorder(2); frameFormat.setBorderStyle(QTextFrameFormat::BorderStyle_Dotted); //边框样式为虚线 QTextCursor cursor = ui->textEdit->textCursor(); //获取光标 cursor.insertFrame(frameFormat); //在光标处插入子框架 }
下面的函数中获取了上面程序中的富文本编辑器的文档的根框架,然后遍历了根框架中所有子框架和文本块:
void MainWindow::on_pushButton_2_clicked() { QTextDocument* document = ui->textEdit->document(); //获取富文本编辑器的文档对象 QTextFrame* frame = document->rootFrame(); //获取文档的根框架 for(QTextFrame::iterator it = frame->begin(); !it.atEnd(); ++it) //遍历根框架的文档 { QTextFrame* childFrame = it.currentFrame(); //子框架 QTextBlock childBlock = it.currentBlock(); //文本块 qDebug() << childBlock.text(); } }
上面的方法中只获得了文档根框架中的子框架和文本块,而子框架的文本块却无法遍历到,下面的方法获得了文档的所有文本块:
void MainWindow::on_pushButton_clicked() { QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); QTextDocument* document = ui->textEdit->document(); //获取富文本编辑器的文档对象 QTextBlock block = document->firstBlock(); //获取文档的第一个文本块 for(int i = 0; i < document->blockCount(); ++i) { qDebug() << tr("文本块%1: 首行行号为%2, 长度为%3, 内容为").arg(i).arg(block.firstLineNumber()).arg(block.length()) << block.text(); block = block.next(); //获取下一个文本块 } }
文本块QTextBlock可以看做一个段落,但不能使用回车换行,因为一个回车换行就表示创建一个新的文本块。QTextBlock提供了只读接口,文本块的格式由QTextBlockFormat类来处理,包括对齐方式、文本块四周的边白、缩进等,文本内容的格式由QTextCharFormat类来设置,包括字体大小、加粗、下划线等。
下面的方法是编辑文本块及其内容的格式,比如水平居中显示、字体、字体颜色、下划线等:
void MainWindow::on_pushButton_clicked() { QTextCursor cursor = ui->textEdit->textCursor(); QTextBlockFormat blockFormat; //文本块格式 blockFormat.setAlignment(Qt::AlignCenter); //水平居中 cursor.insertBlock(blockFormat); //使用文本块格式 QTextCharFormat charFormat; //字符格式 charFormat.setBackground(Qt::lightGray); //字体背景色 charFormat.setForeground(Qt::blue); //字体颜色 charFormat.setFont(QFont(QString::fromUtf8(u8"宋体"), 12, QFont::Bold, true)); //宋体、12号、加粗、倾斜 charFormat.setFontUnderline(true); //下划线 cursor.setCharFormat(charFormat); //使用字符格式 cursor.insertText(QString::fromUtf8(u8"测试文本")); }
插入表格QTextTable:
void MainWindow::on_pushButton_clicked() { QTextCursor cursor = ui->textEdit->textCursor(); QTextTableFormat format; //表格格式 format.setCellSpacing(2); //表格外白边 format.setCellPadding(10); //表格内白边 cursor.insertTable(2, 2, format); //插入两行两列的表格 }
表格QTextTable、列表QTextList也支持使用QTextFrame::iterator来遍历它们。表格的cellAt()方法可以获得指定的单元格(单元格对应的类是QTextTableCell,其对应格式为QTextTableCellFormat),其它的还有插入列、插入行、合并单元格、拆分单元格等方法。列表提供了获取列表项个数count()、item()获得指定项的文本块等方法。
插入列表QTextList:
void MainWindow::on_pushButton_2_clicked() { QTextListFormat format; //列表格式 format.setStyle(QTextListFormat::ListDecimal); //使用数字编号 ui->textEdit->textCursor().insertList(format); //插入列表 }
插入图片:
void MainWindow::on_pushButton_3_clicked() { QTextImageFormat format; //图片格式 format.setName("logo.png"); //指定图片 format.setHeight(20.0); //设置图片高度 ui->textEdit->textCursor().insertImage(format); //插入图片 }
QTextEdit的find()方法可以进行文本的查找,它返回其所在的行和列,更多的查找功能可以使用QTextDocument类find()方法。
QTextEdit配合QSyntaxHighlighter可以实现语法高亮,通过重写QSyntaxHighlighter的highlightBlock方法,再将QTextDocument类对象作为QSyntaxHighlighter对象的父部件即可。如下是对"char"单词进行高亮显示的方法:
void MySyntaxHighlighter::highlightBlock(const QString& text) { QTextCharFormat format; //高亮显示文本格式 format.setFontWeight(QFont::Bold); //字体加粗 format.setForeground(Qt::green); //绿色字体 QString pattern = "\bchar\b"; //匹配"char"单词 QRegExp expression(pattern); //创建正则表达式 int idx = text.indexOf(expression); //开始匹配字符串 while(idx >= 0) //匹配成功 { int len = expression.matchedLength(); //要匹配字符串的长度 setFormat(idx, len, format); //对要匹配的字符串设置高亮显示格式 idx = text.indexOf(expression, idx + len); //继续匹配 } }
可以在QLabel或者QTextEdit添加文本时使用HTML标签或CSS属性,具体可在帮助中参考Supported HTML Subset关键字。QCompleter类用来实现编辑自动补全,可以参考Qt的示例程序Custom Completer。关于富文本编辑器其它用法可以参考Rich Text分类下的几个程序。
5、文件拖放
Qt提供了文件拖放机制,可以在帮助中查看Drag and Drop关键字来了解。当文件拖动时拖动数据会被存储为MIME类型,在Qt中使用QMimeData类来表示MIME类型数据,QMimeData中提供了几个函数来处理常见的MIME数据,如下所示:
下面是实现拖动一个txt文件到QMainWindow上的QTextEdit中,然后在编辑器中显示文件内容的示例:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setAcceptDrops(true); //接收拖动放下事件 } //拖动进入事件 void MainWindow::dragEnterEvent(QDragEnterEvent *event) { if(event->mimeData()->hasUrls()) //是否包含URL event->acceptProposedAction(); //接收动作 else event->ignore(); //忽略事件 } //放下事件 void MainWindow::dropEvent(QDropEvent *event) { const QMimeData* mimeData = event->mimeData(); //获取MIME数据 if(mimeData->hasUrls()) //如果包含URL { QList<QUrl> urlList = mimeData->urls(); //获取URL列表 QString fileName = urlList.at(0).toLocalFile(); //获取拖入的第一个文件的本地路径 if(!fileName.isEmpty()) { QFile file(fileName); if(!file.open(QIODevice::ReadOnly)) return; QTextStream in(&file); ui->textEdit->setText(in.readAll()); //将文件内容读入编辑器 } } }
当调用close()方法关闭窗口实际上会向widget发送一个关闭事件QCloseEvent,默认窗口将会隐藏而不是被释放,除非对窗口设置了Qt::WA_DeleteOnClose属性。
QDrag可以用来完成数据的转移,比如下面的程序就是实现拖动QMainWindow上的一个图片Label后移动该图片Label的效果:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setAcceptDrops(true); //设置窗口可以接收拖入 //添加图片显示部件 QLabel* label = new QLabel(this); QPixmap pix("logo.png"); label->setPixmap(pix); label->resize(pix.size()); label->move(100, 100); label->setAttribute(Qt::WA_DeleteOnClose); //当窗口关闭时销毁图片 } void MainWindow::mousePressEvent(QMouseEvent* event) //鼠标按下事件 { //获取图片 QWidget* widget = childAt(event->pos()); //获得鼠标所在的部件 QLabel* child = static_cast<QLabel*>(widget); //将鼠标所在的部件强转为QLabel if(!child->inherits("QLabel")) //如果部件是否是QLabel类型 return; QPixmap pixmap = *child->pixmap(); //获取Label中的图片 //自定义MIME类型,将MIME类型数据放入到QMimeData中 QByteArray itemData; //创建字节数组 QDataStream dataStream(&itemData, QIODevice::WriteOnly); //创建字节数组的数据流 dataStream << pixmap << QPoint(event->pos() - child->pos()); //将图片、位置信息保存到字节数组中 QMimeData* mimeData = new QMimeData; //创建QMimeData来存放要移动的数据 mimeData->setData("myImage/png", itemData); //将字节数组放入到QMimeData中 //将QMimeData数据放入到QDrag中 QDrag* drag = new QDrag(this); //创建QDrag,它用来移动数据 drag->setMimeData(mimeData); //设置MIME数据 drag->setPixmap(pixmap); //设置在拖动过程中显示图片 drag->setHotSpot(event->pos() - child->pos()); //拖动时鼠标指针的位置不变 //拖动时给原图片添加阴影 QPixmap tempPixmap = pixmap; QPainter painter; //创建QPainter来绘制图片 painter.begin(&tempPixmap); painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127)); //添加一层透明的淡黑色 painter.end(); child->setPixmap(tempPixmap); //使用QDrag的exec()方法来执行拖放操作,exec()可以设定支持的放下动作和默认的放下动作,该方法不会影响主事件循环造成阻塞 if(drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction) //设置拖放可以是移动或复制操作,默认是复制操作 { //移动操作 child->close(); } else { //复制操作 child->show(); //拖放完成后显示label child->setPixmap(pixmap); //不再显示阴影效果 } } void MainWindow::dragEnterEvent(QDragEnterEvent *event) //拖动进入事件 { if(event->mimeData()->hasFormat("myImage/png")) //如果有我们定义的MIME类型数据,则执行移动操作 { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->ignore(); } } void MainWindow::dragMoveEvent(QDragMoveEvent *event) //拖动事件 { if(event->mimeData()->hasFormat("myImage/png")) //如果有我们定义的MIME类型数据,则执行移动操作 { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->ignore(); } } void MainWindow::dropEvent(QDropEvent *event) //放下事件 { if(event->mimeData()->hasFormat("myImage/png")) { QByteArray itemData = event->mimeData()->data("myImage/png"); //获得拖放数据到字节数组 //将字节数组中数据读入QPixmap和QPoint对象 QDataStream dataStream(&itemData, QIODevice::ReadOnly); QPixmap pixmap; QPoint offset; dataStream >> pixmap >> offset; //新建Label,添加图片后显示 QLabel* newLabel = new QLabel(this); newLabel->setPixmap(pixmap); newLabel->resize(pixmap.size()); newLabel->move(event->pos() - offset); newLabel->show(); newLabel->setAttribute(Qt::WA_DeleteOnClose); event->setDropAction(Qt::MoveAction); event->accept(); } else { event->ignore(); } }
如果想要实现拖动复制图片Label效果的话,将dragEnterEvent、dragMoveEvent、dropEvent方法中event->setDropAction()参数设置为Qt::CopyAction,或者将setDropActio()和accept()替换成event->acceptProposedAction(),因为上面QDrag的exec方法设置了拖放默认动作是复制。
6、QPrinter
下面代码通过QPrinter打印器对象实现了保存文本编辑器textEdit上内容到pdf文件的功能:
void MainWindow::on_pushButton_clicked() { //显示保存文件对话框并获得用户的选择 QString fileName = QFileDialog::getSaveFileName(this, QString::fromUtf8(u8"保存文件对话框"), QString(), "*.pdf"); if(fileName.isEmpty()) return; if(QFileInfo(fileName).suffix().isEmpty()) fileName.append(".pdf"); //将textEdit中内容保存为pdf文件 QPrinter printer; printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(fileName); ui->textEdit->print(&printer); }