存档

‘Noshow’ 分类的存档

Qt学习之路(18): Qt标准对话框之QInputDialog

2009年11月5日

这是Qt标准对话框的最后一部分。正如同其名字显示的一样,QInputDialog用于接收用户的输入。QInputDialog提供了一些简单的static函数,用于快速的建立一个对话框,正像QColorDialog提供了getColor函数一样。

首先来看看getText函数:

bool isOK;
QString text = QInputDialog::getText(NULL, "Input Dialog",
                                                   "Please input your comment",
                                                   QLineEdit::Normal,
                                                   "your comment",
                                                   &isOK);
if(isOK) {
       QMessageBox::information(NULL, "Information",
                                           "Your comment is: <strong>" + text + "</strong>",
                                           QMessageBox::Yes | QMessageBox::No,
                                           QMessageBox::Yes);
}

代码比较简单,使用getText函数就可以弹出一个可供用户输入的对话框:

200910311256957470355

下面来看一下这个函数的签名:

static QString QInputDialog::getText ( QWidget * parent,
                                                      const QString &amp; title,
                                                      const QString &amp; label,
                                                      QLineEdit::EchoMode mode = QLineEdit::Normal,
                                                      const QString &amp; text = QString(),
                                                      bool * ok = 0,
                                                      Qt::WindowFlags flags = 0 )

第一个参数parent,也就是那个熟悉的父组件的指针;第二个参数title就是对话框的标题;第三个参数label是在输入框上面的提示语句;第四个参数mode用于指明这个QLineEdit的输入模式,取值范围是QLineEdit::EchoMode,默认是Normal,也就是正常显示,你也可以声明为password,这样就是密码的输入显示了,具体请查阅API;第五个参数text是QLineEdit的默认字符串;第六个参数 ok是可选的,如果非NLL,则当用户按下对话框的OK按钮时,这个bool变量会被置为true,可以由这个去判断用户是按下的OK还是Cancel,从而获知这个text是不是有意义;第七个参数flags用于指定对话框的样式。

虽然参数很多,但是每个参数的含义都比较明显,大家只要参照API就可以知道了。

函数的返回值是QString,也就是用户在QLineEdit里面输入的内容。至于这个内容有没有意义,那就要看那个ok参数是不是true了。

QInputDialog不仅提供了获取字符串的函数,还有getInteger,getDouble,getItem三个类似的函数,这里就不一一介绍。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/219338

Noshow ,

Code Project:创建一个FFMPEG前端

2009年11月4日

命令行没有什么不好。对于我们很多人来说,这是使用Linux的最佳理由之一。可以通过输入内容实现几乎所有功能,而且命令行工具对于它们的运行方式通常能够提供极好的控制。但是命令行并不适合所有人,觉得命令行难以理解和令人生畏的Linux用户数量多得令人吃惊,这或许是完全避免使用Linux的理由之一。尽管如今不愿意使用命令行的用户可以不必再使用它,但这仍然意味着他们将遗漏一些很优秀的实用工具。

Qt正好可以扭转这种局面。它是为你最喜爱的命令行工具创建友好GUI的理想工具。它不需要任何顶级的编程技巧,而且工作量也不大,但在此过程中你可以为讨厌命令行的朋友提供帮助,并且对开源应用程序开发做出自己的贡献。为命令行工具创建GUI是最佳起点之一!

如果你已经完成了我们以前的Qt Creator代码项目,如何创建自己的媒体播放器,就能够更加自如地完成这个任务……

如果说有一种工具非常需要GUI,那就是FFMPEG。FFMPEG是一个十分优秀的命令行应用程序,它可以将视频和电影文件从一种格式转换为另一种格式。同时它的复杂程度也令人吃惊。有好几位开发人员都曾经试过为这个工具创建一个GUI,但是FFMPEG的发展速度让人很难驾驭所有额外的组件和用于各个选项的不断改变的语法。

我们将会构建我们自己的FFMPEG GUI,但本指南的意义并不在于帮助人们了解FFMPEG的非凡之处。本指南将展示为几乎任意命令行工具创建GUI是多么轻松的事情——只要替换几行代码即可,而且结果可能对我们大有好处。

qt_menq_step_01

* 应该只有意志坚定和不怕阅读符号文字的人才会输入“ffmpeg -h”

应用程序设计

我们将假定已经启动并正在运行Creator,而且对于使用开发环境相对比较满意。如果不满足上述条件,请复习我们以前的指南。运行应用程序之后,创建一个新的项目并使用“Qt Gui Application”模板,同时保留其他选项的默认值。

和以前的指南一样,我们的开发工作仍然从GUI开始。点击“.ui”文件,Designer视图随之出现。如果想从空白画布开始,可在视图中删除菜单栏、工具栏和状态栏,方法是在窗口右上方的对象视图中选中它们,右键单击每个组件,然后从出现的菜单中选择删除。

