C++ Qt开发社交好友列表

   山前大桥下,有一个傻瓜。一枚小学生,幽居荒山中。养了一群狗,还有一只猫。喜欢搞编程,缺钱打零工。数理史皆通,法文记心中。下厨会做饭,挥毫能舞文。

 1、申明:不是专业的Qt开发者,项目需要国产化系统场景,因此作者四个小时入门Qt,从此拉开了Qt的战场。

 2,场景:在做支持国产化OS的社交软件时 考虑了很多UI框架,首先考虑的是Electron,其次是QML,然后是QWidget和GTK。经过详细的需求考量之后选择使用Qt作为GUi开发基础库。选择QWidget的原因包括以下几点:

a、资源占用合理,虽然相比较windows自带的GUI框架例如WIn32 SDKs和MFC以及和第三方的windows GUI库比如duilib soui等内存占用稍多,但相比较Electron相同的Ui只需要五分之一甚至十分之一的资源利用即可完成。比起QML和WPF也仅需三分之一的内存即可实现相同效果的UI。当然WPF不能跨平台。程序员口中的性能过剩在企事业单位中不存在,在4G内存的电脑上Electron等方案并不可行。

b、高性能,QWidget完全使用C++开发,因此一如既往的继承了C++的高性能特性,尤其是在低配置的计算机上,优势明显。但是由于QWidget使用CPU绘图,因此在高配置的电脑上除了节省资源,并不能体现出太大的Ui渲染优势。

c、使用C++开发,具备更底层的控制和灵活性。得益于C++的语言优势。Qt可以使用所有的C++第三方库,具备非常强的扩展和更高级的计算机使用权限,于此同时提供更加底层的设备访问功能和灵活的外部程序嵌入。由于使用C++开发,因此可以通过C风格导出风格封装 可作为插件提供用于其他语言跨语言调用。方便系统集成,这是高级语言无法做到的。

d、对计算机配置要求低。得益于C++的内存结构和执行原理,导致C++程序在资源利用上有着先天优势,相同的功能只需要高级语言内存的五分之一到十分之一即可完成。

e、跨平台,在完善的全方位跨平台的GUI框架选择上Qt似乎唯一而非之一,因此不再多说。

f、完善程度高,相比较GTK,Qt可以说是最佳的选择,无论从组件功能还是底层APi封装上都提供了相对完整的体验,没有技术断层。

g、支持高分屏,其实Qt在5之前高分屏支持很不理想,即便是在Qt中 5.14.2之前的版本都存在问题。在5.12之前只支持整倍缩放,在5.14.2时虽然支持了非整倍缩放,但是存在非整倍缩放时控件底部会存在一条绝对一像素的穿透。

注意:Qt的商业使用许可价格不便宜。

第一篇文章,前奏有点长,现在开始正文。

本文主题是使用QListView开发一个社交好友列表功能,功能丰富,支持非常丰富的属性功能,可以低代码实现各种风格的列表风格,比如微信,QQ,等主流社交软件的好友列表,包含但不限于以下功能

a.支持分组显示或不分组显示。

b.支持自定义分组和索引导航分组

c.支持头像的圆角比例

d.支持灰度头像

e.支持标头状态线

f.支持分割线

g.支持各种自定义颜色

h.支持使用文字头像或图像头像或图像不存在时是用文字头像

i.支持多种排序模式

j.支持vip和svip标识

k.支持列表和图标模式

l.支持自定义所有图标

m.支持选种样式风格

n.支持好友分组成员统计

o.支持鼠标单击,双击,悬停时触发Ui元素事件

p.支持右击时发送分组数据或好友数据信号

q.支持元素悬停效果

r.支持自定义选项按钮

s.支持鼠标移动上去之后显示选项按钮,包括自定义选项,语音通话,视频通话 等功能

PS:先上截图

gif模糊的没法看,那就一张一张来

 

图不多上,由于篇幅有限我们首先重点说明计数要点

1、分组,由于是ListView,因此主要靠Mode的setData的role区分分组和用户,分组展开和闭合是通过设置行隐藏和显示实现,在目标分组下存在一个与分组相同的data role,根据此role隐藏或显示row。由于这个实现没有自定义处理mode,因此在数据多的时候并不理想,但这不是重点。因此此列表视图建议好友数量不要超过一万。否则会影响体验。笔者测试数据为3万条

2、item元素事件,根据Rect区域判断item上的图标或按钮区域,所有按钮皆通过绘制模拟,并非button等类。

重要代码:

1、样式风格结构体

/// 会话列表项风格委托样式数据结构
///
typedef struct LQFRIENDSLIST_STYLE_{

    QColor    cGroupBkgColor = QColor(88,88,88,44);   //分组背景色
    QColor    cGroupExpanClr = QColor(77,77,77,44);   //分组展开背景色
    QColor    cGroupHoverClr = QColor(254,118,19);    //分组鼠标悬停背景颜色
    QColor    cGroupExpHover = QColor(238,156,109);   //分组展开悬停背景色
    QColor    cGroupsBorders = QColor(117,131,218);   //分组边框颜色
    QColor    cItemsBkgColor = QColor(192,192,192,22);//好友背景色
    QColor    cItemsHoverClr = QColor(69,176,142);    //好友鼠标悬停背景色
    QColor    cItemSelectClr = QColor(244,123,64);    //好友选中背景色
    QColor    cIconHoverClrs = QColor(22,22,22,44);   //鼠标悬停颜色
    QColor    cIconSelectClr = QColor(11,11,11,44);   //选中项时颜色

    QColor    cItemNamesClrs = QColor(33,33,33);      //好友名称文字颜色
    QColor    cNameHoverClrs = QColor(220,220,220);   //好友名称文字鼠标悬停时颜色
    QColor    cNameSelectClr = QColor(255,255,255);   //好友名称文字选中时颜色

    QColor    cGroupTxtColor = QColor(44,44,44);       //分组背景色
    QColor    cGroupHoverTxt = QColor(255,255,255);    //分组鼠标悬停背景颜色
    QColor    cGroupExpanTxt = QColor(33,33,33);       //分组鼠标悬停背景颜色
    QColor    cItemsTxtColor = QColor(33,33,33);       //好友文字颜色
    QColor    cItemsHoverTxt = QColor(255,255,255);    //好友鼠标悬停背景色
    QColor    cItemSelectTxt = QColor(235,235,235);    //好友选中背景色
    QColor    cGroupCountBkg = QColor(127,127,127);    //分组成员统计背景颜色
    QColor    cGroupCountTxt = QColor(220,220,220);    //分组成员统计文字颜色
    QColor    cGroupNodeClrs = QColor(127,127,127);    //分组节点图标颜色
    QColor    cGroupNodeBkgd = QColor(58,191,136);     //分组节点背景颜色
    QColor    cGroupNodesExp = QColor(114,133,218);    //分组节点展开背景颜色

    QColor    cDescHoverClrs = QColor(144,144,144);    //摘要文字鼠标悬停颜色
    QColor    cDescSelectClr = QColor(200,200,200);    //摘要文字选中颜色

    QColor    cItemHeadLines = QColor(63,166,148,160); //头部参考线颜色
    QColor    cItemSplitLine = QColor(63,166,148,66);  //分割线颜色
    QColor    cAvatarFrmClrs = QColor(250,74,33);      //头像边框颜色
    QColor    cMouseHoverBkg = QColor(124,144,218);    //鼠标悬停背景颜色
    QColor    cAvatarBkgClrs = QColor(55,55,55);       //头像背景填充颜色
    QColor    cUsersNodesClr = QColor(145,162,229);    //头像节点颜色

    QColor    cVipIconBkgClr = QColor(228,200,200,180);   //会员图标背景颜色
    QColor    cVipNameColors = QColor(251,50,44);     //会员名称颜色
    QColor    cVipDescColors = QColor(235,155,51);    //会员消息颜色
    QColor    cVipNameHovers = QColor(252,75,40);     //会员名称颜色
    QColor    cVipDescHovers = QColor(250,116,42);    //会员消息颜色
    QColor    cVipNameSelect = QColor(240,240,240);   //会员名称颜色
    QColor    cVipDescSelect = QColor(250,250,250);   //会员消息颜色
    QColor    cDeviceBkgClrs = QColor(127,156,230);   //设备背景颜色

    QColor    cOptionBkgClrs = QColor(248,148,72);    //选项按钮背景颜色
    QColor    cVideosBkgClrs = QColor(130,144,228);   //选项按钮悬停颜色
    QColor    cAudiosBkgClrs = QColor(71,186,152);    //选项按钮悬停颜色
    QColor    cOptionHoveClr = QColor(248,74,99);     //选项按钮悬停颜色

    bool      bRoundAvtiveBg = true;                  //选中背景是否绘制成半圆
    bool      bShowHeadLines = true;                  //是否显示头部线条
    bool      bShowSplitLine = true;                  //是否显示分割线
    bool      bShowDeviceInf = true;                  //是否显示设备信息
    uint      nSplitLineType = 2;                     //分割线样式,0实线,1虚线,2点线,3点划线,4双点划线
    uint      nHeadeLineType = 2;                     //标头参考线样式,0实线,1虚线,2点线,3点划线,4双点划线
    bool      bIconViewModel = false;                 //是否为列表视图,默认是
    uint      uItemHeightVal = 50;                    //行高
    uint      uItemMinHeight = 34;                    //小图标模式行高
    uint      uGroupsHeights = 26;                    //分组行高
    uint      uVipShowStyles = 1;                     //VIP显示风格,0不显示,1显示等级,2显示logo
    bool      uShowStatusIco = true;                  //是否在头像处显示联机状态

    uint      uHeadRectWidth = 40;                    //头部宽度

    uint      uIconGridWidth = 72;                    //ICON视图格子宽度
    uint      uIconGridHeigh = 100;                   //ICON视图格子高度

    uint      uGroupStyleIds = 0;                     //分组风格,0默认风格,1居中
    uint      uGroupBorderId = 0;                     //分组边框风格,0不显示边框,1全部边框,2上,3下
    float     fBordersWidths = 0.5;                   //边框宽度
    double    dGroupRadiusId = 0;                     //分组边框半径
    uint      uItemsStyleIds = 0;                     //列表项风格,0大图标模式,1小图标模式
    uint      uExpandGroupId = 1;                     //展开和闭合分组操作方式,0单击箭头,1单击整个分组项目,0双击简单,2双击整个分组项
    uint      uHorizeMarginG = 0;                     //分组项水平Margin
    uint      uVerticMarginG = 0;                     //分组项垂直Margin
    uint      uHorizeMarginU = 0;                     //用户项水平Margin
    uint      uVerticMarginU = 0;                     //用户项垂直Margin
    double    dAvatarsRadius = 0.5;                   //用户头像圆角半径

    bool      bShowUserCount = true;                  //是否显示分组成员统计
    bool      bIsShowSummary = true;                  //显示用户摘要,如果为false则表示显示来源
    bool      bShowOptionBtn = true;                  //显示选项按钮
    bool      bIsShowMoreBtn = true;                  //是否显示自定义处理按钮
    bool      bRandomAvbgClr = true;                  //在显示文字头像事是否使用随机背景颜色
    uint      bAvatarsStyles = 1;                     //头像显示风格索引,0仅显示图像,无头像图像时显示默认头像,1无图像时显示名称第一字符,2仅显示名称第一字符
    bool      bGrayAvatarImg = true;                  //是否启用头像灰度,启用后如果用户脱机则显示灰色头像

    bool operator==(const LQFRIENDSLIST_STYLE_& rhs) // 操作运算符重载
    {
       //省略篇幅已删减
    }

    bool operator != (const LQFRIENDSLIST_STYLE_& rhs) // !=操作运算符重载
    {
        return !(*this == rhs);
    }
}LQFRIENDSLIST_STYLE,*PLQFRIENDSLIST_STYLE;

 2、委托绘制类头文件部分代码

class LNCF_QTSOCIALLIBS_API Lncf_QFriendsItemd:public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit Lncf_QFriendsItemd(QObject *parent = nullptr);
    ~Lncf_QFriendsItemd();

private:
    /// 初始化好友列表委托
    /// \brief InitFriendsItemd
    ///
    void InitFriendsItemd();
protected:
    /// 好友列表项风格委托样式数据结构变量
    /// \brief tDelegateStyle
    ///
    LQFRIENDSLIST_STYLE  tDelegateStyle;

    QString              sDevicePcIcons;   //PC客户端图标
    QString              sDeviceMbIcons;   //手机客户端图标
    QString              sDeviceWbIcons;   //web客户端图标
    QString              sOtherAppIcons;   //其它应用图标

    QString              sVipLogoImages;   //会员图标
    QString              sSvipLogoImage;   //超级会员图标

    QString              sOnlineImgLogo;   //在线状态图标
    QString              sOffLineImages;   //离线状态图标
    QString              sLeaveLogosImg;   //离开状态图标
    QString              sBusyStatusImg;   //忙碌状态图标
    QString              sStealthImages;   //隐身状态图标
    QString              sNotDisturbImg;   //勿扰状态图标
    QString              sCustomizeImgs;   //自定义状态图标
    QString              sCallMeStatImg;   //与我联系状态图标

    QString              sSourceAppsImg;   //默认来源应用图标

    QString              sGroupExpanImg;   //分组展开图标
    QString              sGroupCloseImg;   //分组关闭图标

    QString              sVideoChatImgs;   //视频会话图标
    QString              sAudioChatImgs;   //音频通话图标
    QString              sMoreOptionImg;   //更多选项图标

    QStringList          sVipLogoImgLst;   //会员等级图标列表

    QStringList          sSvipLogosList;   //超级会员等级图标列表

    QImage               pGroupExpanImg;   //分组展开图标对象
    QImage               pGroupCloseImg;   //分组收起图标对象

    bool                 bHasMouseEvent;   //是否启用鼠标事件

    bool                 bHasHoverEvent = false;  //是否处理鼠标悬停事件

    bool                 bShowItemsTips = true;   //是否显示鼠标悬停时提示信息

    bool                 bNavGroupStyle = false;  //是否为索引分组风格
protected:
    /// 重写创建编辑器
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const override;

    /// 重写系统绘制
    virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;

    /// 绘制背景
    /// \brief DrawBackgrounds
    /// \param painter
    /// \param option
    /// \param bGroup
    /// \param bExpand
    ///
    virtual void DrawBackgrounds(QPainter *painter,const QStyleOptionViewItem &option,bool bGroup,bool bExpand) const;

    /// 绘制分组项
    /// \brief DrawItemsGroups
    /// \param painter
    /// \param option
    /// \param itemData
    ///
    virtual void DrawItemsGroups(QPainter *painter,const QStyleOptionViewItem &option,bool bExpand,LNCFQFRIENDS_LISTGROUP itemData,const QModelIndex &index) const;

    /// 绘制头部参考线
    /// \brief DrawItemsHeader
    /// \param painter
    /// \param option
    /// \param bGroup
    /// \param uStatus
    ///
    virtual void DrawItemsHeader(QPainter *painter,const QStyleOptionViewItem &option,bool bGroup,uint uStatus) const;

    /// 绘制头像
    /// \brief DrawItemsAavtar
    /// \param painter
    /// \param option
    /// \param sImagePath
    /// \param bHover
    /// \param uSex
    /// \param uStatus
    /// \param uDevice
    /// \param sName
    ///
    virtual void DrawItemsAavtar(QPainter *painter,const QStyleOptionViewItem &option,std::u16string sImagePath,bool bHover,uint uSex,uint uStatus,std::u16string sName=u"") const;

    /// 绘制会员等级图标
    /// \brief DrawVipLevelImg
    /// \param painter
    /// \param option
    /// \param bSvip
    /// \param nVipLevel
    /// \param bHover
    /// \param sImage
    ///
    virtual void DrawVipLevelImg(QPainter *painter,const QStyleOptionViewItem &option,bool bSvip,int nVipLevel,bool bHover,std::u16string sImage) const;

    /// 绘制好友显示名
    /// \brief DrawDisplayName
    /// \param painter
    /// \param option
    /// \param sName
    ///
    virtual void DrawDisplayName(QPainter *painter,const QStyleOptionViewItem &option,std::u16string sName,bool bVips) const;

    /// 绘制用户摘要信息
    /// \brief DrawUserSummary
    /// \param painter
    /// \param option
    /// \param sDesc
    /// \param bVips
    ///
    virtual void DrawUserSummary(QPainter *painter,const QStyleOptionViewItem &option,std::u16string sDesc,bool bVips) const;

    /// 绘制来源信息
    /// \brief DrawSourcesInfo
    /// \param painter
    /// \param option
    /// \param sName
    /// \param sImage
    ///
    virtual void DrawSourcesInfo(QPainter *painter,const QStyleOptionViewItem &option,std::u16string sName,std::u16string sImage,bool bVips) const;

    /// 绘制设备图标
    /// \brief DrawDeviceImage
    /// \param painter
    /// \param option
    /// \param nDeviceType
    /// \param bHover
    ///
    virtual void DrawDeviceImage(QPainter *painter,const QStyleOptionViewItem &option,int nDeviceType,bool bHover) const;

    /// 绘制选项图标
    /// \brief DrawOptionsBtns
    /// \param painter
    /// \param option
    /// \param uHover    :鼠标移动的控件索引,0无,1音频通话,2视频通话,3更多
    /// \param bAudio    :是否绘制音频通话快速按钮
    /// \param bVideo    :是否绘制视频通话快速按钮
    ///
    virtual void DrawOptionsBtns(QPainter *painter,const QStyleOptionViewItem &option,uint uHover,bool bAudio,bool bVideo) const;

    /// 绘制分割线
    /// \brief DrawSplitupLine
    /// \param painter
    /// \param option
    ///
    virtual void DrawSplitupLine(QPainter *painter,const QStyleOptionViewItem &option) const;

public:
    /// 获取视图项目风格数据结构
    /// \brief GetItemStyleData
    /// \return
    ///
    LQFRIENDSLIST_STYLE GetItemStyleData() const;

/*属性接口省略*/

protected:
    /// 获取默认头像路径
    /// \brief GetDefaultAvatar
    /// \param uSex
    /// \return
    ///
    QString GetDefaultAvatar(uint uSex) const;

    /// 获取元素矩形区域
    /// \brief GetElementRectan
    /// \param option
    /// \param index
    /// \param bGroup :是否为分组项
    /// \param nType,用户项索引“0:头像区域,1设备图标,2vip图标,3视频通话区域,4音频通话区域,5更多选项区域,6名称区域,7摘要区域”,分组项索引0节点图标,1名称,2统计数量
    /// \return
    ///
    virtual QRect GetElementRectan(const QStyleOptionViewItem &option,const QModelIndex &index,const bool bGroup,const int nType) const;

    /// 重写视图项尺寸定义
    virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;

    /// 重写视图项编辑事件
    virtual bool editorEvent(QEvent *event,QAbstractItemModel *model,const QStyleOptionViewItem &option,const QModelIndex &index) override;

    /// 重写视图项辅助事件
    virtual bool helpEvent(QHelpEvent *event,QAbstractItemView *view,const QStyleOptionViewItem &option,const QModelIndex &index) override;
signals:
    /// 列表分组展开或收起
    /// \brief GroupItemsExpand
    /// \param bExpand 是否展开,true表示展开否则为收起
    /// \param uGroup :分组ID
    /// \param index  :模型索引ID
    ///
    void GroupItemsExpand(const bool &bExpand,const uint64_t uGroup, const QModelIndex &index);

    /// 列表视图元素单击事件信号
    /// 注意:调用SetHasMouseEvent(true)接口设置鼠标事件之后此信号将被发送
    /// \brief ItemsElementEvts
    /// \param nElement 元素索引定义[0:头像,1设备图标,2vip图标,3视频通话,4音频通话,5其它选项]
    /// \param index
    ///
    void ItemElementClick(const int &nElement,const QModelIndex &index);

    /// 列表视图元素双击事件信号
    /// 注意:调用SetHasMouseEvent(true)接口设置鼠标事件之后此信号将被发送
    /// \brief ItemElementDbClick
    /// \param nElement  0:头像,1设备图标,2vip图标,3视频通话,4音频通话,5其它选项
    /// \param index
    ///
    void ItemElementDbClick(const int &nElement,const QModelIndex &index);

    /// 分组右键单击事件
    /// \brief GroupsRightClicked
    /// \param tGroupInf
    /// \param point
    ///
    void GroupsRightClicked(const LNCFQFRIENDS_LISTGROUP &tGroupInf,const QPoint &point,const QModelIndex &index);

    /// 好友右键单击事件
    /// \brief UserRightClickEvts
    /// \param tUsersInf
    /// \param point
    ///
    void UserRightClickEvts(const LNCFQFRIENDS_LISTVIEWS &tUsersInf,const QPoint &point,const QModelIndex &index);

    /// 列表视图Ui元素鼠标悬停事件
    /// 注意:调用SetHasHoverEvent(true)接口设置悬停事件启用之后此信号将被发送
    /// \brief ItemsElementHovers
    /// \param nElement           元素索引定义[0:头像,1设备图标,2vip图标,3视频通话,4音频通话,5其它选项]
    /// \param index
    /// \param itemRect           当前项目矩形区域
    /// \param evtPos             鼠标悬停位置坐标[当前窗口]
    /// \param evtGlobalPos       鼠标悬停位置坐标[桌面窗口]
    ///
    void ItemsElementHovers(const int &nElement,const QModelIndex &index,const QRect &itemRect,const QPoint &evtPos,const QPoint &evtGlobalPos);

};

 5、部分重要绘图部分代码

/// 绘制背景
/// \brief DrawBackgrounds
/// \param painter
/// \param option
/// \param bGroup
///
void Lncf_QFriendsItemd::DrawBackgrounds(QPainter *painter,const QStyleOptionViewItem &option,bool bGroup,bool bExpand) const
{
    painter->save();
    if(bGroup){
        QRect rcGroup;
        painter->setBrush(bExpand?this->tDelegateStyle.cGroupExpanClr:this->tDelegateStyle.cGroupBkgColor);

        if (option.state.testFlag(QStyle::State_MouseOver))
            painter->setBrush(!bExpand?this->tDelegateStyle.cGroupHoverClr:this->tDelegateStyle.cGroupExpHover);

        rcGroup = QRect(option.rect.left()+tDelegateStyle.uHorizeMarginG,option.rect.top()+tDelegateStyle.uVerticMarginG,option.rect.width()-tDelegateStyle.uHorizeMarginG*2,option.rect.height()-tDelegateStyle.uVerticMarginG*2);

        double dRadius = tDelegateStyle.dGroupRadiusId*rcGroup.height();
        painter->drawRoundedRect(rcGroup,dRadius,dRadius);

        if(tDelegateStyle.uGroupBorderId>0&&tDelegateStyle.uGroupBorderId<4)
        {
            painter->setBrush(Qt::NoBrush);
            QPen penBorder(tDelegateStyle.cGroupsBorders);
            penBorder.setWidthF(tDelegateStyle.fBordersWidths);
            painter->setPen(penBorder);
        }

        //分组边框风格,0不显示边框,1全部边框,2上,3下
        switch (tDelegateStyle.uGroupBorderId) {
        case 1:{
            painter->drawRoundedRect(rcGroup,dRadius,dRadius);
        }break;
        case 2:{
            painter->drawLine(rcGroup.left(),rcGroup.top()-tDelegateStyle.fBordersWidths,rcGroup.right(),rcGroup.top()-tDelegateStyle.fBordersWidths);
        }break;
        case 3:{
            painter->drawLine(rcGroup.left(),rcGroup.bottom()+1,rcGroup.right(),rcGroup.bottom()+1);
        }break;
        }
    }
    else{
        if(tDelegateStyle.bIconViewModel)
            painter->setBrush(Qt::transparent);
        else{
            painter->setBrush(this->tDelegateStyle.cItemsBkgColor);
            painter->drawRect(option.rect);
        }

        bool bDarw = true;

        painter->setPen(Qt::NoPen);
        if (option.state.testFlag(QStyle::State_MouseOver))
            painter->setBrush(!tDelegateStyle.bIconViewModel?tDelegateStyle.cItemsHoverClr:tDelegateStyle.cIconHoverClrs);

        if (option.state.testFlag(QStyle::State_Selected)){
            painter->setBrush(!tDelegateStyle.bIconViewModel?tDelegateStyle.cItemSelectClr:tDelegateStyle.cIconSelectClr);
            if(tDelegateStyle.bRoundAvtiveBg&&!tDelegateStyle.bIconViewModel){
                QRect rcBkg =option.rect;
                rcBkg.setLeft(rcBkg.left()+rcBkg.height()/2-1);
                QRect rcRnd =QRect(option.rect.left(),option.rect.top(),option.rect.height(),option.rect.height());
                painter->drawPie(rcRnd,90.0*16,180.0*16);
                painter->drawRect(rcBkg);
                bDarw = false;
            }
        }

        if (option.state.testFlag(QStyle::State_Selected)&&option.state.testFlag(QStyle::State_MouseOver))
            painter->setBrush(!tDelegateStyle.bIconViewModel?tDelegateStyle.cItemsHoverClr:tDelegateStyle.cIconHoverClrs);

        if(!tDelegateStyle.bIconViewModel){
            if(bDarw)
                painter->drawRect(option.rect);
        }
        else{
            painter->drawRoundedRect(option.rect, 5, 5);
        }
    }

    painter->restore();
}

