使用QGraphicsView实现气泡聊天窗口+排雷功能

经过多方调查,用Qt实现气泡聊天窗口的方式有如下几个:

  • 使用QWebEngineView控件内嵌html+CSS
  • 使用QTextEdit内嵌html
  • 使用QGraphicsView实现
  • 使用QWidget自己绘制气泡样式实现

作为一名C++程序员,对CSS+html这套结构的不熟悉导致无法使用前两个方案,而第三个方案又不够高效,所以最终我选择了最后一个方案。
最终效果:

存在问题:无法选择文字及跨选(但理论上可以通过重写鼠标相关事件,达到模拟选择的效果)

左侧和右侧的消息分别是封装的两个Item,而这两个Item又从同一个基类继承而来。
气泡通过重写void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);函数,在里面根据文字的宽高计算气泡的位置并画上去,然后再把字写上去。
并且当窗口大小发生变化时,需要重新计算文字尺寸,进行绘制。

#pragma once

#include <QGraphicsRectItem>
//聊天元素所有item的基类
class ChatBaseItem : public QGraphicsRectItem
{
public:
    ChatBaseItem();
    virtual ~ChatBaseItem();
    virtual int Resize(int width);      //传入值为viewport宽,返回值为item高
};
#include "chatbaseitem.h"

ChatBaseItem::ChatBaseItem()
    : QGraphicsRectItem()
{
}
ChatBaseItem::~ChatBaseItem()
int ChatBaseItem::Resize(int width)
    return 0;

左侧聊天气泡Item

#pragma once

#include "chatbaseitem.h"
#include <QDateTime>
class OtherMsgItem : public ChatBaseItem
{
public:
    OtherMsgItem(QPixmap icon, QString name, QString msg, QDateTime datetime = QDateTime());
    virtual ~OtherMsgItem();
    virtual int Resize(int width);  //返回整个item的高度
protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    virtual QRectF boundingRect() const;
private:
    QGraphicsPixmapItem icon_item_;
    QGraphicsSimpleTextItem name_item_;
    QString text_;
    QSize text_size_;     //文字尺寸
    QDateTime datetime_;
};
#include <QPainter>
#include <QMargins>
#include <QTextOption>

#include "othermsgitem.h"
const int kMsgFontSize = 14;
const int kNameFontSize = 13;
const QPoint kNamePos = QPoint(64, 0);
const QPoint kIconPos = QPoint(20, 8);
const QPoint kBorderPos = QPoint(kNamePos.x(), kNamePos.y()+18);
const QMargins kMargins = QMargins(12,11,12,11);        //文字距边框的距离
const QPoint kTextPos = QPoint(kBorderPos.x()+ kMargins.left(), kBorderPos.y() + kMargins.top());
const int kMarginRight = 40;                           //边框距窗口右侧的距离
OtherMsgItem::OtherMsgItem(QPixmap icon, QString name, QString msg, QDateTime datetime /*= QDateTime()*/)
    : ChatBaseItem()
    , datetime_(datetime)
{
    icon_item_.setPixmap(icon);
    icon_item_.setPos(kIconPos);
    text_ = msg;
    QFont font("Microsoft YaHei");
    font.setPixelSize(kNameFontSize);

    name_item_.setText(name);
    name_item_.setPos(kNamePos);
    name_item_.setFont(font);
    name_item_.setBrush(QColor(153, 153, 153));
    icon_item_.setParentItem(this);
    name_item_.setParentItem(this);
}
OtherMsgItem::~OtherMsgItem()
int OtherMsgItem::Resize(int width)
    //每行最大可容纳文字的宽度
    int row_width = width - kTextPos.x() - kMarginRight-kMargins.right();
    //计算文字总共需要多宽
    font.setPixelSize(kMsgFontSize);
    QFontMetrics font_matrics(font);
    int text_total_width = font_matrics.width(text_);
    int text_row_height = font_matrics.lineSpacing();
    if(row_width<text_total_width)
    {
        int row = text_total_width / row_width;
        ++row;
        int text_total_height = row* text_row_height;
        text_size_.setWidth(row_width);
        text_size_.setHeight(text_total_height);
    }
    else
        text_size_.setWidth(text_total_width);
        text_size_.setHeight(text_row_height);
    return text_size_.height()+kMargins.top()+kMargins.bottom()+kBorderPos.y();
void OtherMsgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    QSize rnd(17,17);
    QRectF border(kBorderPos.x(), kBorderPos.y(), text_size_.width()+kMargins.left()+ kMargins.right(),text_size_.height() + kMargins.top() + kMargins.bottom());
    //气泡加边
    painter->setPen(QPen(QColor(229, 229, 229), 1, Qt::SolidLine));
    painter->drawRoundedRect(border.x(), border.y(), border.width(), border.height(), rnd.width(), rnd.height());
    //气泡
    painter->setBrush(QBrush(Qt::white));
    painter->setPen(Qt::NoPen);
    painter->drawRoundedRect(border.x()+1, border.y()+1, border.width()-2, border.height()-2, rnd.width(), rnd.height());
    //三角,用矩形实现
    QRect rect1(border.x()+1, border.y()+1, 20, 20);
    painter->drawRect(rect1);
    //三角加边
    QPen pen;
    pen.setColor(QColor(229, 229, 229));
    painter->setPen(pen);
    painter->drawLine(border.x() , border.y() , border.x() +20, border.y() );
    painter->drawLine(border.x() , border.y() , border.x() , border.y() +20);
    QPen penText;
    penText.setColor(QColor(51, 51, 51));
    painter->setPen(penText);
    QTextOption option1(Qt::AlignLeft);
    option1.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    painter->setFont(font);
    QRectF text_rect(kTextPos.x(), kTextPos.y(), text_size_.width(), text_size_.height());
    painter->drawText(text_rect, text_, option1);
QRectF OtherMsgItem::boundingRect() const
    QRectF border(kBorderPos.x(), kBorderPos.y(), text_size_.width() + kMargins.left() + kMargins.right(), text_size_.height() + kMargins.top() + kMargins.bottom());
    return QRectF(0,0,border.width(),border.height());

右侧气泡和左侧气泡不同,计算位置时,左端点需要根据窗口宽度事实计算。

#pragma once

#include "chatbaseitem.h"
#include <QDateTime>
class SelfMsgItem : public ChatBaseItem
{
public:
    SelfMsgItem(QPixmap icon, QString msg, QDateTime datetime = QDateTime());
    virtual ~SelfMsgItem();
    virtual int Resize(int width);  //返回整个item的高度
protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    virtual QRectF boundingRect() const;
private:
    QGraphicsPixmapItem icon_item_;
    QString text_;
    QSize text_size_;     //文字尺寸
    QDateTime datetime_;
    int port_width_;
};
#include <QPen>
#include <QPainter>
#include "selfmsgitem.h"
const int kMsgFontSize = 14;
const int kNameFontSize = 13;
const int kIconY = 0;
const int kBorderY = 10;
const int kIconWidth = 34;
const QMargins kIconMargins = QMargins(10,0,20,0);
const QMargins kMargins = QMargins(12, 11, 12, 11);        //文字距边框的距离
const int kMarginLeft = 40;                                //边框距窗口左侧的距离
SelfMsgItem::SelfMsgItem(QPixmap icon, QString msg, QDateTime datetime /*= QDateTime()*/)
    : ChatBaseItem()
    , datetime_(datetime)
    , text_(msg)
{
    icon_item_.setPixmap(icon);
    icon_item_.setY(kIconY);
    icon_item_.setParentItem(this);
}
SelfMsgItem::~SelfMsgItem()
{
}
int SelfMsgItem::Resize(int width)
{
    port_width_ = width;
    //每行最大可容纳文字的宽度
    int row_width = width - kMarginLeft - kMargins.left() - kMargins.right() - kIconWidth - kIconMargins.left() - kIconMargins.right();
    //计算文字总共需要多宽
    QFont font("Microsoft YaHei");
    font.setPixelSize(kMsgFontSize);
    QFontMetrics font_matrics(font);
    int text_total_width = font_matrics.width(text_);
    int text_row_height = font_matrics.lineSpacing();
    if (row_width < text_total_width)
    {
        int row = text_total_width / row_width;
        int text_total_height = (row+1)* text_row_height;   //row从零开始,需要补加1
        text_size_.setWidth(row_width);
        text_size_.setHeight(text_total_height);
    }
    else
    {
        text_size_.setWidth(text_total_width);
        text_size_.setHeight(text_row_height);
    }
    return text_size_.height() + kMargins.top() + kMargins.bottom() + kBorderY;
}
void SelfMsgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QSize rnd(17, 17);
    //气泡聊天框左端点需要根据控件宽度计算
    QRectF border(port_width_- kIconMargins.left()-kIconMargins.right()-kIconWidth-text_size_.width()-kMargins.left()-kMargins.right()
        , kBorderY
        , text_size_.width() + kMargins.left() + kMargins.right()
        , text_size_.height() + kMargins.top() + kMargins.bottom());
    icon_item_.setX(border.x()+ border.width() + 10);
    //气泡
    painter->setBrush(QBrush(QColor(149,182,57)));
    painter->setPen(Qt::NoPen);
    painter->drawRoundedRect(border.x(), border.y(), border.width(), border.height(), rnd.width(), rnd.height());
    //三角,用矩形实现
    QRect rect1(border.x() + border.width() - 20, border.y(), 20, 20);
    painter->setPen(Qt::NoPen);
    painter->setBrush(QBrush(QColor(149, 182, 57)));
    painter->drawRect(rect1);
    QPen penText;
    penText.setColor(QColor(255, 255, 255));
    painter->setPen(penText);
    QTextOption option1(Qt::AlignLeft);
    option1.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    QFont font("Microsoft YaHei");
    font.setPixelSize(kMsgFontSize);
    painter->setFont(font);
    QRectF text_rect(border.x()+kMargins.left(), border.y() + kMargins.top(), text_size_.width(), text_size_.height());
    painter->drawText(text_rect, text_, option1);
}
QRectF SelfMsgItem::boundingRect() const
{
    QRectF border(port_width_ - kIconMargins.left() - kIconMargins.right() - kIconWidth - text_size_.width() - kMargins.left() - kMargins.right()
        , kBorderY
        , text_size_.width() + kMargins.left() + kMargins.right()
        , text_size_.height() + kMargins.top() + kMargins.bottom());
    return QRectF(0, 0, border.width(), border.height());
}