我们应用程序的GUI布局显然依赖于要运行的命令行工具。但对于我们的FFMPEG例子,我们要尽可能尝试保持内容的开放性和可修改性。我们需要一个按钮来添加要转换的源文件,以及一个用于显示此文件位置的文本字段。我们需要一些途径来选择转换过程的最终格式,并显示FFMPEG命令的结果。我们还需要另一个按钮,点击它便可启动整个转换过程。

但是用户界面的主要部分将会被我们选出让用户编辑的FFMPEG选项所占据。我们没有理由向普通用户公开FFMPEG中数以百计的选项,因此这里只会出现最常用和最简单的使用选项。

qt_menq_step_02

* 我们的目标是构建出的布局能够最好地利用空间。这也正是我们选择在另一个页面上隐藏命令行输出的原因。

GUI的构建

从左边的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”操作。

qt_menq_spacers

最好把间距器看作弹簧,并重新复习一些旧的物理课本。

为了保证我们的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()”。

qt_menq_step_03

我们从GUI创建我们自己的槽,而且后面需要在源代码编辑器中为它们添加代码。

完成这项任务之后,Designer编辑就结束了,保存项目并切换为编辑MainWindow.h。我们需要在文件顶部添加几个头:

#include <QProcess>
#include <QByteArray>
#include <QTextBrowser>
#include <QFileDialog>
#include <QDesktopServices>
#include <QComboBox>

因为我们要对已经在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;

qt_menq_step_04

* 我们从GUI创建我们自己的槽,而且后面需要在源代码编辑器中为它们添加代码。

编程

现在我们到了最富于技巧性的部分,即给应用程序添加功能。首先,我们要为三种要处理的不同输出格式编写“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)。需要为其他目的设备添加更多参数,才能让转换过程开始工作。

qt_menq_step_05

* 来自FFPMEG命令的输出将显示在我们的主应用程序的输出选项卡中。

在用户点击我们在GUI中创建的“Go”按钮时,将执行这个函数,而且由于我们早先配置好的信号和槽,这个过程也是自动的。但是我们还需要捕捉该过程的输出,以便在文本视图中显示出来,同时给用户提供一些可视的反馈。令人高兴的是,由于信号与槽的神奇魔力,Qprocess可以不太费力地完全执行这种操作。

在MainWindow::MainWindow初始化例行程序中,我们需要在Qprocess输出信号和我们将用于把输出转换为文本以便于显示的“outputCommand”槽之间手动创建连接。在“ui->setupUi”行前面添加如下两行:

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::MoviesLocation));
    ui->lineEdit_2->setText(file);
}

第一行创建了一个Qt文件请求器,自动指向系统默认的电影位置,而因为我们已经使用了“getSaveFileName”,用户将被询问一个不一定存在的文件的名称。这与“setSource”完全相反:

void MainWindow::setSource()
{
     QString file = QFileDialog::getOpenFileName(this, tr("Select Source File"),
         QDesktopServices::storageLocation(QDesktopServices::MoviesLocation));
    ui->lineEdit->setText(file);
 
}

输入这最后两个函数后,我们应用程序的源代码就已经完成了,应该有一个可用的FFMPEG GUI,可以自定义它以使用所需的任意参数。编译并运行就可以了。还应该看到,修改这些代码以使用其他命令行工具是多么轻松的事情。

qt_menq_final_pic

下载此项目的代码:qt_menq.tar

借助基于Qt的前端,用户就不必深入研究FFMPEG的命令行用法了。

原文:http://www.tuxradar.com/content/code-project-create-ffmpeg-front-end

翻译:CSDN

Noshow , , ,

Code Project:创建一个Qt RSS阅读器

2009年10月30日

我们将构建一个完整的应用程序,使其不必太费事便可重新发布为一个真正的开源应用程序。这个应用程序就是一个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”文件中。

设计GUI

和我们其他的Qt编程指南一样,在运行Creator创建一个新项目后,接下来要做的工作就是GUI设计。点击“ui”文件打开Designer视图。这一次,我们将采用稍微开放一点的方法进行设计。主窗口将被划分为两个面板。在左侧,我们将添加RSS消息列表,并让用户能够添加他们自己的种子。而窗口的右半部分将是web浏览器,我们将对这部分使用WebKit小部件。

但是Qt的聪明之处在于,我们可以根据用户是否想使用内部浏览器来使每个面板变得可扩展或可隐藏,或者干脆使用他们最惯于使用的浏览器。例如,如果用户不想看到web视图,只需要把中间的分离线拖到右边,它就会消失。这给予了我们的应用程序很大的灵活性,并不强迫想使用自己的浏览器阅读新闻的用户使用web视图。

