安卓/平台QtQuick+socket.io+C++搭建聊天系统

流程有些复杂,QML不支持调用很多常见的js引擎,我们可以利用webview来达到。不过在使用socket.io,发现必须要在安卓4.4版本或更高才行,这个不是安卓的问题,是最新版的Qt没有优化老版本的WebView,唉。
开发流程图:
从发送到回调: QML -> WEBVIEW中的socke.io  -> node.js服务器  ->WEBVIEW  -> (标注)C++ -> QML
标注:由于socket.io是异步的,QML调用JS允许有回调,但是不支持在WEBVIEW中的异步回调。因此用C++来转发。
#ifndef TQMLHELPER_H
#define TQMLHELPER_H

#include 
#include
#include
class TQmlHelper : public QObject
{
    Q_OBJECT
public:

    explicit TQmlHelper(QObject *parent = 0);
// 代码规范: set开头 供QML调用 用来设置本类的QML对象
// do开头 给HTTP服务器调用 用来间接通信QML
    Q_INVOKABLE void  setroot(QObject* obj);
    Q_INVOKABLE void  setTXL(QObject* obj);

    //从聊天服务器获取好友 请求Qml刷新好友列表
    void do_updataFlist(QByteArray& b);

signals:

public slots:

 private:
QObject*  root;

//PageTongxunlv.qml:
//Component.onCompleted: {
// myapp.setTXL(gen);
// console.log("cout:"+model.count);
// model.append({name:"代码统计行数:"+model.count,tip:"代码生成的行"});
// }
//PageTongxunlv中的跟节点 包含好友列表的一些成员函数
QObject*  gen;
};

#endif // TQMLHELPER_H
#include "tqmlhelper.h"
#include <QDebug>
#include <QQuickWindow>
TQmlHelper::TQmlHelper(QObject *parent) : QObject(parent)
{
        root = NULL;
}



void  TQmlHelper::setroot(QObject* obj)
{
    qDebug()<<"Get Root *******************"<<obj<<",and RootName:"+obj->objectName();
    root = obj;
    QObjectList  objs = obj->children();
   for(auto  x  :  objs)
   {
         qDebug()<< "auto x : objs :QQuickWindow:"<< x->objectName();
   }

}


void  TQmlHelper::setTXL(QObject* obj)
{
        gen = obj;
        qDebug()<< "gen::::::::::::--->>>:"<< obj->objectName();
}

 void TQmlHelper::do_updataFlist(QByteArray& b)
 {
            qDebug()<<"do_updataFlist:"<<b;
            QString a = b; ;
            QMetaObject::invokeMethod(gen, "刷新好友列表", Qt::ConnectionType::BlockingQueuedConnection, Q_ARG(QVariant, a));

 }






#include"myhttp.h"
#include<QtWidgets/QMessageBox>
extern TQmlHelper* tmphelp;
Helloworldcontroller3::Helloworldcontroller3(QObject* parent):HttpRequestHandler(parent)
{

}
#include <qmutex.h>


void Helloworldcontroller3::service(HttpRequest &request, HttpResponse &response) {

    //允许跨域
      response.setHeader("Access-Control-Allow-Origin", "*"); 

         QByteArray path=request.getPath();
        qDebug("RequestMapper: path=%s",path.data());

        //登录状态
        if (path=="/loging") {
            QByteArray a = request.getParameter("p1");
            qDebug()<<a;

            if(a.toInt()==1)
            {
                qDebug()<<"loging ok";
            }else{
                qDebug()<<"loging erro";
            }
             response.setStatus(200,"OK");
           // HelloWorldController().service(request, response);
        }
         //获取好友列表的返回结果
        else if (path=="/Flist") {
             QByteArray a = request.getParameter("p1");
             tmphelp->do_updataFlist(a);
             response.write("ok",true);
             response.setStatus(200,"ok");
        }

        else {
            response.setStatus(404,"oo!Not found");
            QString  s =QString::fromLocal8Bit("请请不要跨域哦");
            QByteArray s2(s.toStdString().c_str());
            response.write(s2,true);
        }

        qDebug("RequestMapper: finished request");
}
*****main.qml
TQmlHelper*  tmphelp;
void  RegFunc(QQmlApplicationEngine&  engine)
{

       tmphelp = new TQmlHelper(&engine);

        engine.rootContext()->setContextProperty("myapp", (QObject*)tmphelp);
}
#include
int main(int argc, char *argv[])
{


    QApplication::setApplicationName("Myapp");
   QApplication::setOrganizationName("QtProject");
     QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);

// qputenv("QT_SCALE_FACTOR", b);
// system("su");
    QApplication app(argc, argv);

// MyWait* label = new MyWait();
// label->show();

   //

qDebug("********开始*********");
    QSettings* listenerSettings= new QSettings("assets:/demo.ini",QSettings::IniFormat,&app);

    qDebug("config file loaded");

    listenerSettings->beginGroup("listener");

       // Start the HTTP server

       new HttpListener(listenerSettings, new Helloworldcontroller3(&app), &app);

     QQuickStyle::setStyle("Material");
    QQmlApplicationEngine engine;


   RegFunc(engine);
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
            return -1;

// label->hide();

qDebug("********结束*********");
    return app.exec();
}
由于如果用C++主动获取QML对象,可能存在QML对象尚未初始化问题,因此,在qml初始化后调用C++来设置QML对象指针。
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls.Material 2.0
import QtGraphicalEffects 1.0
import QtQuick.Particles 2.0
import "Ndk.js"   as  Ndk
import "./code"
import QtWebView 1.1

 ApplicationWindow {
    id:root;
    visible: true;



     height: 480;
     width: 320;
    Component.onCompleted: {

        //把窗口对象传给Qt 之所以加载完毕后才传递 很多对象提前是无内存块
        myapp.setroot(root);

    }

    property real pixelDensity: 4.46
    property real multiplierH: root.height/480 //default multiplier, but can be changed by user
    property real multiplierW: root.width/320 //default multiplier, but can be changed by user
    function dpH(numbers) {

        return Math.round(numbers*((pixelDensity*25.4)/160)*multiplierH);
    }
    function dpW(numbers) {
        return Math.round(numbers*((pixelDensity*25.4)/160)*multiplierW);
    }
    function dpi2(px) {

        return Math.round(px*((Screen.pixelDensity*25.4)/160))
    }


    /** * 将px值转换为dip或dp值,保证尺寸大小不变 * * @param pxValue * @param scale * (DisplayMetrics类中属性density) * @return */
    function px2dip(pxValue) {
        var scale = myapp.getdensity();
        return  (pxValue / scale + 0.5);
    }

    property color  accentcol:"red"
    property color  backgroundcol:"white"
    property color  foregroundcol:"#000000"
    property color  primarycol:"blue"
    Material.accent:accentcol
    Material.background:backgroundcol
    Material.foreground:foregroundcol
    Material.primary: primarycol

//消息框
            MessageDialog {
                id:dlg;
                objectName: "xiaoxikuang";
                width: Screen.width;
                height: Screen.hight;
                visible: false;
                  title: "进度"
                    icon: StandardIcon.Question
                    text: "file.txt already exists. Replace?"
                    detailedText: "To replace a file means that its existing contents will be lost. " +
                        "The file that you are copying now will be copied over it instead."
                    standardButtons: StandardButton.Yes | StandardButton.No 
            }
            function messaggeBox(title,txt) {
                dlg.text = txt;
                dlg.title = title;
                dlg.visible = true;
            }


//调试打印

            Item {
                visible: true
                width:parent.width;
                height:dpH(100);
                z:100

// color:Ndk.SDColor_warning;
                        ListView{
                            id:dbgtxt;
                            width:parent.width;
                            height:dpH(100);
                            model: ListModel{
                                id:dbgmodel;
                                ListElement{txt:"123"}
                            }
                            delegate: Label{
                                font.pixelSize: dpW(18);
                                font.bold: true;
                                color: Ndk.green_1
                                text:txt
                            }
                            displaced: Transition {
                                      NumberAnimation { properties: "x,y"; duration: 1000 }
                                  }
                            move: Transition {
                                      NumberAnimation { properties: "x,y"; duration: 1000 }
                                  }
                        }
                }
            function dbg(txt) {
                if(dbgmodel.count>=5)
                {
                    dbgmodel.clear();
                }
                dbgmodel.append({txt:txt});
            }


    //////////////////////加载控制器//////////////////////////
    property alias mwebview: mywebview;


            property var txtprogress_var: 0
            property var txtmainpage_var: 0
             property var txtsubpage1_var: 0
            Item {
                width: Screen.width;
                height: Screen.height
                visible: true
                z:100
                id: bk;
                Rectangle{
                    id:zezao;
                    opacity: 0.9;
                    anchors.fill: parent;
                    color:"#cccccc"
                    Column{
                        width: parent.width;
                        spacing: 2;
                        //WebView加载进度
                        Label{
                            font.pixelSize: dpW(18);
                            font.bold: true;
                            color: Ndk.文字色偏白
                            id:txtprogress
                            text:"WebKit加载进度:"+txtprogress_var
                        }

                        Rectangle{

                            anchors.margins: dpW(5);
                            width: parent.width;
                            height: dpH(20);
                            color: Ndk.SDColor_success;
                            radius: dpW(2)
                            Rectangle{
                                 radius: dpW(2)
                                property var pass: 0
                                id:bartxt;
                                width: parent.width/100*pass;
                                height: dpH(20);
                                color: Ndk.SDColor_info;
                                 Behavior on width {NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}}
                            }

                        }

                         //mainpage加载进度
                        Label{
                            id:txtmainpage;
                            color: Ndk.文字色偏白
                            font.pixelSize: dpW(18);
                            font.bold: true;
                            text:"主页面加载进度:"+txtmainpage_var
                        }

                        Rectangle{
                            anchors.margins: dpW(5);
                            width: parent.width;
                            height: dpH(20);
                            color: Ndk.SDColor_success;
                            radius: dpW(2)
                            Rectangle{
                                 radius: dpW(2)
                                property var pass: 0
                                id:barmainpage;
                                width: parent.width/100*pass;
                                height: dpH(20);
                                color: Ndk.SDColor_info;
                                 Behavior on width {NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}}
                            }
                        }

                        //子页面加载进度
                        Label{
                            id:txtsubpage1;
                            color: Ndk.文字色偏白
                            font.pixelSize: dpW(18);
                            font.bold: true;
                            text:"首页加载进度:"+txtmainpage_var
                        }

                        Rectangle{
                            anchors.margins: dpW(5);
                            width: parent.width;
                            height: dpH(20);
                            color: Ndk.SDColor_success;
                            radius: dpW(2)
                            Rectangle{
                                 radius: dpW(2)
                                property var pass: 0
                                id:barsubpage1;
                                width: parent.width/100*pass;
                                height: dpH(20);
                                color: Ndk.SDColor_info;
                                 Behavior on width {NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}}
                            }
                        }

                    }

                    Behavior on opacity {
                      NumberAnimation{ easing.type: Easing.InOutBounce;duration: 2000}

                    }
                    onOpacityChanged: {
                        if(opacity==0)
                        {
                            bk.visible = false;
                            bk.deleteLater();
                            dbg("onOpacityChanged");
                        }
                    }
                }
                // 禁止事件穿透
                   MouseArea{
                       anchors.fill: parent;
                       onPressed:{
                            mouse.accepted = true
                       }
                     // drag.target: root // root可拖动
                   }
                   //外部调用刷新更新状态 并且判断是否全部加载成功
                function changgepro(type,pro) {
                    if(type == 0)//webview加载进度
                    {
                        bartxt.pass = pro;
                        txtprogress_var = pro;
                    }else  if(type == 1)//mainpage加载进度
                    {
                         barmainpage.pass = pro;
                        txtmainpage_var = pro;
                    }else  if(type == 2)//mainpage加载进度
                    {
                         barsubpage1.pass = pro;
                        txtsubpage1_var = pro;
                    }
                    if(txtprogress_var == 100  && txtmainpage_var == 100 && txtsubpage1_var ==100)
                    {
                        oninitOk();

                    }
                }
                //前台页面加载完毕后的行为
                function oninitOk() {
                    foot.visible = true;
                    zezao.opacity = 0;
                     benavShow = true;
                    messaggeBox("状态","加载完毕");
// bk.visible = false;
                }



            }


    WebView{
        //加载完毕 loading变成false
        visible: false;
        id:mywebview;
        objectName: "webview"
          url:"file:///android_asset/index.html";
        onLoadProgressChanged: {

// txtprogress.text="WebKit加载进度:"+mywebview.loadProgress;
             bk.changgepro(0,mywebview.loadProgress);
        }
        onLoadingChanged: {

        }
    }

    //页头
    property alias roothd: hd
     header: ToolBar{
        id:hd;
        states: [
            State {
                name: "hide"
                PropertyChanges {
                    target: hd;opacity:0;height:0;width:0;
                }
                PropertyChanges {
                    target: lisetview;opacity:0;rotation:360;height:0;
                }
            }
        ]

        transitions: Transition {
            // Make the state changes smooth
            ParallelAnimation {
                NumberAnimation { duration: 500; properties: "opacity,x,contentY,height,width" }
                ColorAnimation { property: "color"; duration: 888 }
                NumberAnimation { duration: 888; properties: "rotation" }


            }
        }


        height:dpH(60);
        Text{
            text:"mywebview.loadProgress+"
            anchors.centerIn: parent
            color: "white"
            font.pixelSize: dpW(18)
        }

        layer.enabled: true
        layer.effect: DropShadow {
            transparentBorder: true//绘制边框阴影
            color: "#000000";
            radius: dpH(15)
            id:drop;

            //cached: true;
            horizontalOffset: 0;
            verticalOffset: 0;
            samples: 16;
            smooth: true;


        }
    }

    function getdpistype() { 
        console.log("SCALA: "+Screen.pixelDensity*25.4/160)
        console.log(Screen.pixelDensity)
        var curdpi = Screen.pixelDensity*25.4;
        var  mydpi = curdpi.toFixed(0);
        console.log(mydpi);
        console.log("my dpi "+myapp.getdpi())
        return "MYDPI+"+mydpi;
        if(mydpi>=480)
        {
            console.log("XXHDPI");
            return "XXHDPI";
        }else  if(mydpi>=320)
        {
            console.log("XHDPI");
            return "XHDPI";
        }else  if(mydpi>=240)
        {
            console.log("HDPI");
            return "HDPI";
        }else  if(mydpi>=180)
        {
            console.log("MDPI");
            return "MDPI";
        }else
        {
            console.log("LDPI");
            return "LDPI";
        }


    }
    Loader{
        id:mainpage;
        asynchronous: true
        anchors.fill: parent;
        sourceComponent: commapnpage;
        onProgressChanged: {
            bk.changgepro(1,progress*100);

        }

    }
    Component{
        id:commapnpage;
        SwipeView{
            state: "hide1"
            currentIndex: tabindex

            onCurrentIndexChanged: {
                console.log("onCurrentIndexChanged:"+currentIndex);
                tabindex = currentIndex;//导航栏的序号与这里同步,手动滑动触发这里
                if(currentIndex==1)
                {
                    tongxinlu.item.获取好友列表();
                }

            }


            //首页
            Page{

                id:gouzhen;
                Loader{
                    id:shouye
                     asynchronous: true//异步加载组件
                     anchors.fill: parent
                    sourceComponent: Page_gouzhen{}
                    onProgressChanged: {
                        bk.changgepro(2,shouye.progress*100);
                    }


                }
            }
        //通讯录
            Page{
                id:tongxun;
                Loader{
                  asynchronous: true
                    id:tongxinlu;
                     anchors.fill: parent;
                    sourceComponent: PageTongxunlv{}

                }
            }



        }
    }

    //当前选中的导航序号
   property var  benavShow  :false;//是否显示底部导航
    property int tabindex: mainpage.item.currentIndex
    footer: Row{
        visible: benavShow;

        id:foot;
        width:benavShow? parent.width:0;
        height:benavShow? width/5*0.75:0;






        Repeater{
            id:rep
            delegate: NavNewDelegate{
                width: benavShow?parent.width/5:0;
                height:benavShow?width*0.75:0;
                rotation:benavShow?0:Math.random()*360
                id:navitem;
               Behavior on width {NumberAnimation {duration: 2000; easing.type: Easing.InOutQuad}}
               Behavior on height {NumberAnimation {duration: 2000; easing.type: Easing.InOutQuad}}
               Behavior on rotation {NumberAnimation {duration: 2000; easing.type: Easing.InOutQuad}}

               onClick: {
                    dbg("Delegate传递过来的下标:"+index+tabindex);
                    console.log("Delegate传递过来的下标:"+index+tabindex);

                }

            }
            model:NavNewModel{id:model1}
        }
    }
    Keys.enabled: true;
    Keys.onReleased:  {
        console.log("key"+event.key);
        if(event.key==Qt.Key_Back)
        {
            console.log("back");
        }
    }
}
****关键代码  QML和C++以及webview中的js库交互的部分
import QtQuick 2.7
import QtQuick.Controls 1.4 as Old
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
import QtGraphicalEffects 1.0
import QtQuick.Particles 2.0
import QtWebSockets 1.0
import "../"
Item {
    property alias mview: view
    objectName: "FList";
        anchors.fill: parent;
        id:gen;
        ListView{
            id:view;

             anchors.fill: parent;
            delegate: ListTongxunlvDelegate{
            }
            model: ListTongxunlvModel{
                id:model;
            }
            Component.onCompleted: {
                    myapp.setTXL(gen);
                     console.log("cout:"+model.count);
                     model.append({name:"代码统计行数:"+model.count,tip:"代码生成的行"});
                }
        }

        //专门被C++调用的函数
        function 刷新好友列表(x)
        {
            console.log("get c++ *****"+x)
            root.messaggeBox("刷新好友列表:",x);

        }

    function 获取好友列表()
    {

        mywebview.runJavaScript("获取好友列表()",function(result){
            for(var x in  result)
            {
                root.dbg(result[x]);
            }
             root.dbg(result);


        });
    }



}
**********HTML代码 使用裸页面只为加载JS功能

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
    <title>htitle>
    <script src="jquery-1.12.3.js" type="text/javascript" charset="utf-8">script>
   <script src="js/mui.min.js" type="text/javascript" charset="utf-8">script>
    <script src="socket.io.js" type="text/javascript" charset="utf-8">script>
    <script src="index2.js" type="text/javascript" charset="utf-8">script>
head>
<body> 
body>
html>
*****js代码
var msocket;
mui.init({})
mui.ready(lod);
function 登录成功(arg1) {
// $.post("http://localhost:8080/loging?p1="+arg1, function(data){
// alert("Data Loaded: " + data.name);
// },"json");
 $.post("http://localhost:8080/loging?p1="+arg1);
}
function 发送到C加加(path,arg1) {
// $.post("http://localhost:8080/loging?p1="+arg1, function(data){
// alert("Data Loaded: " + data.name);
// },"json");
  $.post("http://localhost:8080/"+path+"?p1="+arg1);
}
var  isloging = false;
function lod(){


        alert("ok");

        console.log("模拟器连接");
// msocket = io.connect('ws://10.0.2.2:8081', { 'reconnect': true });//模拟器访问局域网
        msocket = io.connect('ws://192.168.0.101:8081', { 'reconnect': true });//模拟器访问局域网

        msocket.on('connect',function(){
            alert("连接成功;");
            isloging = true;
            msocket.emit("denglu","admin","123",function(callbackdata){

                alert("登录结果:"+callbackdata);
            })
            登录成功(1);

        });


    //正在连接
    msocket.on('connecting',function(){
        alert("正在连接");
    });

    //连接超时
    msocket.on('connect_timeout',function(){
        console.log("connect_timeout");
    });


    //连接失败
    msocket.on('connect_failed',function(){
        alert("连接失败");
    });

    //错误发生 并且无法被其他事件类型所处理
    msocket.on('error',function(data){
        alert("错误发生 并且无法被其他事件类型所处理");

    });



    //重连失败
    msocket.on('reconnect_failed',function(){
        alert("reconnect_failed");
    });

    //成功重连
    msocket.on('reconnect',function(TheNumber){
        alert("reconnectOk"+TheNumber);
    });

    //正在重连
    msocket.on('reconnecting',function(TheNumber){
        console.log("reconnecting"+TheNumber);

    });


}


function Testfunc() {
// alert("get")
    return 123;
}
//对应QML代码:
//function 获取好友列表()
// {
//
 // mywebview.runJavaScript("获取好友列表()",function(result){
// for(var x in result)
// {
  // root.dbg(result[x]);
  // }
 // root.dbg(result);
//
//
// });
  // }
var  mdata;
function 获取好友列表() {

    if(isloging==false)
    {
        alert("暂未登录,无法拉取好友");
        return "";
    } 
        alert("获取好友中");



             msocket.emit("获取好友",/*"获取好友",*/function(data){
        //传过来的是字符串 不需要转换了 交给QtQuick转换吧 一样
        //不对 貌似qtquick可以直接解析对象
        console.log(data)
        发送到C加加("Flist",data);

        mdata = data;
// console.log(JSON.parse(data))
            });

    //由于是异步的 因此可能先返回了 才接收到服务器发过来的数据 解决方式很多 这里用同步方式
    alert("最后层返回");
    return mdata;//此返回值无用 真正的返回值是由服务器转发过来经过 发送到C加加("Flist",data) 转发给QML
}

你可能感兴趣的:(Qt,Quick,Qt)