文章结构

  • 最终实现效果
    • 基本功能
    • 代码主要结构
    • FriendTree类主要工作解析
    • ItemDelegate类主要工作解析
  • 工程源码路径/下载地址

最终实现效果

以上是实现的最终样式,自己电脑上安装的QQ9.0版本,就按这个版本来了。


基本功能

实现的一些基本功能总结:

  1. 分组展示好友列表 ,一个组下多个好友;
  2. Item上绘制头像、在线状态、个性签名、用户名+昵称(依据是否VIP设置成不同颜色)、视频通话图标;
  3. 头像、在线状态、视频通话图标采用svg图标格式
  4. hover效果,鼠标移至Item不同位置,ToolTip显示不同的信息,如鼠标移动至头像时提示“鼠标移到头像上啦!”,鼠标移到视频通话按钮上显示"视频通话"等,默认显示用户名+昵称+QQ号。
  5. 当鼠标移动到某个好友Item上时,对应Item显示视频通话图标。
  6. 双击Item事件,打开聊天(仅演示捕获事件,进行弹窗提示事件处理结果);
  7. 点击视频通话图标,进行视频通话(仅演示捕获事件,进行弹窗提示事件处理结果)

代码主要结构


说明:公共UI库主要是一些通用的处理,比如DelegatePainter类,专门用来绘制文本、图片等,TreeView增加了一些自定义的事件信号,在此处不一一赘述,若有需要,可直接拿过去复用即可,也可以自己定义其他信号等。我们的好友列表TreeView是继承此类的。源码路径见文章最后。


FriendTree类主要工作解析

前提:使用上面提到的公共Ui库。
下面讲解下FriendTree主要做的事情,类头文件如下:

#pragma once#include <QTreeView>
#include "PublicGui/TreeView/TreeView.h"
#include "GlobalDefines.h"using namespace publicgui;namespace qqfriendlist
{class ItemDelegate;class FriendTree : public TreeView{Q_OBJECTpublic:FriendTree(QWidget *parent = Q_NULLPTR);~FriendTree();// 赋值 传入分组/好友结构数据void setValues(const std::vector<Group>& groups);private:void initUi();void initConnection();// 自定义的hover处理void onHoverHandle(const QModelIndex& index, int role);// 自定义的点击事件处理void onClickedHandle(const QModelIndex& index, int role);private:QStandardItemModel* m_model{ nullptr }; // modelItemDelegate* m_delegate{ nullptr };};
}

以下四个成员函数

#include "FriendTree.h"
#include "ItemDelegate.h"
#include <QHeaderView>
#include <QTime>
#include <QMessageBox>
#include "GlobalDefines.h"namespace qqfriendlist
{FriendTree::FriendTree(QWidget *parent): TreeView(parent){initUi();initConnection();}FriendTree::~FriendTree(){}/****************************************!* @brief  赋值接口* @param  [in]  const std::vector<Group> & groups* @return void****************************************/void FriendTree::setValues(const std::vector<Group>& groups){m_model->clear();for (const auto& group : groups){// 添加分组QStandardItem* item = new QStandardItem(group.groupName);item->setEditable(false);item->setData(group.groupName, Qt::ToolTipRole);item->setData(true, static_cast<int>(CustomRole::IsGroupRole));m_model->appendRow(item);for (const auto& contact : group.contactList){// 分组下的联系人QStandardItem* contactItem = new QStandardItem(contact.name);contactItem->setEditable(false);contactItem->setData(contact.name, Qt::ToolTipRole);QVariant value{};value.setValue(contact);contactItem->setData(value, static_cast<int>(CustomRole::ContactRole));item->appendRow(contactItem);}}}/****************************************!* @brief  初始化界面* @return void****************************************/void FriendTree::initUi(){setWindowTitle(QStringLiteral("QQ好友列表"));// basic initheader()->hide();  // 隐藏表头setIndentation(0);   // 左边距设置为0setAnimated(true);  // 展开时动画m_model = new QStandardItemModel(this);setModel(m_model);m_delegate = new ItemDelegate(this);setItemDelegate(m_delegate);}/****************************************!* @brief  初始化信号槽链接* @return void****************************************/void FriendTree::initConnection(){// 点击事件connect(this, &QTreeView::clicked, [&](const QModelIndex& index){if (index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool()){setExpanded(index, !isExpanded(index)); // 单击展开/收缩列表}});// 双击打开聊天connect(this, &QTreeView::doubleClicked, [&](const QModelIndex& index){if (!index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool()){// 不是分组Item才去处理双击事件auto info = index.data(static_cast<int>(CustomRole::ContactRole)).value<Contact>();QMessageBox msgBox;msgBox.setWindowTitle(QStringLiteral("双击打开聊天"));msgBox.setText(QStringLiteral("你好,") + info.name + QStringLiteral("。在不?"));msgBox.exec();}});// 展开时更换左侧的展开图标connect(this, &QTreeView::expanded, [&](const QModelIndex& index){m_model->itemFromIndex(index)->setData(true, static_cast<int>(CustomRole::IsExpandedRole));});// 收起时更换左侧的展开图标connect(this, &QTreeView::collapsed, [&](const QModelIndex& index){m_model->itemFromIndex(index)->setData(false, static_cast<int>(CustomRole::IsExpandedRole));});// 自定义hover事件connect(this, &TreeView::signalHover, this, &FriendTree::onHoverHandle);// 自定义点击事件connect(this, QOverload<const QModelIndex&, int>::of(&TreeView::signalClicked), this, &FriendTree::onClickedHandle);}/****************************************!* @brief  hover事件处理* @param  [in]  const QModelIndex & index 索引项* @param  [in]  int role 角色* @return void****************************************/void FriendTree::onHoverHandle(const QModelIndex& index, int role){if (index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool()){return; // 群组的hover事件 退出}else{// 不同区域显示不同tooltipauto info = index.data(static_cast<int>(CustomRole::ContactRole)).value<Contact>();QString displayName{};switch (role){case static_cast<int>(CustomRole::PortraitRole) : // 视频通话{displayName = QStringLiteral("鼠标移到头像上啦!");break;}case static_cast<int>(CustomRole::VideoRole) : // 视频通话{displayName = QStringLiteral("视频通话");break;}case static_cast<int>(CustomRole::SignatureRole) : // 个性签名{displayName = info.signature;break;}default:{// 默认tooltip显示用户名称+QQ号displayName = info.name + "(" + info.nickName + ")" + "(" + info.id + ")";break;}}m_model->itemFromIndex(index)->setData(displayName, Qt::ToolTipRole);}}/****************************************!* @brief  点击事件角色处理* @param  [in]  const QModelIndex & index* @param  [in]  int role* @return void****************************************/void FriendTree::onClickedHandle(const QModelIndex& index, int role){// 不同区域显示不同tooltipauto info = index.data(static_cast<int>(CustomRole::ContactRole)).value<Contact>();switch (role){case static_cast<int>(CustomRole::VideoRole) : // 视频通话{QMessageBox msgBox;msgBox.setWindowTitle(QStringLiteral("视频通话"));msgBox.setText(QTime::currentTime().toString("hh:mm:ss")+ QStringLiteral(",向") + info.name + QStringLiteral("发起视频通话。"));msgBox.exec();break;}default:{break;}}}
}

FriendTree类仅需要初始化model delegate,以及相关的点击事件,hover事件等。


ItemDelegate类主要工作解析

此类是TreeView列表样式绘制部分,样式基本全部在这个类中完成

#pragma once#include "PublicGui/TreeView/StyledDelegate.h"using namespace publicgui;namespace qqfriendlist
{class ItemDelegate : public StyledDelegate{Q_OBJECTpublic:ItemDelegate(QObject *parent = Q_NULLPTR);~ItemDelegate();// 完成Item具体内容的绘制virtual void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;// 绘制群组virtual void paintGroup(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;// 绘制联系人virtual void paintContact(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;protected:QSize sizeHint(const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE;// hover的rolevirtual int getHoverEventRole(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex &index) const;// 点击的rolevirtual int getMouseEventRole(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex &index) const;};}

cpp文件

#include "ItemDelegate.h"
#include "PublicGui/TreeView/DelegatePainter.h"
#include "GlobalDefines.h"namespace qqfriendlist
{namespace{const int kGroupItemHeight{ 35 };const int kContactItemHeight{ 60 };const QRect kGroupPullIconRect{ 10,12,11,11 }; // 群组下拉图标const QRect kGroupNameRect{ 30,0,200,35 };      // 群组名称const QRect kContactPortraitRect{ 10,10,40,40 }; // 联系人头像const QRect kContactNameRect{ 60,10,200,20 };     // 联系人名字const QRect kSignatureRect{ 60,30,160,20 };         // 联系人个性签名const QRect kVipIconRect{ 60,30,30,12 };      // 联系人VIP图标const QRect kOnlineStateIconRect{ 40,35,14,14 }; // 在线状态图标const QRect kVideoIconRect{ 0,25,20,13 };      // 视频通话图标}ItemDelegate::ItemDelegate(QObject *parent): StyledDelegate(parent){}ItemDelegate::~ItemDelegate(){}/****************************************!* @brief  代理绘制* @param  [in]  QPainter * painter* @param  [in]  const QStyleOptionViewItem & option* @param  [in]  const QModelIndex & index* @return void****************************************/void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const{DelegatePainter delegatePainter;OperateActions operateActions = getOperateActions(option, index);QColor color;color = (operateActions.isHovered) ? QColor("#f0f0f0") //背景色选中: (!operateActions.isSelected && operateActions.isHovered) ? QColor("lightblue") // hover: QColor("#ffffff");if (index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool()){paintGroup(painter, option, index);return;}else{paintContact(painter, option, index); // 绘制联系人}}/****************************************!* @brief  绘制群组Item* @param  [in]  QPainter * painter* @param  [in]  const QStyleOptionViewItem & option* @param  [in]  const QModelIndex & index* @return void****************************************/void ItemDelegate::paintGroup(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const{DelegatePainter delegatePainter;OperateActions operateActions = getOperateActions(option, index);QColor color;color = (operateActions.isHovered) ? QColor("#f0f0f0") : QColor("#ffffff"); // hover时变灰// 背景色painter->setPen(Qt::NoPen);painter->setBrush(color);painter->drawRect(option.rect);// 下拉列表图标QRect pullIconRect(option.rect.left() + kGroupPullIconRect.x(), option.rect.top() + kGroupPullIconRect.y(),kGroupPullIconRect.width(), kGroupPullIconRect.height());QString pullIconPath{ ":/QQFriendList/Resources/images/expand_down.svg" };if (!index.data(static_cast<int>(CustomRole::IsExpandedRole)).toBool()){pullIconPath = ":/QQFriendList/Resources/images/expand_right.svg";}delegatePainter.paintSvgImage(painter, pullIconPath, pullIconRect);// 群组名称QRect nameRect(option.rect.left() + kGroupNameRect.x(), option.rect.top() + kGroupNameRect.y(), kGroupNameRect.width(), kGroupNameRect.height());delegatePainter.paintText(painter, option, index, Qt::DisplayRole, Qt::AlignLeft, QColor("black"), nameRect, 13);}/****************************************!* @brief  绘制联系人信息* @param  [in]  QPainter * painter* @param  [in]  const QStyleOptionViewItem & option* @param  [in]  const QModelIndex & index* @return void****************************************/void ItemDelegate::paintContact(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const{DelegatePainter delegatePainter;OperateActions operateActions = getOperateActions(option, index);QColor backgroundColor;if (operateActions.isHovered && !operateActions.isSelected){backgroundColor = QColor("#f2f2f2");}else if (operateActions.isSelected){backgroundColor = QColor("#ebebeb");}else{backgroundColor = QColor("#ffffff");}// 背景色painter->setPen(Qt::NoPen);painter->setBrush(backgroundColor);painter->drawRect(option.rect);// 联系人信息auto info = index.data(static_cast<int>(CustomRole::ContactRole)).value<Contact>();// 联系人头像{QRect contactHeadPortraitRect(option.rect.left() + kContactPortraitRect.x(), option.rect.top() + kContactPortraitRect.y(),kContactPortraitRect.width(), kContactPortraitRect.height());QString contactHeadPortraitPath{ ":/QQFriendList/Resources/images/portrait_boy.svg" };if (!info.sex){contactHeadPortraitPath = ":/QQFriendList/Resources/images/portrait_girl.svg";}delegatePainter.paintSvgImage(painter, contactHeadPortraitPath, contactHeadPortraitRect);}// 联系人名称{QRect nameRect(option.rect.left() + kContactNameRect.x(), option.rect.top() + kContactNameRect.y(),kContactNameRect.width(), kContactNameRect.height());QColor nameColor{ "black" };if (info.isVip) // 是vip{nameColor = QColor("#ff0000");}delegatePainter.paintText(painter, option, index, Qt::DisplayRole,Qt::AlignLeft, nameColor, nameRect, 13, info.name + "(" + info.nickName + ")");}// 个性签名{QRect signatureRect(option.rect.left() + kSignatureRect.x(), option.rect.top() + kSignatureRect.y(),kSignatureRect.width(), kSignatureRect.height());delegatePainter.paintText(painter, option, index, Qt::DisplayRole,Qt::AlignLeft, QColor("black"), signatureRect, 13, info.signature);}// 在线状态图标{QRect onlineStateIconRect(option.rect.left() + kOnlineStateIconRect.x(), option.rect.top() + kOnlineStateIconRect.y(),kOnlineStateIconRect.width(), kOnlineStateIconRect.height());QString onlineStateIconPath{ ":/QQFriendList/Resources/images/online-im.svg" };switch (info.onlineState){case OnlineState::Busy:{onlineStateIconPath = ":/QQFriendList/Resources/images/busy-im.svg";break;}case OnlineState::Leave:{onlineStateIconPath = ":/QQFriendList/Resources/images/leave-im.svg";break;}case OnlineState::Online:{break;}default:break;}// 防背景透明 先把背景处理了 用背景色backgroundColor画一个圆形区域painter->setPen(Qt::NoPen);painter->setBrush(backgroundColor);painter->drawRoundedRect(onlineStateIconRect, onlineStateIconRect.width() / 2, onlineStateIconRect.height() / 2);delegatePainter.paintSvgImage(painter, onlineStateIconPath, onlineStateIconRect);}// 视频通话{// 只有hover状态才会显示视频通话图标if (operateActions.isHovered){QRect videoRect(option.rect.left() + option.rect.width() - kVideoIconRect.width() - 16, option.rect.top() + kVideoIconRect.y(),kVideoIconRect.width(), kVideoIconRect.height());delegatePainter.paintSvgImage(painter, ":/QQFriendList/Resources/images/video.svg", videoRect);}}}/****************************************!* @brief  根据Item不同调整高度* @param  [in]  const QStyleOptionViewItem & option* @param  [in]  const QModelIndex & index* @return QSize****************************************/QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const{QSize size = QStyledItemDelegate::sizeHint(option, index);if (index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool()){return QSize(size.width(), kGroupItemHeight); // 群组Item高度}return QSize(size.width(), kContactItemHeight);   // 联系人Item高度}/****************************************!* @brief  根据鼠标位置 判断hover的role是哪个 并返回* @param  [in]  const QPoint & pos 鼠标位置* @param  [in]  const QStyleOptionViewItem & option* @param  [in]  const QModelIndex & index 索引位置* @return int****************************************/int ItemDelegate::getHoverEventRole(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex &index) const{// 视频通话图标位置QRect videoRect(option.rect.left() + option.rect.width() - kVideoIconRect.width() - 16, option.rect.top() + kVideoIconRect.y(),kVideoIconRect.width(), kVideoIconRect.height());if (!index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool() && videoRect.contains(pos)){return static_cast<int>(CustomRole::VideoRole);}// 个性签名QRect signatureRect(option.rect.left() + kSignatureRect.x(), option.rect.top() + kSignatureRect.y(),kSignatureRect.width(), kSignatureRect.height());if (!index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool() && signatureRect.contains(pos)){return static_cast<int>(CustomRole::SignatureRole);}// 头像QRect contactHeadPortraitRect(option.rect.left() + kContactPortraitRect.x(), option.rect.top() + kContactPortraitRect.y(),kContactPortraitRect.width(), kContactPortraitRect.height());if (!index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool() && contactHeadPortraitRect.contains(pos)){return static_cast<int>(CustomRole::PortraitRole);}return -1;}/****************************************!* @brief  返回点击的Item的角色* @param  [in]  const QPoint & pos* @param  [in]  const QStyleOptionViewItem & option* @param  [in]  const QModelIndex & index* @return int****************************************/int ItemDelegate::getMouseEventRole(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex &index) const{// 视频通话图标位置QRect videoRect(option.rect.left() + option.rect.width() - kVideoIconRect.width() - 16, option.rect.top() + kVideoIconRect.y(),kVideoIconRect.width(), kVideoIconRect.height());if (!index.data(static_cast<int>(CustomRole::IsGroupRole)).toBool() && videoRect.contains(pos)){return static_cast<int>(CustomRole::VideoRole);}return -1;}}

工程源码路径/下载地址

开发环境:
vs2015+Qt5.9.6+ qt-vsaddin-msvc2015-2.2.2.vsix

所有源码路径:
https://github.com/lesliefish/Qt/tree/master/UI/QQFriendList

QQ好友列表的实现(QQ9.0版本样式) -- 使用QTreeView相关推荐

  1. QQ好友列表导出用JTree树实现

    最近学习了一下JTree的使用方法:QQ好友列表导出用JTree树实现 先来看一下树的实例: 构建一个树, DefaultMutableTreeNode root = new DefaultMutab ...

  2. iOS之仿QQ好友列表展开收缩效果的实现

    使用UICollectionView实现 思路 很明显整体它是一个列表,它的分组是一个列表,它里面的好友列表也是一个列表,所以就可以使用组头来设置分组列表,使用cell设置好友列表: 当点击组头的时候 ...

  3. tableView练习 -- QQ好友列表

    LWTViewController.h // // LWTViewController.h // tableView练习 -- QQ好友列表 // // Created by apple on 14- ...

  4. html仿qq最小化怎么实现,JS仿QQ好友列表展开、收缩功能(第一篇)

    JS仿QQ好友列表展开.收缩功能(第一篇) 发布时间:2020-10-17 14:20:03 来源:脚本之家 阅读:96 作者:erdouzhang 效果图如下所示: html: 我的好友 张三 李四 ...

  5. Android开发学习之QQ好友列表的实现

    今天想和大家分享的是QQ好友列表的实现,我们知道,在默认情况下,QQ好友列表是处于收缩状态的,此时,列表显示好友分组名称.当我们单击分组时,列表处于展开状态,列表显示该分组下的项目.当再次单击分组时, ...

  6. python获取qq好友ip_使用Python模拟登录QQ邮箱获取QQ好友列表

    最近因开发项目的需要,有一个需求,就是很多SNS网站都有的通过 Email地址 导入好友列表,不过这次要导入的不是Email 列表,而是QQ的好友列表. 实现方式: 通过google一搜,实现的方式大 ...

  7. iOS开发-QQ好友列表展示

    那么今天给同学写了一个QQ好友列表展示的Demo,涉及很多的内部细节以及高度封装自定义的cell和自定义view,那么内部所用知识和细节全部呈现在代码和注释中,那么废话不多说直接上代码,先看效果图! ...

  8. 自动抓取QQ好友列表?Windows UIA教你轻松实现

    目录:导读 引言 选择Windows UIA框架进行自动化测试的原因 查找窗口 读取QQ软件的好友列表 结语 引言 每个使用QQ的人都有自己的好友列表,但是如果你想要查看所有好友信息,手动一个个点击会 ...

  9. android 实现QQ好友列表(扩展listview:ExpandableListView)

    在某些android开发群里,看到有些新手问怎么实现QQ好友列表,其实网上一搜挺多的.接触Android,也才一年的时间,大部分时间花在工作上(解bug...),界面上开发很少参与.自己维护的系统应用 ...

最新文章

  1. Android LayoutInflater 的使用
  2. 原创:去繁存简,回归本源:微信小程序公开课信息分析《一》
  3. 还在担心写的一手烂SQL,送你4款工具
  4. android电话拨号器
  5. History命令用法
  6. LeetCode 1691. 堆叠长方体的最大高度(排序+最大上升子序DP)
  7. Keil MDK编译器(V4.03)与J-LINK使用
  8. 操作系统(1) 发展历史
  9. ASP.NET MVC显示UserControl控件(扩展篇)
  10. 2021年最佳开源软件榜单出炉!
  11. 问题解决:你需要trustedinstaller提供的权限才能删除
  12. PS将可见图层创建为一个新的图层,保留原来的图层,Photoshop 导出可见图层
  13. 【Verilog基础】卡诺图化简要点总结
  14. SGX攻防部分POC
  15. 纵横捭阖C++之从异步谈起
  16. 华为云快成长直播间大数据AI专场,加速经济物联网智能化提升
  17. 通过Element开发基础增删改查页面——Vue项目实战(三)
  18. 《Unix Linux 大学教程》 - 附录F
  19. 自己动手搭建Fabric网络,修改当前工作目录名之后出现的错误
  20. [系统资源攻略]IO第二篇

热门文章

  1. md语法|LaTex数学公式
  2. IDEA调试技巧--看线程生命周期
  3. 简单代码实现MQTT客户端功能
  4. 手机日程提醒在哪里设置?
  5. DAY09_继承拼图游戏案例
  6. Ymodem协议要点
  7. 提高工作效率,减少浪费生命
  8. 朋友,在准备大厂面试吗,内部技术了解一下,Android框架体系架构的知识
  9. IC_EDA_ALL虚拟机(丰富版):questasim、vivado、vcs、verdi、dc、pt、spyglass、icc2、synplify、INCISIVE、IC617、MMSIM、工艺库
  10. idea 控制台乱码问题的问题