这项特别的功能是通过Qt中的Dock Widget小部件实现的。当应用程序分为几个部分时,它提供了很强的灵活性,允许用户在四周拖放窗口的不同部分。从Creator页面的Containers列表中拖出两个Dock Widget小部件到空白的应用程序画布上。如果在应用程序中用不着,还可以从Object视图删除多余的菜单、工具栏和状态面板小部件。我们已经添加了两个可停靠小部件,因为我们要在应用程序的两侧使用它们来保存小部件,而且它们是可停靠小部件,用户能够拖动它们之间的分离线,从而改变应用程序每个半区的尺寸。

但在添加更多小部件之前,我们需要确保只启用了每个可停靠小部件的一组有限功能。我们不想让用户完全访问Qt 可停靠小部件更多难以驾驭的功能,KDevelop已经很好地证明了这一点。在Object列表中选择每个可停靠小部件,然后在下面的属性窗口中,确保将选中的“Features”字段设置为“NoDockWidgetFeatures”。这将阻止用户将小部件拖动至窗口外部或者完全关闭它们。你可能想对浏览器面板启用这项功能,这由你自己决定。

a

* 可停靠小部件的优点是,用户可在应用程序运行时改变两侧的比例差异。

小部件面板

在添加其他小部件之前,选择可停靠小部件并点击“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”模式,从而锁定可缩放窗口的布局。

a

* 在可停靠小部件的左侧,我们添加了树视图,XML 提要的 URL,以及用于从Internet抓取数据的按钮。

连接

既然我们的布局已经最终确定,下一步就要添加槽/信号连接,用于补充我们应用程序的功能。切换到Signals/Slots编辑器,方法是按下F4键或者在工具栏中点击相应按钮。从“Add Feed”按钮拖动一个信号到应用程序窗口的轮廓处,当“Configure Connection”窗口出现时,点击右侧面板上的“Edit”按钮。

我们需要添加两个槽。第一个用于给树视图添加种子,而另一个用于当用户在种子列表中选择一个新闻内容时更新web视图。我们将第一个槽称为“fetch()”,而将第二个槽称为“itemActivated(QTreeWidgetItem*)”。这是我们首次遇到通过信号/槽机制传递的参数,要在设计器中使用它们,必须满足一些严格的规则。其中最重要的一条是,对于一个在传递这类参数时要连接到槽的信号,二者都必须完全支持同一类型。在这个例子中是QTreeWidgetItem类型。

创建这两个槽并将“clicked”连接到“fetch()”之后,从树视图拖一个新连接到窗口背景。我们将看到,很多函数将QItemTreeTree参数作为一个参数包含在内。这是树视图中每一项的类型,以这种方式传递它使我们能够轻松抓取到当前选中的新闻内容的URL,并使用它来更新web浏览器。只要将位于左边的“’itemActivated(QTreeWidgetItem*)”与我们刚刚为自己的应用程序创建的名称相同的新槽连接起来即可。

c

* 这是我们在Creator中建立并用于应用程序中各个函数的SIGNAL和SLOT连接的一个视图。

代码

现在我们已经建立了框架,是时候添加代码了。和我们其他的项目一样,我们从“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();
 
 }

d

* 尝试使用调试器来理解我们项目中一些更为复杂的函数。

运行应用程序

以上就是需要完成的全部工作。剩下的就是保存项目,编译和运行。点击“Add Feed”按钮可以添加我们在GUI中创建的默认RSS种子,而且我们应该看到,位于左侧的树视图使用了来自TuxRadar.com的所有最新内容进行填充。点击其中任意的内容,右侧的web查看器就会加载相应的页面。

但这个应用程序的最大优点是缩放两个面板的方式。在树视图和web页面之间,应该能够找到三个很小的垂直点。可以把这些点拖到左边或右边,从而改变RSS种子或正在显示的web页面的比例。如果将这一条完全移到右边,web视图就会完全关闭。如果我们只想看到种子列表,这是个不错的主意。

可以轻松给这个应用程序添加所需要的内容。我们从一个具有自动功能的刷新按钮开始。这个按钮功能是大约每小时增加一次新种子,或者在点击它时手动刷新。应用程序还迫切需要保存其设置的功能。这是一项十分艰巨的任务,几乎可以作为另一份指南的主题——为什么不亲自写写试试看呢?

e

完成之后的应用程序:密切注意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

Noshow , , , ,

Qt学习之路(17): Qt标准对话框之QMessageBox

2009年10月30日

这次来说一下QMessageBox以及类似的几种对话框。其实,我们已经用过QMessageBox了,就在之前的几个程序中。不过,当时是大略的说了一下,现在专门来说说这几种对话框。

先来看一下最熟悉的QMessageBox::information。我们在以前的代码中这样使用过:

QMessageBox::information(NULL, "Title", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

下面是一个简单的例子:

200910271256653086390

现在我们从API中看看它的函数签名:

static StandardButton QMessageBox::information ( QWidget * parent, const QString &amp; title, const QString &amp; text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton );

首先,它是static的,所以我们能够使用类名直接访问到(怎么看都像废话…);然后看它那一堆参数,第一个参数parent,说明它的父组件;第二个参数title,也就是对话框的标题;第三个参数text,是对话框显示的内容;第四个参数buttons,声明对话框放置的按钮,默认是只放置一个OK按钮,这个参数可以使用或运算,例如我们希望有一个Yes和一个No的按钮,可以使用QMessageBox::Yes | QMessageBox::No,所有的按钮类型可以在QMessageBox声明的StandarButton枚举中找到;第五个参数defaultButton就是默认选中的按钮,默认值是NoButton,也就是哪个按钮都不选中。这么多参数,豆子也是记不住的啊!所以,我们在用QtCreator写的时候,可以在输入 QMessageBox::information之后输入(,稍等一下,QtCreator就会帮我们把函数签名显示在右上方了,还是挺方便的一个功能!

Qt提供了五个类似的接口,用于显示类似的窗口。具体代码这里就不做介绍,只是来看一下样子吧!

QMessageBox::critical(NULL, "critical", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

200910271256654451593

QMessageBox::warning(NULL, "warning", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

200910271256654473703

QMessageBox::question(NULL, "question", "Content", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

200910271256654490093

QMessageBox::about(NULL, "About", "About this application");

200910271256654506515

请注意,最后一个about()函数是没有后两个关于button设置的按钮的!

QMessageBox对话框的文本信息时可以支持HTML标签的。例如:

QMessageBox::about(NULL, "About", "About this &lt;font color='red'&gt;application&lt;/font&gt;");

运行效果如下:

200910271256654692546

如果我们想自定义图片的话,也是很简单的。这时候就不能使用这几个static的函数了,而是要我们自己定义一个QMessagebox来使用:

QMessageBox message(QMessageBox::NoIcon, "Title", "Content with icon.");
message.setIconPixmap(QPixmap("icon.png"));
message.show();

需要注意的是,同其他的程序类似,我们在程序中定义的相对路径都是要相对于运行时的.exe文件的地址的。比如我们写”icon.png”,意思是是在.exe的当前目录下寻找一个”icon.png”的文件。这个程序的运行效果如下:

200910271256656595281

还有一点要注意,我们使用的是png格式的图片。因为Qt内置的处理图片格式是png,所以这不会引起很大的麻烦,如果你要使用jpeg格式的图片的话,Qt是以插件的形式支持的。在开发时没有什么问题,不过如果要部署的话,需要注意这一点。

最后再来说一下怎么处理对话框的交互。我们使用QMessageBox类的时候有两种方式,一是使用static函数,另外是使用构造函数。

首先来说一下static函数的方式。注意,static函数都是要返回一个StandardButton,我们就可以通过判断这个返回值来对用户的操作做出相应。

QMessageBox::StandardButton rb = QMessageBox::question(NULL, "Show Qt", "Do you want to show Qt dialog?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if(rb == QMessageBox::Yes)
{
    QMessageBox::aboutQt(NULL, "About Qt");
}

如果要使用构造函数的方式,那么我们就要自己运行判断一下啦:

QMessageBox message(QMessageBox::NoIcon, "Show Qt", "Do you want to show Qt dialog?", QMessageBox::Yes | QMessageBox::No, NULL);
if(message.exec() == QMessageBox::Yes)
{
    QMessageBox::aboutQt(NULL, "About Qt");
}

其实道理上也是差不多的。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/217694

Noshow ,

Code Project:创建一个媒体播放器

2009年10月30日

无论从功能还是大小来讲,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对话即可。

1(2)

* Qt Creator通常是标准Qt 4.5安装的一部分,但Ubuntu用户将需要手动抓取这个包。

起步

现在,如你已经安装了所有软件,接下来就可以正式开始了。运行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用于编译项目的项目文件。

2(2)

* 如果你忘记在Creator的启动向导中包含所需模块,可以自己把它们添加到项目的“.pro”文件中。

GUI设计

在项目窗格中双击“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参数的最大值设为和最小尺寸相同的数字。

3(2)

* 使用Creator构建GUI需要一个熟悉的过程,但是基本上只要从左侧拖动组件,并将它们固定在布局引擎的合适位置上即可。

编写

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编辑的最后一部分,保存工作进展,让我们开始下一个步骤。

3(3)

* 了解信号和槽是Qt编程最重要的方面。

编码:mainwindow.h

我们已经为我们的应用程序创建了框架,现在只要添加功能即可。点击“mainwindow.h”,并在顶部添加如下代码行:

#include <QList>
#include <QFileDialog>
#include <QDesktopServices>
#include <Phonon>

以上代码的作用是,通过头文件导入我们要在代码中使用的Qt函数。现在我们需要添加我们的槽,它们在我们前面编辑过的ui文件中已经定义好了。在“public:”部分中的“~MainWindow();”行正下方,添加如下代码:

private slots:
    void playPause();
    void addFiles();
    void nextFile();
    void aboutToFinish();
    void finished();

可以看到,这些槽对应于我们在用户界面中创建的名称,而且我们还添加了更多的槽来处理内部通信。最后,在“private”部分中添加如下变量,我们将在应用程序的主要逻辑中用到这些变量:

    QList<Phonon::MediaSource> 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();”命令之后将自动从当前位置继续。

4
* 在Creator中输入代码时,可使用自动完成功能找到所需函数。

我们还尝试给用户提供一些关于当前播放状态的反馈。如果音乐正在播放,我们会让“Play”按钮凹下去,使其看起来好像是被按下一样。如果音乐处于任意其他状态,我们会让“Play”按钮恢复原样,使其看起来处于停止状态。为了实现这一点,需要将播放按钮取名为“pushButtonPlay”。可以在Designer视图中修改它的名称,方法是选择该按钮并修改“objectName”值。

void MainWindow::addFiles()
{
    QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Music Files"),
        QDesktopServices::storageLocation(QDesktopServices::MusicLocation));
 
    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(index));
        mediaObject->setCurrentSource(metaInformationResolver->currentSource());
 
    }
}

现在,让我们开始实现添加文件的部分。创建文件请求器几乎是自动的,我们可以把结果放到一个字符串列表中。“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);
 
}

下载此项目的代码

5

它看起来可能不起眼,但我们的音乐播放应用程序具备了一款音乐播放器所需要的大部分功能。

原文链接:http://www.tuxradar.com/content/code-project-create-media-player
翻译CSDN:http://qt.csdn.net/articles.aspx?pointid=174

Noshow , , ,

Qt学习之路(tip): parent参数

2009年10月23日

这是一篇很简单的文章,仅仅是用来说明一下一个参数的作用,因此我把它写成了tip,而不是接下来的17.

程序写的多了,你会发现几乎所有的Qt类的构造函数都会有一个parent参数。这个参数通常是QObject* 或者是 QWidget* 类型的。很多情况下它都会有一个初始值0,因此,即便你不去给它复制也没有丝毫的问题。于是,稍微偷懒一下,就会不自觉的忽略了这个参数。那么,这个参数到底是干什么用的呢?

其实,这个参数有很多用处。就像它的名字一样,这个参数指定了组件的父组件。对于一个对话框来说,对话框一般是不作为顶层容器出现的,因此在任务栏上一般是没有对话框的位置的。怎么指定这个对话框不是顶层容器呢?有父组件的组件不就不是顶层容器了吗?因此,只要你指定对话框的parent属性,任务栏就不会出现它的身影。当然,如果你不指定,这个对话框就成为顶层容器了,任务栏会给它留个位置的——利用这个特性,就可以实现特殊对话框可以在任务栏出现的效果,比如“关于”对话框的出现。

另外比较通用,也是很重要的作用是,parent参数指明了组件的父组件,这样,当父组件delete时,Qt可以保证所有子组件——也就是 parent指针指向这个组件的所有组件——都会被正确的delete掉。这是Qt能够帮助我们管理一部分内存的原因所在。Qt是通过遍历parent属性来防止了这一部分内存泄漏的。因此,必要情况下还是不要忘记设置这个parent属性。当然,如果你不声明这个属性,当整个程序关闭时,操作系统会回收内存——因此我们所说的内存泄漏一般是指我们自己写的应用程序的内部,而不会影响到整个操作系统——当然,如果你实现太可恶,操作系统也会受不了自动关掉你的程序的:-)

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/214166

