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