/// 绘制分组项
/// \brief DrawItemsGroups
/// \param painter
/// \param option
/// \param itemData
///
void Lncf_QFriendsItemd::DrawItemsGroups(QPainter *painter,const QStyleOptionViewItem &option,bool bExpand,LNCFQFRIENDS_LISTGROUP itemData,const QModelIndex &index) const
{
    painter->save();
    (void)index;
    QRectF rcGroup,rcIcon,rcNode,rcText,rcCount;

    rcGroup = QRect(option.rect.left()+tDelegateStyle.uHorizeMarginG,option.rect.top()+tDelegateStyle.uVerticMarginG,option.rect.width()-tDelegateStyle.uHorizeMarginG*2,option.rect.height()-tDelegateStyle.uVerticMarginG*2);

    rcIcon = QRectF(rcGroup.left()+4,rcGroup.top()+rcGroup.height()/2-7,14,14);
    rcText = QRect(rcGroup.left()+25,rcGroup.top(),rcGroup.right()-50,rcGroup.height());
    rcNode = QRect(rcGroup.left()+7,rcGroup.top()+rcGroup.height()/2-4,8,8);

    QFont fontObj=painter->font();
    QFontMetrics fontMetrics(fontObj);
    fontObj.setPixelSize(7);
    QString sCount = QString("%1").arg(itemData.uGroupMemCount);
    int countWidth = fontMetrics.horizontalAdvance(sCount);

    if(this->bNavGroupStyle){
        rcCount = QRect(rcNode.left()+(this->tDelegateStyle.uGroupStyleIds==1?10:40),rcGroup.top()+rcGroup.height()/2-7,countWidth+7,14);
        painter->setPen(Qt::NoPen);
        painter->setBrush(bExpand?tDelegateStyle.cGroupNodesExp:tDelegateStyle.cGroupNodeBkgd);
        painter->drawEllipse(rcNode);
    }
    else{
        rcCount = QRect(rcGroup.right()-countWidth-11,rcGroup.top()+rcGroup.height()/2-8,countWidth+7,14);
        QImage icoNode = bExpand?pGroupExpanImg:pGroupCloseImg;
        if(icoNode.isNull())
            icoNode = bExpand?QImage(sGroupExpanImg):QImage(sGroupCloseImg);

        painter->drawImage(rcIcon, icoNode);
    }

    painter->setPen(tDelegateStyle.cGroupTxtColor);
    if (option.state.testFlag(QStyle::State_MouseOver))
        painter->setPen(tDelegateStyle.cGroupHoverTxt);
    if(bExpand)
        painter->setPen(tDelegateStyle.cGroupExpanTxt);

    if(this->tDelegateStyle.uGroupStyleIds==1)
        painter->drawText(rcText,Qt::AlignCenter,QString::fromStdU16String(itemData.sItemGroupName));
    else
        painter->drawText(rcText,Qt::AlignVCenter,QString::fromStdU16String(itemData.sItemGroupName));

    if(tDelegateStyle.bShowUserCount){
        painter->setPen(Qt::NoPen);
        painter->setBrush(tDelegateStyle.cGroupCountBkg);
        painter->drawRoundedRect(rcCount,rcCount.height()/2,rcCount.height()/2);

        painter->setPen(tDelegateStyle.cGroupCountTxt);
        painter->setFont(fontObj);
        painter->drawText(rcCount,Qt::AlignCenter,QString("%1").arg(itemData.uGroupMemCount));
    }
    painter->restore();
}

/// 绘制头部参考线
/// \brief DrawItemsHeader
/// \param painter
/// \param option
/// \param bGroup
///
void Lncf_QFriendsItemd::DrawItemsHeader(QPainter *painter,const QStyleOptionViewItem &option,bool bGroup,uint uStatus) const
{
    if(!this->tDelegateStyle.bIconViewModel){
        painter->save();
        //绘制辅助线
        QPen pen(tDelegateStyle.cItemHeadLines);
        pen.setWidthF(0.5F);
        switch (tDelegateStyle.nHeadeLineType) {
        case 0: pen.setStyle(Qt::SolidLine); break;
        case 1: pen.setStyle(Qt::DashLine); break;
        case 2: pen.setStyle(Qt::DotLine); break;
        case 3: pen.setStyle(Qt::DashDotLine); break;
        case 4: pen.setStyle(Qt::DashDotDotLine); break;
        }
        painter->setPen(pen);
        painter->setBrush(Qt::NoBrush);
        painter->drawLine(QPointF(option.rect.left()+11,option.rect.top()),QPointF(option.rect.left()+11,option.rect.bottom()));
        painter->drawLine(QPointF(option.rect.left(),option.rect.top()+option.rect.height()/2+pen.widthF()),QPointF(option.rect.left()+(bGroup?11:19),option.rect.top()+option.rect.height()/2+pen.widthF()));

        if(!bGroup)
        {
            painter->setPen(Qt::NoPen);
            painter->setBrush(tDelegateStyle.cUsersNodesClr);
            QRectF nodesRect = QRect(option.rect.left()+6, option.rect.top()+option.rect.height()/2-5, 10, 10);
            QRectF stateRect = QRect(option.rect.left()+6, option.rect.top()+option.rect.height()/2-5, 10, 10);
            if(uStatus>=0&&uStatus<8){
                painter->drawEllipse(nodesRect);
                ///0脱机,1联机,2勿扰,3离开,4忙碌,5隐身,6自定义
                switch (uStatus) {
                case 0:painter->drawImage(stateRect, QImage(sOffLineImages));break;
                case 1:painter->drawImage(stateRect, QImage(sOnlineImgLogo));break;
                case 2:painter->drawImage(stateRect, QImage(sNotDisturbImg));break;
                case 3:painter->drawImage(stateRect, QImage(sLeaveLogosImg));break;
                case 4:painter->drawImage(stateRect, QImage(sBusyStatusImg));break;
                case 5:painter->drawImage(stateRect, QImage(sOffLineImages));break;
                case 6:painter->drawImage(stateRect, QImage(sCallMeStatImg));break;
                case 7:painter->drawImage(stateRect, QImage(sCustomizeImgs));break;
                }
            }
            else{
                painter->drawEllipse(stateRect);
            }
        }

        painter->restore();
    }
}

/// 绘制头像
/// \brief DrawItemsAavtar
/// \param painter
/// \param option
/// \param sImagePath
/// \param bHover
/// \param uSex
/// \param uStatus
/// \param uDevice
///
void Lncf_QFriendsItemd::DrawItemsAavtar(QPainter *painter,const QStyleOptionViewItem &option,std::u16string sImagePath,bool bHover,uint uSex,uint uStatus,std::u16string sName) const
{
    painter->save();
    int leftMargin =0;
    QRectF avatarRect,borderRect,stateRect,stateFrm;
    if(!this->tDelegateStyle.bIconViewModel){
        leftMargin=!tDelegateStyle.bShowHeadLines?option.rect.left()+5:option.rect.left()+20;
        if(option.rect.height()>41){
            avatarRect = QRect(leftMargin, option.rect.top()+5, option.rect.height()-10, option.rect.height()-10);
            borderRect = QRect(leftMargin-2, option.rect.top()+3, option.rect.height()-6, option.rect.height()-6);
            stateRect = QRect(leftMargin, option.rect.top()+5,12,12);
            stateFrm = QRect(leftMargin, option.rect.top()+5,12,12);
        }
        else{
            avatarRect = QRect(leftMargin, option.rect.top()+4, option.rect.height()-8, option.rect.height()-8);
            borderRect = QRect(leftMargin-2, option.rect.top()+2, option.rect.height()-4, option.rect.height()-4);
            stateRect = QRect(leftMargin, option.rect.top()+4,10,10);
            stateFrm = QRect(leftMargin, option.rect.top()+4,10,10);
        }
    }
    else{
        borderRect = QRect(option.rect.left()+8, option.rect.top()+8, this->tDelegateStyle.uIconGridWidth-16, this->tDelegateStyle.uIconGridWidth-16);
        avatarRect = QRect(option.rect.left()+12, option.rect.top()+12, this->tDelegateStyle.uIconGridWidth-24, this->tDelegateStyle.uIconGridWidth-24);
        stateRect = QRect(option.rect.left()+13, option.rect.top()+13,12,12);
        stateFrm = QRect(option.rect.left()+13, option.rect.top()+13,12,12);
    }
    if(tDelegateStyle.bIconViewModel){
        painter->setPen(Qt::NoPen);
        int nRadius=borderRect.height()*tDelegateStyle.dAvatarsRadius;
        if (option.state.testFlag(QStyle::State_MouseOver)) {
            painter->setBrush(tDelegateStyle.cItemsHoverClr);
            painter->drawRoundedRect(borderRect,nRadius,nRadius);
        }
        if (option.state.testFlag(QStyle::State_Selected)) {
            painter->setBrush(tDelegateStyle.cItemSelectClr);
            painter->drawRoundedRect(borderRect,nRadius,nRadius);
        }
    }
    else{
        painter->setPen(Qt::NoPen);
        int nRadius=borderRect.height()*tDelegateStyle.dAvatarsRadius;
        if (option.state.testFlag(QStyle::State_Selected)) {
            painter->setBrush(tDelegateStyle.cAvatarFrmClrs);
            painter->drawRoundedRect(borderRect,nRadius,nRadius);
        }
        else{
            if(bHover&&this->bHasMouseEvent){
                painter->setBrush(tDelegateStyle.cMouseHoverBkg);
                painter->drawRoundedRect(borderRect,nRadius,nRadius);
            }
            else{
                painter->setBrush(Qt::transparent);
                painter->drawRoundedRect(borderRect,nRadius,nRadius);
            }
        }
    }


    if(tDelegateStyle.dAvatarsRadius>0){
        QPainterPath pathAvatar;
        int nRadius=avatarRect.height()*tDelegateStyle.dAvatarsRadius;
        pathAvatar.addRoundedRect(avatarRect , nRadius, nRadius);
        painter->setClipPath(pathAvatar);
    }

    //头像显示风格索引,0仅显示图像,无头像图像时显示默认头像,1无图像时显示名称第一字符,2仅显示名称第一字符
    switch (tDelegateStyle.bAvatarsStyles) {
    case 1:{
        painter->setBrush(tDelegateStyle.bRandomAvbgClr?QColor::fromHsl(rand()%360,rand()%256,rand()%200):tDelegateStyle.cAvatarBkgClrs);
        painter->drawRect(avatarRect);

        QImage imgAvatar = QImage(QString::fromStdU16String(sImagePath));

        if(imgAvatar.isNull()&&!sName.empty()){
            painter->setPen(this->tDelegateStyle.cItemsTxtColor);
            QFont fontObj=painter->font();
            fontObj.setPixelSize(avatarRect.height()/2.5);
            painter->setFont(fontObj);
            QString szName = QString::fromStdU16String(sName.substr(0,1));
            painter->drawText(avatarRect,Qt::AlignCenter,szName);
        }
        else{
            if(imgAvatar.isNull())
                imgAvatar = QImage(GetDefaultAvatar(uSex));
            if((uStatus==0||uStatus==5)&&tDelegateStyle.bGrayAvatarImg)
                imgAvatar = imgAvatar.convertToFormat(QImage::Format_Grayscale8,Qt::AutoColor);
            painter->drawImage(avatarRect, imgAvatar);
        }
    }break;
    case 2:{
        painter->setBrush(tDelegateStyle.bRandomAvbgClr?QColor::fromHsl(rand()%360,rand()%256,rand()%200):tDelegateStyle.cAvatarBkgClrs);
        painter->drawRect(avatarRect);

        QString szName = QString::fromStdU16String(sName.substr(0,1));
        painter->setPen(this->tDelegateStyle.cItemsTxtColor);

        QFont fontObj=painter->font();
        fontObj.setPixelSize(avatarRect.height()/2.5);
        painter->setFont(fontObj);

        painter->drawText(avatarRect,Qt::AlignCenter,szName.isEmpty()?"#":szName);
    }break;
    default:{
        painter->setBrush(tDelegateStyle.cAvatarBkgClrs);
        painter->drawRect(avatarRect);

        QImage imgAvatar = QImage(QString::fromStdU16String(sImagePath));

        if(imgAvatar.isNull())
            imgAvatar = QImage(GetDefaultAvatar(uSex));

        if((uStatus==0||uStatus==5)&&tDelegateStyle.bGrayAvatarImg)
            imgAvatar = imgAvatar.convertToFormat(QImage::Format_Grayscale8,Qt::AutoColor);

        painter->drawImage(avatarRect, imgAvatar);
    };break;
    }

    painter->restore();

    if((tDelegateStyle.bIconViewModel||!tDelegateStyle.bShowHeadLines)&&tDelegateStyle.uShowStatusIco){
        painter->save();
        painter->setPen(Qt::NoPen);
        if(uStatus!=0&&uStatus!=5){
            painter->setBrush(tDelegateStyle.cUsersNodesClr);
            painter->drawEllipse(stateFrm);
        }

        if(uStatus>0&&uStatus<8&&uStatus!=5){
            ///0脱机,1联机,2勿扰,3离开,4忙碌,5隐身,6自定义
            switch (uStatus) {
            case 0:painter->drawImage(stateRect, QImage(sOffLineImages));break;
            case 1:painter->drawImage(stateRect, QImage(sOnlineImgLogo));break;
            case 2:painter->drawImage(stateRect, QImage(sNotDisturbImg));break;
            case 3:painter->drawImage(stateRect, QImage(sLeaveLogosImg));break;
            case 4:painter->drawImage(stateRect, QImage(sBusyStatusImg));break;
            case 5:painter->drawImage(stateRect, QImage(sOffLineImages));break;
            case 6:painter->drawImage(stateRect, QImage(sCallMeStatImg));break;
            case 7:painter->drawImage(stateRect, QImage(sCustomizeImgs));break;
            }
        }
        painter->restore();
    }
}

