转载地址: /u010519432/article/details/26988515
一直都认为,用最通俗的语言,讲解最深刻的技术,是每一个技术交流者应该考虑的事情,今天朋友问我,好友列表该怎么实现。我想起之前上网查阅的时候,发现网上介绍这块的内容甚少,而且讲解的不够好,于是,本着互相交流的精神,在这里讲解一下我是怎么实现QQ好友列表的。
1、Q:关于好友列表到底是QTreeWidget/QTreeView还是QListWidget/QListView的问题?
A:相信大家初次一看,大部分都认为是QTreeWidget,其实是用QListWidget或者QListView均可简单实现,在数据多的时候,QListWidget性能会降低,不过,对于好友列表来说,QListWidget足以,并且更加简单。所以,我继承的是QListWidget来实现。
2、Q:关于如何实现一个Item具有多种信息,包括头像、用户名、个性签名等?
A:该Item其实是一个继承了QWidget的自定义buddy类,把你所想要的信息全部在该buddy类里面布局好,甚至可以加进按钮,自定义的好处就在于,你想到什么,就能干什么,然后在QListWiget里面里通过实现
[cpp]view plaincopy print? QListWidgetItem*newItem=newQListWidgetItem();//创建一个newItemthis->insertItem(row(currentItem)+tem.count(),newItem);//将该newItem插入到后面this->setItemWidget(newItem,buddy);//将buddy赋给该newItem
QListWidgetItem *newItem = new QListWidgetItem(); //创建一个newItemthis->insertItem(row(currentItem)+tem.count(),newItem); //将该newItem插入到后面this->setItemWidget(newItem, buddy); //将buddy赋给该newItem
即可。
3、Q:关于如何实现好友的展开与隐藏?
A:这部分里我设置了两个容器:
[cpp]view plaincopy print? QMap<QListWidgetItem*,QListWidgetItem*>groupMap;//组容器-key:项value:组QMap<QListWidgetItem*,bool>isHideMap;//用来判断该组是否隐藏了
QMap<QListWidgetItem*,QListWidgetItem*> groupMap; // 组容器 - key:项 value:组QMap<QListWidgetItem*,bool> isHideMap;//用来判断该组是否隐藏了
其中,groupMap用来存放key为项,value为组的数据,比如我增加了一个“我的好友”的组,则存进去是key:我的好友,value:我的好友;接着,在isHideMap存放key:我的好友,value:false,表示默认该组是未展开的;紧接着,如果在“我的好友”里,我增加了一个好友“逍遥圣帝”,则存进去的是:key:逍遥圣帝,value:我的好友,这样处理的关键是为了保存好组与好友的关系;最后再利用isHideMap来判断组的状态,如果是隐藏,则通过遍历groupMap里面的好友,使之显示,否则,反之。
4、Q:关于如何实现美化效果?
A:直接用QSS就可以了。
下面直接贴出源代码,我已经在源代码里面详细给每一个关键步骤进行了说明,所以就不进行阐述了,相信大家看得懂的,如有不懂可以追加评论,第一时间回复你们,下面是实现一个QQ好友列表的简单功能,对于其他功能大家好好拓展即可~~
一、首先是实现具有各种信息的Buddy类:
personListBuddy.h
[cpp]view plaincopy print? #ifndefPERSONLISTBUDDY_H#definePERSONLISTBUDDY_H#include<QWidget>#include<QLabel>#include<QEvent>//自定义信息Item类classpersonListBuddy:publicQWidget{Q_OBJECTpublic:explicitpersonListBuddy(QWidget*parent=0);voidinitUi();//初始化UiQWidget*head;//头像QLabel*name;//用户名QLabel*sign;//个性签名QStringheadPath;//头像路径booleventFilter(QObject*obj,QEvent*event);//事件过滤器signals:publicslots:};#endif//PERSONLISTBUDDY_H
#ifndef PERSONLISTBUDDY_H#define PERSONLISTBUDDY_H#include <QWidget>#include <QLabel>#include <QEvent>//自定义信息Item类class personListBuddy : public QWidget{Q_OBJECTpublic:explicit personListBuddy(QWidget *parent = 0);void initUi();//初始化UiQWidget *head; //头像QLabel *name; //用户名QLabel *sign; //个性签名QString headPath;//头像路径bool eventFilter(QObject *obj, QEvent *event);//事件过滤器signals:public slots:};#endif // PERSONLISTBUDDY_H
personListBuddy.cpp
[cpp]view plaincopy print? #include"personlistbuddy.h"#include<QPainter>personListBuddy::personListBuddy(QWidget*parent):QWidget(parent){initUi();}//初始化UivoidpersonListBuddy::initUi(){//初始化head=newQWidget(this);name=newQLabel(this);sign=newQLabel(this);//设置头像大小head->setFixedSize(40,40);//设置个性签名字体为灰色QPalettecolor;color.setColor(QPalette::Text,Qt::gray);sign->setPalette(color);//布局head->move(7,7);name->move(54,10);sign->move(54,27);//装载事件过滤器head->installEventFilter(this);}//事件过滤器,主要是为了让图片能够全部填充在head里面boolpersonListBuddy::eventFilter(QObject*obj,QEvent*event){if(obj==head){if(event->type()==QEvent::Paint){QPainterpainter(head);painter.drawPixmap(head->rect(),QPixmap(headPath));}}returnQWidget::eventFilter(obj,event);}
#include "personlistbuddy.h"#include <QPainter>personListBuddy::personListBuddy(QWidget *parent) :QWidget(parent){initUi();}//初始化Uivoid personListBuddy::initUi(){//初始化head=new QWidget(this);name=new QLabel(this);sign=new QLabel(this);//设置头像大小head->setFixedSize(40,40);//设置个性签名字体为灰色QPalette color;color.setColor(QPalette::Text,Qt::gray);sign->setPalette(color);//布局head->move(7,7);name->move(54,10);sign->move(54,27);//装载事件过滤器head->installEventFilter(this);}//事件过滤器,主要是为了让图片能够全部填充在head里面bool personListBuddy::eventFilter(QObject *obj, QEvent *event){if(obj == head){if(event->type() == QEvent::Paint){QPainter painter(head);painter.drawPixmap(head->rect(), QPixmap(headPath));}}return QWidget::eventFilter(obj, event);}
二、实现好友列表personList类:
personList.h
[cpp]view plaincopy print? #ifndefPERSONLIST_H#definePERSONLIST_H#include<QListWidget>#include<QMenu>#include<QMouseEvent>#include<QLineEdit>//自定义QListWidgetclasspersonList:publicQListWidget//继承QListWidget,可以使用它本身自带的函数,更方便{Q_OBJECTpublic:explicitpersonList(QListWidget*parent=0);voidmousePressEvent(QMouseEvent*event);//鼠标点击事件voidcontextMenuEvent(QContextMenuEvent*);//菜单事件,为了显示菜单voidinitMenu();//初始化菜单QMenu*blankMenu;//点击空白上的菜单QMenu*groupMenu;//点击组上的菜单QMenu*personMenu;//点击人上的菜单QMap<QListWidgetItem*,QListWidgetItem*>groupMap;//组容器-key:项value:组QMap<QListWidgetItem*,bool>isHideMap;//用来判断该组是否隐藏了QLineEdit*groupNameEdit;//组的名字,重命名的时候需要用到QListWidgetItem*currentItem;//当前的项signals:publicslots:voidslotAddGroup();//添加组voidslotDelGroup();//删除组voidslotAddBuddy();//添加好友voidslotDelBuddy();//删除好友voidslotRename();//重命名组voidslotRenameEditFshed();//命名完成};#endif//PERSONLIST_H
#ifndef PERSONLIST_H#define PERSONLIST_H#include <QListWidget>#include <QMenu>#include <QMouseEvent>#include <QLineEdit>//自定义QListWidgetclass personList : public QListWidget //继承QListWidget,可以使用它本身自带的函数,更方便{Q_OBJECTpublic:explicit personList(QListWidget *parent = 0);void mousePressEvent(QMouseEvent *event);//鼠标点击事件void contextMenuEvent(QContextMenuEvent*);//菜单事件,为了显示菜单void initMenu();//初始化菜单QMenu *blankMenu;//点击空白上的菜单QMenu *groupMenu;//点击组上的菜单QMenu *personMenu;//点击人上的菜单QMap<QListWidgetItem*,QListWidgetItem*> groupMap; // 组容器 - key:项 value:组QMap<QListWidgetItem*,bool> isHideMap;//用来判断该组是否隐藏了QLineEdit *groupNameEdit;//组的名字,重命名的时候需要用到QListWidgetItem *currentItem;//当前的项signals:public slots:void slotAddGroup(); //添加组void slotDelGroup(); //删除组void slotAddBuddy(); //添加好友void slotDelBuddy(); //删除好友void slotRename();//重命名组void slotRenameEditFshed();//命名完成};#endif // PERSONLIST_H
personList.cpp
[cpp]view plaincopy print? #include"personlist.h"#include<QAction>#include<QIcon>#include"personlistbuddy.h"personList::personList(QListWidget*parent):QListWidget(parent){setFocusPolicy(Qt::NoFocus);//去除item选中时的虚线边框setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//水平滚动条关闭initMenu();}//初始化菜单voidpersonList::initMenu(){//初始化:blankMenu=newQMenu();groupMenu=newQMenu();personMenu=newQMenu();groupNameEdit=newQLineEdit();QAction*addGroup=newQAction("添加分组",this);QAction*delGroup=newQAction("删除该组",this);QAction*rename=newQAction("重命名",this);QAction*addBuddy=newQAction("添加好友",this);QAction*delBuddy=newQAction("删除好友",this);//设置:groupNameEdit->setParent(this);//设置父类groupNameEdit->hide();//设置初始时隐藏groupNameEdit->setPlaceholderText("未命名");//设置初始时的内容//布局:blankMenu->addAction(addGroup);groupMenu->addAction(delGroup);groupMenu->addAction(rename);groupMenu->addAction(addBuddy);personMenu->addAction(delBuddy);//信息槽:connect(groupNameEdit,SIGNAL(editingFinished()),this,SLOT(slotRenameEditFshed()));connect(addGroup,SIGNAL(triggered()),this,SLOT(slotAddGroup()));connect(delGroup,SIGNAL(triggered()),this,SLOT(slotDelGroup()));connect(rename,SIGNAL(triggered()),this,SLOT(slotRename()));connect(addBuddy,SIGNAL(triggered()),this,SLOT(slotAddBuddy()));connect(delBuddy,SIGNAL(triggered()),this,SLOT(slotDelBuddy()));}//鼠标点击事件voidpersonList::mousePressEvent(QMouseEvent*event){QListWidget::mousePressEvent(event);//如果不调用基类mousePressEvent,item被select会半天不响应,调用父类,让QSS起效,因为QSS基于父类QListWidget,子类就是子窗口,就是最上层窗口,是覆盖在父窗口上的,所以先于父窗口捕获消息//防止一种特殊情况:给新item命名、点击其他item或空白处时,指向新item的currentItem被赋予其他itemif(groupNameEdit->isVisible()&&!(groupNameEdit->rect().contains(event->pos()))){if(groupNameEdit->text()!=NULL)currentItem->setText(groupNameEdit->text());groupNameEdit->setText("");groupNameEdit->hide();}currentItem=this->itemAt(mapFromGlobal(QCursor::pos()));//鼠标位置的Item,不管右键左键都获取if(event->button()==Qt::LeftButton&¤tItem!=NULL&¤tItem==groupMap.value(currentItem))//如果点击的左键并且是点击的是组{if(isHideMap.value(currentItem))//如果先前是隐藏,则显示{foreach(QListWidgetItem*subItem,groupMap.keys(currentItem))//遍历组的对应的项(包括自身和好友)if(subItem!=currentItem)//如果是组的话不进行处理{subItem->setHidden(false);//好友全部显示}isHideMap.insert(currentItem,false);//设置该组为显示状态currentItem->setIcon(QIcon(":/arrowDown"));}else//否则,先前是显示,则隐藏{foreach(QListWidgetItem*subItem,groupMap.keys(currentItem))//遍历组的对应的项(包括自身和好友)if(subItem!=currentItem)//如果是组的话不进行处理{subItem->setHidden(true);//好友全部隐藏}isHideMap.insert(currentItem,true);//设置该组为隐藏状态currentItem->setIcon(QIcon(":/arrowRight"));}}}//菜单事件,为了显示菜单,点击鼠标右键响应,鼠标点击事件mousePressEvent优先于contextMenuEventvoidpersonList::contextMenuEvent(QContextMenuEvent*event){QListWidget::contextMenuEvent(event);//调用基类事件if(currentItem==NULL)//如果点击到的是空白处{blankMenu->exec(QCursor::pos());return;}if(currentItem==groupMap.value(currentItem))//如果点击到的是组groupMenu->exec(QCursor::pos());else//否则点击到的是好友personMenu->exec(QCursor::pos());}//添加组voidpersonList::slotAddGroup(){QListWidgetItem*newItem=newQListWidgetItem(QIcon(":/arrowRight"),"未命名");//创建一个ItemnewItem->setSizeHint(QSize(this->width(),25));//设置宽度、高度this->addItem(newItem);//加到QListWidget中groupMap.insert(newItem,newItem);//加到容器groupMap里,key和value都为组isHideMap.insert(newItem,true);//设置该组隐藏状态groupNameEdit->raise();groupNameEdit->setText(tr("未命名"));//设置默认内容groupNameEdit->selectAll();//设置全选groupNameEdit->setGeometry(this->visualItemRect(newItem).left()+15,this->visualItemRect(newItem).top()+1,this->visualItemRect(newItem).width(),this->visualItemRect(newItem).height()-2);//出现的位置groupNameEdit->show();//显示groupNameEdit->setFocus();//获取焦点currentItem=newItem;//因为要给group命名,所以当前的currentItem设为该group}//删除组voidpersonList::slotDelGroup(){foreach(QListWidgetItem*item,groupMap.keys(currentItem))//遍历该组的所有好友和自身的组{groupMap.remove(item);//移除deleteitem;//删除}isHideMap.remove(currentItem);//移除}//重命名voidpersonList::slotRename(){groupNameEdit->raise();groupNameEdit->setGeometry(this->visualItemRect(currentItem).left()+15,this->visualItemRect(currentItem).top()+1,this->visualItemRect(currentItem).width(),this->visualItemRect(currentItem).height()-2);//出现的位置groupNameEdit->setText(currentItem->text());//获取该组名内容groupNameEdit->show();//显示groupNameEdit->selectAll();//全选groupNameEdit->setFocus();//获取焦点}//添加好友,主要是为了测试功能,实际工程里可以改成动态读取数据库进行添加好友voidpersonList::slotAddBuddy(){personListBuddy*buddy=newpersonListBuddy();//创建一个自己定义的信息类buddy->headPath=":/head";//设置头像路径buddy->name->setText("逍遥圣帝");//设置用户名buddy->sign->setText("用通俗的语言,讲深刻的技术。");//设置个性签名QList<QListWidgetItem*>tem=groupMap.keys(currentItem);//当前组对应的项(包括组本身和好友)复制给tem//关键代码QListWidgetItem*newItem=newQListWidgetItem();//创建一个newItemthis->insertItem(row(currentItem)+tem.count(),newItem);//将该newItem插入到后面this->setItemWidget(newItem,buddy);//将buddy赋给该newItemgroupMap.insert(newItem,currentItem);//加进容器,key为好友,value为组if(isHideMap.value(currentItem))//如果该组是隐藏,则加进去的好友设置为隐藏newItem->setHidden(true);else//否则,该好友设置为显示newItem->setHidden(false);}//删除好友voidpersonList::slotDelBuddy(){groupMap.remove(currentItem);//移除该好友deletecurrentItem;//删除}//重命名完成voidpersonList::slotRenameEditFshed(){if(groupNameEdit->text()!=NULL)//如果重命名编辑框不为空currentItem->setText(groupNameEdit->text());//更新组名groupNameEdit->setText("");groupNameEdit->hide();//隐藏重命名编辑框}
#include "personlist.h"#include <QAction>#include <QIcon>#include "personlistbuddy.h"personList::personList(QListWidget *parent) :QListWidget(parent){setFocusPolicy(Qt::NoFocus); // 去除item选中时的虚线边框setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//水平滚动条关闭initMenu();}//初始化菜单void personList::initMenu(){//初始化:blankMenu = new QMenu();groupMenu = new QMenu();personMenu = new QMenu();groupNameEdit=new QLineEdit();QAction *addGroup = new QAction("添加分组", this);QAction *delGroup = new QAction("删除该组", this);QAction *rename = new QAction("重命名", this);QAction *addBuddy = new QAction("添加好友",this);QAction *delBuddy = new QAction("删除好友", this);//设置:groupNameEdit->setParent(this); //设置父类groupNameEdit->hide(); //设置初始时隐藏groupNameEdit->setPlaceholderText("未命名");//设置初始时的内容//布局:blankMenu->addAction(addGroup);groupMenu->addAction(delGroup);groupMenu->addAction(rename);groupMenu->addAction(addBuddy);personMenu->addAction(delBuddy);//信息槽:connect(groupNameEdit,SIGNAL(editingFinished()),this,SLOT(slotRenameEditFshed()));connect(addGroup,SIGNAL(triggered()),this,SLOT(slotAddGroup()));connect(delGroup,SIGNAL(triggered()),this,SLOT(slotDelGroup()));connect(rename,SIGNAL(triggered()),this,SLOT(slotRename()));connect(addBuddy,SIGNAL(triggered()),this,SLOT(slotAddBuddy()));connect(delBuddy,SIGNAL(triggered()),this,SLOT(slotDelBuddy()));}//鼠标点击事件void personList::mousePressEvent(QMouseEvent *event){QListWidget::mousePressEvent(event); // 如果不调用基类mousePressEvent,item被select会半天不响应,调用父类,让QSS起效,因为QSS基于父类QListWidget,子类就是子窗口,就是最上层窗口,是覆盖在父窗口上的,所以先于父窗口捕获消息//防止一种特殊情况:给新item命名、点击其他item或空白处时,指向新item的currentItem被赋予其他itemif(groupNameEdit->isVisible() && !(groupNameEdit->rect().contains(event->pos()))){if(groupNameEdit->text()!=NULL)currentItem->setText(groupNameEdit->text());groupNameEdit->setText("");groupNameEdit->hide();}currentItem = this->itemAt(mapFromGlobal(QCursor::pos()));//鼠标位置的Item,不管右键左键都获取if(event->button()==Qt::LeftButton && currentItem!=NULL && currentItem==groupMap.value(currentItem))//如果点击的左键并且是点击的是组{if(isHideMap.value(currentItem)) //如果先前是隐藏,则显示{foreach(QListWidgetItem* subItem, groupMap.keys(currentItem))//遍历组的对应的项(包括自身和好友)if(subItem!=currentItem) //如果是组的话不进行处理{subItem->setHidden(false); //好友全部显示}isHideMap.insert(currentItem,false);//设置该组为显示状态currentItem->setIcon(QIcon(":/arrowDown"));}else //否则,先前是显示,则隐藏{foreach(QListWidgetItem* subItem, groupMap.keys(currentItem))//遍历组的对应的项(包括自身和好友)if(subItem!=currentItem) //如果是组的话不进行处理{subItem->setHidden(true); //好友全部隐藏}isHideMap.insert(currentItem,true);//设置该组为隐藏状态currentItem->setIcon(QIcon(":/arrowRight"));}}}//菜单事件,为了显示菜单,点击鼠标右键响应,鼠标点击事件mousePressEvent优先于contextMenuEventvoid personList::contextMenuEvent(QContextMenuEvent *event){QListWidget::contextMenuEvent(event); //调用基类事件if(currentItem==NULL) //如果点击到的是空白处{blankMenu->exec(QCursor::pos());return;}if(currentItem==groupMap.value(currentItem)) // 如果点击到的是组groupMenu->exec(QCursor::pos());else //否则点击到的是好友personMenu->exec(QCursor::pos());}//添加组void personList::slotAddGroup(){QListWidgetItem *newItem=new QListWidgetItem(QIcon(":/arrowRight"),"未命名"); //创建一个ItemnewItem->setSizeHint(QSize(this->width(),25));//设置宽度、高度this->addItem(newItem); //加到QListWidget中groupMap.insert(newItem,newItem);//加到容器groupMap里,key和value都为组isHideMap.insert(newItem,true); //设置该组隐藏状态groupNameEdit->raise();groupNameEdit->setText(tr("未命名")); //设置默认内容groupNameEdit->selectAll(); //设置全选groupNameEdit->setGeometry(this->visualItemRect(newItem).left()+15,this->visualItemRect(newItem).top()+1,this->visualItemRect(newItem).width(),this->visualItemRect(newItem).height()-2);//出现的位置groupNameEdit->show(); //显示groupNameEdit->setFocus();//获取焦点currentItem = newItem; // 因为要给group命名,所以当前的currentItem设为该group}//删除组void personList::slotDelGroup(){foreach(QListWidgetItem* item, groupMap.keys(currentItem)) //遍历该组的所有好友和自身的组{groupMap.remove(item); //移除delete item; //删除}isHideMap.remove(currentItem); //移除}//重命名void personList::slotRename(){groupNameEdit->raise();groupNameEdit->setGeometry(this->visualItemRect(currentItem).left()+15,this->visualItemRect(currentItem).top()+1,this->visualItemRect(currentItem).width(),this->visualItemRect(currentItem).height()-2);//出现的位置groupNameEdit->setText(currentItem->text()); //获取该组名内容groupNameEdit->show(); //显示groupNameEdit->selectAll(); //全选groupNameEdit->setFocus(); //获取焦点}//添加好友,主要是为了测试功能,实际工程里可以改成动态读取数据库进行添加好友void personList::slotAddBuddy(){personListBuddy *buddy=new personListBuddy(); //创建一个自己定义的信息类buddy->headPath=":/head";//设置头像路径buddy->name->setText("逍遥圣帝"); //设置用户名buddy->sign->setText("用通俗的语言,讲深刻的技术。"); //设置个性签名QList<QListWidgetItem*> tem = groupMap.keys(currentItem);//当前组对应的项(包括组本身和好友)复制给tem//关键代码QListWidgetItem *newItem = new QListWidgetItem(); //创建一个newItemthis->insertItem(row(currentItem)+tem.count(),newItem); //将该newItem插入到后面this->setItemWidget(newItem, buddy); //将buddy赋给该newItemgroupMap.insert(newItem,currentItem); //加进容器,key为好友,value为组if(isHideMap.value(currentItem))//如果该组是隐藏,则加进去的好友设置为隐藏newItem->setHidden(true);else //否则,该好友设置为显示newItem->setHidden(false);}//删除好友void personList::slotDelBuddy(){groupMap.remove(currentItem); //移除该好友delete currentItem; //删除}//重命名完成void personList::slotRenameEditFshed(){if(groupNameEdit->text()!=NULL)//如果重命名编辑框不为空currentItem->setText(groupNameEdit->text()); //更新组名groupNameEdit->setText("");groupNameEdit->hide(); //隐藏重命名编辑框}
三、美化用到的QSS:
[cpp]view plaincopy print? QListWidget{background:white;color:black;border:none;}QListWidget::item{border:none;height:54px;}QListWidget::item:hover{background:rgb(252,240,193)}QListWidget::item:selected{background:rgb(252,233,161);color:black;}QScrollBar:vertical{background:transparent;width:9px;margin:0px0px2px0px;}QScrollBar::handle:vertical{background:rgb(195,195,195);min-height:20px;border-radius:3px;}QScrollBar::handle:vertical:hover{background:rgba(0,0,0,30%);}QScrollBar::add-line:vertical{height:0px;subcontrol-position:bottom;subcontrol-origin:margin;}QScrollBar::sub-line:vertical{height:0px;subcontrol-position:top;subcontrol-origin:margin;}
QListWidget{background:white;color:black;border:none;}QListWidget::item{border:none;height: 54px;}QListWidget::item:hover{background:rgb(252,240,193)}QListWidget::item:selected{background:rgb(252,233,161);color:black;}QScrollBar:vertical {background:transparent;width:9px;margin: 0px 0px 2px 0px;}QScrollBar::handle:vertical {background: rgb(195, 195, 195);min-height: 20px;border-radius: 3px;}QScrollBar::handle:vertical:hover{background:rgba(0,0,0,30%);}QScrollBar::add-line:vertical {height: 0px;subcontrol-position: bottom;subcontrol-origin: margin;}QScrollBar::sub-line:vertical {height: 0px;subcontrol-position: top;subcontrol-origin: margin;}
四、用到的素材:
五、效果图: