存档在 ‘GUI开发’ 分类

基于socket API的C/S通信:将Qt程序从Linux移植到windows

2011年7月30日

Qt是一个跨平台的GUI开发语言,它是对C++在图形设计方面上的一种扩充。Qt本身包含一系列用来设计图形界面的类,并且对C++原有的类都进行了再次封装。如果你的程序采用Qt的类库,那么源程序在不同平台重新编译即可;如果程序中使用某些C++或C库函数,那么在移植时就需要针对不同平台作一些改动。

本文所举例的移植代码是基于socket API的C/S模型的客户端,该客户端的原始代码在linux系统下完成(以下称为“源代码”),现在要将其移植到windows平台下(以下称“移植代码”)。源代码的界面部分采用Qt完成,而通信部分则是采用原始的socket接口。

1.加入动态库

在linux系统中,socket API属于libc库,因此只需在程序中加头文件即可。而在windows系统下,由于socket接口是继承Unix系统而来,因此需要加入wsock32库,并且加上相应的头文件winsock2.h。加入该库具体的做法是在工程问价中加入下面的语句:

LIBS += -lwsock32

2.初始化套接字库

在windows下有两套socket API,一种是经过C++封装的csocket类,而另一种则是原始的socket接口,为了降低移植的复杂性,我们采用后者。

在将wsock32动态库加入移植程序后,socket接口还不能使用,因为在这之前必须使用WSAStartup函数对该库进行初始化。具体的初始化代码可以参考如下:

    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1,1);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
     return;
    }

    if ( LOBYTE(wsaData.wVersion) != 1 ||
           HIBYTE(wsaData.wVersion) != 1 ) {
     WSACleanup();
     return;

WSAStartup函数有两个参数,wVersionRequested用来向该函数传递socket接口调用者可以使用socket的最高版本号,通过MAKEWORD宏即可完成版本号的组装,该变量的低字节指定主版本号而低字节指定次版本号。wsaData用来接收socket函数执行期间的一些数据。
当WSAStartup函数初始化动态库成功时返回0,否则返回-1。

当初始化成功后,还要再确认已加载的动态库的版本是否和我们所指定的动态库版本相吻合,如果不符合,则通过WSACleanup函数清除已加载的动态库。

动态库加载成功后,接下来就可以使用socket接口函数了,当使用完毕时,需要用WSACleanup函数卸载动态库。

3.更改套接字描述符

在使用一系列socket接口之前,必须使用socket函数创建套接字。在linux下,原始的socket函数返回的套接字描述符是整型,但是在windows下,该函数返回的套接字描述符是SOCKET类型。因此源代码和移植代码参考如下:

源代码:

    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        ui->statusLabel->setText("ERROR:socket connceting fail!");
        exit(1);
    }

移植代码:

    SOCKET sockfd;
    if ((cli.sockfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        ui->statusLabel->setText("ERROR:socket connceting fail!");
        exit(1);
    }

既然在移植代码中套接字描述符不是整型,那么其错误返回值也应该不是-1而是标准的错误返回值INVALID_SOCKET。

4.close函数

源代码中关闭套接字选用close(),而移植程序相应的使用closesocket()。并且此时用WSACleanup()关闭已加载的动态库。

进过上述几个部分的修改,此时移植程序在windows下即可编译成功。

QTableWidget基本功能总结

2011年5月7日

QTableWidget类提供了一种基于条目(item)的表格视图模型,在该部件中数据以item为基本单位,每条数据(item)对应一个QTableWidgetItem类的对象,所有数据形成的item组成整个表格。接下来我们创建一个用来显示学生信息的表格,以此为例说明TableWidget的一些常用功能。

1.创建QTableWidget

首先创建studentInfo类,然后在Qt Desinger模式下创建一个QTableWidget部件,对其命名为stuTableWidget。通过在该部件上点击右键创建列项表头,创建完毕后也就同时指定了该表格的列项数。如下:

2.初始化

除了直接在设计模式下创建行数和列数外,我们还可以通过调用相应的方法来设定。比如我们通过setRowCount方法在studentInfo类的构造方法中即可指定行项数。

ui->stuTableWidget->setRowCount(30);

该方法在创建行的同时会自动创建一个用来显示行号的列项,如下:

如果我们的表格只用来显示信息,并不涉及对相应信息的修改,则可以通过下述方法将表格设置为只读模式:

 ui->stuTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);