事件和其他代码示例

/// 重写视图项尺寸定义
QSize Lncf_QFriendsItemd::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    //如果是分组
    Q_UNUSED(index)
    auto size =  QStyledItemDelegate::sizeHint(option, index);
    if(index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool()){
        Lncf_QFriendsListv *pListView = static_cast(index.model()->parent());
        if(pListView) {
            return QSize(pListView->viewport()->width(), tDelegateStyle.bIconViewModel?this->tDelegateStyle.uGroupsHeights*1.5:this->tDelegateStyle.uGroupsHeights);
        }
    }
    else{ //如果是用户
        if(!tDelegateStyle.bIconViewModel){
            uint uRowHeight = this->tDelegateStyle.uItemHeightVal;
            if(this->tDelegateStyle.uItemsStyleIds==1)
                uRowHeight = index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_ACTIV).toBool()?this->tDelegateStyle.uItemHeightVal:this->tDelegateStyle.uItemMinHeight;

            return QSize(option.rect.width(), uRowHeight);
        }
        else
        {
            //size.rheight() = uIconGridHeigh;
            return QSize(this->tDelegateStyle.uIconGridWidth, this->tDelegateStyle.uIconGridHeigh);
        }
    }

    return  size;
}

/// 重写视图项编辑事件
bool Lncf_QFriendsItemd::editorEvent(QEvent *event,QAbstractItemModel *model,const QStyleOptionViewItem &option,const QModelIndex &index)
{
    if(index.isValid())
    {
        auto mouseEvent = static_cast(event);
        if(!mouseEvent) {
            return QStyledItemDelegate::editorEvent(event, model, option, index);
        }

        //判断是分组还是用户
        if(index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool())
        {
            QVariant  varGroup = index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP);
            LNCFQFRIENDS_LISTGROUP ItemGroupData = varGroup.value();
            //printf("group id:%zd,expand:%d\n",ItemGroupData.uItemsGroupIds,bExpand);

            switch (mouseEvent->type()) {
            case QEvent::MouseMove:
            {

            }break;
            case QEvent::MouseButtonRelease:
            {
                if(mouseEvent->button() != Qt::LeftButton){
                    //分组右键单击事件
                    emit GroupsRightClicked(ItemGroupData,mouseEvent->globalPos(),index);
                    return QStyledItemDelegate::editorEvent(event, model, option, index);
                }
             
            }break;
            case QEvent::MouseButtonDblClick:
            {
                if(mouseEvent->button() != Qt::LeftButton){
                    return QStyledItemDelegate::editorEvent(event, model, option, index);
                }
                if(this->tDelegateStyle.uExpandGroupId>1&&this->tDelegateStyle.uExpandGroupId<4){
                    bool bExpand = index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN).toBool();
                    if(this->tDelegateStyle.uExpandGroupId==2){
                        if(GetElementRectan(option, index,true, 0).contains(mouseEvent->pos()))
                        {
                            model->setData(index, bExpand?false:true, LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN);
                            emit GroupItemsExpand(bExpand?false:true,!bNavGroupStyle?ItemGroupData.uItemsGroupIds:index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_INDEX).toULongLong(),index);
                            break;
                        }
                    }
                    if(this->tDelegateStyle.uExpandGroupId==3)
                    {
                        model->setData(index, bExpand?false:true, LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN);
                        emit GroupItemsExpand(bExpand?false:true,!bNavGroupStyle?ItemGroupData.uItemsGroupIds:index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_INDEX).toULongLong(),index);
                        break;
                    }
                }
            }break;
            }
        }
        else{
            if(this->bHasMouseEvent){
                {
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDAVATAR_HOVER);                     // 鼠标移入头像
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDVIPICO_HOVER);                     // 鼠标移入VIP按钮
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDDEVICE_HOVER);                     // 鼠标移入设备按钮
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDRSUMARY_HOVER);                    // 鼠标移入摘要数据区域
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDVIDEOS_HOVER);                     // 鼠标移入视频通话按钮区域
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDAUDIOS_HOVER);                     // 鼠标移入音频通话按钮区域
                    model->setData(index, false, LQFRIENDSLIST_ROLES::FRIENDSMORES_HOVER);                     // 鼠标移入更多选项按钮区域
                }

                QVariant  varUsers = index.data(LQFRIENDSLIST_ROLES::FRIENDSLIST_ITEMS);
                LNCFQFRIENDS_LISTVIEWS ItemUsersData = varUsers.value();
                //0:头像区域,1设备图标,2vip图标,3视频通话区域,4音频通话区域,5更多选项区域,6名称区域,7摘要区域”,分组项索引0节点图标,1名称,2统计数量
                switch (mouseEvent->type()) {
                case QEvent::MouseMove:
                {
                    if(bShowItemsTips)QToolTip::hideText();
                    //鼠标移入设备图标区域
                    auto device_hover = GetElementRectan(option, index,false, 1).contains(mouseEvent->pos());
                    model->setData(index, (ItemUsersData.uDeviceTypeIds>=0&&ItemUsersData.uDeviceTypeIds<4)?device_hover:false, LQFRIENDSLIST_ROLES::FRIENDDEVICE_HOVER);

  

                }break;
                case QEvent::MouseButtonRelease:
                {
                    //发送头像区域单击信号
                    if(GetElementRectan(option, index,false, 0).contains(mouseEvent->pos()))
                    {
                        emit ItemElementClick(0,index);
                        return QStyledItemDelegate::editorEvent(event, model, option, index);
                    }
                    model->setData(index,true,LQFRIENDSLIST_ROLES::FRIENDSLIST_CLICK);
                }break;
              
                }
            }
        }
    }
    return QStyledItemDelegate::editorEvent(event, model, option, index);
}

 受限于篇幅限制,无法提供所有代码。上面只是委托绘制代码,下面为列表部分头文件代码