Noshow ,

Qt学习之路(16): Qt标准对话框之QColorDialog

2009年10月20日

继续来说Qt的标准对话框,这次说说QColorDialog。这是Qt提供的颜色选择对话框。

使用QColorDialog也很简单,Qt提供了getColor()函数,类似于QFileDialog的getOpenFileName(),可以直接获得选择的颜色。我们还是使用前面的QAction来测试下这个函数:

QColor color = QColorDialog::getColor(Qt::white, this);
QString msg = QString("r: %1, g: %2, b: %3").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()));
QMessageBox::information(NULL, "Selected color", msg);

不要忘记include QColorDialog哦!这段代码虽然很少,但是内容并不少。

第一行QColorDialog::getColor()调用了QColorDialog的static函数getColor()。这个函数有两个参数,第一个是QColor类型,是对话框打开时默认选择的颜色,第二个是它的parent。

第二行比较长,涉及到QString的用法。如果我没记错的话,这些用法还没有提到过,本着“有用就说”的原则,尽管这些和 QColorDialog毫不相干,这里还是解释一下。QString(“r: %1, g: %2, b: %3″)创建了一个QString对象。我们使用了参数化字符串,也就是那些%1之类。在Java的properties文件中,字符参数是用{0}, {1}之类实现的。其实这都是一些占位符,也就是,后面会用别的字符串替换掉这些值。占位符的替换需要使用QString的arg()函数。这个函数会返回它的调用者,因此可以使用链式调用写法。它会按照顺序替换掉占位符。然后是QString::number()函数,这也是QString的一个 static函数,作用就是把int、double等值换成QString类型。这里是把QColor的R、G、B三个值输出了出来。关于QString 类,我们会在以后详细说明。

