最早的时候喜欢用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窗体的一些基本设置和通用技巧
设置窗口不允许最大化,右上角只显示最小化和关闭:
设置窗口大小固定,不可拉伸:
显示中文/UNICODE:
// 宏
#define UNICODE_ENCODE(a) QString::fromWCharArray(a)
#define CHINESE_ENCODE(a) UNICODE_ENCODE(a)
QTreeWidget的使用
一般地,可以用QTreeWidget实现列表及树形图。首先在QtDesigner里对TreeWidget右键“编辑项目”,然后添加几个Column,手动输入标题。不过这里好像没法设置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()方法一次性全部添加。
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的指针。
效果如图:
QTreeWidget删除表项直接释放QTreeWidgetItem对象即可,析构函数会做处理。释放完毕后,列表中将不再显示该项。清空列表的话只需使用QTreeWidget的.clear()方法。
uiWndMgr.tblWnd->clear(); // 清空列表
还有一个比较重要的,就是如何得到QTreeWidget当前选中的条目信息,以便日后处理。我的方法是先用一个槽函数接收itemPressed()信号,某个条目被选中时会触发此信号,并传入QTreeWidgetItem的指针,我们可以用一个私有成员变量记录该指针,易知该指针总是能代表当前选中的条目。
有了QTreeWidgetItem的指针后,只需调用.data()成员便能获取不同Role中不同列的表项信息。要注意.data()返回的是一个QVariant变体类型,需要用.toXXX()方法才能转换为其他数据类型。
// 字符串型
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槽函数实现未给出,在里面添加事件处理代码即可):
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);
}
}
暂时先写这么多,随着学习进程,以后可能不定期更新。