class LNCF_QTSOCIALLIBS_API Lncf_QFriendsListv : public Lncf_IndexNavListv
{
    Q_OBJECT
    Q_PROPERTY(QString sDevicePcIcons READ GetDevicePcIcons WRITE SetDevicePcIcons)   //PC客户端图标
    Q_PROPERTY(QString sDeviceMbIcons READ GetDeviceMbIcons WRITE SetDeviceMbIcons)   //手机客户端图标
    Q_PROPERTY(QString sDeviceWbIcons READ GetDeviceWbIcons WRITE SetDeviceWbIcons)   //web客户端图标
    Q_PROPERTY(QString sOtherAppIcons READ GetOtherAppIcons WRITE SetOtherAppIcons)   //其它应用图标

    Q_PROPERTY(QString sVipLogoImages READ GetVipLogoImages WRITE SetVipLogoImages)   //会员图标
    Q_PROPERTY(QString sSvipLogoImage READ GetSvipLogoImage WRITE SetSvipLogoImage)   //超级会员图标

    Q_PROPERTY(QString sOnlineImgLogo READ GetOnlineImgLogo WRITE SetOnlineImgLogo)   //在线状态图标
    Q_PROPERTY(QString sOffLineImages READ GetOffLineImages WRITE SetOffLineImages)   //离线状态图标
    Q_PROPERTY(QString sLeaveLogosImg READ GetLeaveLogosImg WRITE SetLeaveLogosImg)   //离开状态图标
    Q_PROPERTY(QString sBusyStatusImg READ GetBusyStatusImg WRITE SetBusyStatusImg)   //忙碌状态图标
    Q_PROPERTY(QString sStealthImages READ GetStealthImages WRITE SetStealthImages)   //隐身状态图标
    Q_PROPERTY(QString sNotDisturbImg READ GetNotDisturbImg WRITE SetNotDisturbImg)   //勿扰状态图标
    Q_PROPERTY(QString sCustomizeImgs READ GetCustomizeImgs WRITE SetCustomizeImgs)   //自定义状态图标
    Q_PROPERTY(QString sCallMeStatImg READ GetCallMeStatImg WRITE SetCallMeStatImg)   //与我联系状态图标

    Q_PROPERTY(QString sSourceAppsImg READ GetSourceAppsImg WRITE SetSourceAppsImg)   //默认来源应用图标

    Q_PROPERTY(QString sGroupExpanImg READ GetGroupExpanImg WRITE SetGroupExpanImg)   //分组展开图标
    Q_PROPERTY(QString sGroupCloseImg READ GetGroupCloseImg WRITE SetGroupCloseImg)   //分组关闭图标

    Q_PROPERTY(QString sVideoChatImgs READ GetVideoChatImgs WRITE SetVideoChatImgs)   //视频会话图标
    Q_PROPERTY(QString sAudioChatImgs READ GetAudioChatImgs WRITE SetAudioChatImgs)   //音频通话图标
    Q_PROPERTY(QString sMoreOptionImg READ GetMoreOptionImg WRITE SetMoreOptionImg)   //更多选项图标

    Q_PROPERTY(bool bHasMouseEvent READ GetItemsMouseEvt WRITE SetItemsMouseEvt)     //是否启用鼠标事件

    Q_PROPERTY(bool bHasHoverEvent READ GetHasHoverEvent WRITE SetHasHoverEvent)     //是否处理鼠标悬停事件

    Q_PROPERTY(bool bShowItemsTips READ GetShowItemsTips WRITE SetShowItemsTips)      //是否显示鼠标悬停时提示信息
    Q_PROPERTY(uint uGroupStyleIds READ GetGroupStyleIds WRITE SetGroupStyleIds)      //分组风格
    Q_PROPERTY(uint uItemsStyleIds READ GetItemsStyleIds WRITE SetItemsStyleIds)      //列表项风格
    Q_PROPERTY(uint uExpandGroupId READ GetExpandGroupId WRITE SetExpandGroupId)      //展开和闭合分组操作方式
    Q_PROPERTY(uint uUserRowHeight READ GetUserRowHeight WRITE SetUserRowHeight);     //好友行高
    Q_PROPERTY(uint uGroupsHeights READ GetGroupsHeights WRITE SetGroupsHeights);     //分组项行高
    Q_PROPERTY(bool bNavGroupStyle READ GetNavGroupStyle WRITE SetNavGroupStyle);     //分组类别
    Q_PROPERTY(bool bHasOnlyOnline READ GetHasOnlyOnline WRITE SetHasOnlyOnline);     //显示联机用户或全部
    Q_PROPERTY(bool bShowGroupItem READ GetShowGroupItem WRITE SetShowGroupItem);     //是否显示好友分组
public:
    explicit Lncf_QFriendsListv(QWidget *parent = nullptr);
    ~ Lncf_QFriendsListv();
protected:
    /// 初始化列表虚函数
    /// \brief InitFriendsList
    ///
    virtual void InitFriendsList();

private:
    /// 初始导航组
    /// \brief InitNavigatGroup
    ///
    void InitNavigatGroup();

private:
    QStandardItemModel *pListItemModel = nullptr;    //列表模型对象指针
    Lncf_QFriendsItemd *pItemsDelegate = nullptr;    //列表项委托对象指针
    Lncf_QFriendSortlm *pSortFilteMode = nullptr;    //列表筛选器模型对象指针

    /// 是否启用鼠标跟踪
    /// \brief bMouseTracking
    ///
    bool                bMouseTracking = false;

    /// 是否为索引分组类别,如果为索引分组则不显示用户分组,而是以A-Z的字母顺序分组
    /// \brief bNavGroupStyle
    ///
    bool                bNavGroupStyle = false;

    /// 仅显示在线用户
    /// \brief bHasOnlyOnline
    ///
    bool                bHasOnlyOnline = false;

    /// 是否显示分组
    /// \brief bShowGroupItem
    ///
    bool                bShowGroupItem = true;

    /// 导航分组是否初始化
    /// \brief bInitNaviGroup
    ///
    bool                bInitNaviGroup = false;

    /// 索引分组文本
    /// \brief sNaviTextsList
    ///
    QList   sNaviTextsList;
public:
    /// 获取指定索引模型分组数据
    /// \brief GetItemsViewData
    /// \param index
    /// \return
    ///
    LNCFQFRIENDS_LISTGROUP GetItemGroupData(const QModelIndex &index) const;

    /// 获取指定索引模型视图数据
    /// \brief GetItemsViewData
    /// \param index
    /// \return
    ///
    LNCFQFRIENDS_LISTVIEWS GetItemsUserData(const QModelIndex &index) const;

    /// 获取指定索引模型自定义应用数据
    /// \brief GetItemsAppsData
    /// \param index
    /// \param bGroup :是否为分组项
    /// \return
    ///
    QVariant GetItemsAppsData(const QModelIndex &index,bool &bGroup) const;

    /// 获取会话列表所有数据链表
    /// \brief GetItemsDataList
    /// \param vList
    /// \return
    ///
    bool GetItemsDataList(std::vector &vList);


public:
    实现QListView函数//
    void setViewMode(ViewMode mode);

    void setSpacing(int space);

    void setGridSize(const QSize &size);
public:
    /// 添加好友列表分组
    /// \brief AddFriendsGroup
    /// \param data
    /// \param appData
    ///
    void AddFriendsGroup(LNCFQFRIENDS_LISTGROUP data);

    /// 添加会话项目
    /// \brief AddSessionItems
    /// \param data     :默认视图数据
    /// \param appData  :自定义app数据,此数据由app自行维护,默认情况下此数据不赋值
    ///
    void AddFriendsUsers(LNCFQFRIENDS_LISTVIEWS data);

    /// 添加一个好友分组列表项
    /// \brief AddFriendsLists
    /// \param data
    ///
    void AddFriendsLists(LNCFQFRIENDS_LISTDATAS data);

    /// 设置会话列表数据集合
    /// \brief SetSessionDatas
    /// \param vList   : 包含两条数据属性,1视图数据(LNCFQSESSION_VIEWDATAS),2应用自定义数据(QVariant)
    ///
    void SetFriendsDatas(std::vector &vList);

    /// 删除分组,如果该分组有成员则不会被删除
    /// \brief RemoveGroupData
    /// \param uGroupID :分组ID
    /// \param uErrMsg  :返回错误引用参数
    /// \return
    ///
    bool RemoveGroupData(uint64_t uGroupID,std::u16string &uErrMsg);

    /// 删除好友成员
    /// \brief RemoveUsersData
    /// \param tUserInf:为指定的好友数据结构,只用到了此结构体的三个变量,gViewsItemsIds;uViewsItemsIds;sViewsItemsIds;
    /// \param uErrMsg :返回错误引用参数
    /// \return
    ///
    bool RemoveUsersData(LNCFQFRIENDS_LISTVIEWS tUserInf,std::u16string &uErrMsg);

