这份代码是参照网上的一个开源项目写的,主要实现了简易人工智能的聊天功能。原项目通过C++调用后台python进程来获取聊天的内容。我通过修改部分逻辑后,使用了基于纯C++来调用后台结果来返回聊天的数据。
实现的主要思路如下:
1.用户在输入框输入消息后,点击发送,调用槽函数on_btnSend_clicked(),然后通过布局器以及绘图功能绘制出文本框与气泡,显示在聊天窗口;
2. 当我方消息发送成功后,程序会自动调用后台接口,将我们的消息以GET请求方法发送给后台。服务器会根据我们的消息去自动生成json格式的回复反馈给程序。
3. 程序接收到json字符串后,会对其进行解析,提取出文本消息字段后,通过信号与槽将文本字符串发给ui类。ui类再将消息显示即可。
效果如下:
源代码如下:
//common.h
#ifndef COMMON_H
#define COMMON_H
enum E_COLOR {
eWechatGreen,
eWhite
};
enum E_WHOSAY {
eMe,
eFriend
};
#define WECHATGREEN 152, 225, 101
#define WHITE 255,255,255
#endif // COMMON_H
//custommsgitem.h
#ifndef CUSTOMMSGITEM_H
#define CUSTOMMSGITEM_H
#include <QWidget>
#include "common.h"
namespace Ui {
class CustomMsgItem;
}
class QLabel;
class CustomMsgItem : public QWidget
{
Q_OBJECT
public:
explicit CustomMsgItem(QWidget *parent = 0, E_WHOSAY = E_WHOSAY::eMe);
~CustomMsgItem();
void paintEvent(QPaintEvent *);
void setMsg(QString strMsg);
QString getMsg();
void setMsgBackgroudColor(E_COLOR);
private:
Ui::CustomMsgItem *ui;
QString m_strMsg;
QLabel *m_pLbMsg;
QColor m_colorBackgroundMsg;
E_WHOSAY m_eWhoSay;
};
#endif // CUSTOMMSGITEM_H
//iknow.h
#ifndef IKNOW_H
#define IKNOW_H
#include <QString>
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
class QProcess;
class IKnow : public QObject
{
Q_OBJECT
public:
IKnow();
~IKnow();
QString answer();
void setQuestion(QString);
bool findAnswer();
void exec();
signals:
void signalFindAnswer(QString&);
void sgnAnswreString(QString&);
private slots:
void recvStandOut();
protected slots:
void OnGetReplyFromUrlRtn(QNetworkReply* pRply);
private:
QProcess *m_pProc;
QString m_strAnswer;
QString m_strQuestion;
QNetworkAccessManager* m_pNetAcsMgr;
};
#endif // IKNOW_H
```cpp
//topaopao.h
#ifndef TOPAOPAO_H
#define TOPAOPAO_H
#include <QWidget>
namespace Ui {
class ToPaoPao;
}
class QVBoxLayout;
class IKnow;
class ToPaoPao : public QWidget
{
Q_OBJECT
public:
explicit ToPaoPao(QWidget *parent = 0);
~ToPaoPao();
void paintEvent(QPaintEvent *);
bool eventFilter(QObject *, QEvent *);
private slots:
void on_btnSend_clicked();
void slotFindAnswer(QString&);
void slider();
void OnGetRetString(QString&);
private:
Ui::ToPaoPao *ui;
QVBoxLayout *m_pVboxLayout;
IKnow *m_pIknow;
QString m_strRet;
};
#endif // TOPAOPAO_H
//custommsgitem.cpp
#include "custommsgitem.h"
#include "ui_custommsgitem.h"
#include <QPainter>
#include <QLabel>
#include <QDebug>
CustomMsgItem::CustomMsgItem(QWidget *parent, E_WHOSAY eWhoSay) :
QWidget(parent),
ui(new Ui::CustomMsgItem),
m_strMsg(""),
m_colorBackgroundMsg(WECHATGREEN),
m_eWhoSay(eWhoSay)
{
ui->setupUi(this);
m_pLbMsg = new QLabel(this);
m_pLbMsg->setTextInteractionFlags(Qt::TextSelectableByMouse);
ui->horizontalLayout->addWidget(m_pLbMsg);
m_pLbMsg->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
QFont font;
font.setPixelSize(20);
m_pLbMsg->setFont(font);
m_pLbMsg->setWordWrap(true);
}
CustomMsgItem::~CustomMsgItem()
{
delete ui;
}
void CustomMsgItem::paintEvent(QPaintEvent *)
{
m_pLbMsg->setText(m_strMsg);
//根据当前文字长度设置气泡长度
QFontMetrics fontMetrics(m_pLbMsg->font());
//如果当前字体长度大于窗体宽度的二分之一
//就不断分割
if (fontMetrics.width(m_pLbMsg->text()) > this->parentWidget()->width() / 2) {
setFixedWidth(30 + this->parentWidget()->width() / 2);
int iLine = (fontMetrics.width(m_pLbMsg->text()) % (this->parentWidget()->width() / 2)) == 0 ? 0 : fontMetrics.height();
setFixedHeight(30 + fontMetrics.height() * ((fontMetrics.width(m_pLbMsg->text()) / (this->parentWidget()->width() / 2))) + iLine);
} else {
setFixedWidth(fontMetrics.width(m_pLbMsg->text()) + 30);
setFixedHeight(fontMetrics.height() + 25);
}
QPainter painter(this);
QRectF rectangle;
QPainterPath trianglePath;
if (m_eWhoSay == E_WHOSAY::eMe) {
rectangle = QRectF(0, 0, width() - 5, height());
trianglePath = QPainterPath(QPointF(width() - 5, height() / 2 - 7));
trianglePath.lineTo(width(), height() / 2);
trianglePath.lineTo(width() - 5, height() / 2 + 7);
trianglePath.lineTo(width() - 5, height() / 2 - 7);
} else if (m_eWhoSay == E_WHOSAY::eFriend) {
m_colorBackgroundMsg.setRgb(WHITE);
rectangle = QRectF(5, 0, width() - 5, height());
trianglePath = QPainterPath(QPointF(5, height() / 2 - 7));
trianglePath.lineTo(0, height() / 2);
trianglePath.lineTo(5, height() / 2 + 7);
trianglePath.lineTo(5, height() / 2 - 7);
}
painter.setPen(Qt::NoPen);
painter.setBrush(m_colorBackgroundMsg);
//画矩形
painter.drawRoundedRect(rectangle, 5.0, 5.0);
//画三角形
painter.drawPath(trianglePath);
}
void CustomMsgItem::setMsg(QString strMsg)
{
this->m_strMsg = strMsg;
}
QString CustomMsgItem::getMsg()
{
return m_strMsg;
}
void CustomMsgItem::setMsgBackgroudColor(E_COLOR eColor)
{
if (eColor == E_COLOR::eWechatGreen) {
m_colorBackgroundMsg.setRgb(WECHATGREEN);
} else if (eColor == E_COLOR::eWhite) {
m_colorBackgroundMsg.setRgb(WHITE);
}
}
//iknow.cpp
#include "iknow.h"
#include <QProcess>
#include <QDebug>
#include <QTextCodec>
#include <QCoreApplication>
#include <QByteArray>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
IKnow::IKnow()
{
m_pNetAcsMgr = new QNetworkAccessManager(this);
QString strPorc = QCoreApplication::applicationDirPath() + "/IKNOW.exe";
m_pProc = new QProcess();
m_pProc->setReadChannel(QProcess::StandardOutput);
m_pProc->start(strPorc);
connect(m_pProc, SIGNAL(readyReadStandardOutput()), this, SLOT(recvStandOut()));
}
IKnow::~IKnow()
{
m_pProc->kill();
delete m_pProc;
m_pProc = nullptr;
}
void IKnow::recvStandOut()
{
exec();
}
QString IKnow::answer()
{
return m_strQuestion;
}
void IKnow::setQuestion(QString strQuestion)
{
//向IKNOW.exe发送消息
/*QByteArray byte = strQuestion.toLocal8Bit();
const char *pChar = byte.data();
m_pProc->write(pChar);
m_pProc->write("\r\n");
qDebug() << m_pProc->waitForBytesWritten(2000);*/
m_strQuestion = strQuestion;
}
void IKnow::OnGetReplyFromUrlRtn(QNetworkReply* pRply)
{
int iError = pRply->error();
if (iError == QNetworkReply::NoError)
{
QByteArray bytes = pRply->readAll();
QString jsonString = QString::fromUtf8(bytes);
QJsonParseError jsonParseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes, &jsonParseError);
if (!jsonDoc.isNull() && jsonParseError.error == QJsonParseError::NoError)
{
if (jsonDoc.isArray())
{
}
else
{
QJsonObject jsonObject = jsonDoc.object();
QString strRet = jsonObject["content"].toString();
//用换车替换{br}
strRet.replace("{br}", QChar('\n'));
emit signalFindAnswer(strRet);
}
}
else
{
}
}
}
bool IKnow::findAnswer()
{
//收到IKNOW.exe的消息
// 方案1.根据C++调用python进程来获取答案
/*QByteArray byte = m_pProc->readAllStandardOutput();
QTextCodec *pTextCodec = QTextCodec::codecForName("GBK");
QString strMsg = pTextCodec->toUnicode(byte);
if (strMsg.isEmpty()) {
return false;
}
//用换车替换{br}
strMsg.replace("{br}", QChar('\n'));
m_strQuestion = strMsg;
return true;*/
//方案2.C++独立请求后台接口来得到答案
QObject::connect(m_pNetAcsMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(OnGetReplyFromUrlRtn(QNetworkReply*)));
QString strUrl = QString("http://api.qingyunke.com/api.php?key=free&appid=0&msg=%1").arg(m_strQuestion);
QUrl url(strUrl);
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=utf-8"));
m_pNetAcsMgr->get(req);
return true;
}
void IKnow::exec()
{
if (findAnswer()) {
//emit signalFindAnswer();
}
}
//topaopao.cpp
#include "topaopao.h"
#include "ui_topaopao.h"
#include "custommsgitem.h"
#include "common.h"
#include "iknow.h"
#include <QPlainTextEdit>
#include <QPropertyAnimation>
#include <QDebug>
#include <QScrollBar>
#include <QThread>
ToPaoPao::ToPaoPao(QWidget *parent) :
QWidget(parent),
ui(new Ui::ToPaoPao)
{
ui->setupUi(this);
setFocus();
m_pVboxLayout = new QVBoxLayout();
ui->scrollAreaWidgetContents_2->setLayout(m_pVboxLayout);
m_pIknow = new IKnow();
connect(m_pIknow, SIGNAL(signalFindAnswer(QString&)), this, SLOT(slotFindAnswer(QString&)));
connect(m_pIknow, SIGNAL(sgnAnswreString(QString&)), this, SLOT(OnGetRetString(QString&)));
ui->texteditInput->installEventFilter(this);
QString strQuestion = ui->texteditInput->toPlainText();
m_pIknow->setQuestion(strQuestion);
connect(ui->scrollArea->verticalScrollBar(), &QAbstractSlider::rangeChanged, this, &ToPaoPao::slider);
}
ToPaoPao::~ToPaoPao()
{
delete m_pIknow;
delete m_pVboxLayout;
delete ui;
}
void ToPaoPao::slider()
{
ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
}
void ToPaoPao::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
}
bool ToPaoPao::eventFilter(QObject *pObj, QEvent *pEvent)
{
if (pObj == ui->texteditInput) {
QKeyEvent *pKeyEvent = dynamic_cast<QKeyEvent *>(pEvent);
if (pKeyEvent) {
if (pKeyEvent->key() == Qt::Key_Return || pKeyEvent->key() == Qt::Key_Return) {
on_btnSend_clicked();
}
}
}
return QWidget::eventFilter(pObj, pEvent);
}
void ToPaoPao::slotFindAnswer(QString& strAnswer)
{
QHBoxLayout *pHboxLayout = new QHBoxLayout();
CustomMsgItem *pItem = new CustomMsgItem(ui->scrollAreaWidgetContents_2, E_WHOSAY::eFriend);
pHboxLayout->addWidget(pItem);
pHboxLayout->insertStretch(1);
pItem->setMsg(strAnswer);
//pItem->setMsg(m_pIknow->answer());
m_pVboxLayout->addLayout(pHboxLayout);
}
void ToPaoPao::OnGetRetString(QString& strRet)
{
m_strRet = strRet;
}
void ToPaoPao::on_btnSend_clicked()
{
QString strInput = ui->texteditInput->toPlainText();
//去除回车
strInput.remove(QChar('\n'));
ui->texteditInput->clear();
//判断输入框是否为空
if (strInput.isEmpty()) {
return ;
}
QHBoxLayout *pHboxLayout = new QHBoxLayout();
CustomMsgItem *pItem = new CustomMsgItem(ui->scrollAreaWidgetContents_2, E_WHOSAY::eMe);
pHboxLayout->insertStretch(0);
pHboxLayout->addWidget(pItem);
pItem->setMsg(strInput);
m_pVboxLayout->addLayout(pHboxLayout);
m_pIknow->setQuestion(strInput);
m_pIknow->findAnswer();
}