setEditTriggers()是QAbstractItemView类中的一个方法,通过向该方法传递相应的参数以设置item的一些属性,比如NoEditTriggers参数可将item设置为只读,DoubleClicked代表双击时item可修改。而QTableWidget继承了QAbstractItemView方法,因此它可以使用该函数。

3.信息显示

表格视图中数据的获取随用途的不同而不同。如果使用于C/S模型的客户端,那么表格中的信息需要从服务器端发送到本地,再相应解析;如果使用在数据库中,则需要从数据库中获取相应信息。这里假定数据已经到达本地,我们通过下面的方法来显示数据信息。

void studentInfo::showInfo()
{
    QTableWidgetItem *tmpItem;

    tmpItem = new QTableWidgetItem(QString("04065061"));
    ui->stuTableWidget->setItem(0, 0, tmpItem);

    tmpItem = new QTableWidgetItem(QString("edsionte"));
    ui->stuTableWidget->setItem(0, 1, tmpItem);

    tmpItem = new QTableWidgetItem(QString("1988.01.28"));
    ui->stuTableWidget->setItem(0, 2, tmpItem);

    tmpItem = new QTableWidgetItem(QString("male"));
    ui->stuTableWidget->setItem(0, 3, tmpItem);

    tmpItem = new QTableWidgetItem(QString("Xi'an Institute of Posts and Telecommunications"));
    ui->stuTableWidget->setItem(0, 4, tmpItem);
}

上述的showInfo方法为第一行设定了相应信息,我们可以看到表格的一行中每个具体的列项都对应一个QTableWidgetItem对象,并通过在setItem方法中指定行号和列号将该item对象设置到表格的具体位置。在上述的showInfo方法中,我们分别通过该方法创建了第一行的第一到第五列的数据(行列下表从0开始)。

4.为表格数据添加右键菜单

有时候我们想通过点击鼠标右键对表格数据进行一些其他操作,比如复制、查看详情等,我们可以按照下面的方法来实现。为了实现点击右键弹出菜单这个功能,我们必须在类studentInfo类中声明一个菜单变量popMenu和一个菜单选项变量action。

class studentInfo : public QMainWindow
{
…………
private:
    Ui::studentInfo *ui;
    QMenu *popMenu;
    QAction *action;

private slots:
    void on_stuTableWidget_customContextMenuRequested(QPoint pos);
…………
};

声明完毕后,我们在studentInfo类的构造函数中对其进行初始化,如下:

    ui->stuTableWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    popMenu = new QMenu(ui->stuTableWidget);
    action = new QAction("Copy", this);

setContextMenuPolicy方法用来设置widget菜单项的显示方法,而CustomContextMenu是唯一与邮件菜单有关的参数,因此这里我们将菜单显示方法设置为该类型。如果widget设置为CustomContextMenu时,当在数据上点击右键时就会发送customContextMenuRequested ( const QPoint & pos )信号,该信号还会捕捉到点击右键的位置,并用pos参数来存储。与此信号关联的槽函数我们定义如下:

void studentInfo::on_stuTableWidget_customContextMenuRequested(QPoint pos)
{
    popMenu->addAction(action);
    popMenu->exec(QCursor::pos());
}

我们首先将菜单选项action添加到邮件弹出菜单popMenu中,再通过exec方法在pos()位置显示该邮件菜单,pos()返回的位置即为点击鼠标的位置。

现在,如果点击右键菜单选项并不会发生任何动作,这是因为我们并没有关联相应的槽函数。由于具体的菜单选项不同,其函数的实现也不同,这里我们只给出框架,如下:

void studentInfo::rightClickedOperation()
{
    //do something
}

定义好槽函数,最关键的是与相应的信号连接。对于上述两个槽函数,我们可以使用两种方法进行信号和槽的关联:在Qt Desinger模式下添加或手动进行connect关联。对于customContextMenuRequested信号,我们使用前种方法实现信号和槽的关联;对于右键菜单选项的功能实现,我们可以通过connect函数实现,如下:

connect(action, SIGNAL(triggered()), this, SLOT(rightClickedOperation()));

基于Qt的多窗口设计-窗体切换的实现

2011年3月26日

窗体类的关系

在布局管理部分,我们已经说过将主界面和两个子系统分别封装成三个不同的类。首先我们讨论每个类中应该封装什么,其次再讨论这三个类之间的具体关系。

通过Qt Dsigner设计好界面后会在工程文件中对应一个.ui的文件;编译器会将该文件转换成能被C++所识别的.h文件。比如configUI.ui文件就对应一个ui_configUI.h文件,该头文件中包含了类Ui::configUI的定义。这个类完全与我们所设计的用户界面等价,也就是说它包含对主界面中各个部件的定义、该界面的布局以及初始化窗体的setupUi()等。

但是应该注意的是,该类仅仅是对我们设计的界面的一种等价转化。我们对该界面的实际操作(比如对各种槽以及信号的定义)并不包含在该类中。为了完成配置系统的具体功能,我们会创建一个新类configUI,并让这个新类继承Ui::configUI。通常在configUI类中会有一个Ui::configUI类的成员变量ui,因此可以通过ui直接获取界面中的各个部件。

注意,由于界面所对应的类configUI被定义在UI这个名字空间中,因此两个同名的类并不会发生名字冲突。这两个类的关系可以通过下面的代码进一步理解:

//将界面对应的configUI类定义在Ui名字空间中;
namespace Ui {
    class configUI;
}

class configUI : public QWidget
{
    Q_OBJECT

public:
    explicit configUI(QWidget *parent = 0);
    ~configUI();
//将 Ui::configUI的对象作为configUI的成员变量;
private:
    Ui::configUI *ui;

private slots:
//在此定义槽函数;
};

在configUI类中,还可以根据需要包含一些成员函数、信号和槽函数等。其他两个界面对应的类也有类似的封装关系,不再赘述。

在布局管理中,通过点击主界面上的按钮就可以切换到相应的窗体。由此引发出我们对这几个窗体类之间关系的思考。具体的做法是,我们可以将两个子系统对应类的对象作为主界面类的成员变量,比如:

namespace Ui {
    class frontPage;
}

class frontPage : public QWidget
{
    Q_OBJECT

public:
    explicit frontPage(QWidget *parent = 0);
    ~frontPage();

private:
    Ui::frontPage *ui;
    configUI *configWidget;
    logUI *logWidget;

signals:
    //在此定义信号;

private slots:
   //在此定义槽函数;
};

那么在切换各个窗体时,就可以方便的通过show()和hide()近来完成。至此,我们已经完成了界面的设计和类的定义,下面要做的就是实现具体窗口的跳转工作。

信号和槽函数的设计

由上文可得知,我们要实现的功能即通过点击每个按钮就可以跳转到相应的窗口。所以三个窗体对应的按钮就对应三个槽函数,触发这几个槽函数的信号即为clicked()。在类frontPage中对上述三个槽函数的声明如下:

signals:
    void goToWidget(int);
private slots:
    void on_logSysBtn_clicked();
    void on_frontPageBtn_clicked();
    void on_configSysBtn_clicked();
    void on_quitBtn_clicked();
    void runningWidget(int);
    void on_quitBtn_clicked();

除了三个窗口按钮对应的槽函数外,还包含其他函数的声明,我们在稍候会解释。按照以往的做法,我们声明了按钮对应的槽函数后,就应该依次去实现这些函数以便实现窗体间的跳转。不过,由于本程序中窗体跳转之间有一定的规律,所以将采用下面的方法:

void frontPage::on_frontPageBtn_clicked()
{
    emit goToWidget(0);
}