    /// 移动用户分组
    /// \brief MoveUsersGroups
    /// \param tUserInf
    /// \param uOldGroupId
    /// \param uNewGroupId
    /// \return
    ///
    bool MoveUsersGroups(LNCFQFRIENDS_LISTVIEWS tUserInf,uint64_t uOldGroupId,uint64_t uNewGroupId,std::u16string &uErrMsg);

    /// 好友数据是否存在
    /// \brief HasFriendsExist
    /// \param data
    /// \return
    ///
    bool HasFriendsExist(LNCFQFRIENDS_LISTDATAS data);

    /// 检查目标属性分组是否存在。主要检查分组ID
    /// \brief HasGroupsExists
    /// \param data
    /// \return
    ///
    bool HasGroupsExists(LNCFQFRIENDS_LISTGROUP data);

    /// 检查目标联系人属性是否存在,检查三个itemid属性
    /// \brief HasContactExist
    /// \param data,主要用结构体三个参数:gViewsItemsIds,uViewsItemsIds,sViewsItemsIds
    /// \return
    ///
    bool HasContactExist(LNCFQFRIENDS_LISTVIEWS data);

    /// 更新用户数据
    /// \brief UpdateUserDatas
    /// \param data     :默认视图数据
    ///
    bool UpdateUsersData(LNCFQFRIENDS_LISTVIEWS data,std::u16string &uErrMsg);

    /// 更新分组数据
    /// \brief UpdateGroupData
    /// \param data     :默认视图数据
    ///
    bool UpdateGroupData(LNCFQFRIENDS_LISTGROUP data,std::u16string &uErrMsg);

    /// 根据模型索引更新模型用户数据
    /// \brief UpdateUsersData
    /// \param index    :索引ID
    /// \param data     :视图数据
    ///
    bool UpdateUsersData(const QModelIndex &index,LNCFQFRIENDS_LISTVIEWS data,std::u16string &uErrMsg);

    /// 根据模型索引更新模型分组数据
    /// \brief UpdateUsersData
    /// \param index    :索引ID
    /// \param data     :视图数据
    ///
    bool UpdateGroupData(const QModelIndex &index,LNCFQFRIENDS_LISTGROUP data,std::u16string &uErrMsg);

    /// 设置目标索引的自定义app数据
    /// \brief SetItemAppsData
    /// \param index    :模型索引ID
    /// \param appData  :应用数据,数据设置不会查重,而是直接替换原有数据[如果存在]。因此如果有必要请先获取然后在写入
    ///
    bool SetItemAppsData(const QModelIndex &index,QVariant appData,std::u16string &uErrMsg);

    /// 激活指定的用户项
    /// \brief ActiveUsersItem
    /// \param tagUser
    ///
    bool ActiveUsersItem(const LNCFQFRIENDS_LISTVIEWS tagUser,std::u16string &uErrMsg);

    /// 更新列表风格
    /// \brief UpdateListDatas
    ///
    void UpdateListDatas();

    /// 展开所有节点
    /// \brief ExpandAllsNodes
    ///
    void ExpandAllsNodes();

    /// 闭合所有节点
    /// \brief ClosureAllNodes
    ///
    void ClosureAllNodes();

    /// 获取联系人总数
    /// \brief GetFriendsCount
    /// \return
    ///
    uint64_t GetFriendsCount();

public:
/*属性代码段省略*/
private slots:
    /// 处理分组展开或收起
    /// \brief HandGroupsExpand
    /// \param bExpand 是否展开,true表示展开否则为收起
    /// \param uGroup :分组ID
    /// \param index  :模型索引ID
    ///
    void HandGroupsExpand(const bool &bExpand,const uint64_t uGroup, const QModelIndex &index);

    /// 处理列表项单击
    /// \brief HandItemsClicked
    /// \param index
    ///
    void HandItemsClicked(const QModelIndex &index);
protected:
    void resizeEvent(QResizeEvent *event) override;

private slots:
    /// 列表视图元素单击事件槽
    /// \brief ItemsElementEvts
    /// \param nElement 元素索引定义[0:头像,1设备图标,2vip图标,3未读消息,4屏蔽按钮]
    /// \param index
    ///
    void OnUserElementClick(const int &nElement,const QModelIndex &index);

    /// 列表视图元素双击事件槽
    /// \brief ItemElementDbClick
    /// \param nElement
    /// \param index
    ///
    void OnUserElementDbClick(const int &nElement,const QModelIndex &index);

    /// 列表视图Ui元素鼠标悬停事件
    /// \brief OnElementsHovers
    /// \param nElement           元素索引定义 0:头像,1设备图标,2vip图标,3视频通话,4音频通话,5其它选项
    /// \param index
    /// \param itemRect           当前项目矩形区域
    /// \param evtPos             鼠标悬停位置坐标[当前窗口]
    /// \param evtGlobalPos       鼠标悬停位置坐标[桌面窗口]
    ///
    void OnUserElementsHovers(const int &nElement,const QModelIndex &index,const QRect &itemRect,const QPoint &evtPos,const QPoint &evtGlobalPos);

    /// 分组右键单击事件
    /// \brief OnGroupRightClick
    /// \param tGroupInf
    /// \param point
    ///
    void OnGroupRightClick(const LNCFQFRIENDS_LISTGROUP &tGroupInf,const QPoint &point,const QModelIndex &index);

    /// 好友右键单击事件
    /// \brief OnUsersRightClick
    /// \param tUsersInf
    /// \param point
    ///
    void OnUsersRightClick(const LNCFQFRIENDS_LISTVIEWS &tUsersInf,const QPoint &point,const QModelIndex &index);

    /// 处理导航按钮选中事件
    /// \brief NaviBtnCheckEvts
    /// \param uIndex :选中的按钮索引0表示所有#按钮,1-26表示字母A到Z,27=数字按钮,28=其它按钮
    ///
    void OnNaviBtnCheckEvt(const uint uIndex);
};

 列表部分实现代码

Lncf_QFriendsListv::Lncf_QFriendsListv(QWidget *parent)
    :Lncf_IndexNavListv(parent)
{
    this->InitNavBaseList(false,false);
    this->InitFriendsList();
}

Lncf_QFriendsListv::~Lncf_QFriendsListv()
{

}

/// 初始化列表虚函数
/// \brief InitFriendsList
///
void Lncf_QFriendsListv::InitFriendsList()
{
    sNaviTextsList = {u"A",u"B",u"C",u"D",u"E",u"F",u"G",u"H",u"I",u"J",u"K",u"L",u"M",u"N",u"O",u"P",u"Q",u"R",u"S",u"T",u"U",u"V",u"W",u"X",u"Y",u"Z",u"❉",u"☼"};
    SetNavPaddingVal(9);
    pItemsDelegate = new Lncf_QFriendsItemd(this);
    this->setItemDelegate(pItemsDelegate);

    pListItemModel = new QStandardItemModel(this);
    pSortFilteMode = new Lncf_QFriendSortlm(this);
    pSortFilteMode->setSourceModel(pListItemModel);            //将model放入代理中
    this->setModel(pSortFilteMode);                            //在视图中安装代理

    connect(pItemsDelegate, &Lncf_QFriendsItemd::GroupItemsExpand, this, &Lncf_QFriendsListv::HandGroupsExpand);
    connect(pItemsDelegate, &Lncf_QFriendsItemd::ItemElementClick, this, &Lncf_QFriendsListv::OnUserElementClick);
    connect(pItemsDelegate, &Lncf_QFriendsItemd::ItemElementDbClick, this, &Lncf_QFriendsListv::OnUserElementDbClick);
    connect(pItemsDelegate, &Lncf_QFriendsItemd::ItemsElementHovers, this, &Lncf_QFriendsListv::OnUserElementsHovers);
    connect(pItemsDelegate, &Lncf_QFriendsItemd::GroupsRightClicked, this, &Lncf_QFriendsListv::OnGroupRightClick);
    connect(pItemsDelegate, &Lncf_QFriendsItemd::UserRightClickEvts,this, &Lncf_QFriendsListv::OnUsersRightClick);

    connect(this, &Lncf_IndexNavListv::clicked, this, &Lncf_QFriendsListv::HandItemsClicked);
    connect(this, &Lncf_IndexNavListv::NavBtnCheckEvts, this, &Lncf_QFriendsListv::OnNaviBtnCheckEvt);

    this->setEditTriggers(QAbstractItemView::NoEditTriggers);
    this->setResizeMode(Lncf_IndexNavListv::Adjust );
    this->SetItemsMouseEvt(bMouseTracking);
    this->setVerticalScrollMode(ScrollPerPixel);
}

