QML Login 实现登录

参考: QMLBook-in-chinese

  1. 新建一带软键盘的, 主题为 Material 的 qt quick 空项目,
  2. 新建资源文件 res.qrc,添加字体,logo 等;
  3. 新建 Login 类,与数据库交互验证;
  4. 完善基础组件及登录、主页的 qml 界面。

技术点:1. Material 主题黑白切换,图标字体的使用; 2. 软键盘(只输入英文字母数字,只输入数字等,及大小控制)3. js 函数及正则应用; 4. Loader 动态加载卸载组件实现页面切换; 5.常见基础组件的组合使用; 6. C++/QML 混合干活;7. qml 信号槽绑定; 8. 定时器 timer; 9. 可适应不同大小及分辨率显示

效果图

QML Login 实现登录_第1张图片


源代码

QML Login 实现登录_第2张图片

main.cpp

#include 
#include 
#include 
#include 
#include   // pro 中添加 QT += quickcontrols2
#include "login.h"

int main(int argc, char *argv[])
{
    qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));   //虚拟键盘
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);//自适应高分辨率屏幕

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    
    Login login; //登录验证模块
    engine.rootContext()->setContextProperty("login", &login);
    
    QQuickStyle::setStyle("Material"); // Material 样式
    QFontDatabase::addApplicationFont(":/fonts/fontello.ttf"); //图标 http://fontello.com
    
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

登录 Login 类

#ifndef LOGIN_H
#define LOGIN_H

#include 

class Login : public QObject
{
    Q_OBJECT
public:
    explicit  Login(QObject *parent = nullptr);

	// 让QML 可访问
    Q_INVOKABLE void signIn(const QString &hostname,quint16 port,const QString &username, const QString &password);

signals:
    void successed(bool succeeded, QString data);
    void err(const QString &err);

private:
};

#endif // LOGIN_H
#include "login.h"
Login::Login(QObject *parent) : QObject(parent)
{
}

void Login::signIn(const QString &hostname, quint16 port,const QString &username, const QString &password)
{
    // 这里略去复杂数据库验证过程
    if( "admin"!=username.trimmed() ){
        emit err("账号错误");
        return;
    }
    if( "admin"!=password.trimmed() ){
        emit err("密码错误");
        return;
    }
    emit successed(true, "登录成功");
}

main.qml

import QtQuick 2.14
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3  //颜色参考: 打开帮助:Material Style
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.1
import QtQuick.Window 2.14
import QtQuick.VirtualKeyboard 2.14

import "Pages"
import "Pages/Base"

ApplicationWindow {
    id: window
    width: 1024
    height: 768
    title: _title
    visible: true
    visibility: Window.Maximized
    font.pixelSize: dp(30);  // 全局字体
    Material.primary: dark ? "#000" : "#F8F8F8"
    Material.theme:   dark ? Material.Dark : Material.Light

    // 全局变量
    property string _title:"Hello QML - 标题"
    property string userName
    property string passWord
    property string hostName
    property int    port
    property bool   dataPrepared: true
    property var    myData
    property bool   dark
    property real   dpScale: 1     // 分辨率: 范围(0.8 ~ 2)  //还可以添加 PinchArea 指捏缩放

    readonly property real pixelPerDp: Screen.pixelDensity * 0.12

    function dp(x) { return x * pixelPerDp * dpScale }


    //Loader加载不同组件,实现切换页面的功能
    Loader{
        id:myLoader
        anchors.centerIn: parent
        sourceComponent: loginPage
    }
    // 轮播图 SwipeView

    // 登录
    Component{ id:loginPage;  LoginPage {} }
    // 主页
    Component{ id:mainPage; MainPage {} }

    RoundButton {
        text: dark ? "\uF185" : "\uF186"
        flat: true
        font.pixelSize:Math.min(dp(30),parent.height*0.02)
        font.family: "fontello"
        anchors.top: parent.top
        anchors.right: parent.right
        onClicked: dark = !dark
    }
    //提示
    TipPopup { id: tip }
    //分辨率滑块
    MySlider{}
    //记住 user 等输入参数,方便下次登录
    Settings {
        fileName: "settings.ini"
        property alias username: window.userName
        property alias password: window.passWord
        property alias hostName: window.hostName
        property alias port: window.port
        property alias dark: window.dark
    }


    // ----------- 系统自带键盘,默认很宽,很高。而且不好切换语言 ------------------------
    function getwidth(w,h,v){
        var width=0;var tmp=v
        if(w>tmp)
            if(h>tmp) width=tmp; else width=h;
        else
            if(w>tmp) width=tmp; else width=w;
        return width
    }
    InputPanel {
        id: inputPanel
        z: 99
        x: (window.width-inputPanel.width)*0.5
        y: window.height
        width:getwidth(window.width,window.height,dp(1100))

        states: State {
            name: "visible"
            when: inputPanel.active
            PropertyChanges {
                target: inputPanel
                y: window.height - inputPanel.height
            }
        }
        transitions: Transition {
            from: ""
            to: "visible"
            reversible: true
            ParallelAnimation {
                NumberAnimation {
                    properties: "y"
                    duration: 100
                    easing.type: Easing.InOutQuad
                }
            }
        }
    }
}

登录页 Pages/LoginPage.qml

import QtQuick 2.14
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.14

import "Comp"

Item {
    width: window.width;
    height: window.height
    anchors.centerIn: parent
    property string device_id: "98323fa12432c923c"

    Component.onCompleted: window._title="账户登录"
    Component{ id:comp_login; Login{ id: login_column } }
    Component{ id:compIpPort; Server{ id: body } }

    // Logo
    Item {
        id:logo_rect
        width: logo.width;
        height: parent.height*0.5
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: parent.top

        Image {
            id:logo
            width: logo.height*5
            height:getwidth(window.width*0.15,window.height*0.15,dp(120))
            anchors.centerIn: parent
            fillMode: Image.PreserveAspectFit
            source: "qrc:/imgs/logo.png"
        }
    }

    // login 或者 ip/port
    Loader{
        id: bodyLoader
        anchors.centerIn: parent
        sourceComponent: comp_login
        width: logo.width*1.15
        height: logo.height*3
    }

    // dev id
    Label {
        id: devid
        font.pixelSize: Math.min(window.height*0.025, dp(30))
        text: "设备 ID: "+device_id
        anchors.bottom: copyright_rect.top
        anchors.horizontalCenter: parent.horizontalCenter
    }

    // Footer
    Rectangle {
        id: copyright_rect
        width: parent.width; height: Math.min(dp(250),window.height*0.15)
        anchors.bottom: parent.bottom
        color:"transparent"

        Label {
            id: copyright
            font.pixelSize: Math.min(window.height*0.026, dp(28))
            text: "版权所有  XXX科技公司"
            anchors.bottom: copyright2.top
            anchors.bottomMargin: Math.min(dp(10),window.height*0.005)
            anchors.horizontalCenter: parent.horizontalCenter
        }
        Label {
            id: copyright2
            font.pixelSize: Math.min(window.height*0.022, dp(25))
            text: "Copyright © 2010-"+ Qt.formatDateTime(new Date(), "yyyy") +" XXX Technology Co., LTD."
            anchors.bottom: parent.bottom
            anchors.bottomMargin: Math.min(dp(60),window.height*0.04)
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

登录后进入主界面 Pages/MainPage.qml

import QtQuick 2.14
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3

Rectangle {
    id:mainPage
    width: window.width
    height: window.height
    color:"transparent"

    Component.onCompleted: {window._title="主页"; timer.start()}

    //当前日期时间
    function currentDateTime(){
        return Qt.formatDateTime(new Date(), "  hh:mm:ss\nM月d日 ddd");
    }
    //定时器
    Timer{
        id: timer
        repeat: true   //重复
        interval: 1000 //间隔(单位毫秒)
        triggeredOnStart:true //启动时立即触发,而不是等待 1 秒后触发
        onTriggered:  textDateTime.text = currentDateTime();
    }

    //显示系统当前时间
    Label {
        id: textDateTime
        text: currentDateTime();
        anchors.centerIn: parent
    }

    Button {
        text: "退出登录"
        anchors.top: textDateTime.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        onClicked:myLoader.sourceComponent = loginPage
    }
}

下面是"组合控件"
Pages/Comp/Login.qml

import QtQuick 2.14
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3

import "../Base"

Item {
    //输入框: 用户名
    MyTextField {
        id: unInput
        width: parent.width
        icon: "\uE80b"
        text: userName
        showClearButton: true
        placeholderText: "请输入账号"
        onAccepted: pwInput.focus = true
    }

    //输入框: 密码
    MyTextField {
        id: pwInput
        width: parent.width
        anchors.top: unInput.bottom        
        anchors.topMargin: Math.min(dp(10),parent.height*0.005)
        icon: pwInput.passwordVisible? "\uE820": "\uE81F"
        passwordMode: true
        showClearButton: true
        text: passWord
        placeholderText: "请输入密码"
        onAccepted: signInButton.focus = true
    }

    // 登录按钮
    Button {
        id: signInButton
        anchors.top: pwInput.bottom
        anchors.topMargin: Math.min(dp(50),parent.height*0.03)
        width:pwInput.width;  height: unInput.height*1.1
        anchors.horizontalCenter: parent.horizontalCenter
        highlighted: true
        enabled: (unInput.text && pwInput.text)? true:false
        text: "登  录"
        //font.pixelSize:Math.min(dp(40),window.height*0.03)
        Material.background: "Green"
        onClicked: login.signIn(hostName,port,unInput.text, pwInput.text)  // 切换显示主页面// c++ 信号槽

        Connections{
            target: login
            onErr: tip.show(err,"red",5000) //登录错误
            onSuccessed:{ //登录成功
                userName = unInput.text
                passWord = pwInput.text
                myLoader.sourceComponent = mainPage
                bodyLoader.sourceComponent=undefined //卸载
                tip.show(data, "green",500)
            }
        }
    }

    // 设置服务端 ip/port
    RoundButton {
        id: roundbtn
        text: "\uE839"
        flat: true
        font.family: "fontello"
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: signInButton.bottom
        onClicked: bodyLoader.sourceComponent = compIpPort
    }
}

Pages/Comp/Server.qml

import QtQuick 2.14
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3

import "../Base"

Item {
    //
    MyTextField {
        id: ipInput
        width: parent.width
        icon: "\uF108"
        text: hostName
        placeholderText: "服务器 IP"
        inputHint: Qt.ImhDigitsOnly
        validator: RegExpValidator{regExp:/((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))/}
        showClearButton: true
        onAccepted: portInput.focus = true
    }

    //
    MyTextField {
        id: portInput
        width: parent.width
        anchors.top: ipInput.bottom
        anchors.topMargin: dp(10)
        icon: "\uF288"
        text: port
        placeholderText: "服务器端口"
        inputHint: Qt.ImhDigitsOnly
        validator: RegExpValidator{regExp:/^([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/}
        showClearButton: true
        onAccepted: pop_sure.focus = true
    }

    Row {
        spacing: dp(10)
        anchors.top: portInput.bottom
        anchors.topMargin: dp(50)
        anchors.horizontalCenter: parent.horizontalCenter
        Button {
            text: "取消"
            //font.pixelSize:Math.min(dp(40),window.height*0.03)
            width:pop_sure.width
            height: ipInput.height
            onClicked: {
                ipInput.text = hostName
                portInput.text = port
                bodyLoader.sourceComponent = comp_login
            }
        }
        Button {
            id:pop_sure
            width:ipInput.width*0.5
            height: ipInput.height
            highlighted: true
            text: "确认"
            //font.pixelSize:Math.min(dp(40),window.height*0.03)
            Material.background: "Green"
            onClicked: {
                hostName = ipInput.text
                port = parseInt(portInput.text)
                bodyLoader.sourceComponent = comp_login
            }
        }
    }
}

下面是自定义的基础控件 (首字母大写,才能在其它 qml 文件中 import 引入)
Pages/Base/MyTextField.qml (带 icon 和清除按钮的输入框)

import QtQuick 2.14
import QtQuick.Controls 2.5

TextField {
    property bool showClearButton: false
    property bool passwordMode: false
    property bool passwordVisible: false
    property int inputHint: Qt.ImhLatinOnly | Qt.ImhPreferLowercase | Qt.ImhNoAutoUppercase

    property string icon: ""
    inputMethodHints: inputHint
    focus: true
    selectByMouse: true
    font.pixelSize:Math.min(dp(45),parent.height*0.1)
    horizontalAlignment: Text.AlignHCenter
    leftPadding: icon ? leftIcon.width*0.8 : dp(15)
    rightPadding: clsBtn.text ? clsBtn.width : dp(5)
    echoMode: (passwordMode && !passwordVisible) ? TextInput.Password : TextInput.Normal
    passwordMaskDelay: 500

    onPressed: inputPanel.visible = true; //当选择输入框的时候才显示键盘

    // left icon
    Label {
        id: leftIcon
        width: icon ? parent.height : 0; height: parent.height
        text: icon
        color:parent.text ? "Green" : "DarkGray"
        font {family: "fontello"; pixelSize: parent.height*0.6}
        verticalAlignment: Text.AlignVCenter
        leftPadding: dp(5)
    }

    //清除btn
    RoundButton {
        id: clsBtn
        focusPolicy: Qt.NoFocus
        visible: (showClearButton && parent.activeFocus && parent.text)
        anchors.right: parent.right
        anchors.rightMargin: dp(10)
        anchors.verticalCenter: parent.verticalCenter
        width: Math.min(dp(45),parent.width*0.6); height:  Math.min(dp(45),parent.height*0.4)
        flat: true
        text: showClearButton ? "\uE811" : ""
        font {family: "fontello"; pixelSize: Math.min(dp(45),parent.height*0.4)}
        onClicked:  if(showClearButton) clear();
    }

    //右侧:密码可见btn
    RoundButton {
        id: eyeItem
        focusPolicy: Qt.NoFocus
        visible: (passwordMode && parent.activeFocus && parent.text)
        width:  Math.min(dp(45),parent.width*0.6); height: Math.min(dp(45),parent.height*0.4)
        anchors.right: clsBtn.left
        anchors.rightMargin: dp(5)
        anchors.verticalCenter: parent.verticalCenter

        flat: true
        font {family: "fontello"; pixelSize:  Math.min(dp(45),parent.height*0.4)}
        text: (passwordMode && passwordVisible) ? "\uE823" :
                                                  (passwordMode && !passwordVisible) ? "\uE822" : ""
        onClicked: {
            if(passwordMode)
                passwordVisible = !passwordVisible
        }
    }
}

Pages/Base/TipPopup.qml (提示语弹框, 用于程序出错或正确时给予提示)

import QtQuick 2.14
import QtQuick.Controls 2.5

Popup {
    property bool isQuit: false
    property bool isBusy: false
    function show(msg, color,timeout) {
        tipText.text = msg
        tipBg.color = color
        tipTimer.interval=timeout
        open()
        tipTimer.start()
    }
    function setBusy(bsy) {
        isBusy = bsy
        if(bsy)
            open()
        else
            close()
    }

    anchors.centerIn: parent
    padding: dp(20)
    contentWidth: isBusy ? 40 : tipText.width
    contentHeight: isBusy ? 40 : tipText.height
    dim: false
    modal: isBusy
    closePolicy: Popup.NoAutoClose
    background: Rectangle {
        id: tipBg
        radius: dp(5)
        opacity: isBusy ? 0.0 : 0.8
    }
    BusyIndicator {
        id: busyIndicator
        anchors.fill: parent
        visible: isBusy
        running: isBusy
    }
    Text {
        id: tipText
        anchors.centerIn: parent
        visible: !isBusy
        color: "white"
        font.bold: true
        font.family: "Microsoft YaHei UI"
        font.pixelSize: dp(36)
    }
    Timer {
        id: tipTimer
        onTriggered: {
            isQuit = false
            close()
        }
    }
}

滑块用于调分辨率(字号图片大小调节)


Pages/Base/MySlider.qml

import QtQuick.Controls 2.5

Slider {
    from:0.8 ; to:2; stepSize: 0.1
    value:1
    height: 100
    opacity: 0.1
    orientation:Qt.Vertical
    onValueChanged: dpScale = value
    anchors.verticalCenter: parent.verticalCenter
    anchors.right: parent.right
}

最后是 Material 主题全局配置


qml/qml.qrc/qtquickcontrols2.conf (这个文件是新建"Material主题"项目时产生的,需要改字体,颜色)

; This file can be edited to change the style of the application
; Read "Qt Quick Controls 2 Configuration File" for details:
; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html

[Controls]
Style=Material

[Material]
Theme=Dark
Primary=Blue
Accent=DeepPurple
Font\Family=Microsoft Yahei UI

参考:

QT/QML大牛前辈: https://blog.51cto.com/9291927/category19.html/p3
配置 Material 界面风格: https://uzshare.com/view/819927
Qt - Quick控件配置文件(qtquickcontrols2.conf) : https://www.jianshu.com/p/c7d91f15dc25

ps: 不足之处欢迎交流

你可能感兴趣的:(QT,qml,login,c++,qt,数据库)