void frontPage::on_configSysBtn_clicked()
{
    emit goToWidget(1);
}

void frontPage::on_logSysBtn_clicked()
{
    emit goToWidget(2);
}

也就是说,在每个按钮对应的槽函数中再次发送goToWidget(int)信号,不同按钮将传递不同的整数。在这里我们使用emit关键字显示的发送信号,这和平时我们在Qt Designer中所使用的关联方发不同,但是本质是相同的。由于这个信号是我们自己定义的,并且信号本身就是一个函数,因此需要在frontPage类中对这个信号进行声明,具体可参见上面的示例代码。

我们将此信号和槽函数runningWidget(int)关联。也就是说点击不同窗体对应的按钮都会执行runningWidget(int)槽函数。只不过在该函数内部则会根据所传递的整形参数执行不同的程序段。runningWidget函数的示例代码如下:

void frontPage::runningWidget(int widgetNum)
{
    switch (widgetNum) {
    case 0:
        ui->introTextBrowser->show();
        configWidget->hide();
        logWidget->hide();
        break;
    case 1:
        ui->introTextBrowser->hide();
        configWidget->show();
        logWidget->hide();
        break;
    case 2:
        ui->introTextBrowser->hide();
        configWidget->hide();
        logWidget->show();
        break;
    }
}

从上面的代码中可以看出,通过传递不同的整形参数就可以显示三个窗体中的某一个,并同时隐藏另外两个窗体。通过上面的方法,这样就可以实现多窗体之间的切换了。另外,退出按钮的槽函数的实现基本方法即为直接调用exit函数。

至此,一个基本的多窗体程序就设计完成了。

基于Qt的多窗口编程-界面的设计

2011年3月25日

对于应用程序中的多窗体切换,我们已经习以为常。通常一个应用程序中,不同的窗口代表不同功能的工作区。本文将详细描述基于Qt的多窗体程序的设计方法。在阅读本文之前,你最好了解面向对象的基本思想以及Qt的基本使用方法。

接下来我们以编写一个客户端为例来具体说明多窗体程序的编程方法。该客户端包括多个子系统,每个子系统对应一个窗口;在客户端的主界面,通过点击相应的按钮实现多个窗体之间的切换。主界面图如下:

 

如图所示,该客户端主界面包含顶部的标签、右方的按钮区、中间的主窗口区以及底部的状态栏。通过点击不同的按钮则进入不同的子系统。配置信息子系统和日志信息管理子系统界面设计如下:

布局管理

上述三个界面都可以通过Qt Designer轻松设计完成,这里只对窗体部件的布局管理作说明。通常我们可以显示指定窗体中各个部件的大小以及位置,但是这样麻烦而且缺少灵活性。因此在实际的应用中,使用布局管理器对窗体中的各个子部件进行布局管理。布局管理器会为每个窗体部件提供合理的默认值,并随着个别部件大小的变化而调整整体的窗体布局。

常用的布局管理器有QHBoxLayout、QVBoxLayout和QGridLayout,即依次为水平布局管理、垂直布局管理器和网格布局管理器。结合上面的主界面图示,其对应的布局管理图如下:

使用布局管理器的一般方法为:先创建相应布局管理器的对象;再将要进行布局管理窗体部件加入到布局管理器对象中;为当前窗体设置该布局管理器。除了可以将窗体部件加入到当前的布局管理器中,还可以将另外一个布局管理器对象加入到当前的布局管理器中;此外,设置布局管理器只需在所有布局管理器都添加好之后进行一次的设置即可。具体使用方法可参考随后给出的示例代码。

在topLayout这个顶部的布局管理器中,我们仅加入一个标签。正如上面所述,我们首先创建一个水平布局管理器对象,再将这个mainlabel标签加入其内。

    QHBoxLayout *topLayout = new QHBoxLayout;
    topLayout->addWidget(ui->mainLabel);

