«

»

28

Qt GUI开发学习笔记

最早的时候喜欢用VB6写界面,因为非常方便,上手特别快。不过这也容易造就很多问题,而且时至今日,VB6已经太老了,项目开发也很少用VB甚至很少用BASIC语言,C/C++/Java成了主流。学C/C++的时候基本都是在控制台下做实验,没怎么涉及图形界面,不过真的到要用GUI的时候又发现自己什么都不会。后来,了解了Qt这个东西,它支持纯粹的C++语言(不像.NET的C++编程方式,有很多是自定义的特性),跨平台,构建界面也很方便,而且Qt是在持续更新的,不会很快就过时,于是决定要研究一下Qt。在这里贴出学习过程中的一些笔记,方便以后自己查阅,也给同样学习Qt的同学一些参考。

 

开发环境与工程结构

安装VisualStudio-Add-In后可以用VC结合Qt Designer进行开发,在VC里写代码,Qt Designer上画界面。

不过VC新建工程后要更改项目配置,在文件查找路径里添加上Qt的头文件、库文件等的所在路径,不然编译时会提示找不到文件。

对于每个窗体,Qt Designer会生成ui_frmName.h文件,其中包括class Ui_frmName的声明,用来描述用户在此窗口上设置的属性和添加的控件等信息。并且会在namespace UI里声明class frmName: public Ui_frmName {};

每个窗口类class frmNameClass都继承自QMainWindow或QDialog,并声明私有成员Ui::frmName ui_frmName用来控制显示窗口及控件(Ui::frmName即来自上面提到的Qt Designer生成的ui_frmName.h文件,一般地,用户不需要更改ui_frmName.h的代码)。其它的槽函数、内部功能函数等都可以实现在class frmNameClass里。

 

Qt窗体的一些基本设置和通用技巧

设置窗口不允许最大化,右上角只显示最小化和关闭:

setWindowFlags(Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint);

设置窗口大小固定,不可拉伸:

setFixedSize(this->width (), this->height());

显示中文/UNICODE:

QString strDisplay = QString::fromWCharArray(L"中文");
// 宏
#define UNICODE_ENCODE(a) QString::fromWCharArray(a)
#define CHINESE_ENCODE(a) UNICODE_ENCODE(a)

 

QTreeWidget的使用

一般地,可以用QTreeWidget实现列表及树形图。首先在QtDesigner里对TreeWidget右键“编辑项目”,然后添加几个Column,手动输入标题。不过这里好像没法设置Column的宽度,只能用代码:

// tblWnd是QTreeWidget实例,setColumnWidth参数一代表要设置第n+1个column,参数二代表宽度
uiWndMgr.tblWnd->setColumnWidth(0, 300);   // WindowText
uiWndMgr.tblWnd->setColumnWidth(1, 200);   // ClassName
uiWndMgr.tblWnd->setColumnWidth(2, 60);    // Handle
uiWndMgr.tblWnd->setColumnWidth(3, 60);    // ParentHandle
uiWndMgr.tblWnd->setColumnWidth(4, 50);    // Pid
uiWndMgr.tblWnd->setColumnWidth(5, 50);    // Tid
uiWndMgr.tblWnd->setColumnWidth(6, 70);    // Status

以上是表头的设置,使用QTreeWidget时我们通常要动态地向其中添加表项。

QTreeWidget添加表项:

首先,每个表项都与一个QTreeWidgetItem对象相关联,首先要实例化一个QTreeWidgetItem,然后再用.setData方法设置内容。

setData方法有三个参数。参数一代表要设置哪一列内容;参数二表示一个role,这个可以理解为一个标志,代表设置的这个内容的用途,一般我们把它设为Qt::DisplayRole代表该内容用于显示(后面会介绍这个的其他用法);参数三是一个QVariant类型的变量,也就是变体,它可以是任意数据类型,代表设定内容的值。