第三行就比较简单了,使用一个消息对话框把刚刚拼接的字符串输出。

现在就可以运行这个测试程序了。看上去很简单,不是吗?

QColorDialog还有一些其他的函数可以使用。

QColorDialog::setCustomColor()可以设置用户自定义颜色。这个函数有两个值,第一个是自定义颜色的索引,第二个是自定义颜色的RGB值,类型是QRgb,大家可以查阅API文档来看看这个类的使用,下面只给出一个简单的用发:

QColorDialog::setCustomColor(0, QRgb(0x0000FF));

getColor()还有一个重载的函数,签名如下:

QColorDialog::( const QColor &amp; initial, QWidget * parent, const QString &amp; title, ColorDialogOptions options = 0 )

第一个参数initial和前面一样,是对话框打开时的默认选中的颜色;

第二个参数parent,设置对话框的父组件;

第三个参数title,设置对话框的title;

第四个参数options,是QColorDialog::ColorDialogOptions类型的,可以设置对话框的一些属性,如是否显示Alpha值等,具体属性请查阅API文档。特别的,这些值是可以使用OR操作的。

QColorDialog相对简单一些,API文档也很详细,大家遇到问题可以查阅文档的哦!

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/214164

Noshow ,

Qt学习之路(15): Qt标准对话框之QFileDialog

2009年10月20日

《Qt学习之路》已经写到了第15篇,然而现在再写下去却有点困难,原因是当初并没有想到会连续的写下去,因此并没有很好的计划这些内容究竟该怎样去写。虽然前面说过,本教程主要线路参考《C++ Gui Programming with Qt 4, 2nd Edition》,然而最近的章节由于原文是一个比较完整的项目而有所改变,因此现在不知道该从何写起。

我并不打算介绍很多组件的使用,因为Qt有很多组件,各种组件用法众多,根本不可能介绍完,只能把API放在手边,边用边查。所以,对于很多组件我只是简单的介绍一下,具体用法还请自行查找(确切地说,我知道的也并不多,很多时候还是要到API里面去找)。

下面还是按照我们的进度,从Qt的标准对话框开始说起。所谓标准对话框,其实就是Qt内置的一些对话框,比如文件选择、颜色选择等等。今天首先介绍一下QFileDialog。

QFileDialog是Qt中用于文件打开和保存的对话框,相当于Swing里面的JFileChooser。下面打开我们前面使用的工程。我们已经很有先见之明的写好了一个打开的action,还记得前面的代码吗?当时,我们只是弹出了一个消息对话框(这也是一种标准对话框哦~)用于告知这个信号槽已经联通,现在我们要写真正的打开代码了!

修改MainWindow的open函数:

void MainWindow::open()
{
QString path = QFileDialog::getOpenFileName(this, tr("Open Image"), ".", tr("Image Files(*.jpg *.png)"));
if(path.length() == 0) {
QMessageBox::information(NULL, tr("Path"), tr("You didn't select any files."));
} else {
QMessageBox::information(NULL, tr("Path"), tr("You selected ") + path);
}
}

编译之前别忘记include QFileDialog哦!然后运行一下吧!点击打开按钮,就会弹出打开对话框,然后选择文件或者直接点击取消,会有相应的消息提示。

QFileDialog提供了很多静态函数,用于获取用户选择的文件。这里我们使用的是getOpenFileName(), 也就是“获取打开文件名”,你也可以查看API找到更多的函数使用。不过,这个函数的参数蛮长的,而且类型都是QString,并不好记。考虑到这种情况,Qt提供了另外的写法:

QFileDialog *fileDialog = new QFileDialog(this);
fileDialog-&gt;setWindowTitle(tr("Open Image"));
fileDialog-&gt;setDirectory(".");
fileDialog-&gt;setFilter(tr("Image Files(*.jpg *.png)"));
if(fileDialog-&gt;exec() == QDialog::Accepted) {
QString path = fileDialog-&gt;selectedFiles()[0];
QMessageBox::information(NULL, tr("Path"), tr("You selected ") + path);
} else {
QMessageBox::information(NULL, tr("Path"), tr("You didn't select any files."));
}

