和上次一样,新建一个Qt Widget Application工程。不同的是,这次我们不使用ui文件,而是直接用c++代码来控制界面。
在main.cpp中,已经默认写好了MainWindow的创建。QMainWindow是包含菜单栏、工具栏和状态栏的。但是现在运行一下,什么也没有。
创建菜单
我们需要自己创建菜单,也就是MenuBar
在MainWindow的构造函数里添加三句。高亮的部分是新添的代码。
1 2 3 4 5 6 7 8 9 |
#include <QMenu> #include <QMenuBar> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenuBar *menubar = menuBar(); QMenu *file = menubar->addMenu("&File"); QMenu *help = menubar->addMenu("&Help"); } |
menuBar()是MainWindow的成员函数,它返回一个MenuBar。我们调用一次menuBar就会创建一个菜单栏。
使用MenuBar的addMenu函数,可以返回一个菜单。addMenu的参数就是菜单的名字,其中&表示快捷键,windows中按下Alt+F就相当于点击了这个菜单。
使用Qt的类要包含相应的头文件,Qt的类名和头文件名字是相同的,所以我们include了用到的QMenu和QMenuBar
运行一下
可以看到菜单栏出现了,有File和Help两个项目。现在点击一下,是没有东西出现的。
要添加菜单的内容,就要使用QAction对象了。QAction是一个动作,它不但可以添加到菜单,还可以添加到工具栏。
我们继续在MainWindow的构造函数里添加QAction
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <QMenu> #include <QMenuBar> #include <QAction> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenuBar *menubar = menuBar(); QMenu *file = menubar->addMenu("&File"); QMenu *help = menubar->addMenu("&Help"); QAction *openAction = new QAction("&Open", this); file->addAction(openAction); QAction *saveAction = new QAction("&Save", this); file->addAction(saveAction); } |
大多数类的构造函数都接受一个parent参数,表示它的父控件。如果为0表示没有父控件,是一个顶级的控件。
QAction的构造函数有几种形式的重载,把光标放到QAction按下F1键可以查看帮助文档
可以看到有三种:
1 2 3 |
QAction(QObject * parent) QAction(const QString & text, QObject * parent) QAction(const QIcon & icon, const QString & text, QObject * parent) |
第一个仅仅需要父对象指针,第二个是动作的名称和父对象,第三个是图标、名称和父对象。
现在运行一下
可以看到动作已经自动添加到菜单了。
创建工具栏
创建工具栏和菜单栏类似,不一样的是添加动作的时候直接添加到工具栏QToolBar。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <QMenu> #include <QMenuBar> #include <QAction> #include <QtoolBar> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenuBar *menubar = menuBar(); QMenu *file = menubar->addMenu("&File"); QMenu *help = menubar->addMenu("&Help"); QAction *openAction = new QAction("&Open", this); file->addAction(openAction); QAction *saveAction = new QAction("&Save", this); file->addAction(saveAction); QToolBar *toolBar = addToolBar("Toolbar"); toolBar->addAction(openAction); toolBar->addAction(saveAction); } |
因为本程序只有一个工具栏,取名为Toolbar ,如果添加多个工具栏,也许需要命名更具体一些。
和QMenu类似,使用成员函数addAction就可以添加动作。可以发现这里QAction在菜单栏和工具栏都添加了。这也是这种机制的好处,只需要关心动作,而菜单和工具栏的显示由Qt负责。
运行一下可以看到工具栏,还可以自由拖动
现在工具栏显示的是文字,一点也不像普通的工具栏。我们尝试给它添加图标。其实添加图标还是在QAction里添加的
给QAction添加图标
图标的路径可以是绝对路径或相对路径,还可以把图片添加到程序中保存。下面演示把图片加到资源文件中。
首先我们找两个图标文件,命名为file_open.png和file_save.png
然后在Qt中右键点击项目名字,新建文件
选择Qt类型下面的Qt Resource File
我把它命名为res
在res.qrc上右键选择Open in Editor
然后可以添加前缀和文件了。我们添加前缀/Action并添加两个文件。
然后我们修改一下QAction的代码,使用第三种构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <QMenu> #include <QMenuBar> #include <QAction> #include <QtoolBar> #include <QIcon> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenuBar *menubar = menuBar(); QMenu *file = menubar->addMenu("&File"); QMenu *help = menubar->addMenu("&Help"); QAction *openAction = new QAction(QIcon(":/Action/file_open.png"), "&Open", this); file->addAction(openAction); QAction *saveAction = new QAction(QIcon(":/Action/file_save.png"), "&Save", this); file->addAction(saveAction); QToolBar *toolBar = addToolBar("Toolbar"); toolBar->addAction(openAction); toolBar->addAction(saveAction); } |
在QIcon的构造函数中,字符串英文冒号开头表示从资源文件中找,不带冒号可以是绝对路径或相对路径。
运行一下就可以看到图标了
添加编辑区域
编辑区域我们直接使用Qt中的QTextEdit。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <QMenu> #include <QMenuBar> #include <QAction> #include <QtoolBar> #include <QIcon> #include <QTextEdit> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenuBar *menubar = menuBar(); QMenu *file = menubar->addMenu("&File"); QMenu *help = menubar->addMenu("&Help"); QAction *openAction = new QAction(QIcon(":/Action/file_open.png"), "&Open", this); file->addAction(openAction); QAction *saveAction = new QAction(QIcon(":/Action/file_save.png"), "&Save", this); file->addAction(saveAction); QToolBar *toolBar = addToolBar("Toolbar"); toolBar->addAction(openAction); toolBar->addAction(saveAction); QTextEdit *textedit = new QTextEdit(this); setCentralWidget(textedit); } |
使用MainWindow的setCentralWidget设置窗口的主控件就是QTextEdit,这是合理的。运行一下,有记事本的样子了
QTextEdit本身支持一些鼠标右键的文本框通用行为
open 和save
现在点击open和save按钮是没有反应的,我们需要添加自己的函数来进行相应的动作。
修改mainwindow.h文件,添加两个成员函数
1 2 3 4 5 6 7 8 9 10 11 |
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private: void openfile(); void savefile(); }; |
openfile和savefile的时候,都需要把文件内容显示到QTextEdit或者从QTextEdit读内容。
在构造函数中,我们所有的指针变量都是构造函数的局部变量,这样构造完之后就找不到这些指针了。所以我们把构造函数的
1 |
QTextEdit *textedit |
变成mainwindow的成员变量。
mainwindow.h
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class QTextEdit; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private: void openfile(); void savefile(); QTextEdit *textedit; }; |
这样mainwindow.cpp里的构造函数里就不用定义QTextEdit *textedit了,直接用textedit就可以啦。
然后我们在mainwindow.cpp里写openfile和savefile的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QMenuBar *menubar = menuBar(); QMenu *file = menubar->addMenu("&File"); QMenu *help = menubar->addMenu("&Help"); QAction *openAction = new QAction(QIcon(":/Action/file_open.png"), "&Open", this); file->addAction(openAction); QAction *saveAction = new QAction(QIcon(":/Action/file_save.png"), "&Save", this); file->addAction(saveAction); QToolBar *toolBar = addToolBar("Toolbar"); toolBar->addAction(openAction); toolBar->addAction(saveAction); textedit = new QTextEdit(this); setCentralWidget(textedit); connect(openAction, QAction::triggered, this, openfile); connect(saveAction, QAction::triggered, this, savefile); } void MainWindow::openfile() { } void MainWindow::savefile() { } |
注意 我们使用connect来连接信号和槽。connect有多种参数重载的形式,我们用的是其中一种
1 2 3 4 5 6 7 8 |
QMetaObject::Connection QObject::connect ( const QObject * sender, PointerToMemberFunction signal, const QObject * receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection ) |
第一个参数是发送信号的控件,第二个参数是成员函数。
第三个参数是响应信号的控件,第四个参数是响应控件的槽函数。
也就是说 当sender发出signal信号时,自动调用receiver的method函数
我们添加的这个,当动作触发的时候,调用响应的成员函数。
1 2 |
connect(openAction, QAction::triggered, this, openfile); connect(saveAction, QAction::triggered, this, savefile); |
上面是Qt5的语法,在Qt4中不支持。Qt4和5同时支持的是这种connect
1 2 3 4 5 6 7 8 |
QMetaObject::Connection QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection ) |
用字符串的方式表示函数,怎么把函数变成对应的字符串呢,这就要使用SIGNAL和SLOT宏了。
使用这个需要在类的成员函数的地方声明哪个是slots(槽函数)
修改mainwindow.h
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void openfile(); void savefile(); private: QTextEdit *textedit; }; |
在槽函数前面加上slots修饰。
然后上面两个connect也可以这样写
1 2 |
connect(openAction, SIGNAL(triggered()), this, SLOT(openfile())); connect(saveAction, SIGNAL(triggered()), this, SLOT(savefile())); |
SIGNAL和SLOT有个好处,它包含了参数的信息,可以区分不同的重载函数。SIGNAL()括号中是函数原型,只不过不写参数名。例如SIGNAL(func(int, bool,))
打开文件
打开文件,我们需要QString类,这是Qt的字符串类,我们用来存储文件的路径。使用QFileDialog文件对话框来选择文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <QFileDialog> void MainWindow::openfile() { QString path = QFileDialog::getOpenFileName( this, //父窗口 tr("Open File"), //对话框名字 ".", //默认目录 . 表示当前目录 tr("Text Files(*.txt);;ini File(*.ini)"), //过滤器 只打开txt类型 或 ini 使用;;分隔 ); if (!path.isEmpty()) { } } |
使用QFileDialog的getOpenFileName函数可以打开一个文件对话框,这个函数有六个参数,除了我们用到的4个还有两个:默认过滤器和选项。在上面按F1可以查看函数原型。
现在,我们运行程序,点击openfile ,就会打开一个文件对话框。
选择一个txt文件后,路径就会保存到path中,path不为空的时候,使用QFile打开文件。
为了突出重点,这里文件错误直接返回,没有给出提示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <QFileDialog> #include <QFile> void MainWindow::openfile() { QString path = QFileDialog::getOpenFileName( this, //父窗口 tr("Open File"), //对话框名字 ".", //默认目录 tr("Text Files(*.txt);;ini File(*.ini)"), //过滤器 只打开txt类型 或 ini 使用;;分隔 ); if (!path.isEmpty()) { QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; //打开失败 file.close(); } } |
然后使用QTextStream读取文件流。使用readAll一次性读取,实际应用时只可以用于小文件,若文件过大,应采用其他方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <QFileDialog> #include <QFile> #include <QTextStream> void MainWindow::openfile() { QString path = QFileDialog::getOpenFileName( this, //父窗口 tr("Open File"), //对话框名字 ".", //默认目录 tr("Text Files(*.txt);;ini File(*.ini)") //过滤器 只打开txt类型 或 ini 使用;;分隔 ); if (!path.isEmpty()) { QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; //打开失败 QTextStream in(&file); textedit->setText(in.readAll()); file.close(); } } |
现在运行就可以打开文件了
保存文件
savefile函数和openfile是类似的,使用的是getSaveFileName,打开方式为Write。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void MainWindow::savefile() { QString path = QFileDialog::getSaveFileName( this, //父窗口 tr("Open File"), //对话框名字 ".", //默认目录 tr("Text Files(*.txt);;ini File(*.ini)") //过滤器 只打开txt类型 或 ini 使用;;分隔 ); if (!path.isEmpty()) { QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return; //打开失败 QTextStream out(&file); out << textedit->toPlainText(); file.close(); } } |
到现在,一个可以打开和保存的简陋的记事本那就完成了。