使用QGraphicsView實(shí)現(xiàn)氣泡聊天窗口+排雷功能
經(jīng)過(guò)多方調(diào)查,用Qt實(shí)現(xiàn)氣泡聊天窗口的方式有如下幾個(gè):
- 使用QWebEngineView控件內(nèi)嵌html+CSS
- 使用QTextEdit內(nèi)嵌html
- 使用QGraphicsView實(shí)現(xiàn)
- 使用QWidget自己繪制氣泡樣式實(shí)現(xiàn)
作為一名C++程序員,對(duì)CSS+html這套結(jié)構(gòu)的不熟悉導(dǎo)致無(wú)法使用前兩個(gè)方案,而第三個(gè)方案又不夠高效,所以最終我選擇了最后一個(gè)方案。
最終效果:
存在問(wèn)題:無(wú)法選擇文字及跨選(但理論上可以通過(guò)重寫(xiě)鼠標(biāo)相關(guān)事件,達(dá)到模擬選擇的效果)
左側(cè)和右側(cè)的消息分別是封裝的兩個(gè)Item,而這兩個(gè)Item又從同一個(gè)基類(lèi)繼承而來(lái)。
氣泡通過(guò)重寫(xiě)void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
函數(shù),在里面根據(jù)文字的寬高計(jì)算氣泡的位置并畫(huà)上去,然后再把字寫(xiě)上去。
并且當(dāng)窗口大小發(fā)生變化時(shí),需要重新計(jì)算文字尺寸,進(jìn)行繪制。
#pragma once #include <QGraphicsRectItem> //聊天元素所有item的基類(lèi) 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;
左側(cè)聊天氣泡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); //返回整個(gè)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; //邊框距窗口右側(cè)的距離 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(); //計(jì)算文字總共需要多寬 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()); //三角,用矩形實(shí)現(xiàn) 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());
右側(cè)氣泡和左側(cè)氣泡不同,計(jì)算位置時(shí),左端點(diǎn)需要根據(jù)窗口寬度事實(shí)計(jì)算。
#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); //返回整個(gè)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; //邊框距窗口左側(cè)的距離 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(); //計(jì)算文字總共需要多寬 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從零開(kāi)始,需要補(bǔ)加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); //氣泡聊天框左端點(diǎn)需要根據(jù)控件寬度計(jì)算 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()); //三角,用矩形實(shí)現(xiàn) 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調(diào)用
#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); //檢查是否需要插入時(shí)間,傳入值為當(dāng)前消息時(shí)間 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(); //滾動(dòng)到底部 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) { //截獲鼠標(biāo)點(diǎn)擊事件 } void ChatView::CheckTime(QDateTime datetime) { if (items_.size() == 0|| datetime.secsTo(items_.lastKey())>60 * 5/*5分鐘*/) { //第一條消息前插入時(shí)間 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);
在子類(lèi)化Item時(shí),一定要注意重寫(xiě)virtual QRectF boundingRect() const;
方法,返回實(shí)際item的尺寸,讓scene知道,并且要加入const,不然當(dāng)消息左上角超出窗口范圍時(shí),會(huì)出現(xiàn)無(wú)法觸發(fā)paint的問(wèn)題。
到此這篇關(guān)于使用QGraphicsView實(shí)現(xiàn)氣泡聊天窗口+排雷的文章就介紹到這了,更多相關(guān)QGraphicsView氣泡聊天窗口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++計(jì)算ICMP頭的校驗(yàn)和實(shí)例
這篇文章主要介紹了C++計(jì)算ICMP頭的校驗(yàn)和的方法,代碼簡(jiǎn)單實(shí)用,對(duì)于校驗(yàn)ICMP報(bào)文來(lái)說(shuō)有不錯(cuò)的實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10c語(yǔ)言實(shí)現(xiàn)二叉查找樹(shù)實(shí)例方法
這篇文章主要介紹了一個(gè)c語(yǔ)言版的二叉查找樹(shù)實(shí)現(xiàn),二叉查找樹(shù),支持的操作包括:SERACH、MINIMUM、MAXIMUM、PREDECESSOR、SUCCESSOR、INSERT、DELETE,大家參考使用吧2013-11-11C語(yǔ)言中回調(diào)函數(shù)的含義與使用場(chǎng)景詳解(2)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言中回調(diào)函數(shù)的含義與使用場(chǎng)景,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03使用MinGW使Windows通過(guò)gcc實(shí)現(xiàn)C或C++程序本地編譯執(zhí)行的方法
這篇文章主要介紹了使用MinGW使Windows通過(guò)gcc實(shí)現(xiàn)C或C++程序本地編譯執(zhí)行的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11C++ vector容器實(shí)現(xiàn)貪吃蛇小游戲
這篇文章主要為大家詳細(xì)介紹了C++ vector容器實(shí)現(xiàn)貪吃蛇小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02C++實(shí)現(xiàn)LeetCode(120.三角形)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(120.三角形),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語(yǔ)言實(shí)現(xiàn)教務(wù)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)教務(wù)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03C++結(jié)構(gòu)體與類(lèi)的區(qū)別詳情
這篇文章主要介紹了C++結(jié)構(gòu)體與類(lèi)的區(qū)別,C++中的struct對(duì)C中的struct進(jìn)行了擴(kuò)充,它已經(jīng)不再只是一個(gè)包含不同數(shù)據(jù)類(lèi)型的數(shù)據(jù)結(jié)構(gòu)了,它已經(jīng)獲取了太多的功能。下面我們一起進(jìn)入文章倆姐具體內(nèi)容,需要的朋友也可以參考一下2021-11-11C語(yǔ)言實(shí)現(xiàn)推箱子項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)推箱子項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08