不过,这两种写法虽然功能差别不大,但是弹出的对话框却并不一样。getOpenFileName()函数在Windows和MacOS X平台上提供的是本地的对话框,而QFileDialog提供的始终是Qt自己绘制的对话框(还记得前面说过,Qt的组件和Swing类似,也是自己绘制的,而不都是调用系统资源API)。

为了说明QFileDialog::getOpenFileName()函数的用法,还是先把函数签名放在这里:

QString QFileDialog::getOpenFileName (
QWidget * parent = 0,
const QString &amp; caption = QString(),
const QString &amp; dir = QString(),
const QString &amp; filter = QString(),
QString * selectedFilter = 0,
Options options = 0 )

第一个参数parent,用于指定父组件。注意,很多Qt组件的构造函数都会有这么一个parent参数,并提供一个默认值0;

第二个参数caption,是对话框的标题;

第三个参数dir,是对话框显示时默认打开的目录,”.” 代表程序运行目录,”/” 代表当前盘符的根目录(Windows,Linux下/就是根目录了),也可以是平台相关的,比如”C:\\”等;

第四个参数filter,是对话框的后缀名过滤器,比如我们使用”Image Files(*.jpg *.png)”就让它只能显示后缀名是jpg或者png的文件。如果需要使用多个过滤器,使用”;;”分割,比如”JPEG Files(*.jpg);;PNG Files(*.png)”;

第五个参数selectedFilter,是默认选择的过滤器;

第六个参数options,是对话框的一些参数设定,比如只显示文件夹等等,它的取值是enum QFileDialog::Option,每个选项可以使用 | 运算组合起来。

如果我要想选择多个文件怎么办呢?Qt提供了getOpenFileNames()函数,其返回值是一个QStringList。你可以把它理解成一个只能存放QString的List,也就是STL中的list<string>。

好了,我们已经能够选择打开文件了。保存也是类似的,QFileDialog类也提供了保存对话框的函数getSaveFileName,具体使用还是请查阅API。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/213414

Noshow ,

Qt学习之路(14): 状态栏

2009年10月20日

今天的内容主要还是继续完善前面的那个程序。我们要为我们的程序加上一个状态栏。

状态栏位于主窗口的最下方,提供一个显示工具提示等信息的地方。一般地,当窗口不是最大化的时候,状态栏的右下角会有一个可以调节大小的控制点;当窗口最大化的时候,这个控制点会自动消失。Qt提供了一个QStatusBar类来实现状态栏。

Qt具有一个相当成熟的GUI框架的实现——这一点感觉比Swing要强一些——Qt似乎对GUI的开发做了很多设计,比如 QMainWindow类里面就有一个statusBar()函数,用于实现状态栏的调用。类似menuBar()函数,如果不存在状态栏,该函数会自动创建一个,如果已经创建则会返回这个状态栏的指针。如果你要替换掉已经存在的状态栏,需要使用QMainWindow的setStatusBar()函数。

在Qt里面,状态栏显示的信息有三种类型:临时信息、一般信息和永久信息。其中,临时信息指临时显示的信息,比如QAction的提示等,也可以设置自己的临时信息,比如程序启动之后显示Ready,一段时间后自动消失——这个功能可以使用QStatusBar的showMessage()函数来实现;一般信息可以用来显示页码之类的;永久信息是不会消失的信息,比如可以在状态栏提示用户Caps Lock键被按下之类。

QStatusBar继承自QWidget,因此它可以添加其他的QWidget。下面我们在QStatusBar上添加一个QLabel。

首先在class的声明中添加一个私有的QLabel属性:

private:
QAction *openAction;
QLabel *msgLabel;

然后在其构造函数中添加:

msgLabel = new QLabel;
msgLabel-&gt;setMinimumSize(msgLabel-&gt;sizeHint());
msgLabel-&gt;setAlignment(Qt::AlignHCenter);
 
statusBar()-&gt;addWidget(msgLabel);

这里,第一行创建一个QLabel的对象,然后设置最小大小为其本身的建议大小——注意,这样设置之后,这个最小大小可能是变化的——最后设置显示规则是水平居中(HCenter)。最后一行使用statusBar()函数将这个label添加到状态栏。编译运行,将鼠标移动到工具条或者菜单的 QAction上,状态栏就会有相应的提示:

200910101255188332562

看起来是不是很方便?只是,我们很快发现一个问题:当没有任何提示时,状态栏会有一个短短的竖线:

200910101255188413984

这是什么呢?其实,这是QLabel的边框。当没有内容显示时,QLabel只显示出自己的一个边框。但是,很多情况下我们并不希望有这条竖线,于是,我们对statusBar()进行如下设置:

statusBar()-&gt;setStyleSheet(QString("QStatusBar::item{border: 0px}"));