接下是view调用

#pragma once

#include <QGraphicsView>
#include <QDateTime>
#include <QMap>
class ChatBaseItem;
class ChatView : public QGraphicsView
{
    Q_OBJECT
public:
    ChatView(QWidget *parent);
    ~ChatView();
    void Resize(int width);
    void ClearAll();
    void AppendSelfMessage(QPixmap icon, QString msg, QDateTime datetime);
    void AppendOtherMessage(QPixmap icon,QString name, QString msg, QDateTime datetime);
protected:
    virtual void mousePressEvent(QMouseEvent *e);
private:
    void CheckTime(QDateTime datetime);               //检查是否需要插入时间,传入值为当前消息时间
    void AppendTime(QDateTime datetime, QString time);
    QMap<QDateTime, ChatBaseItem*> items_;
};
#include <QDebug>
#include <QTextEdit>
#include <QScrollBar>
#include <QGraphicsScene>
#include "chatview.h"
#include "chatbaseitem.h"
#include "selfmsgitem.h"
#include "othermsgitem.h"
#include "chattimeitem.h"
#include "src/vapplication.h"
const int kMarkRole = Qt::UserRole;
const int kRoleOtherMsg = kMarkRole + 1;
const int kRoleSelfMsg = kRoleOtherMsg + 1;
const int kRoleTime = kRoleSelfMsg + 1;
ChatView::ChatView(QWidget *parent)
    : QGraphicsView(parent)
{
    setScene(new QGraphicsScene());
    this->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    setStyleSheet("background: rgb(245,245,245) ;border:0px");
    this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    this->verticalScrollBar()->setStyleSheet(theStyleSheet["scrollbar"]);
}
ChatView::~ChatView()
{
    ClearAll();
}
void ChatView::Resize(int width)
{
    int height = 20;
    for (ChatBaseItem* item : items_)
    {
        item->setPos(0, height);
        height = height + item->Resize(width);
        height += 20;
    }
    this->scene()->setSceneRect(QRectF(0, 0, width, height));
}
void ChatView::ClearAll()
{
    for (auto it = items_.begin(); it != items_.end();)
    {
        ChatBaseItem* item = it.value();
        it = items_.erase(it);
        delete item;
    }
}
void ChatView::AppendSelfMessage(QPixmap icon, QString msg, QDateTime datetime)
{
    ChatBaseItem* item = new SelfMsgItem(icon, msg, datetime);
    item->setData(kMarkRole,kRoleSelfMsg);
    CheckTime(datetime);
    scene()->addItem(item);
    items_.insert(datetime, item);
    Resize(this->viewport()->width());
    update();
    //滚动到底部
    QScrollBar *vScrollBar = verticalScrollBar();
    vScrollBar->setValue(vScrollBar->maximum());
}
void ChatView::AppendOtherMessage(QPixmap icon, QString name, QString msg, QDateTime datetime)
{
    ChatBaseItem* item = new OtherMsgItem(icon, name, msg, datetime);
    item->setData(kMarkRole, kRoleOtherMsg);
    CheckTime(datetime);
    scene()->addItem(item);
    items_.insert(datetime, item);
    Resize(this->viewport()->width());
    update();
    QScrollBar *vScrollBar = verticalScrollBar();
    vScrollBar->setValue(vScrollBar->maximum());
}
void ChatView::mousePressEvent(QMouseEvent *e)
{
    //截获鼠标点击事件
}
void ChatView::CheckTime(QDateTime datetime)
{
    if (items_.size() == 0|| datetime.secsTo(items_.lastKey())>60 * 5/*5分钟*/)
    {
        //第一条消息前插入时间
        QDateTime dt = datetime.addMSecs(-1);
        AppendTime(dt,dt.toString("hh:mm:ss"));
    }
}
void ChatView::AppendTime(QDateTime datetime, QString time)
{
    ChatBaseItem* item = new ChatTimeItem(time);
    item->setData(kMarkRole, kRoleTime);
    scene()->addItem(item);
    items_.insert(datetime, item);
    Resize(this->viewport()->width());
    update();
}
#pragma once

