这篇“怎么使用QGraphicsView实现气泡聊天窗口+排雷功能”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用QGraphicsView实现气泡聊天窗口+排雷功能”文章吧。
最终效果:
存在问题:无法选择文字及跨选(但理论上可以通过重写鼠标相关事件,达到模拟选择的效果)
左侧和右侧的消息分别是封装的两个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 ) : 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 ) : 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_OBJECTpublic: 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) { //第一条消息前插入时间 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实现气泡聊天窗口+排雷功能”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。