处理好QTreeWidgetItem后,我们可以用.addTopLevelItem()方法将条目添加到QTreeWidget中,不过如果数据较多的话,这样会影响效率。

对于大数据,应该先使用QList保存所有QTreeWidgetItem的指针,再用.addTopLevelItems()方法一次性全部添加。

QList<QTreeWidgetItem*> lstContents;
for ( … ) {
    lpCurrentItem = new QTreeWidgetItem();
    lpCurrentItem->setData(0, Qt::DisplayRole, wndInfo.strText);
    lpCurrentItem->setData(1, Qt::DisplayRole, wndInfo.strClass);
    lpCurrentItem->setData(2, Qt::DisplayRole, (int)wndInfo.hWnd);
    lpCurrentItem->setData(3, Qt::DisplayRole, (int)wndInfo.hParentWnd);
    lpCurrentItem->setData(4, Qt::DisplayRole, (int)wndInfo.dwPid);
    lpCurrentItem->setData(5, Qt::DisplayRole, (int)wndInfo.dwTid);
    lpCurrentItem->setData(6, Qt::DisplayRole, strWindowStatus);

    lstContents.push_back(lpCurrentItem);
    // uiWndMgr.tblWnd->addTopLevelItem(lpCurrentItem);   // 单项添加
}
uiWndMgr.tblWnd->addTopLevelItems(lstContents);   // 一次性添加全部

另外不要忘记QTreeWidget是树形图,可以把某个表项添加为某个父项的节点,显示出来就是树形图,可以按+号展开。方法是使用QTreeWidgetItem的addChild()方法,该成员函数只有一个参数,是指向作为子项的那个QTreeWidgetItem的指针。

lpParentItem->addChild(lpCurrentItem);

效果如图:

TM截图20140128131947


QTreeWidget删除表项直接释放QTreeWidgetItem对象即可,析构函数会做处理。释放完毕后,列表中将不再显示该项。清空列表的话只需使用QTreeWidget的.clear()方法。

delete lpCurrentItem;   // 删除单独的一项
uiWndMgr.tblWnd->clear();   // 清空列表

还有一个比较重要的,就是如何得到QTreeWidget当前选中的条目信息,以便日后处理。我的方法是先用一个槽函数接收itemPressed()信号,某个条目被选中时会触发此信号,并传入QTreeWidgetItem的指针,我们可以用一个私有成员变量记录该指针,易知该指针总是能代表当前选中的条目。

有了QTreeWidgetItem的指针后,只需调用.data()成员便能获取不同Role中不同列的表项信息。要注意.data()返回的是一个QVariant变体类型,需要用.toXXX()方法才能转换为其他数据类型。

QVariant varTemp;
// 字符串型
varTemp = item->data(0, Qt::DisplayRole);
QString varStr = varTemp.toString();
// 整数型
varTemp = item->data(1, Qt::DisplayRole);
int varInt = varTemp.toInt();

QTreeWidget的内容角色(Role)

这里又出现了第二个神奇的参数Qt::XXXRole。下面给出解释。其实这里就是Qt把附加到QTreeWidget的数据分成了不同的角色(Role),每个角色负责控制不同的内容,DisplayRole就控制着要显示在不同列的条目内容。此外还有很多其他的角色,比如通过设置Qt::BackgroundRole就可以控制某个表项位置的背景颜色。

对于一些特殊的用户自定义数据/数据结构,我们也可以自己为它定义角色,或者使用Qt::UserRole。这个功能是很有用的,比如某个列表中每个条目的所有项目信息都源自一个struct,这个struct的不同成员经过处理后构造成QTreeWidgetItem再添加到图表上,但是假如我们想根据QTreeWidgetItem来求得这个原始的struct就不太好办了,只能解析显示用的字符串,再把它们还原,这样无疑是不推荐的做法。要解决这个问题就可以用Qt::UserRole,通过.setData()向QTreeWidget的UserRole中添加一个源struct的指针,要用到时再通过.data()从UserRole中取得该指针即可。整个过程中UserRole不会对QTreeWidget的显示功能等造成影响。


