工作中,有的老版本代码是用QtWidgets来实现的,在后期的OTA实现中,由于种种原因,比如用qml来实现更方便等原因,某些功能的开发确用qml语言来实现,这就涉及到了QWidget与Qml文件的相互嵌入,以及交互问题。
笔者遇到的需求是,做一个语音助手,窗口会实时显示自己说的话,以及一些对话内容,比如听故事界面,要实现一种无边框,圆角的对话框:
按照常规做法,因为代码是QtWidget框架写的,所以我应该也用QWidgets来实现就好了,但是考虑到也写效果的实现和最近一直在用qml开发,就尝试用qml语言来实现了。
加载qml文件,有几种方式:
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
其中main.qml 可以以Window作为根元素,这个时候QML就完全拥有了控制权,可以直接设置窗体的标题、尺寸等信息;
QQuickView最常见的用法如下:
QQuickView view;
view.setResizeMode (QQuickView::SizeRootObjectToView);
view.setSource (QUrl("qrc:/main.qml"));
view.show ();
注意:其中qml文件不能以Window作为根元素,最佳选择是使用Item为根元素,否则会警告:
QQuickView does not support using windows as a root item.
If you wish to create your root window from QML, consider using QQmlApplicationEngine instead.
官网自带的说明例子.一般用来在QWidget界面上加载QML界面
QQuickWidget *view = new QQuickWidget;
view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));
view->show();
最主要的是qml的嵌入以及与QWidget的交互
1 通过信号槽的方式创建连接
2 通过QMetaObject::invokeMethod(方法调用
这里直接把代码贴在这里,供大家参考,效果如下
main.cpp
#include
#include "dialog.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include
QT_BEGIN_NAMESPACE
namespace Ui { class Dialog; }
QT_END_NAMESPACE
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
void setTitle(const QString &title);
void setTotal(const long v);
void setCurValue(const long v);
signals:
void sigPalyProgress(qreal v);
public slots:
void onSliderMoved(qreal v);
void onPlay();
void onPause();
private:
Ui::Dialog *ui;
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include
#include
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
ui->setupUi(this);
QUrl source("qrc:/listen.qml");
ui->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView );
ui->quickWidget->setSource(source);
ui->quickWidget->setClearColor(QColor(Qt::transparent));
QQuickItem *item=ui->quickWidget->rootObject();
connect(item,SIGNAL(sliderMoved(qreal)),this,SLOT(onSliderMoved(qreal)));
connect(item,SIGNAL(playClicked()),this,SLOT(onPlay()));
connect(item,SIGNAL(pauseClicked()),this,SLOT(onPause()));
connect(this,SIGNAL(sigPalyProgress(qreal)),item,SIGNAL(updatePlayProgress(qreal)));
if(item)
QMetaObject::invokeMethod(item,"setPlayState");
int inum=0;
QTimer *p = new QTimer;
p->setInterval(10);
connect(p,&QTimer::timeout,[=]()mutable{
inum+=10;
sigPalyProgress(inum);
});
p->start();
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::setTitle(const QString &title)
{
QQuickItem *item=ui->quickWidget->rootObject();
if(item)
QMetaObject::invokeMethod(item,"setTitle",Q_ARG(QVariant,title));
}
void Dialog::setTotal(const long v)
{
QQuickItem *item=ui->quickWidget->rootObject();
if(item)
QMetaObject::invokeMethod(item,"setTotalTime",Q_ARG(long,v));
}
void Dialog::setCurValue(const long v)
{
QQuickItem *item=ui->quickWidget->rootObject();
if(item)
QMetaObject::invokeMethod(item,"setCurTime",Q_ARG(long,v));
}
void Dialog::onSliderMoved(qreal v)
{
qDebug() << "onslidermoved:" << v;
}
void Dialog::onPlay()
{
qDebug() << "onPlay";
}
void Dialog::onPause()
{
qDebug() << "onPause";
}
listen.qml
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.3
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
Item {
id:control
visible: true
width: 736
height: 290
// color: "#00000000"
// modality: Qt.WindowModal
// flags: Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowSystemMenuHint
signal sliderMoved(real v);
signal playClicked();
signal pauseClicked();
signal updatePlayProgress(real v);
property string title: qsTr("《一起来听红楼梦》")
property var totalTimeValue: 20000
property var curTimeValue : 100
function setPlayState()
{
playbtn.source ="qrc:/pause96.png";
}
function setTitle(str)
{
title = str;
}
function setTotalTime(v)
{
totalTimeValue = v;
}
function setCurTime(v)
{
curTimeValue = v;
}
function currentTimeMMSS(time)
{
var sec= Math.floor(time/1000);
var hours=Math.floor(sec/3600);
var minutes=Math.floor((sec-hours*3600)/60);
var seconds=sec-hours*3600-minutes*60;
var hh,mm,ss;
if(hours.toString().length<2)
hh="0"+hours.toString();
else
hh=hours.toString();
if(minutes.toString().length<2)
mm="0"+minutes.toString();
else
mm=minutes.toString();
if(seconds.toString().length<2)
ss="0"+seconds.toString();
else
ss=seconds.toString();
if(hh !== "00")
{
return hh + ":" + mm+":"+ss;
}
else
{
return mm+":"+ss;
}
}
Rectangle {
id:root
anchors.fill: parent
color: "#101A24"
radius: 40
clip:true
Text {
id: name
text: title
anchors.verticalCenter: playbtn.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 48
width: 520
elide: Text.ElideRight
font.pixelSize: 52
font.family: "PingFang SC"
font.weight: Font.Medium;
color: "#FFFFFF";
//lineHeight: 64
}
ParallelAnimation{ //点击动画
id: clickedAnimation
PropertyAnimation{
target: playbtn
property: "opacity"
to: 0.5
//from: 0
duration: 300
}
PropertyAnimation{
target: playbtn
property: "scale"
to: 1.3
//from: 1
duration: 300
}
}
ParallelAnimation{ //取消点击动画
id: releaseAnimation
PropertyAnimation{
target: playbtn
property: "opacity"
//from: 0.5
to: 1
duration: 300
}
PropertyAnimation{
target: playbtn
property: "scale"
//from: 1.3
to: 1
duration: 300
}
}
Image {
id:playbtn
source: "qrc:/play96.png"
anchors.right: parent.right
anchors.rightMargin: 48
anchors.top: parent.top
anchors.topMargin: 48
width:96
height: 96
MouseArea
{
id:mouse
anchors.fill: parent
anchors.margins: -16
onClicked:
{
console.log("clicked")
if(playbtn.source == "qrc:/pause96.png")
{
playbtn.source = "qrc:/play96.png";
pauseClicked();
}
else
{
playbtn.source = "qrc:/pause96.png"
playClicked();
}
}
onPressed:
{
clickedAnimation.start();
}
onReleased: {
releaseAnimation.start()
}
}
}
Slider{
id: slider
anchors.left: parent.left
anchors.leftMargin: 48
anchors.bottom: curTime.top
anchors.bottomMargin: 16
implicitWidth: 640;
implicitHeight: 16;
orientation:Qt.Horizontal
padding: 0
from: 0
value: curTimeValue
to:totalTimeValue
stepSize: 1
onMoved: {
sliderMoved(slider.value)
}
background: Rectangle {
id:bg
implicitWidth: slider.implicitWidth
implicitHeight: slider.implicitHeight
color: "#2D4247"
radius: 16
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: bg.width
height: bg.height
radius: bg.radius
}
}
Rectangle {
y: slider.horizontal ? 0 : slider.visualPosition * parent.height
width: slider.horizontal ? slider.position * parent.width : parent.width
height: slider.horizontal ? parent.height : slider.position * parent.height
// color: "#12CFA7"
opacity: 1
radius:16
LinearGradient { ///--[Mark]
anchors.fill: parent
start: Qt.point(0, 0)
end: Qt.point(parent.width, 0) ///1.横向渐变
//end: Qt.point(0, height) ///2.竖向渐变
//end: Qt.point(width, height) ///3.斜向渐变
gradient: Gradient {
GradientStop { position: 0.0; color: "#618CFF" }
GradientStop { position: 1.0; color: "#FF7A6A" }
}
}
}
}
handle: Item {
x: slider.leftPadding + (slider.horizontal ? slider.visualPosition * (slider.availableWidth - width) : (slider.availableWidth - width) / 2)
y: slider.topPadding + (slider.horizontal ? (slider.availableHeight - height) / 2 : slider.visualPosition * (slider.availableHeight - height))
implicitWidth: 28
implicitHeight: 28
Rectangle{
id:rect_out
anchors.fill: parent
radius: width/2
color:"#ffffff"
}
}
}
Text {
id: curTime
text: currentTimeMMSS(curTimeValue)
color: "#ffffff"
font.pixelSize: 28
anchors.left: parent.left
anchors.leftMargin: 48
anchors.bottom: parent.bottom
anchors.bottomMargin: 48
}
Text {
id: totalTime
text: currentTimeMMSS(totalTimeValue)
color: "#ffffff"
font.pixelSize: 28
anchors.right: parent.right
anchors.rightMargin: 48
anchors.bottom: parent.bottom
anchors.bottomMargin: 48
}
}
Component.onCompleted: {
control.updatePlayProgress.connect(control.setCurTime)
}
Component.onDestruction:{
control.updatePlayProgress.disconnect(control.setCurTime)
}
}