#include "chatbaseitem.h"
#include <QGraphicsRectItem>
class ChatTimeItem : public ChatBaseItem
{
public:
    ChatTimeItem(QString time);
    virtual ~ChatTimeItem();
    virtual int Resize(int width);
protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
private:
    QGraphicsSimpleTextItem text_item_;
    QString text_;
    int port_width_;
};
#include <QFont>
#include <QPen>

#include "chattimeitem.h"
#include "color.h"
ChatTimeItem::ChatTimeItem(QString time)
    : ChatBaseItem()
{
    text_item_.setText(time);
    item_tool::SetFontColor(&text_item_, 13, false, colorspace::GetTextLightColor());
    text_item_.setParentItem(this);
}
ChatTimeItem::~ChatTimeItem()
int ChatTimeItem::Resize(int width)
    port_width_ = width;
    return text_item_.boundingRect().height();
void ChatTimeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    int width = text_item_.boundingRect().width();
    text_item_.setX((port_width_ - width) / 2);

在子类化Item时,一定要注意重写virtual QRectF boundingRect() const;方法,返回实际item的尺寸,让scene知道,并且要加入const,不然当消息左上角超出窗口范围时,会出现无法触发paint的问题。

到此这篇关于使用QGraphicsView实现气泡聊天窗口+排雷的文章就介绍到这了,更多相关QGraphicsView气泡聊天窗口内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Qt通过QGraphicsview实现简单缩放及还原效果

    本文主要介绍通过QGraphicsview实现简单的缩放,以及缩放后还原原始大小. 1,自定义一个drawview继承QGraphicsview,缩放主要实现的函数为 void scale(qreal sx,qreal sy); 通过scale可以对view进行放大或缩小. 则可以在drawview中定义缩放slots为 void drawview::zoomIn(qreal delta) { zoom(delta); } void drawview::zoomOut(qreal delta)

  • pyqt5教程QGraphicsScene及QGraphicsView使用基础

    效果图: from PyQt5.QtCore import Qt, QRectF from PyQt5.QtGui import QColor, QPen, QBrush, QFont from PyQt5.QtWidgets import (QGraphicsView, QGraphicsScene, QApplication) class MainWindow(QGraphicsView): def __init__(self, parent=None): super(MainWindow,

  • 使用QGraphicsView实现气泡聊天窗口+排雷功能

    经过多方调查,用Qt实现气泡聊天窗口的方式有如下几个: 使用QWebEngineView控件内嵌html+CSS 使用QTextEdit内嵌html 使用QGraphicsView实现 使用QWidget自己绘制气泡样式实现 作为一名C++程序员,对CSS+html这套结构的不熟悉导致无法使用前两个方案,而第三个方案又不够高效,所以最终我选择了最后一个方案.最终效果: 存在问题:无法选择文字及跨选(但理论上可以通过重写鼠标相关事件,达到模拟选择的效果) 左侧和右侧的消息分别是封装的两个Item,

  • 基于jQuery.validate及Bootstrap的tooltip开发气泡样式的表单校验组件思路详解

    表单校验是页面开发中非常常见的一类需求,相信每个前端开发人员都有这方面的经验.网上有很多成熟的表单校验框架,虽然按照它们默认的设计,用起来没有多大的问题,但是在实际工作中,表单校验有可能有比较复杂的个性化的需求,使得我们用这些插件的默认机制并不能完成这些功能,所以要根据自己的需要去改造它们(毕竟自己还不到那个写一个完美的校验框架的层次).我用过formValidation这个校验框架,虽然它跟bootstrap配合地很好,但是校验风格太死板,不太满足个性化的场景:后来我找到了jquery.val

  • C语言实现扫雷小游戏的全过程记录

    第一步思考要实现的功能 想必大家都知道扫雷这个小游戏,今天我们来用C语言实现一下,首先要扫雷,我们首先就需要有一个布置了雷的棋盘,然后开始扫雷,玩过扫雷的小伙伴都知道,如果选中的格子旁边没有雷,那么旁边的格子就会自动清空,大概的思路有了,现在我们开始实现. 第二步实现 初级版扫雷 首先创建棋盘的作用是用来存储雷的信息,这时我们思考一下,一个棋盘到底够不够用?棋盘多大才合适?我们打印出来的棋盘肯定是不能出现雷的信息的,不然游戏就无法正常进行了,但是我们雷的信息又需要棋盘存储,这样一想,一个棋盘似乎

  • C语言实现一个简单的扫雷游戏

    前言 扫雷跟上一篇文章的三子棋一样,是C语言基础知识的综合运用的实例,对于巩固我们的基础知识非常重要,同时扫雷作为C语言的一个小项目,锻炼我们的编程思维,也是一个不可多得的实践. 提示:以下是本篇文章正文内容 一.扫雷的基本思路 1.用C语言实现简单的扫雷,我们需要创建两个数组,一个数组存放雷的信息,另外一个数组存放排雷后结果的信息. 2.在创建数组时候,需要注意的是数组需要大一圈,什么意思?举个例子,比如说我们实现的是9 ×9的扫雷,那么我们的数组就得创建10×10.为什么呢? 原因如下: 因

  • Vue.js仿微信聊天窗口展示组件功能

    源码:https://github.com/doterlin/vue-wxChat 演示地址:https://doterlin.github.io/vue-wxChat/ 运行 # install dependencies npm install # serve with hot reload at localhost:8080 npm run dev # build for production with minification npm run build 介绍 支持文本和图片的展示(后续将

  • jQuery bt气泡实现悬停显示及移开隐藏功能的方法

    本文实例讲述了jQuery bt气泡实现悬停显示及移开隐藏功能的方法.分享给大家供大家参考,具体如下: jQuery.bt.options.closeWhenOthersOpen = true; $("img.eq-message").bt({ trigger:["mouseover", "click"], contentSelector:"$(this).next().html()", positions:["le

  • python实现桌面气泡提示功能

    在写桌面软件时,通常会使用到托盘上的泡泡提示功能,让我们来看看使用python如何实现这个小功能. 一.Linux系统 在Linux上,实现一个气泡提示非常简单,使用GTK实现的pynotify模块提供了些功能,我的环境是Ubuntu,默认安装此模块,如果没有,下载源文件编译安装一个.实现代码如下: #!/usr/bin/python #coding:utf-8 import pynotify pynotify.init ("Bubble@Linux") bubble_notify =

  • C# winForm实现的气泡提示窗口功能示例

    本文实例讲述了C# winForm实现的气泡提示窗口功能.分享给大家供大家参考,具体如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace WindowsFormsApplication60 { p

  • jCallout 轻松实现气泡提示功能

    jCallout的下载地址:https://github.com/anupamsmaurya/jCallout 需要添加此引用 用户名一行的 html 代码是: 复制代码 代码如下: <tr>    <td class="columnName">用户名:</td>    <td><input id="hTbxUserName" class="needCheck" type="text

  • iOS实现微信/QQ显示最近拍摄图片的功能实例代码

    如果你刚刚拍摄了图片,在使用微信/QQ发生消息时会显示"你可能要发送的图片", 实现原理: 1.打开或重新进入聊天窗口时查询图库最新的照片, 对比拍照时间和当前时间的差,当低于阈值(例如一分钟)时就显示出来. PS:阈值是逻辑上判断是否最近的依据.优点:总能找到最近拍摄的图片: 缺点:每次都要查询图片数据,响应较慢. 2.注册图库变化监听(观察者模式), 响应图库的增删改事件, 拿到变化图片数据后做对应的逻辑. 优点: 实时响应: 缺点:影响性能, 在注册监听前拿不到变化数据. 实现方

随机推荐