整理日期: 2010年4月9日
本文是学习笔记之Qt从入门到精通(二)的接续
Qt4 学习笔记
Qt 可以运行在不同的平台,像是Unix/X11、Windows、Mac OS 与支援framebuffer 的嵌入式Linux 平台(Embedded Linux Platform),所使用的版本为Qt OpenSource 4.3.3 版,
在Vista 作业系统下撰写。
先来尝试一下几个简单的Qt 程式,并初步了解一些核心特性。
第一步,当然是先克服Qt 的安装问题,然后来个简单的Hello!World! Orz…
o Windows XP/Vista 下安装Qt4
o 第一个Qt 程式
o 简单的显示中文(使用Unicode 转换)
Signal 与Slot 是Qt 的特性,让物件之间可以同步的(Synchronous)通知讯息,
但又不必知道彼此, Signal 是由物件发出。
o 使用Signal 与Slot(使用按钮关闭视窗)
o 使用Signal 与Slot(使用拉杆改变LCD 数字)
o 自订Signal 与Slot
事件基本上是非同步的(Asynchronousd),通常由视窗或系统发出,让应用程式可以回应使用者动作或系统讯息。
o 事件类型与处理者
o 事件接受与否、event()方法
o 事件过滤器
o 自订与传送事件
Qt 的三种基本版面配置类型为水平、垂直及格状(Grid)配置。
o QHBoxLayout 与QVBoxLayout 版面配置
o QGridLayout 版面配置
o 较复杂的版面配置
o 自订版面配置管理员(Layout Manager)
Qt 在发行时,本身即带有丰富的参考文件与范例,为入门时的必看资料。
o Qt 参考文件与范例
o 简介Qt Designer
元件讲是讲不完的,这边主要在简介几个元件作用,想了解每个元件详细使用方式, Qt 参考文件与范例才是王道。
按钮与选项是视窗程式中最基本的元件。
o QPushButton
o QCheckBox 与QRadioButton
o QComboBox
对话方块用来与使用者作简单的讯息交换与沟通。
o QInputDialog 与QMessageBox
o QColorDialog 与QFontDialog
o QFileDialog
o 自订对话方块(Dialog)
文字栏位是使用者输入资讯的基本元件。
o QLineEdit
o QTextEdit
这类元件通常用于显示项目清单,可以简单的列示,或者是使用树状或表格等。
o QListWidget 与QListWidgetItem
o QTreeWidget 与QTreeWidgetItem
o QTableWidget 与QTableWidgetItem
o Model 与View 类别
除了上面介绍的三种基本版面配置之外,还可以使用一些版面元件来协助元件的群组与版面的切割。
o QTabWidge t
o QSplitter
o QStackedLayout
o QScrollArea
簇繁不及备载…Orz…
o QScrollBar
o QTimer 与QLCDNumber
o QProgressBar
o QWizard
o QMainWindow
o QMdiArea
o QSplashScreen
Qt 已经不只是个GUI 框架,它提供丰富的API,可作为撰写应用程式的基础。
QString 是常用的类别之一,拥有一些与容器类似的特性,而Qt 提供了一系列的通用容器类别(Container class),使用上更轻量级且安全。
o QString
o 循序容器(QVector、QLinkedList、QList…)
o 关联容器(QMap、QHash…)
o 泛型演算(Generic Algorithms)
档案输入输出是一个应用程式所必备的,藉由档案的输入输出,也可以一同了解Qt 的I/O 处理。
o QFile
o QTextStream
o QDataStream
o QFileInfo 与QDir
o Qt 资源系统
QtSql 模组提供与平台、资料库无关的资料库存取需求,在这边使用MySQL 作为示范。
o Qt 的MySQL 驱动程式
o QSqlQuery
o QSqlQueryModel 与QSqlTableModel
Qt 绘图基于QPainter,可于QPaintDevice 的子类别上,进行几何图案、图像、文字等绘制。
o QPainter
o QMatrix
o QPixmap、 QBitmap、QImage 与QPicture
o QPrinter
拖放动作对于使用者是很直觉的操作,而剪贴簿可以方便使用者于不同的应用程式间分享资料。
o 拖放事件
o 拖放的执行与接受
o 剪贴簿(QClipboard)
Qt 的网路模组提供网路存取时的高阶与低阶API,基于非同步(Asynchronous)
及Signal 的行为。
o QHttp
o QFtp
o QTcpSocket
o QTcpServer
进阶议题就是比较进阶的议题。 。 XD
想要实作多执行绪功能,只要继承QThread 类别,单就表面上来看,执行绪并不困难。
o QThread
o 执行绪的停止
o QMutex 与QMutexLocker
o QWaitCondition
o QReadWriteLock 与QSemaphore
o QThreadStorage
让您的应用程式可以因地制宜,显示不同的语系文字。
o 使用Unicode
o 翻译应用程式
o 多国语系选择与切换
无论从功能还是大小来讲,Amarok 都是一款优秀的KDE 音乐播放器。但它很难称作是一款快速点选式的音乐播放器,因为它要通过好几次点击和一些仔细的GUI 导航才能听到音乐收藏中的音乐,这将给我们的CPU 和大脑带来一定负担。
这里我们将会构建所能想到的最简单和最直观的音乐播放器,给用户提供另一个选择。
从苹果的新款iPod Shuffle 吸取一些灵感,只提供最基本的控制功能。一个按
钮用于选择音乐,另一个按钮用于播放和暂停,还有一个按钮用于跳到下一段音乐。对于大多数用户而言,这些控制功能已经足够。与像Amarok 这样臃肿而琐碎的播放器相比,它简直令人耳目一新。
在进行任何编程工作之前,必须满足大家的播放器的几个要求。第一个要求是Qt 4.5,需要安装它和与它相关的开发库。开发库包含了导入到我们自己项目中的头文件,这样我们就能在自己的应用程序之外使用Qt 功能。
如果你的版本足够新,比如Ubuntu Jaunty,那么会发现Qt 4.5 已经包含在内
了。如果版本较旧,必须自己添加它。Nokia 提供了易于安装的包,而且这些包可以与当前版本的Qt 共存,或者也可以更新版本。
新手还需要一个工作开发环境。测试是否安装了工作开发环境的方法是,在命令行中输入“make”。如果提示无法找到该命令,则需要通过所使用版本的包管理器来安装GNU 系列的编译工具。这包括一些基本的编程工具,比如GCC 编译器套
件、“make”编译系统和GDB 调试器。大多数其他的版本都应该包含此编译环境的默认安装,如果没有也应该能够找到一个名称相似的元包。
Qt 4.5 包括Creator IDE,但Kubuntu 用户需要把它当作一个单独的包进行安装。
因此在Linux 和大多数其他操作系统上,当程序员试着猜测所使用的硬件和软件层时,音频播放是一个问题多发区。幸运的是,Qt 给出了我们所见过的最好的解决方案之一,即集成来自KDE 的Phonon 框架。Phonon 位于我们所使用的任何音频驱动器和路由层之上,并在编程和猜测配置复杂部分之间提供了一个接口。
它让播放音频文件变得相对容易,而且是完全跨平台的。
你需要安装Phonon 及其开发库。连同其他几个依赖项一起,它很可能还需要GStreamer,这是大多数Linux 版本的后端选择。Phonon 将直接与GStreamer 对话,而我们只要与Phonon 对话即可。
现在,如你已经安装了所有软件,接下来就可以正式开始了。运行Creator,方法是使用启动菜单或者在命令行上输入“qtcreator”。此应用程序一开始会显
示一个帮助性质的向导画面,在上面可以加载以前的项目或了解关于Qt 的更多信息。要开始一个新项目,点击菜单File>New。
这将运行新建项目向导,而我们的首个任务就是选择要创建项目的种类。在
“Projects”标题下找到“Qt4 GUI Application”,然后点击OK。现在,需要
为项目输入一个名称,并选择创建项目目录的文件夹。
下一个窗口是模块选择页面,在这里可以选择要包含在项目内的其他组件。每个Qt 项目都会使用的两个核心模块QtCore 和QtGUI 已经被选中,只要选择要使用的任意非核心组件即可。例如,如果要开发浏览器,就需要包含WebKit 模块。
因为我们只需要与操作系统的音频层进行交互,所以只需要选择Phonon。
选择Phonon 后,最后一个页面将列出我们的项目中文件和类的默认名称,只要保持它们的默认值不变即可。点击“Finish”结束向导,创建文件夹和文件模板,所有这些都将自动被加载到Creator 中。
如果你之前使用过任何形式的IDE,就不会对Qt Creator 感到陌生。它有好几
种不同的操作模式。一开始要花最多时间熟悉的是GUI 设计器,点击以“ui”结
尾的文件(User Interface 设计器文件)时会自动运行它。这些文件之一已经
添加到我们的项目中,在主显示区域左边的项目窗格中可以看到这个文件和四个其他文件。
其中两个文件的名称以“.cpp”结尾,用于保存我们的项目的源代码,但
“main.cpp”实际上只能用于运行应用程序,因此我们能进行编辑的只有
“mainwindow.cpp”文件。这个文件有一个对应的头文件,用于描述我们在
mainwinodw.cpp 中使用的对象和开发文件。最后还有一个“.pro”文件,实际
上这个文件是禁止可见的,因为它是Qt 和Creator 用于编译项目的项目文件。
在项目窗格中双击“ui”文件,嵌入的设计器视图就会出现。如果你过去曾经尝
试过一些Qt 编程,就会察觉布局与过去的Qt Designer 应用程序有所不同,它已经更加无缝地嵌入到了Creator 中。没有人曾经说过Designer 使用方便,但它功能强大,而且在创建动态和易扩展的Qt GUI 应用程序方面比使用纯编程方法更加简便。
由于Creator 中包含了Designer,现在我们可以在GUI 修改和编程之间轻松来
回切换,这可以显著提高开发速度。
这个应用程序只有一个十分简单的GUI。向下滚动可用小部件的列表,从Buttons区将三个“Push Buttons”拖动到灰色的应用程序画布上。或者,也可以先拖一个按钮,然后再复制/粘贴两个。这些按钮是应用程序所拥有的全部控件。一个按钮将用于选择要播放的文件,另一个按钮将用于播放和暂停音乐,而最后一个按钮将用于跳到下一首音乐。这与Amarok 的20 多个按钮形成了鲜明对比。
现在, 来注释用户每个按钮的功能。为此,可以双击每个按钮并使用一个词来描述其功能,或者在按钮所在位置放置一个图标。如果要使用图标,选中按钮,然后在Creator 画面的左下区中找到值面板。此面板中保存了GUI 中当前选中小部件的大多数属性。向下滚动,直到在列表中看到“icon”为止。
选择该项,然后点击右侧的“…”按钮,以打开一个文件请求器。现在可以为按
钮选择并使用任意图像文件,但如果安装了标准的KDE,可以看一看
“/usr/share/icons/oxygen/32×32/actions”目录。此目录包含大多数常用功
能的图标,“media-skip-forward”、“media-skip-backward”和
“media-playback-start”特别适合我们要实现的功能。
现在,我们需要垂直排列这三个按钮,同时限制窗口的大小。按下左Ctrl 键,然后分别选中每个按钮。它们现在应该都有突出显示的缩放边界。现在点击工具栏中的“Lay Out Vertically”按钮(或者右键单击菜单)。这将把三个按钮绑定在一起,并由上至下依次排列。
现在点击应用程序背景,并选择“Layout in a Grid”。这将把三个按钮拉伸至
充满所有可用空白,无论窗口大小如何。但我们还想让窗口变小。拖动背景左下方的锚点,直到创建出一个小矩形,或者使用属性面板的“Geometry”区手动输入大小。最后我们获得了一个115 像素宽和162 像素高的窗口。也可以选择固定窗口的大小,方法是将Horizontal 和Vertical 参数的最大值设为和最小尺寸相同的数字。
Creator 中的Designer 视图还有一个好处,即不用编写一行代码便可创建大量编程逻辑。这要感谢Qt 的Signals and Slots 机制。它们基本上是一种远程过程调用,但由Qt 自动处理。在web 浏览器中,例如当用户点击刷新按钮时,浏览器接口可能“发出”一个信号以刷新显示器。
在这个应用程序中,我们要发出三种信号,分别对应于我们的三个按钮——播放/暂停、快进和添加文件。我们需要编写函数来处理每种信号,但在Designer中,我以把应用程序配置为当特定操作发生时调用特定函数。为此,需要切换到“Signals/Slots”编辑模式,方法是使用工具栏中的图标或者按下F4 键。
这种模式的原理是,首先选择要发出信号的小部件,然后将光标拖动到其槽将对信号做出反应的小部件。例如,通过web 浏览器可以在刷新按钮和WebKit 浏览器小部件之间建立连接。然后就可以刷新显示器,同时无需编写一行代码。如果没有特定小部件接收信号,就像在我们的例子中一样,那么就将连接拖动到窗口背景上。我们需要为每个按钮做这项工作。
首次将连接放到背景上时,会出现一个“Configure Connection”窗口。在右侧
面板上点击“Edit”按钮。这将打开另一个窗口,其中列出了作为应用程序背景
的MainWindowClass 的信号和槽。我们需要添加三个槽,以便接受三种不同的信号,而且这些槽将直接对应于我们将要在主应用程序中编写的函数。
按下“+”号,然后添加“playPause()”、“addFiles()”和“nextFile()”函
数。点击OK,使用Edit Signals/Slots 模式,将来自每个按钮的“clicked()”
信号连接到我们刚刚创建的正确槽。例如对于“Add Files”按钮,需将
“clicked()”信号连接到“addFiles()”函数。在编辑器窗口中可以看到所有
连接,其中蓝色的直线和正方形用于指示来自某些小部件的信号与各个槽的对应关系。这是我们所需要进行的GUI 编辑的最后一部分,保存工作进展,让我们开始下一个步骤。
#include
#include
#include
#include
以上代码的作用是,通过头文件导入我们要在代码中使用的Qt 函数。现在我们
需要添加我们的槽,它们在我们前面编辑过的ui 文件中已经定义好了。在
“public:”部分中的“~MainWindow();”行正下方,添加如下代码:
private slots:
void playPause();
void addFiles();
void nextFile();
void aboutToFinish();
void finished();
可以看到,这些槽对应于我们在用户界面中创建的名称,而且我们还添加了更多
的槽来处理内部通信。最后,在“private”部分中添加如下变量,我们将在应
用程序的主要逻辑中用到这些变量:
QList sources;
Phonon::MediaObject *mediaObject;
Phonon::AudioOutput *audioOutput;
Phonon::MediaObject *metaInformationResolver;
编码:mainwindow.cpp
现在,我们需要做的第一件事情是初始化Phonon,并建立内部的信号和槽。这可以通过标准的初始化方法MainWindow::MainWindow 来完成。如果你看一看这个方法的内容,就会发现应用程序的GUI 是由“ui->setupUi(this);”行来运行的。这意味着,我们需要在这之前加入我们的预运行代码。我们将从设置Phonon
开始:
audioOutput = new
Phonon::AudioOutput(Phonon::MusicCategory, this);
mediaObject = new Phonon::MediaObject(this);
metaInformationResolver = new
Phonon::MediaObject(this);
Phonon::createPath(mediaObject, audioOutput);
在Phonon 术语中,我们要创建的audioOutput 对象叫做音频接收槽。它是直接与音频驱动器通信的层的组成部分,并充当MediaObject 的虚拟音频设备。
MediaObject 位于这一层的上层,增加了诸如暂停、播放和倒带之类的功能。顺便提一句,MusicCategory 不一定是必需的,但它可以对未来发展起到作用,比如可以根据正在收听的内容自动变化的KDE 均衡器。
我们将使用“metaInformationResolver”来指向当前音频文件,而最后一行在
接收槽和媒体对象之间建立了连接。Phonon 使用了一种叫做“graph”的框架,这意味着对象就像是一幅图上的节点,需要连接起来才能创建流向。这正是以上代码的最后一行的作用。
现在添加如下用于处理playPause()槽的新函数:
void MainWindow::playPause()
{
switch (mediaObject->state()){
case Phonon::PlayingState:
mediaObject->pause();
ui->pushButtonPlay->setChecked(false);
break;
case Phonon::PausedState:
mediaObject->play();
break;
case Phonon::StoppedState:
mediaObject->play();
break;
case Phonon::LoadingState:
ui->pushButtonPlay->setChecked(false);
break;
}
}
以上代码应该不难理解。我们使用一条Case 语句检查播放的当前状态。Phonon通过MediaObject 为我们提供了这种功能。它实际上就是一个整数,但是每个值代表着特定的状态。例如,如果应用程序是首次启动,播放列表中尚未加入任何文件,就会返回LoadingState 值。播放和暂停由媒体对象进行处理,在“mediaObject->pause();”命令之后将自动从当前位置继续。
void MainWindow::addFiles()
{
QStringList files = QFileDialog::getOpenFileNames(this,
tr("Select Music Files"),
QDesktopServices::storageLocation(QDesktopServices::Music
Location));
ui->pushButtonPlay->setChecked(false);
if (files.isEmpty())
return;
int index = sources.size();
foreach (QString string, files) {
Phonon::MediaSource source(string);
sources.append(source);
}
if (!sources.isEmpty()){
metaInformationResolver->setCurrentSource(sources.at(inde
x));
mediaObject->setCurrentSource(metaInformationResolver->cu
rrentSource());
}
}
现在,让我们开始实现添加文件的部分。创建文件请求器几乎是自动的,我们可以把结果放到一个字符串列表中。“QdesktopServices::MusicLocation”返回一个通常用于保存音乐文件的特定操作系统位置,而我们使用它作为请求器的起始位置。接下来,我们将把已经选择的每个音乐文件添加到我们早先创建的“metaInformationResolver”中,并使用它告诉mediaObject 接下来播放哪个文件。
在所有这些工作间隙,我们进行一点清理工作,以确保队列中有文件,而且当处于播放状态时出现播放按钮。添加文件将自动停止播放。
void MainWindow::nextFile()
{
int index = sources.indexOf(mediaObject->currentSource())
+ 1;
if (sources.size() > index) {
mediaObject->stop();
mediaObject->setCurrentSource(sources.at(index));
mediaObject->play();
}
}
void MainWindow::aboutToFinish()
{
int index = sources.indexOf(mediaObject->currentSource())
+ 1;
if (sources.size() > index) {
mediaObject->enqueue(sources.at(index));
} else {
ui->pushButtonPlay->setChecked(false);
}
}
void MainWindow::finished()
{
ui->pushButtonPlay->setChecked(false);
}
下载此项目的代码
它看起来可能不起眼,但我们的音乐播放应用程序具备了一款音乐播放器所需要的大部分功能。
命令行没有什么不好。对于我们很多人来说,这是使用Linux 的最佳理由之一。
可以通过输入内容实现几乎所有功能,而且命令行工具对于它们的运行方式通常能够提供极好的控制。但是命令行并不适合所有人,觉得命令行难以理解和令人生畏的Linux 用户数量多得令人吃惊,这或许是完全避免使用Linux 的理由之一。
尽管如今不愿意使用命令行的用户可以不必再使用它,但这仍然意味着他们将遗漏一些很优秀的实用工具。
Qt 正好可以扭转这种局面。它是为你最喜爱的命令行工具创建友好GUI 的理想工具。它不需要任何顶级的编程技巧,而且工作量也不大,但在此过程中你可以为讨厌命令行的朋友提供帮助,并且对开源应用程序开发做出自己的贡献。为命令行工具创建GUI 是最佳起点之一!
如果你已经完成了我们以前的Qt Creator 代码项目,如何创建自己的媒体播放器,就能够更加自如地完成这个任务……
如果说有一种工具非常需要GUI,那就是FFMPEG。FFMPEG 是一个十分优秀的命令行应用程序,它可以将视频和电影文件从一种格式转换为另一种格式。同时它的复杂程度也令人吃惊。有好几位开发人员都曾经试过为这个工具创建一个GUI,但是FFMPEG 的发展速度让人很难驾驭所有额外的组件和用于各个选项的不断改变的语法。
我们将会构建我们自己的FFMPEG GUI,但本指南的意义并不在于帮助人们了解FFMPEG 的非凡之处。本指南将展示为几乎任意命令行工具创建GUI 是多么轻松的事情——只要替换几行代码即可,而且结果可能对我们大有好处。
我们将假定已经启动并正在运行Creator,而且对于使用开发环境相对比较满意。如果不满足上述条件,请复习我们以前的指南。运行应用程序之后,创建一个新的项目并使用“Qt Gui Application”模板,同时保留其他选项的默认值。
和以前的指南一样,我们的开发工作仍然从GUI 开始。点击“.ui”文件,
Designer 视图随之出现。如果想从空白画布开始,可在视图中删除菜单栏、工具栏和状态栏,方法是在窗口右上方的对象视图中选中它们,右键单击每个组件,然后从出现的菜单中选择删除。
我们应用程序的GUI 布局显然依赖于要运行的命令行工具。但对于我们的FFMPEG例子,我们要尽可能尝试保持内容的开放性和可修改性。我们需要一个按钮来添加要转换的源文件,以及一个用于显示此文件位置的文本字段。我们需要一些途径来选择转换过程的最终格式,并显示FFMPEG 命令的结果。我们还需要另一个按钮,点击它便可启动整个转换过程。
但是用户界面的主要部分将会被我们选出让用户编辑的FFMPEG 选项所占据。我们没有理由向普通用户公开FFMPEG 中数以百计的选项,因此这里只会出现最常用和最简单的使用选项。
从左边的Button 小部件列表中拖两个按钮到空白画布上,然后双击每个按钮,修改它们的名称。一个按钮的名称定为“Source”,而另一个用于触发转换过程的按钮则需要取类似于“Go!”的名称。另外还可以使用图标代替文本,或者二者同时出现亦无不可(如果这是一种改进的话)。现在从Inputs 列表中添加一个Line Edit 小部件,然后点击左下方面板中的“enabled”属性以禁用它。
由于Line Edit 小部件是交互式的——即默认情况下,用户可以在里面输入内容——我们想禁用这项功能,而只使用它来显示我们要转换文件的源位置。如果想让用户手动输入或修改文件的名称和位置,可以保留此字段的值为enabled。
我们还为用户提供了一条选择输出格式的方便途径。我们选择使用一列单选按钮,每个单选按钮代表一种格式。从调色板中拖动Radio Button 小部件即可添加这些按钮。这种方法的好处在于,每次只能选择一种格式,而这正是我们需要的行为模式。对于每个单选按钮,还需要将其标签的文本修改为能够反映适合FFMPEG 输出的设备。例如,如果选择PSP 作为输出设备,将禁用iPhone 输出和GP2X 输出。
为了最大程度利用可用空间,我们准备使用选项卡小部件来保存余下的参数。就像浏览器中的选项卡式web 页面一样,允许用户在一块GUI 区域的两种不同视图之间进行切换。我们将使用一个选项卡来保存要公开的主要参数,而另一个选项卡可用于保存来自FFMPEG 的原始命令输出。这应该意味着,除非用户切换选项卡,否则不会看到乱七八糟的FFMPEG 输出。
从Creator 中的Containers 列表把选项卡小部件拖到画布上,选中之后,点击属性列表中的“currentTabText”属性,以便修改每个选项卡上显示的文本。我们选择了“Options”和“Output”。
被拖到选项卡小部件中的任意小部件都只显示在当前选项卡上,因此我们给
Options 选项卡添加了5 个组合框和5 个标签。对于像FFMEG 这样的工具而言,组合框是对用户最友好的选项,因为它们将可用选项限制为只出现有效的选项。
它对于程序员意味着额外的工作,但这种额外工作可以为可怜的老用户节省很多时间。借助这5 个组合框,我们选择支持如下配置:视频的分辨率、帧率和比特率,以及视频的采样率和压缩比特率。
我们重新命名了每个组合框小部件,以便在源代码中能够更好地区分它们,同时在每个组合框旁边放置一个标签,并使用标签文本对每个选项进行了说明。在组合框下面,我们添加了更多小部件,用于保存目的文件的名称—— 一个按钮用于选择文件,以及一个禁用的线编辑字段用于显示位置。
最后,切换到选项卡小部件的下一个页面,并添加一个TextBrowser 小部件。我们将使用这个小部件来显示FFMPEG 命令的输出。
现在,我们的应用程序看起来应该相当复杂了。我们需要对各个小部件进行排列,让它们能够很好地缩放并且看起来间隔均匀。Qt 使用一个分组和间距器的系统来创建布局,但了解这个系统的使用原理需要花点功夫。它和DTP 应用程序处理对象的方式不同。例如,要在选项页面中创建良好的布局,首先shift 选择一个标签及其相关组合框,然后从工具栏选择“Lay Out Horizontally”。这将会把两个小部件锁定在并排的位置。
对其他小部件做同样的事情,然后shift 选择用于修改视频设置的分组对,并选择“Lay Out Vertically”。这将把每对小部件都对齐到一列中,对音频设置也
要做同样的事情。我们还为音频和视频列添加了标签,并在垂直布局集合中包含了它们。要想正确实现分组选择,过程更加麻烦,但如果犯错我们始终可以进行“Undo”和“Redo”操作。
最好把间距器看作弹簧,并重新复习一些旧的物理课本。
为了保证我们的GUI 不会明显延伸为一个大窗口,我们需要使用“间距器”。它们看起来有点像弹簧,可以从小部件调色板添加垂直或水平的间距器。当在水平或垂直布局中包含一个正确的间距器时,小部件将会与一块可扩展空间组合在一起,这块空间就是弹簧所在的位置。如果应用程序窗口扩展或收缩,这块空间也会根据弹簧长度成比例地伸缩。
这用语言也许很难描绘,但只要动手实践便不难理解。最后的结果是布局系统十分灵活,但学习起来却比较困难。我们使用三个水平弹簧来分开视频和音频选项,以及它们与窗口边界的空间,同时使用一个垂直弹簧来将视频与音频编码选项和目的文件位置分开。间距器还可用于在单选按钮之间安插一点距离。
当我们大体上安排好所需要的一切小部件后,选择“Lay Out in a Grid”将所
有小部件绑定到主窗口上。此时仍然能够拖动和添加小部件到布局上,但如果需要做较大的改动,则需要首先打破这种绑定,即选择“Break Layout”。
现在,可以把GUI 设计与我们将要在源代码中添加的程序功能联系起来了。这要借助于我们前面提过的Qt 的信号与槽机制。一开始,切换到Signals/Slots 编辑模式(F4),并且从“Go”按钮拖出一个连接到窗口的背景画布上,用于有效地发送信号给MainWindow 类。在出现的窗口中,点击左边的Edit 按钮,从而打开Signals/Slots 编辑窗口。
我们需要添加6 个槽:executeCommand()、setSource()、setDestination()、setPSP()、setIPOD() 和 setGP()。点击OK,选择新创建的“executeCommand()”槽,然后连接到我们当前正在编辑的Go 按钮的clicked信号。需要对我们刚刚为其创建了槽的其他每个按钮做同样的事情,方法是把它们连接到它们对应的槽。例如,应该把来自PSP 单选按钮的“clicked()”信号连接到“setPSP()”。
我们从GUI 创建我们自己的槽,而且后面需要在源代码编辑器中为它们添加代码。
完成这项任务之后,Designer 编辑就结束了,保存项目并切换为编辑
MainWindow.h。我们需要在文件顶部添加几个头:
#include
#include
#include
#include
#include
#include
因为我们要对已经在Designer 中添加给应用程序的小部件进行操作,所以这些头文件中的大部分都是必不可少的。Qprocess 和QbyteArray 用于在Qt 中运行外部的可执行文件,并抓取命令的输出,但我们很快就会讲到这个函数。现在,我们需要为要编写的槽添加定义。在“public:”部分下面添加如下代码:
private slots:
void executeCommand();
void outputCommand();
void setSource();
void setDestination();
void setPSP();
void setIPOD();
void setGP();
最后,我们需要在头文件的“private:”部分中添加一个变量。此变量将用于在
Qt 中处理外部可执行文件(FFMPEG):
QProcess commandProcess;
现在我们到了最富于技巧性的部分,即给应用程序添加功能。首先,我们要为三种要处理的不同输出格式编写“set”函数。这些函数都将使用与设备相关的参数填充组合框,而我们最终将采用这些参数来编译将创建正确输出的FFMPEG 命令行。
当然,在能够给GUI 添加值之前,首先针对每种格式要有一个有效的FFMPEG 命令。例如,我们已经找到用于转换运行在PSP 上的视频的最佳FFMEPG 命令是:
ffmpeg -i space.mpg '-vcodec' 'libxvid' -s 320x240 -r 29.97
-b 1500 -acodec libfaac
-ac 2 -ar 24000 -ab 65535 -f psp M4V80113.mp4 -y
我们准备采用这些参数中的一部分,并使它们能够在我们的GUI 中进行编辑。根据我们在其他项目中的经验,可以通过“ui”对象运行属于GUI 中对象的方法,此对象默认是使用标准Creator 模板创建的。例如,
“ui->comboResolution->clear()”将在comboResolution 组合框上执行清除
工作。
Creator 集成环境的好处在于,可以使用自动完成功能列出每个对象的可用选
项,而不用全靠记忆。下面是我们在setPSP 函数中换到GUI 中的FFMPEG 选项。
需要把它添加到MainWindow.cpp 文件的底部:
void MainWindow::setPSP()
{
ui->comboResolution->clear();
ui->comboFramerate->clear();
ui->comboBitrate->clear();
ui->comboSamplerate->clear();
ui->comboAbitrate->clear();
ui->comboResolution->addItem("240x320");
ui->comboResolution->addItem("160x120");
ui->comboFramerate->addItem("29.97");
ui->comboBitrate->addItem("1500");
ui->comboSamplerate->addItem("2400");
ui->comboAbitrate->addItem("65535");
ui->lineEdit_2->setText("M4V80113.mp4");
}
这段代码的自解释程度相当高。为了节省空间,我们将会给其添加多个选项的惟一组合框是分辨率框,但可以很容易地看到如何添加其他选项。我们还需要为其他两种预设置创建相同模板,并把它们放在“setIPOD”和“setGP”函数槽中。
由于我们只选择了一个参数集合让用户编辑,因此需要将这些参数与FFMPEG 命令中的其他参数结合起来,而我们准备在处理运行外部FFMPEG 命令的函数中做这件事情,这个函数叫做“executeCommand()”。
执行一个命令
void MainWindow::executeCommand()
{
QStringList args;
args << "-i";
args << ui->lineEdit->text();
args << "-y";
args << "-s"; args << ui->comboResolution->currentText();
args << "-r"; args << ui->comboFramerate->currentText();
args << "-b"; args << ui->comboBitrate->currentText();
args << "-ar"; args <<
ui->comboSamplerate->currentText();
args << "-ab"; args << ui->comboBitrate->currentText();
args << ui->lineEdit_2->text();
if (ui->radioButton->isChecked()){
args << "-vcodec"; args << "libxvid";
args << "-acodec"; args << "libfaac";
args << "-ac"; args << "2";
args << "-f"; args << "psp";
}
commandProcess.start("ffmpeg", args);
}
下面是对于以上代码块作用的解释。在Qt 中运行外部命令的关键是一个叫做
“Qprocess”的类。在步骤三结束时,我们在头文件中使用这个类创建了我们自己的对象,现在正是时候使用它来执行FFMPEG。使用“commandProcess”变量时,我们只要使用两个变量运行“start”即可——命令本身和我们的参数列表。
我们使用“QstringList”来快速构造这个参数列表,首先从我们GUI 中的小部
件,然后是包含在有条件的“if”语句中的PSP 特定参数
(ui->radioPSP->isChecked)。需要为其他目的设备添加更多参数,才能让转换过程开始工作。
connect (&commandProcess,
SIGNAL(readyReadStandardOutput()),this,
SLOT(outputCommand()));
connect (&commandProcess,
SIGNAL(readyReadStandardError()),this,
SLOT(outputCommand()));
正如我们看到的那样,从Qprocess 发出的有两种类型的输出信号,而且我们将来自这两种信号的输出都发送给同一个函数“outputCommand”,现在需要把这个函数添加到源代码中:
void MainWindow::outputCommand()
{
QByteArray cmdoutput =
commandProcess.readAllStandardOutput();
QString txtoutput = cmdoutput;
ui->textBrowser->append(txtoutput);
cmdoutput = commandProcess.readAllStandardError();
txtoutput = cmdoutput;
ui->textBrowser->append(txtoutput);
}
这是当Qt 从运行“FFMPEG”的Qprocess 检测输出时执行的函数。这有点繁复,因为我们不能假定命令的输出是文本,而且输出数据有两种流形式——一种用于来自命令的标准输出,而另一种用于错误输出。我们的安全做法是在使用Qt 的优秀转换例行程序将这些数据转换为一个文本字符串之前,将流中的二进制数据复制到一个原始字节数组中。接着把这些数据添加到文本视图中,而且我们对于命令的错误输出重复这个过程。没有什么捷径可以同时抓取到这两种流。
最后,在完成我们的应用程序之前,最后一个步骤是添加两个槽,用于处理源和目的文件位置。这两个槽几乎是完全相同的。下面给出了处理目的文件位置的槽函数:
void MainWindow::setDestination()
{
QString file = QFileDialog::getSaveFileName (this,
tr("Select Destination"),
QDesktopServices::storageLocation(QDesktopServices::Movie
sLocation));
ui->lineEdit_2->setText(file);
}
第一行创建了一个Qt 文件请求器,自动指向系统默认的电影位置,而因为我们已经使用了“getSaveFileName”,用户将被询问一个不一定存在的文件的名称。
这与“setSource”完全相反:
void MainWindow::setSource()
{
QString file = QFileDialog::getOpenFileName(this,
tr("Select Source File"),
QDesktopServices::storageLocation(QDesktopServices::Movie
sLocation));
ui->lineEdit->setText(file);
}
输入这最后两个函数后,我们应用程序的源代码就已经完成了,应该有一个可用的FFMPEG GUI,可以自定义它以使用所需的任意参数。编译并运行就可以了。
还应该看到,修改这些代码以使用其他命令行工具是多么轻松的事情。
下载此项目的代码:qt_menq.tar
借助基于Qt 的前端,用户就不必深入研究FFMPEG 的命令行用法了。
原文:
http://www.tuxradar.com/content/code-project-create-ffmpeg-front-end
我们将构建一个完整的应用程序,使其不必太费事便可重新发布为一个真正的开源应用程序。这个应用程序就是一个RSS 阅读器,它允许用户添加自己的种子,列出该种子上的内容,然后让用户在主应用程序自带的一个浏览器窗口中阅读这些内容。
如果你已经尝试过了我们前两个Qt 代码项目——创建一个ffmpeg 前端和创建一个媒体播放器,而且正在寻求更多Qt 方面的乐趣,那么请读下去…
RSS 是一个以特定方式进行格式化的XML 文本文件。它包含对网站上每段内容的简短描述。它最大的优点就是,始终随着新内容的发布而更新。使用RSS 阅读器或像Firefox 这样与RSS 兼容的浏览器时,用户可以从网站订阅RSS 种子,而且阅读器将定期检查更新,并列出所有新的内容供用户浏览。而这也正是我们的应用程序所要实现的功能。
它还将引入一些主要的Qt 技术,包括处理XML 数据流的手段,如何动态填充树视图小部件,以及使用WebKit 小部件并将所有小部件组合为一个可动态扩展的、将自动更新为显示web 页面的应用程序窗口。这使得RSS 阅读器成为启动更多目标远大的项目的最佳起点,即使你马上弃用RSS 处理的代码,我们为这个应用程序所构建的可扩展GUI 仍然可以发挥作用。
这正是首次运行Qt Creator 并创建一个新项目时,需要选择三个单独的模块在应用程序中使用的原因。在向导中点击Qt4 GUI Application 模块,给它取一个名字,然后启用如下三个模块:QtNetwork, QtWebkit 和 QtXML。这些模块将紧密联系我们将在本指南中讲到的三个新领域,而且从向导添加它们后,便不用再手动把它们添加到项目的“.pro”文件中。
和我们其他的Qt 编程指南一样,在运行Creator 创建一个新项目后,接下来要做的工作就是GUI 设计。点击“ui”文件打开Designer 视图。这一次,我们将采用稍微开放一点的方法进行设计。主窗口将被划分为两个面板。在左侧,我们将添加RSS 消息列表,并让用户能够添加他们自己的种子。而窗口的右半部分将是web 浏览器,我们将对这部分使用WebKit 小部件。
但是Qt 的聪明之处在于,我们可以根据用户是否想使用内部浏览器来使每个面板变得可扩展或可隐藏,或者干脆使用他们最惯于使用的浏览器。例如,如果用户不想看到web 视图,只需要把中间的分离线拖到右边,它就会消失。这给予了我们的应用程序很大的灵活性,并不强迫想使用自己的浏览器阅读新闻的用户使
用web 视图。
这项特别的功能是通过Qt 中的Dock Widget 小部件实现的。当应用程序分为几个部分时,它提供了很强的灵活性,允许用户在四周拖放窗口的不同部分。从Creator 页面的Containers 列表中拖出两个Dock Widget 小部件到空白的应用程序画布上。如果在应用程序中用不着,还可以从Object 视图删除多余的菜单、工具栏和状态面板小部件。我们已经添加了两个可停靠小部件,因为我们要在应用程序的两侧使用它们来保存小部件,而且它们是可停靠小部件,用户能够拖动它们之间的分离线,从而改变应用程序每个半区的尺寸。
但在添加更多小部件之前,我们需要确保只启用了每个可停靠小部件的一组有限功能。我们不想让用户完全访问Qt 可停靠小部件更多难以驾驭的功能,
KDevelop 已经很好地证明了这一点。在Object 列表中选择每个可停靠小部件,然后在下面的属性窗口中,确保将选中的“Features”字段设置为
“NoDockWidgetFeatures”。这将阻止用户将小部件拖动至窗口外部或者完全关闭它们。你可能想对浏览器面板启用这项功能,这由你自己决定。
在添加其他小部件之前,选择可停靠小部件并点击“Lay Out Horizontally”按
钮。接着点击“Lay Out in a Grid”按钮。这样做的效果是同时拉伸跨应用程
序窗口的、中间具有一条分离线的两个可停靠小部件。当用户改变主窗口的大小时,这两个小部件将保持它们的相对位置。
尽管网格被锁定,我们仍然能够以常规方式给可停靠小部件添加小部件,而且我们准备从左侧开始。如果在网格被锁定的情况下编辑GUI,Designer 将使用蓝色光标突出显示每个小部件要插入的位置,这一点十分类似于字处理器。需要将三个小部件拖动到左边面板中——一个行编辑小部件和一个按钮,它们已经在窗口顶部水平对齐了,还有一个位于下方的树视图。行编辑小部件用于给用户输入RSS 种子的URL,按钮用于提交种子给我们的解析器,而树视图用于列出RSS 种子的每个入口。
我们给行编辑小部件添加了一个默认的URL。只要双击该小部件,然后输入类似于“http://www.qteverywhere.com/rss”的内容,再将按钮文本改为“Add
Feed”。双击树视图,再添加两列,将它们分别取名为“Feed”、“Date”和
“URL”。这些列将包含每个新闻内容的信息,但只有“Feed”和“Date”两列
可见。这是因为我们要内部使用URL 列,不显示给用户看。它将保存内容的URL,这样当用户点击它时,我们可以把URL 发送给WebKit。
如果我们不使用这种方法,我们就不得不为应用程序实现一个成熟的MVC 解决方案,而这描述起来都超过4 页纸了。MVC(模型/视图/控制器)是一种将数据(在这个例子中是指URL)与显示数据的视图分离,同时保持二者联系的方法。后一部分由控制器来处理。当我们使用它的任意容器类时,Qt 在后台使用的是MVC,而它用于添加和删除内容项的方法实际上是用于在后台处理MVC 的便利函数。我们将在树视图中利用这一点,隐藏URL 列并在应用程序中使用数据,但我们只能在源代码中做到这一点。
最后,将WebKit 小部件拖动到右侧面板中。这是一个自包含的浏览器窗口,我们不需要添加任何别的内容就可以让它工作。只要保证所有小部件都经过了正确排列,以及你已经在两个面板上使用了一些间距器和“Lay Out in the Grid”模式,从而锁定可缩放窗口的布局。
既然我们的布局已经最终确定,下一步就要添加槽/信号连接,用于补充我们应用程序的功能。切换到Signals/Slots 编辑器,方法是按下F4 键或者在工具栏中点击相应按钮。从“Add Feed”按钮拖动一个信号到应用程序窗口的轮廓处,当“Configure Connection”窗口出现时,点击右侧面板上的“Edit”按钮。
我们需要添加两个槽。第一个用于给树视图添加种子,而另一个用于当用户在种子列表中选择一个新闻内容时更新web 视图。我们将第一个槽称为“fetch()”,而将第二个槽称为“itemActivated(QTreeWidgetItem*)”。这是我们首次遇到通过信号/槽机制传递的参数,要在设计器中使用它们,必须满足一些严格的规则。其中最重要的一条是,对于一个在传递这类参数时要连接到槽的信号,二者都必须完全支持同一类型。在这个例子中是QTreeWidgetItem 类型。
创建这两个槽并将“clicked”连接到“fetch()”之后,从树视图拖一个新连接
到窗口背景。我们将看到,很多函数将QItemTreeTree 参数作为一个参数包含在内。这是树视图中每一项的类型,以这种方式传递它使我们能够轻松抓取到当前选中的新闻内容的URL,并使用它来更新web 浏览器。只要将位于左边的“’itemActivated(QTreeWidgetItem*)”与我们刚刚为自己的应用程序创建的
名称相同的新槽连接起来即可。
现在我们已经建立了框架,是时候添加代码了。和我们其他的项目一样,我们从“mainwindow.h”开始,把它作为需要添加我们刚刚在GUI 中创建的新槽的地方。我们还准备添加要在程序逻辑中使用的新槽,用于告诉我们的应用程序,从Internet 读取web 数据的过程已经结束。
void fetch();
void itemActivated(QTreeWidgetItem * item);
void readData(const QHttpResponseHeader &);
现在,我们需要给项目添加一些私有成员。我们将使用这些私有成员管理数据流,并且为解析从站点的RSS 种子抓取到的XML 数据和HTML 数据而创建数据结构。
void parseXml();
QString currentTag;
QString linkString;
QString titleString;
QString dateString;
QTreeWidgetItem *feed;
int connectionId;
QHttp http;
QXmlStreamReader xml;
这是我们需要给头文件添加的内容。我们余下的编码将限制在
“mainwindow.cpp”文件中,从位于该文件顶部的初始化函数开始。首先,我们需要在“setupUi”前面添加一个连接行,用于当我们知道Qt 的HTTP 抓取器已经正确解析HTTP 时,自动运行我们的“readyRead”方法。其次,我们想隐藏treeWidget 的两列,因为我们只使用这些列来保存数据,而不想让用户看到它们。一旦“setupUi”创建了GUI,我们就可以这样修改它。下面给出相应的代码:
connect(&http, SIGNAL(readyRead(const
QHttpResponseHeader &)), this,
SLOT(readData(const QHttpResponseHeader &)));
ui->setupUi(this);
ui->treeWidget->setColumnHidden(1, true);
ui->treeWidget->setColumnHidden(2, true);
现在,我们准备编写fetch()函数。当我们在应用程序中输入RSS 种子的URL,然后点击“Add Feed”按钮时,将触发这个函数的功能。
void MainWindow::fetch()
{
xml.clear();
QUrl url(ui->lineEdit->text());
http.setHost(url.host());
connectionId = http.get(url.path());
}
这段代码相对较为直观。首先,我们清理了保存XML 日期的流读取对象,然后将我们用于保存URL 的行编辑组件中的文本转换为一个QUrl,这是Qt 中访问在线资源的首选方法。接下来,我们使用这个资源设定QHttp 的位置,这个类是用于实现HTTP 协议的。我们需要 使用这个类来抓取XML 数据,而下一行中使用“get”函数和经过转换的URL 调用了这个函数。当“http”成功打开HTTP 位置时,它将发出我们前面连接到我们自己的“readData”函数的“readyRead”信号。现在我们需要添加这个函数:
void MainWindow::readData(const QHttpResponseHeader &resp)
{
if (resp.statusCode() != 200)
http.abort();
else {
xml.addData(http.readAll());
parseXml();
}
}
这个函数的全部功能就是检查是否找到了URL,如果没有找到,它会中断,而且我们的应用程序也不会再往下执行。但如果远程位置是合法的,在把数据发送给“parseXML”函数之前,我们首先会使用数据填满我们的XML 容器
——xml.addData(http.readAll())。这是应用程序的一个难点,因为需要遍历
从internet 抓取的XML 树,并把我们需要的数据块放到treeView 中。因此,对应代码的篇幅要长很多。
void MainWindow::parseXml()
{
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "item"){
if (titleString!=""){
feed = new QTreeWidgetItem;
feed->setText(0, titleString);
feed->setText(2, linkString);
ui->treeWidget->addTopLevelItem(feed);
}
linkString.clear();
titleString.clear();
dateString.clear();
}
currentTag = xml.name().toString();
} else if (xml.isEndElement()) {
if (xml.name() == "item") {
QTreeWidgetItem *item = new
QTreeWidgetItem(feed);
item->setText(0, titleString);
item->setText(1, dateString);
item->setText(2, linkString);
ui->treeWidget->addTopLevelItem(item);
titleString.clear();
linkString.clear();
dateString.clear();
}
} else if (xml.isCharacters() && !xml.isWhitespace())
{
if (currentTag == "title")
titleString += xml.text().toString();
else if (currentTag == "link")
linkString += xml.text().toString();
else if (currentTag == "pubDate")
dateString += xml.text().toString();
}
}
if (xml.error() && xml.error() !=
QXmlStreamReader::PrematureEndOfDocumentError) {
qWarning() << "XML ERROR:" << xml.lineNumber() << ":
" << xml.errorString();
http.abort();
}
}
这段代码看起来有点吓人,但这主要是因为它包含了几条嵌套的“if”语句,用
于处理我们在RSS 种子中将会遇到的不同类型的XML 元素。
我们从依次读取每个元素开始,然后检查该元素在树上是属于新的内容项,一个元素的末端,还是包含真正的元素数据。
当代码检测到XML 种子中一个新元素的起点时,它会将“currentTag”设为该元素中所包含数据的类型。我们只对“title”、“link”和“pubDate”字段感兴
趣,而且当XML 流移动到文件的字符部分时,每个字段的文本就会根据
currentTag 类型转移到“titleString”、“linkString”和“dateString”
中。当检测到一个元素的终点时,我们已经知道这些字符串是否已经被填充,以及是否可以把数据复制到我们GUI 中的“treeView”对象中。这就是
“item->setText”行的作用,而这些字符串被添加到一个treeView 顶部的内容
项中,该treeView 创建来保存“StartElement”部分中的每个RSS 种子的树视
图。
如果我们在理解这个函数时有困难,使用Creator 的优秀调试器把代码完整地运行一遍,会对我们有所帮助。如果在这个函数中设置一个断点,当应用程序的执行到达代码的这个部分时,我们就能够使用Debug 菜单单步调试每一行,并监视感兴趣参数的值。
最后,我们需要添加的最后一个函数是用于在用户点击RSS 新闻项之一时,将该新闻项指向的web 页面装载到我们的WebKit 小部件中。这个函数是使用我们在GUI 设计器中建立的“itemActivated” SIGNAL/SLOT 连接来执行的。我们从树小部件内容项中找到了由信号传递的URL,并把这些数据发送给webView 小部件,然后把它转换为一个QUrl,而我们也正是这样做的。幸运的是,此功能一共只
需要两行代码:
void MainWindow::itemActivated(QTreeWidgetItem * item)
{
ui->webView->load(QUrl(item->text(2)));
ui->webView->show();
}
以上就是需要完成的全部工作。剩下的就是保存项目,编译和运行。点击“AddFeed”按钮可以添加我们在GUI 中创建的默认RSS 种子,而且我们应该看到,位于左侧的树视图使用了来自TuxRadar.com 的所有最新内容进行填充。点击其中任意的内容,右侧的web 查看器就会加载相应的页面。
但这个应用程序的最大优点是缩放两个面板的方式。在树视图和web 页面之间,应该能够找到三个很小的垂直点。可以把这些点拖到左边或右边,从而改变RSS种子或正在显示的web 页面的比例。如果将这一条完全移到右边,web 视图就会完全关闭。如果我们只想看到种子列表,这是个不错的主意。
可以轻松给这个应用程序添加所需要的内容。我们从一个具有自动功能的刷新按钮开始。这个按钮功能是大约每小时增加一次新种子,或者在点击它时手动刷新。
应用程序还迫切需要保存其设置的功能。这是一项十分艰巨的任务,几乎可以作为另一份指南的主题——为什么不亲自写写试试看呢?
完成之后的应用程序:密切注意Akregator,我们的RSS 应用程序是跨平台的,而且使用WebKit 来呈现web 页面。
下载源代码:qt_mrss.tar
原文链接:
http://www.tuxradar.com/content/code-project-create-qt-rss-reader
翻译CSDN:http://qt.csdn.net/articles.aspx?pointid=178
Graphics View 提供了一个界面,它既可以管理大数量的定制2D graphical
items,又可与它们交互,有一个view widget 可以把这些项绘制出来,并支持旋转与缩放。这个框架也包含一个事件传播结构,对于在scene 中的这些items,它具有双精度的交互能力。 Items 能处理键盘事件,鼠标的按,移动、释放、双击事件,也可以跟踪鼠标移动。Graphics View 使用BSP 树来提供对item 的快速查找,使用这种技术,它可以实时地绘制大规模场景,甚至以百万items计。Graphics View 在Qt 4.2 中被引用,它替代了它的前辈QCanvas。
Graphics View 提供的是一种类似于Qt model-view 的编程。多个views 可以监视同一个场景,而场景包含多个具有多种几何外形的items。
QGraphicsScene 表示Graphics View 中的场景,它有以下职责:
为管理大量的items 提供一个快速的接口。
传播事件到每个item。
管理item 的状态,例如选择,焦点处理。
提供未经变换的渲染功能,主要用于打印。
场景作为QGraphicsItem 对象的容器。通过调用QgraphicsScene::addItem()把这些Items 加入到场景中。可以使用众多的查找函数来获取特定的items。
QGraphicsScene:items()与它的许多重载函数可获取那些与点、矩形,多边形,向量路径等相交或是有包含有关系的items。QGraphicsScene::itemAt()返回特定上最顶端的item。所有的item 查找函数都以出栈序列返回(也就是说,第一个返回的是最顶端的,最后一个返回的是最底端的)。
QGraphicsScene scene;
QGraphicsRectItem
*rect=scene.addRect(QRectF(0,0,100,100));
QGraphicsItem *item=scene.itemAt(50,50);
//item==rect;
QGraphicsScene 的事件传播结构会把场景事件投递到items,也管理多个items之间的传递。假如场景收到了鼠标在某个位置press 事件,场景会把这个事件投递给处在那个位置的item。QGraphicsScene 也管理某种item 状态,像选择与焦点。你可以通过调用QGraphicsScene::setSelectionArea()来选择items,它需要提供一个任意的形状为参数。这个函数也作为在QGraphicsView 实现橡皮筋选择功能的一个基础。为得到这些已经被选择的items,调用QGraphicsScene::selectedItem()。另一个状态处理是是否一个item 拥有键盘输入焦点。你可以调用QGraphicsScene::setFocusItem()或
QGraphics::setFocus()来设定焦点,也可用QGraphicsScene::focusItem()来
得到当前拥有焦点的那个item。最后,QGraphicsScene 允许你通过调用
QGraphicsScene::render()函数把部分场景送到绘图设备进行渲染。
QGraphicsView 提供了视图部件,它可视化场景中的内容。你可以联结多个视图到同一个场景,对这个相同的数据集提供几个视口。视口部件是一个滚动区域,它提供了滚动条以对大场景进行浏览。为了使用OpenGL,你应该调用QGraphicsView::setViewport()来把一个QGLWidget 设为视口。视图从键盘,鼠标接收输入事件,在发送这些事件到场景之前,会对这些事件进行适当的翻译(把事件坐标转换成对应的场景坐标)。
利用转换矩阵,QGraphicsView::matrix(),视图可变换场景的坐标系统。这允许高级的导航特性,如缩放,旋转。为了方便,QGraphicsView 也提供了在视图与场景之间进行坐标转换的函数:
QGraphicsView::mapToScene(),QGraphicsView::mapForScene()。
QGraphicsItem 是场景中图形items 的基类。Graphics View 提供了一些标准的、用于典型形状的items。像矩形(QGraphicsRectItem),椭圆
(QGraphicsEllipseItem),文本 (QGraphicsTextItem),当你写定制的item 时,
那些最有用的一些QGraphicsItem 特性也是有效的。除此这 外,QGraphicsItem支持以下特性:
*鼠标按、移动、释放、双击事件,鼠标悬停事件,滚轮事件,弹出菜单事件。
*键盘输入焦点,键盘事件。
*拖拽
*组,包括父子关系,使用QGraphicsItemGroup
*碰撞检测
Items 如同QGraphicsView 一样,位于本地坐标系,它也为item 与场景之间,item 与item 之间的坐标转换提供许多工具函数。而且,也像QGraphicsView 一样,它使用矩阵来变换它的坐标系统:QGraphicsItem::matrix()。它对旋转与缩放单个的Item 比较有用。
Items 可以包含别的items(孩子)。父items 的转换被它的子孙所继承。然而,它的所有函数(也就是,QGraphicsItem::contains(),QGraphicsItem::boundingRect(),QGraphicsItem
::collidesWith()),不会积累这些转换,依然在本地坐标下工作。
QGraphicsItem 通过QGraphicsItem::shape(),QGraphicsItem::collideWith())
来支持碰撞检测。这两个都是虚函数。从shape()返回你的item 的形状(以本
地坐标QPainterPath 表示),QGraphicsItem 会为你处理所有的碰撞检测。假如你想提供自己的碰撞检测,你应该重新实现QGraphicsItem::collideWith()。
转载自:http://www.cppblog.com/yuanyajie/archive/2007/09/26/32960.html
Graphics View 基于笛卡尔坐标系。item 在场景中的位置与几何形状通过x,y
坐标表示。当使用未经变形的视图来观察场景时,场景中的一个单位等于屏幕上的一个 像素。在Graphics View 中有三个有效的坐标系统:Item 坐标系,场景坐标系,视图坐标系。为了简化你的实现,Graphics View 提供了方便的函数,允许三个坐标系之间相互映射。
当渲染时,Graphics View 的场景坐标对应于QPainter 的逻辑坐标,视图坐标与设备坐标相同。
Items 位于它们自己的坐标系中。它的坐标都以点(0,0)为中心点,这也是所有变换的中心点。在item 坐标系中的几何图元,经常被称为item 点,item 线,item 矩形。当创建一个定制的item,item 坐标是所需要考虑的。QGraphicsScene与QGraphicsView 可以为你执行所有转换,这使得实现定制的item 变得容易。
举例来说,假如你收到鼠标按或是拖进入事件,事件的位置以item 坐标的形式给出。QGraphicsItem::contain()虚函数,当某个点的位置在你的item 范围内时,返回true,否则返回false。这个点参数使用item 坐标,相似地,item 的
包围矩形与形状也使用item 坐标。
Item 位置指的是item 的中心点在它父亲的坐标系中的坐标。以这种思想来看,场景指的就是那些祖先最少的item 的“父亲”。最上级的Item 位置就是在场景中的位置。
子 坐标与父坐标之间是相关的,假如孩子未经变换,子坐标与父坐标之间的差值等于在父坐标系下,父item 与子item 之间的距离。例如,假如一个未经变换的 子item 位置与其父item 的中心重合,那么这两个item 的坐标系统完全相同。
如果孩子的位置是(10,0),那么孩子坐标系中的(0,10)点,对 应于父坐标系中的(10,10)点。
因为item 的位置与变换是相对于父item 的,子item 的坐标不会被父亲的变换
影响,尽管父item 的变 换隐含地对子item 做了变换。在上面的例子中,即使
父item 旋转,缩放,子item 的(0,10)点依然对应于父item 的(10,10)点。然而,相对于场景来讲,子item 会遵循父item 的变换。假如父item 被缩放(2X,2X),子item 的位置在场景中的坐标是(20,0),它的 (10,0)点则与场景中的(40,0)对应 。除了QGraphicsItem::pos(),QGraphicsItem 的函数以Item 坐标工作,如一个item’s 包围矩形总是以item 坐标 的形式给出。
场景坐标系统描述了每个最顶级item 的位置,也是从视图向场景投递场景事件的基础。场景中的每个item 有场景位置与包围矩形
(QGraphicsItem::scenePos(),QGraphicsItem::sceneBoundingRect()), 另
外,它有自己本地item 位置与包围矩形。场景位置描述了item 在场景坐标下的位置,它的场景包围矩形则用于QGraphicsScene 决定场景中哪块区域发生了变化。场景中的变化通过QGraphicsScene::changed()信号来通知,它的参数是场景矩形列表。
视图坐标是widget 的坐 标,视图坐标中每个单位对应一个像素。这种坐标的特殊之处在于它是相对于widget 或是视口的,不会被所观察的场景所影响。
QGraphicsView 的视口的左上角总是(0,0),右下角总是(视口宽,视口高)。
所有的鼠标事件与拖拽事件,最初以视图坐标表示,就应该把这些坐标映射到场景坐标以便与 item 交互。
经常,处理场景中item 时,在场景与item 之间,item 与item 之间,视图与场
景之间进行坐标映射,形状映射是非常有用的。举例来讲,当你在QGraphicsView的视口中点击鼠标时,你应该通过调用QGraphicsView::mapToScence()与
QGraphicsScene::itemAt()来获知光标下是场景中的哪个item。假如你想获知
一个item 位于视口中的什么位置,你应该先在item 上调用
QGraphicsItem::mapToScene(),然后调用QGraphicsView::mapFromScene()。最
后,假如你想在一个视图椭圆中有哪些items,你应该把QPainterPath 传递到
mapToScene(),然后再把映射后的路径传递到QGraphicsScene::items()。
你可以调用QGraphicsItem::mapToScene()与QGraphicsItem::mapFromScene()
在item 与场景之间进行坐标与形状的映射。也可以在item 与其父item 之间通
过QGraphicsItem::mapToParent()与QGraphicsItem::mapFromItem()进行映
射。所有映射函数可以包括点,矩形,多边形,路径。视图与场景之间的映射也与此类似。对于从视图与item 之间的映射,你应该首先映射到场景,然后再从场景向item 进行映射。
转载自:http://www.cppblog.com/yuanyajie/archive/2007/09/26/32961.html
QGraphicsView 通过QGraphicsView::setMatrix()支持同QPainter 一样的仿射
变换,通过对一个视图应用变换,你可以很容易地支持普通的导航特性如缩放与旋转。下面是一个例子:
class View:;public QGraphicsView
{
Q_OBJECT
//.....
public slots:
void zoomIn() {scale(1.2,1.2);}
void zoomOut() {scale(1/1.2,1/1.2);}
void rotateLeft() {rotate(-10);}
void rotateRight() {rotate(10);}
};
这些槽应与QToolButtons 联接,并使autoRepeat 有效。当对视图变换时,
QGraphicsView 会对视图中心进行校正。
因为QGraphicsView 继承自 QWidget,它也提供了像QWidget 那样的拖拽功能,另处,为了方便,Graphics View 柜架也为场景,每个item 提供拖拽支持。当视图接收到拖拽事件,它可翻译为QGraphicsSceneDragDropEvent,再发送到 场景。场景接管这个事件,把它发送到光标下接受拖拽的第一个item。
从一个item 开始拖拽时,创建一个QDrag 对象,传递开始拖拽的那个 widget
的指针。Items 可以同时被多个视图观察,但只有一个视图可以开始拖拽。拖拽在多数情况下是从按下鼠标或是移动鼠标开始的,因此,在 mousePressEvent()
或mouseMoveEvent()中,你可以从事件中得到那个原始的widget 指针,例如:
void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent
*event)
{
QMimeData *data=new QMimeData;
data->setColor(Qt::green);
QDrag *drag=new QDrag(event->widget());
drag->setMimeData(data);
drag->start();
}
为 了在场景中载取拖拽事件,你应重新实现
QGraphicsScene::dragEnterEvent()和在QGraphicsItem 的子类里任何与 你特定场景需要的事件处理器。items 也可以通过调用
QGraphicsItem::setAcceptDrops()获得拖拽支持,为了处理将要进行 的拖拽,你需要重新实现
QGraphicsItem::dragEnterEvent(),QGraphicsItem::dragMoveEvent(),QGraph
icsItem::dragLeaveEvent() 和QGraphicsItem::dropEvent()。
像QWidget 一样,QGraphicsItem 也 支持光标(QgraphicsItem::setCursor)与工具提示(QGraphicsItem::setToolTip())。当光标进入到 item 的区域,光标
与工具提示被QGraphicsView 激活(通过调用QGraphicsItem::contains()检
测)。你也可以直接在 视图上设置一个缺省光标(QGraphicsView::setCursor)。
Graphics View 支持几种级别的动画。你可以很容易地通过把
QGraphicsItemAnimatoin 与你的item 联结来
装配出动画路径,这允许以时间线来控制动画,在所有平台上以稳定的速率运作。
QGraphicsItemAnimation 允许你为item 的位置,旋转,缩放,剪切,变换等产生一条路径,动画可以用QSlider 来控制,或更为普遍使用的QTimeLine。
另一种是从QObject 和QGraphicsItem 继承,item 可以设置自己的定时器,以在QObject::timeEvent()中增加步进的方式来控制动画。
第三种,是通过调用QGraphicsScene::advance()来推进场景,它又依次调用
QGraphicsItem::advance().
为了使用OpenGL 渲染,你要设置一个新的QGLWidget 作为QGraphicsView 的视口:QGraphicsView::setViewPort()。假如你让OpenGL 提供反锯齿功能,你需要OpenGL 采样缓冲支持。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
Item 组通过把一个item 做为另一个item 的孩子,你可以得到item 组的大多数本质特性:这些items 会一起移动,所有变换
会从父到子传递。QGraphicsItem 也可以为它的孩子处理所有的事件,这样就允许以父亲代表它所有的孩子,可以有效地把所有的items 看作一个整体。
另外,QGraphicsItemGroup 是一个特殊的item,它既对孩子事件进行处理又有一个接口把items 从一个组中增加和删除。把一个item 加到
QGraphicsItemGroup 仍会保留item 的原始位置与变换,而给一个item 重新指定父item 则会让item 根据其新的父亲重新定位。可以用
QGraphicsScene::createItemGroup()建组。
转自自:http://www.cppblog.com/yuanyajie/archive/2007/09/27/32962.html
待续。。。学习笔记之Qt从入门到精通(四)
参考链接:https://blog.csdn.net/weixin_41486034/article/details/106379306