右边的按钮区的布局管理rightBtnLayout和上述类似,只不过我们此时使用的是垂直管理器。接下来重点说一下主窗口区布局管理器widgetLayout的设置。两个子系统和主界面的功能不同,因此将他们分别封装成类configUI、logUI和frontPage,我们依次创建三个类的对象。接下来将这三个部件加入到widgetLayout中。由于客户端初始显示主界面,则我们将其他两个窗体暂时隐藏。

    QVBoxLayout *widgetLayout = new QVBoxLayout;
    configWidget = new configUI(this);
    logWidget = new logUI(this);
    mainMenu = new frontPage(this);
    widgetLayout->addWidget(ui->introTextBrowser);
    widgetLayout->addWidget(configWidget);
    widgetLayout->addWidget(logWidget);
    configWidget->hide();
    logWidget->hide();

我们现在已经设置好了rightBtnLayout和widgetLayout,接下来将这两个布局管理器加入到mainWindowLayout中。

    QHBoxLayout *mainWindowLayout = new QHBoxLayout;
    mainWindowLayout->addLayout(widgetLayout);
    mainWindowLayout->addLayout(rightBtnLayout);

这样mainWindowLayout布局管理器就设置好了。最后我们还需要设置bottomLayout布局管理器,只是简单的添加一个标签和一个水平分割线。

至此,我们拥有三个二级布局管理器:topLayout、mainWindowLayout和bottomLayout,我们将这三个布局管理器全部添加到顶级布局管理器mainLayout中:

   QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(topLayout);
    mainLayout->addLayout(mainWindowLayout);
    mainLayout->addLayout(bottomLayout);
    this->adjustSize();

    setLayout(mainLayout);

此时,我们就设置好了主界面的布局。

基于Qt的FTP编程

2011年3月2日

在Qt中,与文件传输协议(FTP)相对应的类为QFtp。在这个类中,提供了许多与文件上传和下载有关的方法,这使得文件的上传与下载变得十分方便。本文所描述的FTP客户端的基本使用方法是:首先输入正确的FTP服务器地址,用户名以及密码;登录成功后将显示服务器中的文件列表;点击下载按钮对某个文件进行下载。用户界面可参考下图:

用户界面设计可见本文参考1和参考2,接下来将对具体的实现过程进行分析。

构造函数

在构造函数中,对用户界面进行了初始化,并隐藏了进度显示条。除此之外,还将itemActivated信号和processItem槽函数进行了连接。当用户双击(或单击,依据具体OS)TreeWidget中的某一条信息时,QTreeWidget类的对象fileList就会发送itemActivated信号。

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->progressBar->setValue(0);

    connect(ui->fileList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
            this, SLOT(processItem(QTreeWidgetItem*,int)));
}

on_connectButton_clicked()的实现

在连接按钮单击事件的槽函数中主要完成了两部分工作:登录FTP服务器以及几个信号和槽函数之间的连接,这几个信号均由FTP对象发出。当FTP命令开始执行时,便发出commandStarted信号;当FTP的命令执行完毕时,便发出commandFinished信号;当使用list命令列表显示目录的内容时候,listInfo信号将发出;最后在数据传输的过程中,会不断的发出dataTransferProgress信号以便更新进度显示条。

由于QFtp类中封装了许多与FTP有关成员函数,因此使用connectToHost成员函数可以登录到FTP服务器,使用login函数可以登录到服务器。

void Widget::on_connectButton_clicked()
{
    ui->fileList->clear();
    currentPath.clear();
    isDirectory.clear();
    ftp = new QFtp(this);

    connect(ftp, SIGNAL(commandFinished(int,bool)),
            this, SLOT(ftpCommandFinished(int,bool)));
    connect(ftp, SIGNAL(listInfo(QUrlInfo)), this, SLOT(addToList(QUrlInfo)));
    connect(ftp, SIGNAL(dataTransferProgress(qint64,qint64)),
            this, SLOT(updateDataTransferProgress(qint64,qint64)));

    QString ftpServer = ui->ftpServerLineEdit->text();
    QString userName = ui->userNameLineEdit->text();
    QString passWord = ui->passWordLineEdit->text();

    ftp->connectToHost(ftpServer, 21);//连接到服务器,默认端口是21;
    ftp->login(userName, passWord);//登录;
}

ftpCommandFinished()的实现

当FTP命令执行完毕后,将执行ftpCommandFinished函数。与本文所描述的命令有ConnectToHost()、Login()、Get()、Close()和List()。通过currentCommand成员函数可以具体得到当前执行完毕命令的id,因此根据不同命令的id在用户界面上显示不同的命令状态。

void Widget::ftpCommandFinished(int, bool error)
{
    if (ftp->currentCommand() == QFtp::ConnectToHost) {
        if (error)
            ui->label->setText(tr("connecting error %1").arg(ftp->errorString()));
        else
            ui->label->setText(tr("connecting success!"));
    }

    if (ftp->currentCommand() == QFtp::Login) {
        if (error)
            ui->label->setText(tr("login error").arg(ftp->errorString()));
        else {
            ui->label->setText(tr("login success!"));
            ftp->list();
        }
    }
    //部分代码省略
}

on_downloadButton_clicked()的实现

当单击下载按钮后,将进入该函数进行相应文件的下载。首先通过fileList对象中的成员函数得到要下载文件的名称,在打开该文件,最后通过get函数获取数据。

void Widget::on_downloadButton_clicked()
{
    QString fileName = ui->fileList->currentItem()->text(0);
    file = new QFile(fileName);
    if (!file->open(QIODevice::WriteOnly)) {
        delete file;
        return;
    }

    ui->downloadButton->setEnabled(false);
    ftp->get(ui->fileList->currentItem()->text(0), file);
}

addToList()的实现

当对FTP服务器上某个目录执行list()命令时,对于list()找到的每一个文件都会发出listInfo()信号。该信号会使得addToList函数执行,以便在treeWidget上增加一条新的文件信息。

因此,该函数首先根据urlInfo参数建立一条新的文件信息,然后再利用addTopLevelItem成员函数将该条新的文件信息加入到treeWidget最顶端。

void Widget::addToList(const QUrlInfo &urlInfo)
{
    QTreeWidgetItem *item = new QTreeWidgetItem;
    item->setText(0, urlInfo.name());
    item->setText(1, QString::number(urlInfo.size()));
    item->setText(2, urlInfo.owner());
    item->setText(4, urlInfo.lastModified().toString("mmm dd yyyy"));
    QPixmap pixmap(urlInfo.isDir() ? "../dir.png" : "../file.png");
    item->setIcon(0, pixmap);
    isDirectory[urlInfo.name()] = urlInfo.isDir();

    //存储该路径是否为目录的信息;
    ui->fileList->addTopLevelItem(item);
    if (!ui->fileList->currentItem()) {
        ui->fileList->setCurrentItem(ui->fileList->topLevelItem(0));
        ui->fileList->setEnabled(true);
    }
}

processItem()的实现

当双击文件列表中某一项时候,如果该项所显示的文件为目录,则进入该目录中显示其内部的文件列表。上述的内容即为processItem函数所要完成的工作。

void Widget::processItem(QTreeWidgetItem *item, int)
{
    //打开一个目录;
    QString name = item->text(0);
    if (isDirectory.value(name)) {
        ui->fileList->clear();
        isDirectory.clear();
        currentPath += '/';
        currentPath += name;
        ftp->cd(name);
        ftp->list();
        ui->cdToParentButton->setEnabled(true);
    }
}

updateDataTransferProgress()的实现

每当FTP服务器端的数据返回时,就引发该函数的执行。该函数的主要工作即更新进度条的显示。

void Widget::updateDataTransferProgress(qint64 readBytes, qint64 totalBytes)
{
    ui->progressBar->setMaximum(totalBytes);
    ui->progressBar->setValue(readBytes);
}

 

未完待续~
参考:

1. Qt Assistant

2. http://www.yafeilinux.com/?p=757

3. C++ GUI Qt 4编程(第二版); 电子工业出版社;Blanchette,J,Summerfield,M 著;闫锋欣 等译;

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key