您现在的位置是:网站首页> C/C++
QML总结笔记
- C/C++
- 2022-03-27
- 1899人已阅读
QML总结笔记
QQmlApplicationEngine与QQuickView QQuickWidget区别
布局组件 Row Colomun Grid Flow GridLayout RowLayout ColumnLayout
QtQuick自定义主题以及控件样式指引
系统自带的几种主题风格
Default Style
Fusion Style
Imagine Style
Material Style
Universal Style
其中Imagine Style是使用图片定制风格,图片需要按照指定的命名来放置,具体操作请看文档:
http://doc.qt.io/qt-5/qtquickcontrols2-imagine.html
在c++中使用QQuickStyle
QQuickStyle::setStyle("Material");
具体内容请在帮助索引中搜索 QQuickStyle
QQmlApplicationEngine与QQuickView QQuickWidget区别
QQmlApplicationEngine
QGuiApplication::setApplicationName("Gallery");
QGuiApplication::setOrganizationName("QtProject");
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
engine.load(QUrl("qrc:/gallery.qml"));
return app.exec();
gallery.qml
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Controls.Universal 2.12
import Qt.labs.settings 1.0
ApplicationWindow {
id: window
width: 360
height: 520
visible: true
QQuickView
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView viewer;
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
viewer.setSource(QUrl("qrc:///main.qml"));
viewer.show();
return app.exec();
}
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
Rectangle{
width: 320;
height: 240;
}
QQuickWidget
QQuickWidget的使用(在qml中和QQuickView差不多,在qtwidget中就不一样了)
QQuickWidget *widget= new QQuickWidget;
widget->setSource(QUrl::fromLocalFile("myqmlfile.qml"));
widget->show();
QQmlApplicationEngine搭配window
QQuickView搭配Item
QQuickView显示QML文档,对窗口的控制权(比如设置窗口标题,Icon 窗口大小等在C++代码控制,而使用QQmlApplicationEngine加载Window为根的QML文档,QML文档则拥有窗口的完整控制权,ApplicationWindow派生自Window
anchors定位等设置
anchors group
anchors.top : AnchorLine
anchors.bottom : AnchorLine
anchors.left : AnchorLine
anchors.right : AnchorLine
anchors.horizontalCenter : AnchorLine
anchors.verticalCenter : AnchorLine
anchors.baseline : AnchorLine
anchors.fill : Item
anchors.centerIn : Item
anchors.margins : real
anchors.topMargin : real
anchors.bottomMargin : real
anchors.leftMargin : real
anchors.rightMargin : real
anchors.horizontalCenterOffset : real
anchors.verticalCenterOffset : real
anchors.baselineOffset : real
anchors.alignWhenCentered : bool
如:
Item {
Image {
id: pic
// ...
}
Text {
id: label
anchors.horizontalCenter: pic.horizontalCenter
anchors.top: pic.bottom
anchors.topMargin: 5
// ...
}
}
Qt_QML布局元素学习
Column(列)元素将它的子对象通过顶部对齐的列方式进行排列。spacing属性用来设置每个元素之间的间隔大小。
Row(行)元素将它的子对象从左到右,或者从右到左依次排列,排列方式取决于layoutDirection属性。spacing属性用来设置每个元素之间的间隔大小。
Grid(栅格)元素通过设置rows(行数)和columns(列数)将子对象排列在一个栅格中。可以只限制行数或者列数。如果没有设置它们中的任意一个,栅格元素会自动计算子项目总数来获得配置,例如,设置rows(行数)为3,添加了6个子项目到元素中,那么会自动计算columns(列数)为2。属性flow(流)与layoutDirection(布局方向)用来控制子元素的加入顺序。spacing属性用来控制所有元素之间的间隔。
Flow(流)。通过flow(流)属性和layoutDirection(布局方向)属性来控制流的方向。它能够从头到底的横向布局,也可以从左到右或者从右到左进行布局。作为加入流中的子对象,它们在需要时可以被包装成新的行或者列。为了让一个流可以工作,必须指定一个宽度或者高度,可以通过属性直接设定,或者通过anchor(锚定)布局设置。
Item所有可视元素的基类
大部分通用属性
x y width height 锚点(anchors) 按键处理
z :显示层数
opacity:透明度
clip裁减 true 子不可以超过父元素
state visible states children transitions 等很多属性
So you can write:
Item {
Text {}
Rectangle {}
Timer {}
}
instead of:
Item {
children: [
Text {},
Rectangle {}
]
resources: [
Timer {}
]
}
响应按键
Keys.enabled:true;
Keys.onEscapePressed:Qt.quit();
Keys.onBackPressed:Qt.quit();
Keys.onPressed:{
switch(event.key)
{
case Qt.Key_0:
...
case Qt.Key_9:
event.accepted=true;
keyvalue.text=event.key-Qt.Key_0;
break;
}
}
显示底部Tab
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Controls.Universal 2.12
import Qt.labs.settings 1.0
ApplicationWindow {
visible: true
width: 360
height: 520
title: qsTr("Hello World")
footer: TabBar {
id: bar
height: 48
width: parent.width
currentIndex: 2
TabButton {
text: qsTr("Home");
icon: icon.source="qrc:/man.png";
display: AbstractButton.TextUnderIcon;
onClicked: {
console.log("Home");
}
}
TabButton {
text: qsTr("Discover");
icon: icon.source="qrc:/man.png";
display: AbstractButton.TextUnderIcon;
}
TabButton {
text: qsTr("Activity");
icon: icon.source="qrc:/man.png";
display: AbstractButton.TextUnderIcon;
}
TabButton {
text: qsTr("个人中心");
icon: icon.source="qrc:/man.png";
display: AbstractButton.TextUnderIcon;
}
}
}
QML导入js文件
大致步骤如下:
(1)新建QML应用程序,项目名称为QMLloadjs。
(2)右击项目视图”资源“-》”qml.qrc"下的“/”节点,选择“添加新文件...”项,选择“Qt”下的“JSFile”模板。
(3)创建js文件
js文件代码示例如下
function getRandomNumber() {
return Math.random()*360;
}
(4)右击项目视图”资源“-》”qml.qrc"下的“/”节点,选择“添加新文件...”项,新建RotateRect.qml文件,示例代码 如下
import QtQuick 2.0
import "demojs.js" as Logic //导入js文件
Rectangle{
id:rect
width: 60
height: 60
gradient: Gradient{
GradientStop{position: 0.0;color:"yellow"}
GradientStop{position: 0.33;color: "blue"}
GradientStop{position: 1.0;color:"aqua"}
}
function getRandomNumber(){
return Math.random()*360;
}
Behavior on rotation {
RotationAnimation{
direction: RotationAnimation.Clockwise
}
}
MouseArea{
anchors.fill: parent
onClicked: rect.rotation=Logic.getRandomNumber();//调用js函数
}
}
(5)MainForm.ui.qml示例代码如下
import QtQuick 2.6
Rectangle {
property alias mouseArea: mouseArea
property alias textEdit: textEdit
width: 360
height: 360
MouseArea {
id: mouseArea
anchors.fill: parent
}
TextEdit {
id: textEdit
visible: false
}
RotateRect{
x:50
y:50
}
}
Connections创建一个对象信号接收
然而, 以下几种情况则无法通过"on<Signal>"来实现:
1.针对某个信号需要多个处理时,也就是有多个槽关联到同一个信号上。
2.在信号的发送者的范围外(这里可以理解为发送者的类定义之外)创建连接。
3.连接的目标不在QML中。
这时就该Connections登场咯
先举个例子,上面的代码用Connections就可以像下面这样写:
MouseArea {
Connections {
onClicked: foo(...)
}
}
而一般来说,我们都会这么写:
MouseArea {
id: area
}
...
Connections {
target: area
onClicked: foo(...)
}
组件加载完毕事件
Component.onCompleted:{
console.log("load over");
}
Component自定义组件
在文件中定义组件
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
Rectangle {
id: compontRect
color: Qt.rgba(0.8, 0.4, 0.4, 1.0)
implicitWidth: 200
implicitHeight: 50
property var currentObject: ''
signal deleteThis(var obj)
// 设置文字的内容
function setCurrentText(textName) {
interText.text = textName
}
Text {
id: interText
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: qsTr("text")
}
Button {
anchors.margins: 5
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
text: '删除'
onClicked: {
compontRect.deleteThis(compontRect)
}
}
Component.onCompleted: {
compontRect.currentObject = parent
}
}
使用Loader加载/删除组件
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
Window {
width: 800
height: 600
visible: true
Rectangle {
id: mainRect
anchors.fill: parent
Loader {
id: loader1
sourceComponent: itemCompont
anchors.top: parent.top
anchors.topMargin: 10
width: mainRect.width
height: 50
function onDisDeleteThis() {
loader1.sourceComponent = undefined
}
onLoaded: {
item.color = 'red'
loader1.item.deleteThis.connect(loader1.onDisDeleteThis) //红色部分即时对象
}
}
Loader {
id: loader2
source: 'qrc:/QML/TestCompont.qml'
anchors.top: loader1.bottom
anchors.topMargin: 10
width: mainRect.width
height: 50
function onDisDeleteThis() {
loader2.source = ''
}
onLoaded: {
loader2.item.deleteThis.connect(loader2.onDisDeleteThis) ////红色部分即时对象
}
}
Component {
id: itemCompont
Rectangle {
id: compontRect
color: 'blue'
implicitWidth: 200
implicitHeight: 50
signal deleteThis()
Text {
id: interText
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: qsTr("text")
}
Button {
anchors.margins: 5
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
text: '删除'
onClicked: {
compontRect.deleteThis()
}
}
}
}
}
}
使用JavaScript中的语句加载/删除组件
QML支持使用JavaScript动态创建/销毁对象,有两种方式动态创建对象:
使用Qt.createComponent()动态创建一个组件对象,然后使用Component的createObject()方法创建对象。
使用Qt.createQmlObject()从一个QML字符串直接创建一个对象。
如果QML文件中嵌入Component,可以直接使用这个组件的createObject()方法创建组件;使用Component的destroy()方法删除已经创建的组件。destroy()方法可以指定一个延时,不过不指定,他会在适当的时候删除。
下面是一个简单的示例:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
Window {
width: 800
height: 600
visible: true
Rectangle {
id: mainRect
anchors.fill: parent
property var mainRectComponent: null
Column {
id: mainColumn
spacing: 5
width: parent.width
property var count: 0
function deleteItems(object) {
object.destroy()
}
function createItem() {
var color = 'red'
if (mainColumn.count % 3 === 1)
color = 'yellow'
else if (mainColumn.count % 3 === 2)
color = 'blue'
mainColumn.count++
// 创建一个组件
var obj = itemCompont.createObject(mainColumn, {"color": color, "width": mainRect.width})
//obj.setCurentObject(obj)
obj.setCurrentText('Component' + mainColumn.count.toString())
obj.deleteThis.connect(mainColumn.deleteItems)
// 创建文件中的组件
var obj2 = mainRect.mainRectComponent.createObject(mainColumn,
{'color': Qt.rgba(0.4, 0.8, 0.6, 1.0)
,'width': mainRect.width})
obj2.setCurrentText('Component' + mainColumn.count.toString() + ', From File TestComponent')
obj2.deleteThis.connect(mainColumn.deleteItems)
}
}
Button {
anchors.top: mainColumn.bottom
anchors.topMargin: 10
anchors.right: mainRect.right
anchors.rightMargin: 10
text: '添加'
onClicked: {
mainColumn.createItem()
}
}
Component.onCompleted: {
if (mainRectComponent == null)
mainRectComponent = mainRectComponent = Qt.createComponent('qrc:/QML/TestCompont.qml')
}
Component {
id: itemCompont
Rectangle {
id: compontRect
color: 'blue'
implicitWidth: 200
implicitHeight: 50
property var currentObject: ''
signal deleteThis(var obj)
// 设置文字的内容
function setCurrentText(textName) {
interText.text = textName
}
Text {
id: interText
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: qsTr("text")
}
Button {
anchors.margins: 5
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
text: '删除'
onClicked: {
compontRect.deleteThis(compontRect)
}
}
Component.onCompleted: {
compontRect.currentObject = parent
}
}
}
}
}
MouseArea组件
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton)
parent.color = 'blue';
else
parent.color = 'red';
}
}
MouseArea { acceptedButtons: Qt.LeftButton | Qt.RightButton }
MouseArea { acceptedButtons: Qt.AllButtons }
cursorShape : Qt::CursorShape
Qt.ArrowCursor
Qt.UpArrowCursor
Qt.CrossCursor
Qt.WaitCursor
Qt.IBeamCursor
Qt.SizeVerCursor
Qt.SizeHorCursor
Qt.SizeBDiagCursor
Qt.SizeFDiagCursor
Qt.SizeAllCursor
Qt.BlankCursor
Qt.SplitVCursor
Qt.SplitHCursor
Qt.PointingHandCursor
Qt.ForbiddenCursor
Qt.WhatsThisCursor
Qt.BusyCursor
Qt.OpenHandCursor
Qt.ClosedHandCursor
Qt.DragCopyCursor
Qt.DragMoveCursor
Qt.DragLinkCursor
drag.target : Item
drag.active : bool
drag.axis : enumeration
drag.minimumX : real
drag.maximumX : real
drag.minimumY : real
drag.maximumY : real
drag.filterChildren : bool
drag.threshold : real
drag指定对象之后,如果对象使用了anchor,会导致drag无效
下面创建一个可拖拽的rectangle,希望实现鼠标拖动黄色的矩形在绿色矩形中平移
Rectangle {
id:background
width:600
height:600
color: "green"
Rectangle {
id:dragElement
color: "yellow"
width:300
height: 200
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
drag.target: parent
onPressed:dragElement.anchors.centerIn = undefined //按下时候使anchor无效
}
}
}
PinchArea 触摸事件
pinch.target : Item
pinch.active : bool
pinch.minimumScale : real
pinch.maximumScale : real
pinch.minimumRotation : real
pinch.maximumRotation : real
pinch.dragAxis : enumeration
pinch.minimumX : real
pinch.maximumX : real
pinch.minimumY : real
pinch.maximumY : real
信号
PinchArea 有三个信号:onPinchStarted() 、 onPinchUpdated() 、 onPinchFinished() 。它们都有一个名为 pinch 的参数,类型是 PinchEvent 。为了有效响应这些信号,必须了解 PinchEvent 类型,我们先介绍它。
PinchEvent 具有下列属性:
accepted ,在 onPinchStarted() 信号处理器中设置为 true 表明你要响应 PinchEvent ,Qt 会持续发给你更新事件;设置为 false ,Qt 就不会再发 PinchEvent 事件给你了。
angle ,表示最近两个触点之间的角度, previousAngle 是上一次事件的角度, rotation 是从捏拉手势开始到当前事件的总的旋转角度。
scale ,表示最近两个触点之间的缩放系数, previousScale 是上一次事件的缩放系数。
center ,两个触点的中心点, previousCenter 是上一次事件的中心点, startCenter 是事件开始时的中心点。
point1 , point2 保存当前触点的位置, startPoint1 , startPoint2 保存第二个触点按下时两个触点的位置。
pointCount 保存到现在为止的触点总数。
oPinchStarted() 信号在第一次识别到捏拉手势时发出,如果你要处理它,那就要将其设置为 true 。然后就可以通过 pinch 参数来设置要变换的 Item 了。
当你在 onPinchStarted() 的信号处理器中接受了 TouchEvent 事件,那么 Qt 就会不断的发送新事件给你, onPinchUpdated() 信号就会不断的发射,你可以在它的信号处理器中通过 pinch 参数,撷取你需要的值来更新你的 Item 。
onPinchFinished() 信号在用户手指离开屏幕时触发。
怎样使用
介绍了 PinchArea 和 PinchEvent,是时候看看怎么使用它们了。
想使用 PinchArea 来变换一个 Item,有两个办法:
设定 target 属性,将其指向要变换的 Item ,然后 PinchArea 就会在合适的时候帮你变换它。
处理 onPinchStarted() / onPinchUpdated() / onPinchFinished() 信号,在信号处理器中变换目标 Item 。这种方式更灵活,你甚至可以同时处理多个 Item 。
选定一种方式后,你可能还要配置 PinchArea.pinch 属性,给不同的参数设置合理的值,比方说最大可以放大到多少倍。
缩放与旋转实例
pinch.target方式
import QtQuick 2.0
Rectangle {
width: 360;
height: 360;
focus: true;
Rectangle {
width: 100;
height: 100;
color: "blue";
id: transformRect;
anchors.centerIn: parent;
}
PinchArea {
anchors.fill: parent
pinch.maximumScale: 20;
pinch.minimumScale: 0.2;
pinch.minimumRotation: 0;
pinch.maximumRotation: 90;
pinch.target: transformRect;
}
}
代码很简单,初始化了最小、最大缩放系数,最小、最大旋转角度,然后将 pinch.target 指向 id 为 transformRect 的蓝色矩形。于是,一切都正常运转,两指捏拉之间,缩放与旋转效果就出来了。
使用 pinch.target 这种方式,你什么都不用关心,甚至不需要弄明白 pinch 属性到底是什么含义,就可以得到一个不错的变换效果, Qt Quick 默认帮你处理所有的事情。
下面看看使用信号的方式。
使用信号
使用 onPinchStarted() / onPinchUpdated() / onPinchFinished() 要稍微麻烦一些,你必须要了解 PinchEvent 每个参数的含义,自己设计变换策略。不过好处是,七十二般变化都由你控制
import QtQuick 2.0
Rectangle {
width: 360;
height: 360;
focus: true;
Rectangle {
width: 100;
height: 100;
color: "blue";
id: transformRect;
anchors.centerIn: parent;
}
PinchArea {
anchors.fill: parent
pinch.maximumScale: 20;
pinch.minimumScale: 0.2;
pinch.minimumRotation: 0;
pinch.maximumRotation: 90;
onPinchStarted: {
pinch.accepted = true;
}
onPinchUpdated: {
transformRect.scale *= pinch.scale;
transformRect.rotation += pinch.rotation;
}
onPinchFinished: {
transformRect.scale *= pinch.scale;
transformRect.rotation += pinch.rotation;
}
}
}
代码大部分都和 pinch.target 方式一样,只是去掉了 "pinch.target: transformRect" 语句,改用信号处理器。代码很直接,不再解释了。
MultiPointTouchArea 多点触控
import QtQuick 2.0
Rectangle {
width: 400; height: 400
MultiPointTouchArea {
anchors.fill: parent
touchPoints: [
TouchPoint { id: point1 },
TouchPoint { id: point2 }
]
}
Rectangle {
width: 30; height: 30
color: "green"
x: point1.x
y: point1.y
}
Rectangle {
width: 30; height: 30
color: "yellow"
x: point2.x
y: point2.y
}
}
布局组件 Row Colomun Grid Flow GridLayout RowLayout ColumnLayout
LayOut和原始的区别,LayOut会自动调节Item的大小来适应界面大小的变化
ComboBox
ComboBox {
width: 200
model: [ "Banana", "Apple", "Coconut" ]
}
ComboBox {
currentIndex: 2
model: ListModel {
id: cbItems
ListElement { text: "Banana"; color: "Yellow" }
ListElement { text: "Apple"; color: "Green" }
ListElement { text: "Coconut"; color: "Brown" }
}
width: 200
onCurrentIndexChanged: console.debug(cbItems.get(currentIndex).text + ", " + cbItems.get(currentIndex).color)
}
TabView
Properties
contentItem : Item
count : int
currentIndex : int
frameVisible : bool
tabPosition : int
tabsVisible : bool
Methods
Tab addTab(string title, Component component)
Tab getTab(int index)
Tab insertTab(int index, string title, Component component)
void moveTab(int from, int to)
void removeTab(int index)
TabView {
Tab {
title: "Red"
Rectangle { color: "red" }
}
Tab {
title: "Blue"
Rectangle { color: "blue" }
}
Tab {
title: "Green"
Rectangle { color: "green" }
}
}
ListView使用
修改数据可以
listview.model.setProperty(5,"name","xunen");
代替数据可以用
listview.model.set(0,{"name":"bbb","old":42});
添加数据
listview.model.append({"name":"bbb","old":42});
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
//ListView 显示一个条目列表
//model 条目对应的数据
//Delegate 条目的外观
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
//数据显示
Component{
id:phoneModel
ListModel{
//id:phoneModel
ListElement{
name:"iphone 3GS"
cost:"1000"
manufacturer:"Apple"
}
ListElement{
name:"iphone 4"
cost:"1800"
manufacturer:"Apple"
}
ListElement{
name:"iphone 4S"
cost:"2300"
manufacturer:"Apple"
}
ListElement{
name:"iphone 5"
cost:"4900"
manufacturer:"Apple"
}
ListElement{
name:"B199"
cost:"1590"
manufacturer:"HuaWei"
}
ListElement{
name:"MI 2S"
cost:"1999"
manufacturer:"Xiao Mi"
}
ListElement{
name:"GALAXY S5"
cost:"4699"
manufacturer:"Samsung"
}
}
}
//表显示
Component{
id:phoneDelegate
Item{
id:wrapper
width: parent.width
height: 30
MouseArea{
anchors.fill: parent
onClicked: {
wrapper.ListView.view.currentIndex = index
console.log("index = "+index)
}
}
RowLayout{
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Text {
id:coll
text: name
color: wrapper.ListView.isCurrentItem?"red":"black"
font.pixelSize: wrapper.ListView.isCurrentItem?22:18
Layout.preferredWidth: 80
}
Text {
text: cost
color: wrapper.ListView.isCurrentItem?"red":"black"
font.pixelSize: wrapper.ListView.isCurrentItem?22:18
Layout.preferredWidth: 80
}
Text {
text: manufacturer
color: wrapper.ListView.isCurrentItem?"red":"black"
font.pixelSize: wrapper.ListView.isCurrentItem?22:18
Layout.fillWidth: true
}
}
}
}
//表头
Component{
id:headerview
Item{
width: parent.width
height: 30
RowLayout{
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Text{
text:"Name"
font.bold: true
font.pixelSize: 20
Layout.preferredWidth: 120
}
Text{
text: "cost"
font.bold: true
font.pixelSize: 20
Layout.preferredWidth: 80
}
Text{
text:"manufacture"
font.bold: true
font.pixelSize: 20
Layout.fillWidth: true
}
}
}
}
//list显示数据
ListView{
id:listView
anchors.fill: parent
delegate: phoneDelegate
model:phoneModel.createObject(listView)
header: headerview
focus: true
highlight: Rectangle{
color: "lightblue"
}
}
}
QML中ListView的几种数据模型
方式一:ListModel:
ListModel是一个简单的ListElement容器,每个容器都包含数据角色。其中内容可以动态定义,也可以在QML中显式定义。
ListModel {
id: m_model
ListElement {
name: "Bill Smith"
number: "555 3264"
color1: "red"
}
ListElement {
name: "John Brown"
number: "555 8426"
color1: "green"
}
ListElement {
name: "Sam Wise"
number: "555 0473"
color1: "blue"
}
}
ListView {
width: 100
height: 100
model: m_model
delegate: Text{
color: color1
text: name+":"+number
}
}
方式二:ObjectModel
当ObjectModel被用于视图的时候,视图不再需要委托,因为对象模型已经包含了可视化的委托(项)
ObjectModel {
id: itemModel
Rectangle {
height: 20; width: 80;
Text {
color: "red"
text: "Bill Smith" + ":" + "555 3264"
}
}
Rectangle {
height: 20; width: 80;
Text{
color: "green"
text: "John Brown" + ":" + "555 8426"
}
}
Rectangle {
height: 20; width: 80;
Text {
color: "blue"
text: "Sam Wise"+":" + "555 0473"
}
}
}
选择该数据模型:
ListView{
width: 100
height: 100
model: itemModel
}
方式三:C++中的QStringList作为数据模型
在main.cpp中
QStringList a;
a << "Bill Smith" << "John Brown" << "Sam Wise"; //QStringList model
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("name1", QVariant::fromValue(a));
在qml文件中:
ListView{
width: 100
height: 100
model: name1
delegate: Text{
text: modelData
}
}
方式四:C++中QList作为数据源
在main.cpp中:
自定义头文件:dataobject.h
#ifndef DATAOBJECT_H
#define DATAOBJECT_H
#include <QObject>
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QString number READ number WRITE setNumber NOTIFY numberChanged)
public:
DataObject(QObject *parent = nullptr);
DataObject(const QString &name,const QString &color,const QString &number,QObject *parent=nullptr);
QString name()const;
void setName(const QString &name);
QString color()const;
void setColor(const QString &color);
QString number()const;
void setNumber(const QString &number);
signals:
void nameChanged();
void colorChanged();
void numberChanged();
private:
QString m_name;
QString m_color;
QString m_number;
};
#endif // DATAOBJECT_H
源文件:
#include "dataobject.h"
DataObject::DataObject(QObject *parent) : QObject(parent)
{
}
DataObject::DataObject(const QString &name, const QString &color, const QString &number, QObject *parent)
:QObject(parent),m_name(name),m_color(color),m_number(number)
{
}
QString DataObject::name() const
{
return m_name;
}
void DataObject::setName(const QString &name)
{
if(name!=m_name)
m_name=name;
emit nameChanged();
}
QString DataObject::color() const
{
return m_color;
}
void DataObject::setColor(const QString &color)
{
if(color!=m_color)
m_color=color;
emit colorChanged();
}
QString DataObject::number() const
{
return m_number;
}
void DataObject::setNumber(const QString &number)
{
if(number!=m_number)
m_number=number;
emit numberChanged();
}
在main.cpp中
QList<QObject*> dataList; //QObject model
dataList << new DataObject("Bill Smith","red","555 3264") << new DataObject("John Brown","green","555 8426")
<< new DataObject("Sam Wise","blue","555 0473");
QQmlApplicationEngine engine; //之前有定义的,这里就不用重复定义了
engine.rootContext()->setContextProperty("myObjectModel", QVariant::fromValue(dataList));
qml文件中:
ListView{
width: 100
height: 100
model: myObjectModel
delegate: Text{
color: model.modelData.color
text: name+":"+number}
}
方式五:自定义类AbstractListModel
自定义头文件abstractlistmodel.h
#ifndef ABSTRACTLISTMODEL_H
#define ABSTRACTLISTMODEL_H
#include <QAbstractListModel>
#include<QStringList>
class AbstractList //抽象数据列表类
{
public:
AbstractList(const QString &name,const QString &color,const QString &number);
QString name() const;
QString color() const;
QString number() const;
private:
QString m_name;
QString m_color;
QString m_number;
};
class AbstractListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum AbstractListRoles{
NameRole=Qt::UserRole+1,
ColorRole,
NumberRole
}; //定义抽象类成员角色
AbstractListModel(QObject *parent=nullptr);
void addList(const AbstractList &list);
int rowCount(const QModelIndex &parent=QModelIndex()) const; //返回给定父项行数
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;//返回索引所在项给定角色的存储数据
protected:
QHash<int,QByteArray> roleNames() const; //返回模型的角色名
private:
QList<AbstractList> m_abstractList; //抽象列表类容器
};
#endif // ABSTRACTLISTMODEL_H
源文件:
#include "abstractlistmodel.h"
AbstractListModel::AbstractListModel(QObject *parent)
:QAbstractListModel(parent)
{
}
void AbstractListModel::addList(const AbstractList &list)
{
beginInsertRows(QModelIndex(),rowCount(),rowCount());
m_abstractList.append(list);
// m_abstractList<<list;
endInsertRows();
}
int AbstractListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_abstractList.count();
}
QVariant AbstractListModel::data(const QModelIndex &index, int role) const
{
if(index.row()<0||index.row()>=m_abstractList.count())
return QVariant();
const AbstractList &abstractList=m_abstractList[index.row()];
if(role==AbstractListRoles::NameRole)
return abstractList.name();
else if(role==AbstractListRoles::ColorRole)
return abstractList.color();
else if(role==AbstractListRoles::NumberRole)
return abstractList.number();
return QVariant();
}
QHash<int, QByteArray> AbstractListModel::roleNames() const
{
QHash<int,QByteArray> roles;
//use operator[]
// roles[AbstractListRoles::NameRole]="name"; //定义对应角色值
// roles[AbstractListRoles::ColorRole]="color1";
// roles[AbstractListRoles::NumberRole]="number";
//use insert
roles.insert(AbstractListRoles::NameRole,"name");
roles.insert(AbstractListRoles::ColorRole,"color1");
roles.insert(AbstractListRoles::NumberRole,"number");
return roles;
}
AbstractList::AbstractList(const QString &name, const QString &color, const QString &number)
:m_name(name),m_color(color),m_number(number)
{
}
QString AbstractList::name() const
{
return m_name;
}
QString AbstractList::color() const
{
return m_color;
}
QString AbstractList::number() const
{
return m_number;
}
main.cpp中
AbstractListModel listmodel;
listmodel.addList(AbstractList("Bill Smith","red","555 3264"));
listmodel.addList(AbstractList("John Brown","green","555 8426"));
listmodel.addList(AbstractList("Sam Wise","blue","555 0473"));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &listmodel);
qml文件中:
ListView{
width: 100
height: 100
model: myModel
delegate: Text {
color: color1
text: name+":"+number}
}
}
main.qml文件的所有代码:
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQml.Models 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListModel {
id: m_model
ListElement {
name: "Bill Smith"
number: "555 3264"
color1: "red"
}
ListElement {
name: "John Brown"
number: "555 8426"
color1: "green"
}
ListElement {
name: "Sam Wise"
number: "555 0473"
color1: "blue"
}
}
ObjectModel {
id: itemModel
Rectangle {
height: 20; width: 80;
Text {
color: "red"
text: "Bill Smith" + ":" + "555 3264"
}
}
Rectangle {
height: 20; width: 80;
Text{
color: "green"
text: "John Brown" + ":" + "555 8426"
}
}
Rectangle {
height: 20; width: 80;
Text {
color: "blue"
text: "Sam Wise"+":" + "555 0473"
}
}
}
Column{
spacing: 10
ListView{
width: 100
height: 100
model: m_model
delegate: Text{
color: color1
text: name+":"+number
}
}
ListView{
width: 100
height: 100
model: itemModel
}
ListView{
width: 100
height: 100
model: name1
delegate: Text{
text: modelData
}
}
ListView{
width: 100
height: 100
model: myObjectModel
delegate: Text{
color: model.modelData.color
text: name+":"+number}
}
ListView{
width: 100
height: 100
model: myModel
delegate: Text {
color: color1
text: name+":"+number}
}
}
}
main.cpp文件:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include<QQmlContext>
#include"dataobject.h"
#include"abstractlistmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QStringList a;
a << "Bill Smith" << "John Brown" << "Sam Wise"; //QStringList model
QList<QObject*> dataList; //QObject model
dataList << new DataObject("Bill Smith","red","555 3264") << new DataObject("John Brown","green","555 8426")
<< new DataObject("Sam Wise","blue","555 0473");
AbstractListModel listmodel;
listmodel.addList(AbstractList("Bill Smith","red","555 3264"));
listmodel.addList(AbstractList("John Brown","green","555 8426"));
listmodel.addList(AbstractList("Sam Wise","blue","555 0473"));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("name1", QVariant::fromValue(a));
engine.rootContext()->setContextProperty("myObjectModel", QVariant::fromValue(dataList));
engine.rootContext()->setContextProperty("myModel", &listmodel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Qt Quick之ListView下拉刷新数据
Qt Quick里的ListView,本身是Flickable的派生类,当你用鼠标拖曳或者手指触摸(触摸屏)时,会产生flickStarted和flickEnded两个信号,利用这两个信号,就可以实现下拉刷新数据,当然上拉刷新也是可以的。
创建一个Qt Quick App项目,添加dynamicModel.h和dynamicModel.cpp两个文件,用于实现DynamicListModel。项目创建过程参考《Qt Quick 之 Hello World 图文详解》。
我们实现的下拉刷新效果有点儿特别,每次刷新后,只保留预定义的一页数据,比如代码中默认的页大小为20。
版权所有foruok,转载请注明出处:http://blog.csdn.net/foruok。
C++ model实现
很简单,直接上代码了。
dynamic.h:
#ifndef DYNAMICMODEL_H
#define DYNAMICMODEL_H
#include <QAbstractListModel>
class DynamicListModelPrivate;
class DynamicListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int pageSize READ pageSize WRITE setPageSize NOTIFY pageSizeChanged)
Q_PROPERTY(int total READ total WRITE setTotal NOTIFY totalChanged)
public:
DynamicListModel(QObject *parent = 0);
~DynamicListModel();
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const;
Q_INVOKABLE void loadMore(bool forward);
int pageSize();
void setPageSize(int size);
int total();
void setTotal(int total);
signals:
void pageSizeChanged(int size);
void totalChanged(int total);
private:
DynamicListModelPrivate *m_dptr;
};
#endif // DYNAMICMODEL_H
dynamicModel.cpp:
#include "dynamicModel.h"
#include <QDebug>
class DynamicListModelPrivate
{
public:
DynamicListModelPrivate(DynamicListModel *model)
: m_model(model), m_start(0), m_end(20)
, m_total(100), m_pageSize(20)
{
m_roleNames.insert(Qt::UserRole, "content");
}
void pageDown()
{
if(m_end < m_total)
{
m_start += m_pageSize;
m_end += m_pageSize;
if(m_end > m_total)
{
m_end = m_total;
m_start = m_end - m_pageSize;
}
}
}
void pageUp()
{
if(m_start > 0)
{
m_start -= m_pageSize;
if(m_start < 0) m_start = 0;
m_end = m_start + m_pageSize;
}
}
void adjustPageRange()
{
if(m_end - m_start < m_pageSize)
{
m_end = m_start + m_pageSize;
if(m_end > m_total)
{
m_end = m_total;
m_start = m_end - m_pageSize;
}
}
}
DynamicListModel *m_model;
int m_start;
int m_end;
int m_total;
int m_pageSize;
QHash<int, QByteArray> m_roleNames;
};
DynamicListModel::DynamicListModel(QObject *parent)
: QAbstractListModel(parent),
m_dptr(new DynamicListModelPrivate(this))
{
}
DynamicListModel::~DynamicListModel()
{
delete m_dptr;
}
int DynamicListModel::rowCount(const QModelIndex &parent) const
{
return m_dptr->m_end - m_dptr->m_start;
}
QVariant DynamicListModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
//qDebug() << "index.row - " << row << " start - " << m_dptr->m_start;
return QString::number(row + m_dptr->m_start);
}
QHash<int, QByteArray> DynamicListModel::roleNames() const
{
return m_dptr->m_roleNames;
}
void DynamicListModel::loadMore(bool forward)
{
beginResetModel();
if(forward)m_dptr->pageDown();
else m_dptr->pageUp();
endResetModel();
}
int DynamicListModel::pageSize()
{
return m_dptr->m_pageSize;
}
void DynamicListModel::setPageSize(int size)
{
m_dptr->m_pageSize = size;
m_dptr->adjustPageRange();
emit pageSizeChanged(size);
}
int DynamicListModel::total()
{
return m_dptr->m_total;
}
void DynamicListModel::setTotal(int total)
{
m_dptr->m_total = total;
m_dptr->adjustPageRange();
emit totalChanged(total);
}
DynamicListModel仅仅是演示用法,使用m_start、m_end、m_total、m_pageSize四个整型变量来模拟实际的数据。而data()方法,将ListView内的行序号加上m_start转换为字符串返回,就是我们在ListView界面上看到了文字了。
loadMore()函数,区分向前还是向后加载数据,它调用DynamicListModel的pageDown()、pageUp()来更新内部的数据状态。在loadMore()一开始,调用beginResetModel(),通知关联到DynamicListModel上的view们刷新自己,当内部数据状态更新结束后,调用endResetModel()来通知view们,这样view们就会刷新,最终在实例化item delegate时调用data()方法来准备数据,此时m_start已变化,所以界面上看到的数字也跟着变了。
导出C++ Model
这个简单,我们在《Qt Quick 之 QML 与 C++ 混合编程详解》一文中已经讲过。直接看main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "dynamicModel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext *ctx = engine.rootContext();
ctx->setContextProperty("dynamicModel", new DynamicListModel());
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
QML代码介绍
是时候看看main.qml了:
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2
Window {
width: 320;
height: 480;
minimumWidth: 300;
minimumHeight: 480;
visible: true;
id: root;
Component {
id: listDelegate;
Text {
id: wrapper;
width: parent.width;
height: 32;
font.pointSize: 15;
verticalAlignment: Text.AlignVCenter;
horizontalAlignment: Text.AlignHCenter;
text: content;
color: ListView.view.currentIndex == index ? "red" : "blue";
MouseArea {
anchors.fill: parent;
onClicked: {
if(wrapper.ListView.view.currentIndex != index){
wrapper.ListView.view.currentIndex = index;
}
}
}
}
}
ListView {
id: dynamicList;
z: 1;
anchors.fill: parent;
anchors.margins: 10;
delegate: listDelegate;
model: dynamicModel;
focus: true;
activeFocusOnTab: true;
highlight: Rectangle {
color: "steelblue";
}
property real contentYOnFlickStarted: 0;
onFlickStarted: {
//console.log("start,origY - ", originY, " contentY - ", contentY);
contentYOnFlickStarted = contentY;
}
onFlickEnded: {
//console.log("end,origY - ", originY, " contentY - ", contentY);
dynamicModel.loadMore(contentY < contentYOnFlickStarted);
}
}
}
定义ListView对象时,指定其model为main()函数中导出的dynamicModel,其它的代码不必细说了,咱们单看实现下拉(上拉)刷新的关键代码。
onFlickStarted信号处理器,在这里我们仅仅是将flick开始时的contentY记录到contentYOnFlickStarted属性中。
onFlickEnded信号处理器,这里比较flick结束时的contentY和开始时的contentY(即contentYOnFlickStarted),结束时小,说明是下拉,结束时大,说明是上拉。根据比较结果调用loadMore()。
好啦,就这么简单了。看看效果。
ListView的 add remove move populate displaced 动画
ListView{
add:Transition{
ParalleAnimation{
NumberAnimation{
property:"opacity";
from:0;
to:1;
duration:1000;
}
NumberAnimation{
property:"x";
from:0;
duration:1000;
}
}
}
}
Canvas对象
Properties
available : bool
canvasSize : size
context : object
contextType : string
renderStrategy : enumeration
renderTarget : enumeration
Signals
imageLoaded()
paint(rect region)
painted()
Methods
cancelRequestAnimationFrame(int handle)
object getContext(string contextId, ... args)
isImageError(url image)
isImageLoaded(url image)
isImageLoading(url image)
loadImage(url image)
markDirty(rect area)
int requestAnimationFrame(callback)
requestPaint()
bool save(string filename)
string toDataURL(string mimeType)
unloadImage(url image)
查询Context2D对象资料
基础代码
Canvas {
id:canvas
onPaint:{
var ctx = canvas.getContext('2d');
ctx.strokeStyle = Qt.rgba(0, 0, 0, 1);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(20, 0);//start point
ctx.bezierCurveTo(-10, 90, 210, 90, 180, 0);
ctx.stroke();
}
}
ctx.scale(2.0, 0.5);
var space = 4
ctx.setLineDash([1, space, 3, space, 9, space, 27, space, 9, space])
...
ctx.stroke();
strokeStyle
See also createLinearGradient(), createRadialGradient(), createPattern(), and fillStyle.
对象内函数如
Rectangle{
Canvas{
id=imageCanvas;
property var poster;
onPaint:{
var ctx=getContentx("2d");
ctx.drawImage(poster,0,0,width,height);
}
Component.onCompleted:loadImage(poster);
onImageLoaded:{
requestPaint();
negative.setImageData(getContext("2d").createImageData(poster); //调用对象函数
}
}
Canvas {
id:negative;
property var imageData=null;
onPaint:
{
if(imageData!=null)
{
context.drawImage(imageData,0,0,width,height);
}
}
function setImageData(data)
{
imageData=data;
var limit=data.width*data.height*4;
for(var i=0;i<limit;i+=4)
{
imagedara.data[i]=255-data.data[[i];
imagedara.data[i+1]=255-data.data[[i+1];
imagedara.data[i+2]=255-data.data[[i+2];
imagedara.data[i+3]=data.data[[i+3];
}
requestPaint();
}
}
translate 平移 x,y
一般
ctx.save();//保存之前状态
ctx.translate(width/2,height/2); //等于把原点移动到画布中心位置
ctx.beginpath();
ctx.arc(0,0,30,0,Math.PI*2); //所有的绘制等于设置的值+translate的x y 如该句实际绘制是 ctx.arc(0+width/2,0+height/2,30,0,Math.PI*2);
....
ctx.strocke();
ctx.restore();//恢复
clip裁减绘制使用
ctx.save();
ctx.beginPath();
ctx.arc(...);
...
ctx.closePath();
ctx.clip();
//其他绘制裁减
ctx.restore();
FileDialog
FileDialog {
id:fds
title: "选择文件"
folder: shortcuts.desktop
selectExisting: true
selectFolder: false
selectMultiple: false
nameFilters: ["json文件 (*.json)"]
onAccepted: {
labels.text = fds.fileUrl;
console.log("You chose: " + fds.fileUrl);
}
onRejected: {
labels.text = "";
console.log("Canceled");
Qt.quit();
}
}
动画
PropertyAnimation 针对的组件的属性值的变化
NumberAnimation PropertyAnimation派生的专门针对数字类型的property
PathAnimation 让目标对象沿着一个既定的路径运动
SmoothedAnimation派生自 NumberAnimation 在from和to之间产生平滑的动画效果
SpringAnimation 模拟弹簧的振荡行为
ParalleAniamtion 组合动画包含多个动画(同时执行)
SequentialAnimation 与ParalleAniamtion类似不过他是顺序执行
State不同状态不同的显示等
states:[
State{
name:"redText";
changes{
PropertyChanges{
target:centerText;color:"red";
}
}
},
State{
name:'dddd';
when:mouseArea.pressed;//条件
changes{
}
}
]
state:"redText";
Qt 多线程同步 与 通信
转自网络
1 多线程同步
Qt提供了以下几个类来完成这一点:QMutex、QMutexLocker、QSemphore、QWaitCondition。
当然可能还包含QReadWriteLocker、QReadLocker、QWriteLocker,但线程同步是应用很少,这里只做简单的讲解!
QMutex、QMutexLocker
QMutex类提供了一个保护一段临界区代码的方法,他每次只允许一个线程访问这段临界区代码。QMutex::lock()函数用来锁住互斥量,如果互斥量处于解锁状态,当前线程就会立即抓住并锁定它;否则当前线程就会被阻塞,直到持有这个互斥量的线程对其解锁。线程调用lock()函数后就会持有这个互斥量直到调用unlock()操作为止。QMutex还提供了一个tryLock()函数,如果互斥量已被锁定,就立即返回。
在问题出来了,如果要在stop()函数中使用mutex进行互斥操作,但unlock()操作写在那里?unlock()操作却不得不再return之后,从而导致unlock()操作永远也无法执行...
Qt提供了QMutexLocker类何以简化互斥量的处理,它在构造函数中接受一个QMutex对象作为参数并将其锁定,在析构函数中解锁这个互斥量。
bool Thread::stop()
{
QMutexLocker locker(&mutex);
m_stop = true;
return m_stop;
}
QReadWriteLocker、QReadLocker、QWriteLocker
下面是一段对QReadWriteLocker类的对象进行,读写锁的操作,比较简单,这里也不多做讲解了,自己看吧
复制代码
MyData data;
QReadWriteLock lock;
void ReaderThread::run()
{
...
lock.lockForRead();
access_data_without_modifying_it(&data);
lock.unlock();
...
}
void WriterThread::run()
{
...
lock.lockForWrite();
modify_data(&data);
lock.unlock();
...
}
复制代码
QSemphore
Qt中的信号量是由QSemaphore类提供的,信号量可以理解为互斥量功能的扩展,互斥量只能锁定一次而信号量可以获取多次,它可以用来保护一定数量的同种资源。
acquire(n)函数用于获取n个资源,当没有足够的资源时调用者将被阻塞直到有足够的可用资源。release(n)函数用于释放n个资源。
QSemaphore类还提供了一个tryAcquire(n)函数,在没有足够的资源是该函数会立即返回。
一个典型的信号量应用程序是在两个线程间传递一定数量的数据(DataSize),而这两个线程使用一定大小(BufferSize)的共享循环缓存。
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
生产者线程向缓存中写入数据,直到它到达终点,然后在起点重新开始,覆盖已经存在的数据。消费者线程读取前者产生的数据。
生产者、消费者实例中对同步的需求有两处,如果生产者过快的产生数据,将会覆盖消费者还没有读取的数据,如果消费者过快的读取数据,将越过生产者并且读取到一些垃圾数据。
解决这个问题的一个有效的方法是使用两个信号量:
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);
freeSpace信号量控制生产者可以填充数据的缓存部分。usedSpace信号量控制消费者可以读取的区域。这两个信号量是互补的。其中freeSpace信号量被初始化为BufferSize(4096),表示程序一开始有BufferSize个缓冲区单元可被填充,而信号量usedSpace被初始化为0,表示程序一开始缓冲区中没有数据可供读取。
对于这个实例,每个字节就看作一个资源,实际应用中常会在更大的单位上进行操作,从而减小使用信号量带来的开销。
复制代码
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire();
buffer[i % BufferSize] = "MING"[uint(rand()) % 4];
usedSpace.release();
}
}
复制代码
在生产者中,我们从获取一个“自由的”字节开始。如果缓存被消费者还没有读取的数据填满,acquire()的调用就会阻塞,直到消费者已经开始消耗这些数据为止。一旦我们已经获取了这个字节,我们就用一些随机数据("M"、"I"、"N"或"G")填充它并且把这个字节释放为“使用的”,所以它可以被消费者线程使用。
复制代码
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
cerr << buffer[i % BufferSize];
freeSpace.release();
}
cerr << endl;
}
复制代码
复制代码
int main()
{
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
复制代码
QWaitCondition
对生产者和消费者问题的另一个解决方法是使用QWaitCondition,它允许线程在一定条件下唤醒其他线程。其中wakeOne()函数在条件满足时随机唤醒一个等待线程,而wakeAll()函数则在条件满足时唤醒所有等待线程。
下面重写生产者和消费者实例,以QMutex为等待条件,QWaitCondition允许一个线程在一定条件下唤醒其他线程。
QWaitCondition.wait 内部会首先解锁mutex,使其他线程能使用mutex ,进入等待状态,当收到wakeAll wakeOne 唤醒线程时候,wait会再次锁定mutex 然后退出阻塞状态,执行后面的语句
QMutex mutex;
QWaitCondition newdataAvliable;
QByteArray array;
典型生产者消费者
mutex.lock();
array.Add(..);
....
mutex.unlock();
newdataAvliable.wakeAll();
消费者
mutex.lock();
newdataAvliable.wait(&mutext);
array[0];
mutex.unlock();
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
QWaitCondition bufferIsNotFull;
QWaitCondition bufferIsNotEmpty;
QMutex mutex;
int usedSpace = 0;
在缓存之外,我们声明了两个QWaitCondition、一个QMutex和一个存储了在缓存中有多少个“使用的”字节的变量。
复制代码
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
buffer[i % BufferSize] = "MING"[uint(rand()) % 4];
++usedSpace;
bufferIsNotEmpty.wakeAll();
mutex.unlock();
}
}
复制代码
在生产者中,我们从检查缓存是否充满开始。如果是充满的,我们等待“缓存不是充满的”条件。当这个条件满足时,我们向缓存写入一个字节,增加usedSpace,并且在唤醒任何等待这个“缓存不是空白的”条件变为真的线程。
for循环中的所有语句需要使用互斥量加以保护,以保护其操作的原子性。
bool wait ( QMutex * mutex, unsigned long time = ULONG_MAX );
这个函数做下说明,该函数将互斥量解锁并在此等待,它有两个参数,第一个参数为一个锁定的互斥量,第二个参数为等待时间。如果作为第一个参数的互斥量在调用是不是锁定的或出现递归锁定的情况,wait()函数将立即返回。
调用wait()操作的线程使得作为参数的互斥量在调用前变为锁定状态,然后自身被阻塞变成为等待状态直到满足以下条件:
其他线程调用了wakeOne()或者wakeAll()函数,这种情况下将返回"true"值。
第二个参数time超时(以毫秒记时),该参数默认情况是ULONG_MAX,表示永不超时,这种情况下将返回"false"值。
wait()函数返回前会将互斥量参数重新设置为锁定状态,从而保证从锁定状态到等待状态的原则性转换。
复制代码
void Consumer::run()
{
forever {
mutex.lock();
if (usedSpace == 0)
bufferIsNotEmpty.wait(&mutex);
cerr << buffer[i % BufferSize];
--usedSpace;
bufferIsNotFull.wakeAll();
mutex.unlock();
}
cerr << endl;
}
复制代码
消费者做的和生产者正好相反,他等待“缓存不是空白的”条件并唤醒任何等待“缓存不是充满的”的条件的线程。
main()函数与上面的基本相同,这个不再多说。
在QThread类的静态函数currentThread(),可以返回当前线程的线程ID。在X11环境下,这个ID是一个unsigned long类型的值。
自己总结:
原子类型 QAtomicInt QAtomicPointer
2.多线程通信
a . 全局变量 即共享内存
b. 管道
c. 信号槽
3.多进程通信
信号槽 socket 文件映射
QT 使用http协议post json数据
首先要添加项目的网络支持,也就是要在pro文件中添加下边这一行
QT += network
添加一个槽函数,这个函数用来连接发送完成信号,从而接收reply
// *.h 声明部分
private slots:
void finishedSlot(QNetworkReply*);
// *.cpp 定义部分
void MainWindow::finishedSlot(QNetworkReply* reply)
{
if (reply->error() == QNetworkReply::NoError)
{
QByteArray bytes = reply->readAll();
// 下边我们将收到的body部分解析为json格式,其中有个key是status
QJsonDocument jsonDocument = QJsonDocument::fromJson(bytes);
QJsonObject jsonObject = jsonDocument.object();
QString value = jsonObject.value("status").toString();
}
reply->deleteLater();
}
发送部分,为简单起见,将整个发送部分放在构造函数里
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QNetworkAccessManager * net_mgr = new QNetworkAccessManager(this);
connect(net_mgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(finishedSlot(QNetworkReply*)));
QNetworkRequest net_request;
net_request.setUrl(QUrl("http://balabala"));
// 下边这行也很重要,要发送json格式的数据必须要在header里设置,不然不会成功的
net_request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("application/json"));
QJsonObject object;
object.insert("name", "xq");
object.insert("sex", "male");
QJsonDocument document=QJsonDocument(object);
// 这里要将json格式的数据转换为QByteArray才行
QByteArray post_data = document.toJson(QJsonDocument::Compact);
net_mgr->post(net_request, post_data);
}
这就完成了,这里只是拿post和json格式举个例子而已,看实际需要做相应的改变。
property var xmlhttp: null
//get method- asyn请求
function get(url){
if(xmlhttp==null){
xmlhttp=new XMLHttpRequest()
xmlhttp.onreadystatechange=onResultReady;
}
if(xmlhttp.readyState===0){
result.remove(0, result.length)
xmlhttp.open("GET", url, true)
xmlhttp.send()
}
}
//post mothod- asyn请求
function post(url, data){
if(xmlhttp==null){
xmlhttp=new XMLHttpRequest()
xmlhttp.onreadystatechange=onResultReady;
}
if(xmlhttp.readyState===0){
result.remove(0, result.length)
xmlhttp.open("POST", url, true)
xmlhttp.send(data)
}
}
//response,.readyState===4:返回请求结果
function onResultReady(){
console.log(xmlhttp.readyState)
if(xmlhttp.readyState===4){
if(xmlhttp.responseText!==null){
result.append("response data: "+ xmlhttp.responseText)
}
xmlhttp.abort()
}
}
用于保存配置信息
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QSettings>
#include <QQuickStyle>
#include <QIcon>
int main(int argc, char *argv[])
{
QGuiApplication::setApplicationName("Gallery");
QGuiApplication::setOrganizationName("QtProject");
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QIcon::setThemeName("gallery");
QSettings settings;
QString style = QQuickStyle::name();
if (!style.isEmpty())
settings.setValue("style", style);
else
QQuickStyle::setStyle(settings.value("style").toString());
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
engine.load(QUrl("qrc:/gallery.qml"));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Controls.Universal 2.12
import Qt.labs.settings 1.0
Settings {
id: settings
property string style: "Default"
}
Settings {
category: 'window'
property alias x: window.x
property alias y: window.x
property alias width: window.width
property alias height: window.height
}
QML快捷键(Shortcut、Keys)
1.Shortcut { }
Shortcut 可以设置当前 Window 或者整个应用的全局快捷键,先看下它的属性和信号:
属性:
1.autoRepeat : bool
重复触发,如长按的情况,默认 true
2.context : enumeration
处理当前Window还是整个应用的快捷键
可以是 Qt.WindowShortcut (默认)或者 Qt.ApplicationShortcut
3.enabled : bool
是否启用
4.nativeText : string
5.portableText : string
sequence快捷设置对应的字符串,
如设置 "delete" 则 nativeText 和 portableText 显示为 Del
6.sequence : keysequence
单个快捷键或序列,可以是 QKeySequence::StandardKey,也可以是最多四个键组合的字符串
如:"Ctrl+E,Ctrl+W" 先按 Ctrl+E,再按 Ctrl+W 触发
如:StandardKey.Delete 删除键,具体平台映射按键值见文档
7.sequences : list<keysequence>
可以触发的快捷键列表,每一个都可以触发
如:["return","enter","space"]
信号:
1.activated()
快捷键触发了
2.activatedAmbiguously()
快捷键触发了,但是被多个 Shortcut 对象捕获,此时不会触发 activated(),
多个 Shortcut 轮流触发 activatedAmbiguously()
使用也很简单
Shortcut {
//过滤整个应用
context: Qt.ApplicationShortcut
//快捷键
//sequence: "left,right" //按了left再按right触发
//sequences: ["left","right"] //按left或者right触发
//sequences: ["return","enter"] //大小键盘回车
sequences: ["+","delete","return","enter","space"]
//只有一个Shortcut关注该快捷键则只触发onActivated
//否则只触发onActivatedAmbiguously
onActivated: {
console.log('Shortcut 1 onActivated.')
}
onActivatedAmbiguously: {
console.log('Shortcut 1 onActivatedAmbiguously.')
}
2.Keys
Keys 是作为附加属性来使用的,相当于过滤当前 Item 的按键事件。只要 Keys.press 和 Keys.release 没有被 accepted 或者被 Shortcut 处理,可以一直往上级传递,多个层级都可以获知该按键事件。流程为先触发 ShortcutOverride,该事件不会冒泡,如果 ShortcutOverride 没被 Shortcut 接收,那么后面的按键事件会往父级冒泡;要是 ShortcutOverride 被 Shortcut 处理了,那么后面的按键事件就不会触发 Keys.press 信号了。
看下它的属性和信号:
属性:
1.enabled : bool
启用开关
2.forwardTo : list<Object>
可以将按键转发给别的对象,一旦被列表中某个Item accepted,将不再转发给列表中往后的Item。
3.priority : enumeration
设置 forwardTo 的优先级,
Keys.BeforeItem(默认值)-在一般的按键处理之前处理该事件
Keys.AfterItem-在一般的按键处理之后处理该事件
要注意的是,event.accepted 就不再继续传递了
信号:
1.shortcutOverride(KeyEvent event)
快捷键事件,如果想拦截某个按键的快捷键不被全局捕获,可以 accepted
如:event.accepted=(event.key===Qt.Key_Space);
2.pressed(KeyEvent event)
某个按键按下,accpted 可以拦截不往上传递事件
3.released(KeyEvent event)
某个按键释放,accpted 可以拦截不往上传递事件
4.一堆具体按键的 pressed 信号
简单的使用
FocusScope {
id: keys_scope
anchors.fill: parent
Keys.enabled: true //activeFocus的时候就能触发了
Keys.onShortcutOverride: {
event.accepted=(event.key===Qt.Key_Space);
}
Keys.onPressed: {
console.log('Keys onPressed.')
}
Keys.onReleased: {
console.log('Keys onReleased.')
}
}
3.实例:屏蔽 Button 空格触发 clicked
Button {
width: 100
height: 30
focus: true
//被Shortcut捕获了就不会触发Keys.press
//被Shortcut或者Keys.press接受了,5.15不会触发click,5.12、5.13会
//5.12、5.13及以下需要接受Keys.release才不会触发click
Keys.onSpacePressed: {
console.log('Keys onSpacePressed')
//5.15 accepted了press就不会触发点击,5.12、5.13需要设置release的accepted
event.accepted=false;
}
Keys.onReleased: {
console.log('Keys onReleased')
event.accepted=(event.key===Qt.Key_Space); //低版本需要
}
//通过onShortcutOverride accepted可以拦截快捷键
//Control组件的空格是在事件函数里处理的,所以不需要这个
//Keys.onShortcutOverride: { }
onClicked: {
console.log('Button onClicked')
}
background: Rectangle {
border.width: parent.activeFocus?2:1
border.color: parent.focus?"red":"gray"
}
}
4.实例:屏蔽全局快捷键,使用当前 FocusScope 的快捷键处理
Shortcut {
sequence: "space"
onActivated: {
console.log('Shortcut onActivated')
}
}
FocusScope {
focus: true
anchors.fill: parent
Keys.onPressed: {
console.log('Keys onPressed')
}
Keys.onReleased: {
console.log('Keys onReleased')
}
//通过onShortcutOverride accepted可以拦截快捷键
Keys.onShortcutOverride: {
console.log('Keys onShortcutOverride')
//accepted接受需要屏蔽的按键后就不再触发全局快捷键
event.accepted=(event.key===Qt.Key_Space);
}
Item {
}
}
5.实例:eventFilter 过滤快捷键
虽然 Keys 可以对当前 activeFocus Item 过滤快捷键,但是组件比较多时,想要对某个焦点域进行快捷键操作,但又需要屏蔽全局快捷键怎么办呢?总不能每个组件都设置一边吧。我的办法就是使用 eventFilter 对 qApp 实例进行事件过滤,大致代码如下:
class KeysFilter : public QObject
{
Q_OBJECT
public:
//过滤类型
enum FilterType {
None = 0x00
,ShortcutOverride = 0x01
,KeyPress = 0x02
,KeyRelease = 0x04
,All = 0xFFFF
};
Q_ENUM(FilterType)
public:
explicit KeysFilter(QObject *parent = nullptr);
void setEnabled(bool enable)
{
if(enabled != enable){
if(enable){
qApp->installEventFilter(this);
}else{
qApp->removeEventFilter(this);
}
enabled = enable;
emit enabledChanged();
}
}
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched)
const int &&e_type = event->type();
switch(e_type)
{
case QEvent::ShortcutOverride:
if(filterType & FilterType::ShortcutOverride) goto Tab_KeyProcess;
break;
case QEvent::KeyPress:
if(filterType & FilterType::KeyPress) goto Tab_KeyProcess;
break;
case QEvent::KeyRelease:
if(filterType & FilterType::KeyRelease) goto Tab_KeyProcess;
break;
}
return false;
Tab_KeyProcess:
{
QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
if(key_event && filterKeys.contains(key_event->key())){
//accepted之后,shortcut就不再处理了
key_event->setAccepted(true);
//下面的逻辑用不到,目前只把快捷键过滤
if(acceptAutoRepeat || !key_event->isAutoRepeat()){
if(e_type == QEvent::KeyPress){
emit pressed(key_event->key());
}else if(e_type == QEvent::KeyRelease){
emit released(key_event->key());
}
}
return true;
}
}
return false;
}
private:
//使能-eventFilter
bool enabled = true;
//使能-长按重复触发
bool acceptAutoRepeat = false;
//过滤类型
FilterType filterType = FilterType::ShortcutOverride;
//过滤的按键列表
QList<int> filterKeys;
};
StackView和Dialog
stackView.push(model.source)
ToolButton {
icon.name: "menu"
onClicked: optionsMenu.open()
Menu {
id: optionsMenu
x: parent.width - width
transformOrigin: Menu.TopRight
MenuItem {
text: "Settings"
onTriggered: settingsDialog.open()
}
MenuItem {
text: "About"
onTriggered: aboutDialog.open()
}
}
}
StackView {
id: stackView
anchors.fill: parent
initialItem: Pane {
id: pane
Image {
id: logo
width: pane.availableWidth / 2
height: pane.availableHeight / 2
anchors.centerIn: parent
anchors.verticalCenterOffset: -50
fillMode: Image.PreserveAspectFit
source: "images/qt-logo.png"
}
Label {
text: "Qt Quick Controls 2 provides a set of controls that can be used to build complete interfaces in Qt Quick."
anchors.margins: 20
anchors.top: logo.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: arrow.top
horizontalAlignment: Label.AlignHCenter
verticalAlignment: Label.AlignVCenter
wrapMode: Label.Wrap
}
Image {
id: arrow
source: "images/arrow.png"
anchors.left: parent.left
anchors.bottom: parent.bottom
}
}
}
Dialog {
id: settingsDialog
x: Math.round((window.width - width) / 2)
y: Math.round(window.height / 6)
width: Math.round(Math.min(window.width, window.height) / 3 * 2)
modal: true
focus: true
title: "Settings"
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: {
settings.style = styleBox.displayText
settingsDialog.close()
}
onRejected: {
styleBox.currentIndex = styleBox.styleIndex
settingsDialog.close()
}
contentItem: ColumnLayout {
id: settingsColumn
spacing: 20
RowLayout {
spacing: 10
Label {
text: "Style:"
}
ComboBox {
id: styleBox
property int styleIndex: -1
model: availableStyles
Component.onCompleted: {
styleIndex = find(settings.style, Qt.MatchFixedString)
if (styleIndex !== -1)
currentIndex = styleIndex
}
Layout.fillWidth: true
}
}
Label {
text: "Restart required"
color: "#e41e25"
opacity: styleBox.currentIndex !== styleBox.styleIndex ? 1.0 : 0.0
horizontalAlignment: Label.AlignHCenter
verticalAlignment: Label.AlignVCenter
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
QML组件
基于文件的组件将QML元素放置在一个单独的文件中,然后给文件一个名字,可以通过名字来使用组件。如果有一个文件名为Cell.qml,就可以在QML中使用Cell { … }形式。自定义组件的文件名的首字母必须大写。
Cell.qml文件:
import QtQuick 2.0
Item
{
id: container
property alias cellColor: rectangle.color
signal clicked(color cellColor)
width: 40; height: 25
Rectangle
{
id: rectangle
border.color: "white"
anchors.fill: parent
}
MouseArea
{
anchors.fill: parent
onClicked: container.clicked(container.cellColor)
}
}
Rectangle有一个Text用于显示按钮的文本;有一个MouseArea用于接收鼠标事件。用户可以定义按钮的文本,用过设置Text的text属性实现的。为了不对外暴露Text元素,给Text的text属性一个别名。signal clicked给Cell一个信号。由于clicked信号是无参数的,也可以写成signal clicked(),二者是等价的。clicked信号会在MouseArea的clicked信号被发出,具体就是在MouseArea的onClicked属性中调用个clicked信号。
Main.qml文件:
import QtQuick 2.0
Rectangle
{
id: page
width: 320; height: 480
color: "lightgray"
Text
{
id: helloText
text: "Hello world!"
y: 30
anchors.horizontalCenter: page.horizontalCenter
font.pointSize: 24; font.bold: true
}
Grid
{
id: colorPicker
x: 4; anchors.bottom: page.bottom; anchors.bottomMargin: 4
rows: 2; columns: 3; spacing: 3
Cell { cellColor: "red"; onClicked: helloText.color = cellColor }
Cell { cellColor: "green"; onClicked: helloText.color = cellColor }
Cell { cellColor: "blue"; onClicked: helloText.color = cellColor }
Cell { cellColor: "yellow"; onClicked: helloText.color = cellColor }
Cell { cellColor: "steelblue"; onClicked: helloText.color = cellColor }
Cell { cellColor: "black"; onClicked: helloText.color = cellColor }
}
}
main.qml中,直接使用了Cell组件。由于Cell.qml与main.qml位于同一目录下,所以不需要额外的操作。但如果将Cell.qml放在不同目录,main.qml的import部分增加一行import ../components才能够找到Cell组件。
Import“qml/”中,qml为文件夹,里面有Monitor.qml和Compass.qml两个?件qml/为文件夹的相对路径
选择一个组件的根元素很重要。比如Cell组件,使用Rectangle作为其根元素。Rectangle元素可以设置背景色等。如果不允许用户设置背景色,可以选择使用Item元素作为根。
利用Qt自制可import的QML插件
Qt的插件种类很多,但有些插件的协议是GPL协议,例如QtCharts。如果项目中使用了这种插件的话,那么就必须要共享源码。
商业软件肯定是不能共享源码的,所以就只能自制插件了。
自制插件的方式有很多种,可以直接写qml文件,使用时导入qml文件就行。但如果插件的代码除了bug需要修改,那么所有使用这个qml文件的源码都需要修改,很不方便。
所以,就决定写一个可以import的插件,方便好用。也可以把插件共享出来给广大网友,而不用担心源码泄露
第一步 新建一个扩展插件项目
扩展插件项目,新建项目>>Library>>Qt Quick 2 Extension Plugin。
我们新建一个名为 QMLPlugin 的项目。
接下来需要输入class-name和URI,我们把URI更改成MyPlugin,class-name先不要改,等跑通整个程序后再做调整
然后一直点击下一步,直到完成,这个时候我们就能得到一个完整的插件扩展项目
请注意,这里有一个叫做 qmldir 的文件,打开文件看看。
module MyPlugin
plugin QMLPlugin
很简单的两句话,这个文件第二步有用。
第二步 构建插件扩展项目
插件扩展项目新建完成后,我们对项目进行构建,分别用debug和release进行构建。
这样就能够得到一个debug的QMLPlugind.dll文件,和release的QMLPlugin.dll文件。注意:如果是linux的话,应该是so文件。
我们在桌面新建一个文件夹 MyPlugin(当然也可以叫其他名字),把我们刚刚构建出来的QMLPlugind.dll和QMLPlugin.dll,还有第一步的qmldir文件,放入到MyPlugin文件夹中。
再将MyPlugin这个文件夹放到Qt安装目录的qml文件夹下面,一定要找准位置。这个文件夹下面还有许多其它的qml插件,比如说QtCharts等。
第三步 新建一个qml项目做测试
在第一步和第二步都做完的情况下,讲道理qml中就已经可以调用了,我们测试一下。
新建一个qml的qt项目。
新建完成后,我们什么都不要改,直接打开main.qml文件,import刚刚我们新做的插件。
还记得刚刚的qmldir文件吗,里面有两行代码。
module MyPlugin
plugin QMLPlugin
这个module 里面的MyPlugin就是我们要导入的对象。我们先导入试用,然后我再说为什么可以导入。
import MyPlugin 1.0
导入成功,程序没有报红也没有报错。
好,还记得我们新建插件扩展项目时,说先不要改动的class-name吗,那个就是可以在qml中调用的插件名 MyItem,试用一下看看。
第四步 讲解为什么可以导入
在第三步我们试用时已经成功了,那为什么可以做到呢。
打开我们之前新建的插件扩展项目QMLPlugin,里面有一个文件qmlplugin_plugin.cpp,打开它
看到了吗,有这么一句代码:
qmlRegisterType<MyItem>(uri, 1, 0, "MyItem");
这个uri就是之前新建项目输入的URI,也是写入qmldir文件的module,也就是我们可以import导入的原因,版本为1.0。
代码最后的参数"MyItem",就是我们在qml中使用的插件名,也可以换个其它任何你喜欢的名字,但最好是要首字母大写。
第五步 创建一个自己的插件
在第四步讲了为什么可以使qml导入后,大家或许也猜到了,只要用qmlRegisterType注册一下其它的对象,那么qml不就可以调用了吗。
我们实验一下,假设我想自定义一个提示板,可以设置颜色、文字、背景等功能。
首先新建一个c++类,名为NoticeBoard。
因为是给qml调用,所以这个类需要继承QQuickItem,又因为需要用到画板Painter,所以继承QQuickPaintedItem类,记得勾选Add Q_OBJECT的选项,因为可能会用到信号。
新建类后,因为没有头函数,所以文件会报错,我们把.h和.cpp文件都补充完整。
注意:
记得要加上这一句话:Q_DISABLE_COPY(NoticeBoard),否则qml中会报红色。
因为是继承了QQuickPaintedItem类,所以一定要记得重写paint函数,否则编译会报错的。
#ifndef NOTICEBOARD_H
#define NOTICEBOARD_H
#include <QQuickPaintedItem>
class NoticeBoard : public QQuickPaintedItem
{
Q_OBJECT
Q_DISABLE_COPY(NoticeBoard)
public:
NoticeBoard(QQuickItem *parent = nullptr);
~NoticeBoard() override;
protected:
void paint(QPainter *painter) override;
};
#endif // NOTICEBOARD_H
#include "noticeboard.h"
NoticeBoard::NoticeBoard(QQuickItem *parent) : QQuickPaintedItem(parent)
{
}
构建一下,没有报错了。如果还有再报错的话,建议把构建出来的文件全部删除后,重新构建。
修改一下qmlplugin_plugin.cpp,使得qml中能够调用到这个插件。
#include "qmlplugin_plugin.h"
#include "myitem.h"
#include "noticeboard.h"
#include <qqml.h>
void QMLPluginPlugin::registerTypes(const char *uri)
{
// @uri MyPlugin
qmlRegisterType<MyItem>(uri, 1, 0, "MyItem");
qmlRegisterType<NoticeBoard>(uri, 1, 0, "NoticeBoard");
}
NoticeBoard::~NoticeBoard()
{
}
void NoticeBoard::paint(QPainter *painter)
{
}
再使用debug和release分别对项目进行构建,将构建出来的两个dll库放入qt安装目录下,我们在第二步做的MyPlugin文件夹下。
记得一定是qt安装目录下的MyPlugin文件夹,这一步后面就不再重复了,每当更改了这个插件的代码时,都需要将dll文件更新一下。
第六步 让自己创建的插件有可修改属性
第四步创建插件提示板成功了,但是这个提示板没有颜色也没有文字,一片空白,现在来修改一下,首先修改颜色,我们希望在qml中可以控制提示板的颜色color属性。
我们就在NoticeBoard中增加一个color属性,用Q_PROPERTY的功能使得qml可以使用。
我们修改一下noticeboard.h和noticeboard.cpp文件。
#ifndef NOTICEBOARD_H
#define NOTICEBOARD_H
#include <QQuickPaintedItem>
#include <QPainter>
class NoticeBoard : public QQuickPaintedItem
{
Q_OBJECT
Q_DISABLE_COPY(NoticeBoard)
Q_PROPERTY(QString color READ getColor WRITE setColor NOTIFY colorChanged)
public:
NoticeBoard(QQuickItem *parent = nullptr);
~NoticeBoard() override;
QString getColor() const;
void setColor(const QString &color);
protected:
void paint(QPainter *painter) override;
private:
QString color = "#FF0000";
signals:
void colorChanged();
};
#endif // NOTICEBOARD_H
#include "noticeboard.h"
NoticeBoard::NoticeBoard(QQuickItem *parent) : QQuickPaintedItem(parent)
{
}
NoticeBoard::~NoticeBoard()
{
}
QString NoticeBoard::getColor() const
{
return color;
}
void NoticeBoard::setColor(const QString &color)
{
this->color = color;
emit colorChanged();
}
void NoticeBoard::paint(QPainter *painter)
{
QBrush brush; //画刷。填充几何图形的调色板,由颜色和填充风格组成
brush.setColor(QColor(color));
brush.setStyle(Qt::SolidPattern);
QPen pen; //画笔。
pen.setColor(QColor(color));
//在画板上画一个跟画板同宽同高的矩形
painter->setPen(pen);
painter->setBrush(brush);
painter->drawRect(0, 0, this->width(), this->height());
}
QML组件事件动态绑定
ReturnBtn.qml 文件
//此处定义一个msgChange信号,该信号带有一个sting类型的参数name,还有一个int类型的参数age
signal msgChange(name:string,age:int)
MouseArea {
anchors.fill: returnIcon_128
onClicked: {
//当点击MouseArea对象类型时,调用msgChange函数,并传入”iriczhao“和999两个参数
msgChange("iriczhao",999)
}
onPressed: {
glow.visible = true
glow.color = "lightskyblue"
}
onReleased:{
glow.visible = false
}
}
test.qml文件
Window {
id: window
width: 640
height: 480
visible: true
color: "#4c79ef"
title: qsTr("Hello World")
ReturnBtn {
id: returnBtn
x: 245
y: 204
width: 100
height: 47
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
//此处在msgChange信号处理函数中,使用function()来接收msgChange信号带入的两个参数
onMsgChange: function(name,age){console.log("recv " + name + " " + age)}
}
}
1、函数中形式参数的名称不必与信号中的名称相匹配。
2、如果不需要处理所有的参数,可以省略后面的参数。例如,只想在onMsgChange信号处理函数中接收name参数,不接受age参数,可以这样写:
onMsgChange: function(name){console.log("recv " + " " + name)}
3、对于function()中的不需要的参数,可以使用下划线(_)来进行参数占位,从而告诉qml引擎忽略掉该参数。例如,如果不想在onMsgChange信号处理函数中接收name参数,可以这样写:
onMsgChange: function(_,age){console.log("recv " + " " + age)}
使用Connections qml类型创建信号连接
import QtQuick 2.0
import QtQuick.Window 2.15
import "Components"
Window {
id: window
width: 640
height: 480
visible: true
color: "#4c79ef"
title: qsTr("Hello World")
ReturnBtn {
id: returnBtn
x: 245
y: 204
width: 100
height: 47
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
onMsgChange: function(name){console.log("recv " + " " + name)}
//附加的信号处理函数
Component.onCompleted: {
console.log("ReturnBtn")
}
}
//附加的信号处理函数
Component.onCompleted: {
console.log("Window")
}
}
使用connect将信号连接到方法和信号
Window {
id: window
width: 640
height: 480
visible: true
color: "#4c79ef"
title: qsTr("Hello World")
ReturnBtn {
id: returnBtn
x: 245
y: 204
width: 100
height: 47
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Component.onCompleted: {
//在该组件加载完成信号处理函数中创建:returnBtn对象的msgChange信号与masChangeHandler JavaScript的
//函数连接
returnBtn.msgChange.connect(msgChangeHandler)
}
//定义msgChangeHandler函数
function msgChangeHandler(messae,value)
{
console.log("recv param :" + messae +" " + value);
}
}
连接到信号
Window {
id: window
width: 640
height: 480
visible: true
color: "#4c79ef"
title: qsTr("Hello World")
signal returnBtnClicked()
ReturnBtn {
id: returnBtn
x: 245
y: 204
width: 100
height: 47
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
onMsgChange: console.log("ReturnBtn")
}
Component.onCompleted: {
returnBtn.msgChange.connect(returnBtnClicked)
}
onReturnBtnClicked:
{
console.log("this is Window signal : returnBtnClicked handler")
}
}
QML 布局中的项目提供特定间距
例如 Column或 Row , 布局被引入以支持 UI 的图形缩放,也是由 引入的。灌装可用空间(在这种特定情况下填充其父级)。在这个意义上,spacing属性不应被视为 Item 之间间距的严格上限s 但作为 最小允许距离 它们之间。
当前解决您的问题的方法是使用“填充物”Item ,它使用 fillHeight 属性(property)。此 Item占据了对方留下的所有空间Item s 在布局中,因此根据需要将它们打包在一起:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 100
height: 200
ColumnLayout {
anchors.fill: parent
spacing: 2
Rectangle {
height: 50
width: 50
color: "red"
}
Rectangle {
height: 50
width: 50
color: "green"
}
Rectangle {
height: 50
width: 50
color: "blue"
}
Item { Layout.fillHeight: true } // <-- filler here
}
}
请注意,您可以利用相同的方法并在布局的开头添加一个填充符以垂直居中子项 Item s。最后,请注意,在这种情况下,建议使用 Column其中放置了 Item s 如预期正确,不考虑剩余的可用空间。
还有 anchors.leftMargin 等
下一篇:全网最靠谱的QML程序发布