用户界面应用程序:
子类化QMainWindow:Spreadsheet例子
首先将MainWindow类定义为QMainWindow的子类。其包含自己的信号和槽,所以包含了Q_OBJECT宏
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class QAction; class QLabel; class FindDialog; class Spreadsheet; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); protected: void closeEvent(QCloseEvent *event);
closeEvent()函数是QWidget类中的一个虚函数,当用户关闭的时候,自动调用。
void MainWindow::closeEvent(QCloseEvent *event) { if (okToContinue()) { writeSettings(); event->accept(); } else { event->ignore(); } }
private slots: void newFile(); void open(); bool save(); bool saveAs(); void find(); void goToCell(); void sort(); void about(); void openRecentFile(); void updateStatusBar(); void spreadsheetModified(); private: void createActions(); void createMenus(); void createContextMenu(); void createToolBars(); void createStatusBar(); void readSettings(); void writeSettings(); bool okToContinue(); bool loadFile(const QString &fileName); bool saveFile(const QString &fileName); void setCurrentFile(const QString &fileName); void updateRecentFileActions(); QString strippedName(const QString &fullFileName);
接下来看看MainWidow的构造函数
首先创建一个Spreadsheet的窗口部件并且把它设置为这个主窗口的中央窗口部件,占据主窗口的中央区域。Spreadsheet是QTableWidget的子类。
MainWindow::MainWindow() { spreadsheet = new Spreadsheet; setCentralWidget(spreadsheet); createActions(); createMenus(); createContextMenu(); createToolBars(); createStatusBar(); readSettings(); findDialog = 0; setWindowIcon(QIcon(":/images/icon.png")); setCurrentFile(""); }
creatActions()等创建窗口的其余部分,readSetting()则是读取这个应用程序存储的一些设置。
Qt用过动作的概念简化有关菜单和工具栏的编程,在Qt中,创建菜单和工具栏包括以下步骤
1.创建并且设置动作
2.创建菜单并且把动作添加到菜单上
3.创建工具栏并且把动作添加到工具栏上
在此应用程序中,动作实在createActions()函数中创建的
void MainWindow::createActions() { newAction = new QAction(tr("&New"), this); newAction->setIcon(QIcon(":/images/new.png")); newAction->setShortcut(QKeySequence::New); newAction->setStatusTip(tr("Create a new spreadsheet file")); connect(newAction, SIGNAL(triggered()), this, SLOT(newFile())); openAction = new QAction(tr("&Open..."), this); openAction->setIcon(QIcon(":/images/open.png")); openAction->setShortcut(QKeySequence::Open); openAction->setStatusTip(tr("Open an existing spreadsheet file")); connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
动作New有一个加速键,一个主窗口,一个图标,一个快捷键以及一个状态提示。
而动作的triggered()信号连接到主窗口的私有槽,确保按下的时候可以调用。
在Qt中,菜单都是QMenu的实例。addMenu()函数可以用给定的文本创建一个QMenu窗口部件,并且会把它添加到菜单栏中。
void MainWindow::createMenus() { fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(newAction); fileMenu->addAction(openAction); fileMenu->addAction(saveAction); fileMenu->addAction(saveAsAction); separatorAction = fileMenu->addSeparator(); for (int i = 0; i < MaxRecentFiles; ++i) fileMenu->addAction(recentFileActions[i]); fileMenu->addSeparator(); fileMenu->addAction(exitAction);
menuBar()函数返回一个指向QMenuBar的指针,菜单栏会在第一次调用menuBar()函数的时候就创建出来。接下来把几个项加入菜单。
separatorAction = fileMenu->addSeparator();
表示插入一个间隔器,可以把上面关系紧密的项放一起。
使用for循环添加一些最初隐藏起来的动作。
如果在一个菜单下的项目中还有小的项目,则一样用addMenu()添加
editMenu = menuBar()->addMenu(tr("&Edit")); editMenu->addAction(cutAction); editMenu->addAction(copyAction); editMenu->addAction(pasteAction); editMenu->addAction(deleteAction); selectSubMenu = editMenu->addMenu(tr("&Select")); selectSubMenu->addAction(selectRowAction); selectSubMenu->addAction(selectColumnAction); selectSubMenu->addAction(selectAllAction); editMenu->addSeparator(); editMenu->addAction(findAction); editMenu->addAction(goToCellAction);
任何Qt窗口部件都可以有一个与之相关联的QActions列表,提供上下文菜单,可以将所需要的动作添加到Spreadsheet窗口部件中,相当于鼠标右击或则是在按键上面按下一个与平台相关的案件时候,就可以激活这些上下文菜单。
void MainWindow::createContextMenu() { spreadsheet->addAction(cutAction); spreadsheet->addAction(copyAction); spreadsheet->addAction(pasteAction); spreadsheet->setContextMenuPolicy(Qt::ActionsContextMenu); }
创建工具栏与创建菜单栏的过程相似,我们下面创建的是file工具栏和edit工具栏。
接下来是调用createStatusBar()函数来设置状态栏
void MainWindow::createStatusBar() { locationLabel = new QLabel(" W999 "); locationLabel->setAlignment(Qt::AlignHCenter); locationLabel->setMinimumSize(locationLabel->sizeHint()); formulaLabel = new QLabel; formulaLabel->setIndent(3); statusBar()->addWidget(locationLabel); statusBar()->addWidget(formulaLabel, 1); connect(spreadsheet, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(updateStatusBar())); connect(spreadsheet, SIGNAL(modified()), this, SLOT(spreadsheetModified())); updateStatusBar(); }
上面包含两个部分,一个是定位指示器,一个是单元格公式指示器。绑定的两个槽函数。
下面是实现File菜单
void MainWindow::newFile() { if (okToContinue()) { spreadsheet->clear(); setCurrentFile(""); } }
单击new按钮的时候,会调用newFile槽,接着执行okToContinue()判断是否有还没有被保存的信息。
bool MainWindow::okToContinue() { if (isWindowModified()) { int r = QMessageBox::warning(this, tr("Spreadsheet"), tr("The document has been modified.\n" "Do you want to save your changes?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (r == QMessageBox::Yes) { return save(); } else if (r == QMessageBox::Cancel) { return false; } } return true; }
返回的是布尔值。首先检测windowModified属性的状态。若是true,则显示出一个消息框。QMessageBox提供的按钮会自动让其中的一个成为默认的确认按钮,一个默认的推出按钮。
warning()函数:
warning(parent,title,message,buttons);
QMessageBox还提供了information(),question()和critical()函数,他们都有各自特定的图标。
open()函数也是一样,
void MainWindow::open() { if (okToContinue()) { QString fileName = QFileDialog::getOpenFileName(this, tr("Open Spreadsheet"), ".", tr("Spreadsheet files (*.sp)")); if (!fileName.isEmpty()) loadFile(fileName); } }
首先还是要处理没有保存的变化,然后使用getOpenFileName()静态函数从用户那里获取一个新的文件名,这个函数会弹出一个对话框,让用户选择一个文件,然后返回文件名。若用户单击了取消按钮,则返回空字符串。其中第三个参数表示它应当从哪一级目录开始的,这里为当前目录。第四个参数是文件过滤器,由一个描述文本和通配符组成。
loadFile()函数用以载入文件。
bool MainWindow::loadFile(const QString &fileName) { if (!spreadsheet->readFile(fileName)) { statusBar()->showMessage(tr("Loading canceled"), 2000); return false; } setCurrentFile(fileName); statusBar()->showMessage(tr("File loaded"), 2000); return true; }
使用readFile()函数从磁盘中读取文件,若载入成功,则会调用setCurrentFiel()函数来更新窗口的标题,否则通过消息窗通知客户。显示2S的信息。
下面是所有保存操作函数
bool MainWindow::save() { if (curFile.isEmpty()) { return saveAs(); } else { return saveFile(curFile); } } bool MainWindow::saveAs() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save Spreadsheet"), ".", tr("Spreadsheet files (*.sp)")); if (fileName.isEmpty()) return false; return saveFile(fileName); }
bool MainWindow::saveFile(const QString &fileName) { if (!spreadsheet->writeFile(fileName)) { statusBar()->showMessage(tr("Saving canceled"), 2000); return false; } setCurrentFile(fileName); statusBar()->showMessage(tr("File saved"), 2000); return true; }
最后是退出。
void MainWindow::closeEvent(QCloseEvent *event) { if (okToContinue()) { writeSettings(); event->accept(); } else { event->ignore(); } }
接下来实现的是在Spreadsheet窗口和Find对话框的切换,Find对话框必须是非模态的,就是运行在应用程序中对于任何其他窗口都是独立的窗口。
void MainWindow::find() { if (!findDialog) { //若Find对话框没有存在过,创建它并且把它的信号和槽连接好。 findDialog = new FindDialog(this); connect(findDialog, SIGNAL(findNext(const QString &, Qt::CaseSensitivity)), spreadsheet, SLOT(findNext(const QString &, Qt::CaseSensitivity))); connect(findDialog, SIGNAL(findPrevious(const QString &, Qt::CaseSensitivity)), spreadsheet, SLOT(findPrevious(const QString &, Qt::CaseSensitivity))); } findDialog->show(); //让一个隐藏的窗口变为可见的,位于最上方的以及是激活的。 findDialog->raise(); //若已经可见了则调用下面函数。 findDialog->activateWindow(); }
Go to Cell 是一个模态的窗口,就是在弹出后可以阻塞应用程序的窗口,直到关闭。
第五章
创建自定义的窗口部件(通过对一个已经存在的Qt窗口部件进行子类话或者直接对QWidget进行子类化来创建)
通过对一个十六进制微调框来说明:一般QSpinBox只支持十进制的整数,但通过子类化方法,可以让它接受并且显示十六进制数。
#ifndef HEXSPINBOX_H #define HEXSPINBOX_H #include <QSpinBox> class QRegExpValidator; class HexSpinBox : public QSpinBox //继承QSpinBox { Q_OBJECT public: HexSpinBox(QWidget *parent = 0); protected: QValidator::State validate(QString &text, int &pos) const; int valueFromText(const QString &text) const; QString textFromValue(int value) const; private: QRegExpValidator *validator; }; #endif
#include <QtGui> #include "hexspinbox.h" HexSpinBox::HexSpinBox(QWidget *parent) : QSpinBox(parent) { setRange(0, 255); //设置默认的范围,0x00到0xff validator = new QRegExpValidator(QRegExp("[0-9A-Fa-f]{1,8}"), this); /*用户可以通过单击微调框的上下键头或者在它的行编辑器中输入数值来修改微调框的当前值。 在后面一种情况中,当我们要严格控制用户输入的数据是合法的十六进制数字的时候,我们使用这条语句 它可以接受1到8个字符,所有的字符都必须是0到9,a到f,以及A到F。 */ } QValidator::State HexSpinBox::validate(QString &text, int &pos) const//检测用户输入文本的合法性,返回三种结果,有效,部分有效无效,无效。 { return validator->validate(text, pos);//直接用validate返回结果就好。有自适应能力 } int HexSpinBox::valueFromText(const QString &text) const//执行从字符串到整数值的逆向转换。 { bool ok; return text.toInt(&ok, 16);//将当前文本转换成整数值,若字符串不是有效的十六进制数,将OK设为false,并且返回0,在这里,因为输入的一定是十六进制数。 } QString HexSpinBox::textFromValue(int value) const //将一个整数值转换成一个字符串 { return QString::number(value, 16).toUpper();//用QString::number将该值转换成小写格式的字符串,再调用toUpper()转换成大写格式。 }
自定义其他Qt的窗口部件也是遵循相同的模式,选择合适的Qt窗口部件,对它进行子类化,并重新实现一些虚函数来改变它的行为。
而如果我们只是想对一个已经存在的窗口部件的外观进行自定义设置,那么只要对其中应用一个样式表或者重新实现一种自定义风格即可,而不必对其进行子类化。
程序启动画面
使用的是QSplashScreen类,通常程序启动画面的代码会放在main()函数中,位于exec()调用之前。
例如
QSplashScreen *splash = new QSplashScreen;
splash->setPixmap(QPixmap("......"));
splash->show();
......
参见C++ GUI QT4
事件处理:
1.键盘事件:
通过重新实现啊keyPressEvent()和keyReleaseEvent()就可以处理键盘事件了,通常只需要实现,keyPressEvent()就可以了,通常释放重要性的键值是Ctrl等,而这些键的状态可以用QKeyEvent::modifiers()检测出来。
void Plotter::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Plus: zoomIn(); break; case Qt::Key_Minus: zoomOut(); break; case Qt::Key_Left: zoomStack[curZoom].scroll(-1, 0); refreshPixmap(); break; case Qt::Key_Right: zoomStack[curZoom].scroll(+1, 0); refreshPixmap(); break; case Qt::Key_Down: zoomStack[curZoom].scroll(0, -1); refreshPixmap(); break; case Qt::Key_Up: zoomStack[curZoom].scroll(0, +1); refreshPixmap(); break; default: QWidget::keyPressEvent(event); } }
Tab键和BackTab(Shift+Tab)键是两种特殊的情况,在窗口部件调用keyPressEvent()之前,QWidget::event()会先处理它们,但在有些窗口部件中,我们更愿意让Tab键起到缩进文本的作用,所以重新实现实现后的event()应该如下所示
bool CodeEditor::event(QEvent *event)
{
if(event->type()==QEvent::KeyPress)
{
QKeyEvent *keyEvent=static_cast<QKeyEvent*>(event);//如果该事件是按键事件,就把QEvent对象强制转换成QKeyevent并检查按下的是哪个键。
if(keyEvent->key()==Qt::Key_Tab)//如果按下的是Tab键,做一些处理并返回true,告诉Qt处理完了。
{
insertAtCurrentPosition('/t');
return true;
}
return QWidget::event(event);//如果不是,将这个事件传递给父窗口部件来处理。
}
}
2.定时器事件:允许应用程序在一定的时间间隔执行事件处理,只是用来实现光标的闪烁以及其他动画的播放,或者只是简单的刷新。
ticket.cpp
#include <QtGui> #include "ticker.h" Ticker::Ticker(QWidget *parent) : QWidget(parent) { offset = 0; //用来绘制文本的X坐标值就取自于这个offset myTimerId = 0; //定时器的ID通常非零,用表示定时器还没有启动。 } void Ticker::setText(const QString &newText) //用来设置要显示的文本 { myText = newText; update(); //强制执行一个重绘操作。 updateGeometry();//通知对Ticker窗口部件负责的任意布局管理器,该窗口部件大小发生改变 } QSize Ticker::sizeHint() const //返回文本所需要的空间大小,并以此作为窗口部件的理想尺寸 { return fontMetrics().size(0, text());//fontmetrics()返回一个QFontMetrics对象,查询并获得与这个窗口部件字体相关的信息。 } void Ticker::paintEvent(QPaintEvent * /* event */) { QPainter painter(this); int textWidth = fontMetrics().width(text());//确定在文本在水平方向上所需要的空间 if (textWidth < 1) return; int x = -offset; while (x < width()) { //多次绘制文本,直到能填充窗口部件的宽度为止 painter.drawText(x, 0, textWidth, height(), Qt::AlignLeft | Qt::AlignVCenter, text()); x += textWidth; } } void Ticker::showEvent(QShowEvent * /* event */) //启动定时器 { myTimerId = startTimer(30);//startTimer()函数返回一个数字,以后能用该数字识别该定时器。 } //大约每30毫秒Qt会产生一个定时器时间,时间精度取决于操作系统 void Ticker::timerEvent(QTimerEvent *event) //系统每隔一段时间,都会调用timerEvent函数 { if (event->timerId() == myTimerId) { ++offset; if (offset >= fontMetrics().width(text()))//滚动完一圈 offset = 0; scroll(-1, 0);//把窗口部件的内容像左移动一个像素 } else { QWidget::timerEvent(event); } } void Ticker::hideEvent(QHideEvent * /* event */) { killTimer(myTimerId); //停止该定时器 myTimerId = 0; }
ticket.h
#ifndef TICKER_H #define TICKER_H #include <QWidget> class Ticker : public QWidget { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText) public: Ticker(QWidget *parent = 0); void setText(const QString &newText); QString text() const { return myText; } QSize sizeHint() const; protected: void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *event); void showEvent(QShowEvent *event); void hideEvent(QHideEvent *event); private: QString myText; int offset; int myTimerId; }; #endif
main.cpp
#include <QApplication> #include "ticker.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Ticker ticker; ticker.setWindowTitle(QObject::tr("Ticker")); ticker.setText(QObject::tr("How long it lasted was impossible to " "say ++ ")); ticker.show(); return app.exec(); }
安装事件过滤器
Qt的事件模型一个强大的功能是一个QObject对象能够监视发送其他QObject对象的事件,在事件到达之前对其进行处理。