/// 初始导航组
/// \brief InitNavigatGroup
///
void Lncf_QFriendsListv::InitNavigatGroup()
{
    // A~Z
    for(int i = 0; i < 26; i++)
    {
        QLatin1Char ch(i + 65);
        auto itemGroup = new QStandardItem(QString(ch));
        itemGroup->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE);
        LNCFQFRIENDS_LISTGROUP groupData;
        groupData.uItemsNavIndex = i+1;
        groupData.uItemsGroupIds = i+65;
        groupData.sItemGroupName = sNaviTextsList[i];
        itemGroup->setData(i+1,LQFRIENDSLIST_ROLES::FRIENDSLIST_INDEX);
        itemGroup->setData(QVariant::fromValue(groupData), LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP);
        itemGroup->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN);
        itemGroup->setData(1, LQFRIENDSLIST_ROLES::FRIENDGROUP_TYPES);
        itemGroup->setData(0, LQFRIENDSLIST_ROLES::FRIENDGROUP_DATAS);
        itemGroup->setData((i+1)*100, LQFRIENDSLIST_ROLES::FRIENDINDEX_DATAS);
        pListItemModel->appendRow(itemGroup);
    }

    //数字开头,标识为❉, 索引未26
    auto itemNumber = new QStandardItem(QString::fromStdU16String(sNaviTextsList[26]));
    itemNumber->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE);
    LNCFQFRIENDS_LISTGROUP numberData;
    numberData.uItemsNavIndex = 27;
    numberData.uItemsGroupIds = 26+65;
    numberData.sItemGroupName = sNaviTextsList[26];
    itemNumber->setData(QVariant::fromValue(numberData), LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP);
    itemNumber->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN);
    itemNumber->setData(1, LQFRIENDSLIST_ROLES::FRIENDGROUP_TYPES);
    itemNumber->setData(27,LQFRIENDSLIST_ROLES::FRIENDSLIST_INDEX);
    itemNumber->setData(0, LQFRIENDSLIST_ROLES::FRIENDGROUP_DATAS);
    itemNumber->setData(27*100, LQFRIENDSLIST_ROLES::FRIENDINDEX_DATAS);
    pListItemModel->appendRow(itemNumber);

    //其它字符以☼表示,索引为27
    auto itemOther = new QStandardItem(QString::fromStdU16String(sNaviTextsList[27]));
    itemOther->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE);
    LNCFQFRIENDS_LISTGROUP otherData;
    otherData.uItemsNavIndex = 28;
    otherData.uItemsGroupIds = 27+65;
    otherData.sItemGroupName = sNaviTextsList[27];
    itemOther->setData(QVariant::fromValue(otherData), LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP);
    itemOther->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN);
    itemOther->setData(1, LQFRIENDSLIST_ROLES::FRIENDGROUP_TYPES);
    itemOther->setData(28,LQFRIENDSLIST_ROLES::FRIENDSLIST_INDEX);
    itemOther->setData(0, LQFRIENDSLIST_ROLES::FRIENDGROUP_DATAS);
    itemOther->setData(28*100, LQFRIENDSLIST_ROLES::FRIENDINDEX_DATAS);
    pListItemModel->appendRow(itemOther);

    bInitNaviGroup = true;
}

/// 获取指定索引模型分组数据
/// \brief GetItemsViewData
/// \param index
/// \return
///
LNCFQFRIENDS_LISTGROUP Lncf_QFriendsListv::GetItemGroupData(const QModelIndex &index) const
{
    if(index.isValid()&&pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool())
    {
        return  pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP).value();
    }
    return LNCFQFRIENDS_LISTGROUP();
}

/// 获取指定索引模型视图数据
/// \brief GetItemsViewData
/// \param index
/// \return
///
LNCFQFRIENDS_LISTVIEWS Lncf_QFriendsListv::GetItemsUserData(const QModelIndex &index) const
{
    if(index.isValid()&&!pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool())
    {
        return  pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_ITEMS).value();
    }
    return LNCFQFRIENDS_LISTVIEWS();
}

/// 获取指定索引模型自定义应用数据
/// \brief GetItemsAppsData
/// \param index
/// \return
///
QVariant Lncf_QFriendsListv::GetItemsAppsData(const QModelIndex &index,bool &bGroup) const
{
    if(index.isValid())
    {
        bGroup = pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool();
        if(bGroup)
            return  pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP).value().tGroupAppsData;
        else
            return  pSortFilteMode->data(index,LQFRIENDSLIST_ROLES::FRIENDSLIST_ITEMS).value().tItemsAppsData;
    }
    return QVariant();
}

/// 获取会话列表所有数据链表
/// \brief GetItemsDataList
/// \param vList
/// \return
///
bool Lncf_QFriendsListv::GetItemsDataList(std::vector &vList)
{
    int rowCount = pSortFilteMode->rowCount();
    for(int i=0;iindex(i, 0);
        if(Index.isValid()){
            LNCFQFRIENDS_LISTDATAS tagItemData;
            if(pSortFilteMode->data(Index,LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool()&&pSortFilteMode->data(Index,LQFRIENDSLIST_ROLES::FRIENDGROUP_TYPES).toUInt()==0){
                tagItemData.tFriendsGroups = pSortFilteMode->data(Index,LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP).value();
                LNCFQFRIENDS_LISTVIEWS itemView;
                for(int x=0;xindex(x, 0);
                    if(!pSortFilteMode->data(sIndex,LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE).toBool()){
                        itemView = pSortFilteMode->data(sIndex,LQFRIENDSLIST_ROLES::FRIENDSLIST_ITEMS).value();
                        tagItemData.vSubsListItems.push_back(itemView);
                    }
                }
            }

            vList.push_back(tagItemData);
        }
    }
    return vList.size()>0;
}

实现QListView函数//

void Lncf_QFriendsListv::setViewMode(ViewMode mode)
{
    if(mode==Lncf_IndexNavListv::IconMode){
        pItemsDelegate->SetHasIconsModel(true);
        this->setContentsMargins(3,3,3,3);
        this->update();
    }
    else{
        pItemsDelegate->SetHasIconsModel(false);
        this->setContentsMargins(1,1,1,1);
    }
    Lncf_IndexNavListv::setViewMode(mode);
}

void Lncf_QFriendsListv::setSpacing(int space)
{
    Lncf_IndexNavListv::setSpacing(space);
}

void Lncf_QFriendsListv::setGridSize(const QSize &size)
{
    pItemsDelegate->SetIconsGridSize(size.width(),size.height());
    //QListView::setGridSize(size);
}

/// 添加好友列表分组
/// \brief AddFriendsGroup
/// \param data
/// \param appData
///
void Lncf_QFriendsListv::AddFriendsGroup(LNCFQFRIENDS_LISTGROUP data)
{
    if(HasGroupsExists(data))
        return;
    QStandardItem *pItem = new QStandardItem;
    pItem->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE);
    pItem->setData(true, LQFRIENDSLIST_ROLES::FRIENDSLIST_EXPAN);
    pItem->setData(0, LQFRIENDSLIST_ROLES::FRIENDGROUP_TYPES);
    pItem->setData(data.uItemsGroupIds+1*10, LQFRIENDSLIST_ROLES::FRIENDGROUP_DATAS);
    pItem->setData(0, LQFRIENDSLIST_ROLES::FRIENDINDEX_DATAS);
    pItem->setData(QVariant::fromValue(data), LQFRIENDSLIST_ROLES::FRIENDSLIST_GROUP);
    pListItemModel->appendRow(pItem);
    UpdateListDatas();
}

/// 添加会话项目
/// \brief AddSessionItems
/// \param data     :默认视图数据
/// \param appData  :自定义app数据,此数据由app自行维护,默认情况下此数据不赋值
///
void Lncf_QFriendsListv::AddFriendsUsers(LNCFQFRIENDS_LISTVIEWS data)
{
    if(HasContactExist(data))
        return;
    QString sName = QString::fromStdU16String(data.uItemsShowName);
    QStandardItem *pUserItem = new QStandardItem(sName);
    pUserItem->setData(false, LQFRIENDSLIST_ROLES::FRIENDSLIST_HEADE);
    pUserItem->setData(false, LQFRIENDSLIST_ROLES::FRIENDSLIST_ACTIV);
    pUserItem->setData(false, LQFRIENDSLIST_ROLES::FRIENDSLIST_CLICK);
    pUserItem->setData(data.uItemsGroupIds+1*10+1, LQFRIENDSLIST_ROLES::FRIENDGROUP_DATAS);
    pUserItem->setData(Lncf_NavCharHelper::GetNavigateIndexVal(sName.at(0)),LQFRIENDSLIST_ROLES::FRIENDSLIST_INDEX);
    pUserItem->setData(Lncf_NavCharHelper::GetNavigateIndexVal(sName.at(0))*100+1, LQFRIENDSLIST_ROLES::FRIENDINDEX_DATAS);
    pUserItem->setData(data.uOnlinesStatus!=5&&data.uOnlinesStatus!=0,LQFRIENDSLIST_ROLES::FRIENDONLINE_STATE);
    pUserItem->setData(QVariant::fromValue(data), LQFRIENDSLIST_ROLES::FRIENDSLIST_ITEMS);
    pListItemModel->appendRow(pUserItem);
    UpdateListDatas();
}

结束:由于整体代码很长,而篇幅有限,因此给个大概的思路和参考。在ListView中继承的Lncf_IndexNavListv我们将在后面的文章中给出,此类主要实现了列表导航封装。

有想一起学习或探讨的朋友可以留言或加QQ群:717743458。

你可能感兴趣的:(c++,qt,开发语言,listview,qq)