Qt右键菜单的使用

首先,菜单由QMenu来实现,每个QMenu的实例可以添加多个QAction,每个QAction即可以理解为菜单中的一个条目。实例化QMenu之后,通过addAction方法添加一个或多个QAction,然后把QAction的
triggered(bool)信号与自定义的槽连结,另外这里有个问题,Qt5.0.2帮助上写的信号名字是toggle(bool),不过测试发现该信号无效,用triggered(bool)才可以正常响应。

连接完槽函数后,当用户选中菜单中的某一项后,QAction便会发出信号,我们的槽函数收到信号后便能及时处理。还有一个问题要解决,就是如何弹出这个菜单,现在假定我希望当用户右键单击QTreeWidget时,弹出右键菜单。

方式是先处理QTreeWidget产生的itemPressed信号,判断按键是否为右键,是的话使用ShowPopMenu()来显示菜单即可(ShowMenu是自定义函数,它先获取当前鼠标的位置,然后用QMenu的exec()方法在当前位置显示菜单实例,具体请看示例代码)。

另外这里还可以动态地更改要显示的内容,比如可以通过QAction的setEnabled方法设置某一项为灰色(不可选),或者用setVisible()方法使该Action不可见(即菜单中不显示这一选项)。

还可以用QMenu的addSeparator()方法在两个菜单项之间添加分隔线。

示例如下(具体的triggered槽函数实现未给出,在里面添加事件处理代码即可):

class frmWndMgr: public QMainWindow {
    Q_OBJECT

private slots:
    // 窗口列表右键菜单
    void menuWnd_actShow_triggered(bool bStatus);
    void menuWnd_actHide_triggered(bool bStatus);
    void menuWnd_actActive_triggered(bool bStatus);
    void menuWnd_actFreeze_triggered(bool bStatus);
    …

public:
    frmWndMgr(QWidget *parent = 0);
    ~frmWndMgr();
    …
    
private:
    QMenu menuWnd;

    // 窗口列表右键菜单动作
    QAction *menuWnd_actShow;
    QAction *menuWnd_actHide;
    QAction *menuWnd_actActive;
    QAction *menuWnd_actFreeze;
    …
};

void frmWndMgr::InitPopMenus()
{
    QIcon defIcon;
    menuWnd_actShow = menuWnd.addAction(CHINESE_ENCODE(L"显示"));
    connect(menuWnd_actShow, SIGNAL(triggered(bool)), this, SLOT(menuWnd_actShow_triggered(bool)));
    menuWnd_actHide = menuWnd.addAction(CHINESE_ENCODE(L"隐藏"));
    connect(menuWnd_actHide, SIGNAL(triggered(bool)), this, SLOT(menuWnd_actHide_triggered(bool)));
    menuWnd.addSeparator();
    menuWnd_actActive = menuWnd.addAction(CHINESE_ENCODE(L"激活"));
    connect(menuWnd_actActive, SIGNAL(triggered(bool)), this, SLOT(menuWnd_actActive_triggered(bool)));
    menuWnd_actFreeze = menuWnd.addAction(CHINESE_ENCODE(L"冻结"));
    connect(menuWnd_actFreeze, SIGNAL(triggered(bool)), this, SLOT(menuWnd_actFreeze_triggered(bool)));
}

void frmWndMgr::tblWnd_itemPressed(QTreeWidgetItem* item, int column)
{
    …
    if (qApp->mouseButtons() == Qt::RightButton) {
        menuWnd_actShow->setVisible(true);
        menuWnd_actHide->setVisible(false);
        menuWnd_actActive->setEnabled(true);
        menuWnd_actFreeze->setEnabled(false);
        ShowPopMenu(menuWnd);
    }
}

 

暂时先写这么多,随着学习进程,以后可能不定期更新。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>