这里先不去深究这句代码是什么意思,简单来说,就是把QStatusBar的子组件的border设置为0,也就是没有边框。现在再编译试试吧!那个短线消失了!

QStatusBar右下角的大小控制点可以通过setSizeGripEnabled()函数来设置是否存在,详情参见API文档。

好了,现在,我们的状态栏已经初步完成了。由于QStatusBar可以添加多个QWidget,因此,我们可以构建出很复杂的状态栏。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/210947

Noshow ,

Qt学习之路(13): 菜单和工具条(续)

2009年10月20日

前面一节我们已经把QAction添加到菜单和工具条上面。现在我们要添加一些图片美化一下,然后把信号槽加上,这样,我们的action就可以相应啦!

首先来添加图标。QAction的图标会显示在菜单项的前面以及工具条按钮上面显示。

为了添加图标,我们首先要使用Qt的资源文件。在QtCreator的项目上右击,选择New File…,然后选择resource file。

200909261253934162015

然后点击next,选择好位置,Finish即可。为了使用方便,我就把这个文件建在根目录下,建议应该在仔细规划好文件之后,建在专门的 rsources文件夹下。完成之后,生成的是一个.qrc文件,qrc其实是Qt Recource Collection的缩写。它只是一个普通的XML文件,可以用记事本等打开。不过,这里我们不去深究它的结构,完全利用QtCreator操作这个文件,

200909261253934734656

点击Add按钮,首先选择Add prefix,然后把生成的/new/prefix改成/。这是prefix就是以后使用图标时需要提供的前缀,以/开头。添加过prefix之后,然后在工程文件中添加一个图标,再选择Add file,选择那个图标。这样完成之后保存qrc文件即可。

说明一下,QToolBar的图标大小默认是32*32,菜单默认是16*16。如果提供的图标小于要求的尺寸,则不做操作,Qt不会为你放大图片;反之,如果提供的图标文件大于相应的尺寸要求,比如是64*64,Qt会自动缩小尺寸。

200909261253935150218

图片的路径怎么看呢?可以看出,Qt的资源文件视图使用树状结构,根是/,叶子节点就是图片位置,连接在一起就是路径。比如这张图片的路径就是/Open.png。

注意,为了简单起见,我们没有把图标放在专门的文件夹中。正式的项目中应该单独有一个resources文件夹放资源文件的。

然后回到前面的mainwindow.cpp,在构造函数中修改代码:

openAction = new QAction(tr("&amp;Open"), this);
openAction-&gt;setShortcut(QKeySequence::Open);
openAction-&gt;setStatusTip(tr("Open a file."));
openAction-&gt;setIcon(QIcon(":/Open.png")); // Add code.

我们使用setIcon添加图标。添加的类是QIcon,构造函数需要一个参数,是一个字符串。由于我们要使用qrc中定义的图片,所以字符串以 : 开始,后面跟着prefix,因为我们先前定义的prefix是/,所以就需要一个/,然后后面是file的路径。这是在前面的qrc中定义的,打开 qrc看看那张图片的路径即可。

好了,图片添加完成,然后点击运行,看看效果吧!

200909261253936132515

瞧!我们只需要修改QAction,菜单和工具条就已经为我们做好了相应的处理,还是很方便的!

下一步,为QAction添加事件响应。还记得Qt的事件响应机制是基于信号槽吗?点击QAction会发出triggered()信号,所以,我们要做的是声名一个slot,然后connect这个信号。

mainwindow.h
 
class MainWindow : public QMainWindow
{
Q_OBJECT
 
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
 
private slots:
void open();
 
private:
QAction *openAction;
};

因为我们的open()目前只要在类的内部使用,因此定义成private slots即可。然后修改cpp文件:

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
openAction = new QAction(tr("&amp;Open"), this);
openAction-&gt;setShortcut(QKeySequence::Open);
openAction-&gt;setStatusTip(tr("Open a file."));
openAction-&gt;setIcon(QIcon(":/Open.png"));
connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
 
QMenu *file = menuBar()-&gt;addMenu(tr("&amp;File"));
file-&gt;addAction(openAction);
 
QToolBar *toolBar = addToolBar(tr("&amp;File"));
toolBar-&gt;addAction(openAction);
}
 
void MainWindow::open()
{
QMessageBox::information(NULL, tr("Open"), tr("Open a file"));
}

注意,我们在open()函数中简单的弹出一个标准对话框,并没有其他的操作。编译后运行,看看效果:

200909261253936622515

好了,关于QAction的动作也已经添加完毕了!

至此,QAction有关的问题先告一段落。最后说一下,如果你还不知道怎么添加子菜单的话,看一下QMenu的API,里面会有一个addMenu函数。也就是说,创建一个QMenu然后添加就可以的啦!

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/205958

Noshow ,