【Qt】QML基本使用

待补充

开发环境

Win11,QT5.14.2

main.qml

所有控件示例使用Loader载入到界面中

Loader
Allows dynamic loading of a subtree from a URL or Component.

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Loader {
        anchors.fill: parent

        id: rootLoader
        property string homeUrl: "./XXX.qml"

        source: homeUrl
        clip: true
    }
}

原生控件使用

TreeView

先记录一下个人觉得最麻烦的一个控件,主要参考了官方demo,Simple Tree Model Example,和腾讯课堂的qml课程

思路: 从官方demo中可以了解到,TreeView的model只能是来自于C++类,且必须是继承于QAbstractItemModel,这个类中有以下5个纯虚函数必须实现;

  • virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0
  • virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0
  • virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0
  • virtual QModelIndex parent(const QModelIndex &index) const = 0
  • virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0

除了上面这个类,我们还需要自己定义树的节点,demo中的名字是TreeItem;树有三种表示方法,即双亲表示法、孩子表示法、孩子兄弟表示法,但是在实际使用中,为了便于操作,我们是有冗余的,节点中不仅有指向孩子的指针数组,还有指向双亲节点的parent指针,为了保存节点的数据,当然,还有数据域;

文字描述太多,也不清楚,show the code to you

TreeItem.h

#pragma once

// C lib import
#include 

// Qt lib import
#include 
#include 
#include 
#include 

//可以增加函数,实现 增删改查
class TreeItem
{
private:
    //父节点指针
    TreeItem *parentItem;
    //孩子指针
    QList<TreeItem*> childItems;
    //数据,可能存在多列数据,所以使用QList类型
    QList<QString> itemData;

public:
    TreeItem(const QList<QString> &data, TreeItem *parentItem = nullptr);

    ~TreeItem();

    //添加孩子
    void appendChild(TreeItem *child);

    //删除孩子
    void removeChild(int x);

    //返回第x个孩子
    TreeItem *child(int x);

    //孩子数量
    int childCount() const;

    //总列数
    int columnCount() const;

    //返回对应列的数据
    QVariant data(int column) const;

    //当前行数,表示是第几个孩子
    int row() const;

    //父节点
    TreeItem *parent();

    void setParent(TreeItem *parent);
};

TreeItem.cpp

#include "TreeItem.h"

TreeItem::TreeItem(const QList<QString> &data, TreeItem *parentItem)
{
    itemData = data;
    qDebug()<<itemData.value(0);
    this->parentItem = parentItem;
}

TreeItem::~TreeItem()
{
    qDeleteAll(childItems);
}

void TreeItem::appendChild(TreeItem *item)
{
    item->setParent(this);
    childItems.append(item);
}

void TreeItem::removeChild(int x)
{
    childItems.removeAt(x);
}

TreeItem *TreeItem::child(int x)
{
    return childItems.value(x);
}

int TreeItem::childCount() const
{
    return childItems.count();
}

int TreeItem::columnCount() const
{
    return itemData.count();
}

QVariant TreeItem::data(int column) const
{
    return itemData.value(column);
}

TreeItem *TreeItem::parent()
{
    assert(parentItem);
    return parentItem;
}

void TreeItem::setParent(TreeItem *parent)
{
    parentItem = parent;
}

int TreeItem::row() const
{
    if (!parentItem) {
        return 0;
    }

    return parentItem->childItems.indexOf(const_cast<TreeItem*>(this));
}

TreeModel.h

#pragma once

#include 
#include 
#include 

#include "TreeItem.h"

class TreeModel: public QAbstractItemModel
{
    Q_OBJECT

private:
    TreeItem *rootItem;

public:
    TreeModel(QObject *parent = NULL);

    ~TreeModel();

    //Returns the data stored under the given role for the item referred to by the index.
    QVariant data(const QModelIndex &index, int role) const;

    Qt::ItemFlags flags(const QModelIndex &index) const;

    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;

    //Returns the index of the item in the model specified by the given row, column and parent index.
    //When reimplementing this function in a subclass,
    //call createIndex() to generate model indexes that other components can use to refer to items in your model.
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;

    QModelIndex parent(const QModelIndex &index) const;

    //Returns the number of rows under the given parent.
    //When the parent is valid it means that rowCount is returning the number of children of parent.
    int rowCount(const QModelIndex &parent = QModelIndex()) const;

    //Returns the number of columns for the children of the given parent.
    //In most subclasses, the number of columns is independent of the parent.
    int columnCount(const QModelIndex &parent = QModelIndex()) const;

    void insert(QString value, TreeItem * parentItem = nullptr);

    //Returns the model's role names.
    QHash<int, QByteArray> roleNames() const;

public slots:
    QAbstractItemModel *model();
};

TreeModel.cpp

#include "TreeModel.h"

TreeModel::TreeModel(QObject *parent) :
    QAbstractItemModel(parent)
{
    //定义根节点
    rootItem = new TreeItem({"treeRoot"});

    //一级节点
    auto item1 = new TreeItem({QStringLiteral("A")}, rootItem);
    auto item2 = new TreeItem({QStringLiteral("B")}, rootItem);
    auto item3 = new TreeItem({QStringLiteral("C")}, rootItem);
    rootItem->appendChild(item1);
    rootItem->appendChild(item2);
    rootItem->appendChild(item3);

    //
    auto item11 = new TreeItem({"test11"}, item1);
    auto item12 = new TreeItem({"test12"}, item1);
    item1->appendChild(item11);
    item1->appendChild(item12);
}

void TreeModel::insert(QString value, TreeItem *parentItem){
    auto temp = new TreeItem({value}, parentItem);
    rootItem->appendChild(temp);
}

TreeModel::~TreeModel()
{
    delete rootItem;
}

int TreeModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
    {
        return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
    }
    else
    {
        return rootItem->columnCount();
    }
}

//自定义roleName
QHash<int, QByteArray> TreeModel::roleNames() const
{
	//virtual QHash roleNames() const
	//qt中原来就有一些roleName,为了不把原先有的覆盖到,key需要从Qt::UserRole+1算起;
    QHash<int, QByteArray> names(QAbstractItemModel::roleNames());
    names[Qt::UserRole + 1] = "name";
    names[Qt::UserRole + 2] = "other";
    return names;
}

QVariant TreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    switch (role)
    {
    case Qt::UserRole + 1:
        {
            return static_cast<TreeItem*>(index.internalPointer())->data(0);
        }
    case Qt::UserRole + 2:
        {
            return static_cast<TreeItem*>(index.internalPointer())->data(1);
        }
    case Qt::DisplayRole:
        {
            return static_cast<TreeItem*>(index.internalPointer())->data(index.column());
        }
    default:
        {
            return QVariant();
        }
    }
}

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
    {
        return 0;
    }

    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
    {
        return rootItem->data(section);
    }

    return QVariant();
}

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
    {
        return QModelIndex();
    }

    TreeItem *parentItem;

    if (!parent.isValid())
    {
        parentItem = rootItem;
    }
    else
    {
        parentItem = static_cast<TreeItem*>(parent.internalPointer());
    }

    TreeItem *childItem = parentItem->child(row);

    if (childItem)
    {
        return createIndex(row, column, childItem);
    }
    else
    {
        return QModelIndex();
    }
}

QModelIndex TreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
    {
        return QModelIndex();
    }

    TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
    TreeItem *parentItem = childItem->parent();

    if (parentItem == rootItem)
    {
        return QModelIndex();
    }

    return createIndex(parentItem->row(), 0, parentItem);
}

int TreeModel::rowCount(const QModelIndex &parent) const
{
    TreeItem *parentItem;

    if (parent.column() > 0)
    {
        return 0;
    }

    if (!parent.isValid())
    {
        parentItem = rootItem;
    }
    else
    {
        parentItem = static_cast<TreeItem*>(parent.internalPointer());
    }

    return parentItem->childCount();
}

QAbstractItemModel *TreeModel::model()
{
    return this;
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <TreeModel.h>
#include <QQmlContext>
#include <QTextCodec>
#include "TreeModel.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QTextCodec *codec = QTextCodec::codecForName("UTF-8");
    QTextCodec::setCodecForLocale(codec);

    QQmlApplicationEngine engine;
    /// qml与c++交互
    engine.rootContext()->setContextProperty("treeModel", new TreeModel);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

MyTreeView.qml

import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQml.Models 2.2

Rectangle{
    property variant nodePic: ["qrc:/pic/pic/pan.png","qrc:/pic/pic/dir.png","qrc:/pic/pic/file.png"]
    id: root

    color: parent.color

    TreeView{
        id: myTree
        anchors.fill: parent
        style: treeViewStyle
        selection: sel
        //隐藏列头,可以注释掉运行看效果
        headerVisible: false

        Component.onCompleted: {
            //从TreeModel获取model
            model = treeModel.model()
        }

        //节点代理
        itemDelegate: Item{
            id: treeItem

            //节点前的图片
            Image {
                id: nodeImg
                height: parent.height

                //不同级的节点图片宽度不一样
                width: {
                    if(styleData.depth === 0){
                        return parent.width / 6
                    }else if(styleData.depth === 1){
                        return parent.width / 8
                    }else{
                        return parent.width / 6
                    }
                }

                //不同级的节点图片不一样
                source: {
                    if(styleData.depth === 0){
                        return nodePic[0]
                    }else if(styleData.depth === 1){
                        return nodePic[1]
                    }else {
                        return nodePic[2]
                    }
                }
            }

            Text{
                id:itemText
                anchors.left: nodeImg.right
                anchors.leftMargin: 4
                anchors.bottom: parent.bottom

                //显示来自model的文本
                text: styleData.value

                //选中时字体颜色切换
                color: styleData.selected ? "red":"black"

                //选中时字体大小改变
                font.pointSize: styleData.selected ? 10 : 9
            }

            //节点代理的鼠标事件
            MouseArea{
                id: itemMosue
                hoverEnabled: true
                anchors.fill: parent

                onClicked:{
                    //点击了文字,选中该节点
                    sel.setCurrentIndex(styleData.index, 0x0010)

                    //切换节点的展开状态
                    if(styleData.isExpanded){
                        myTree.collapse(styleData.index)
                    }
                    else{
                        myTree.expand(styleData.index)
                    }

                    //添加子节点
                    console.log("点击了: " + itemText.text)
                }
            }
        }

        //定义列
        TableViewColumn {
            title: qsTr("资源管理软件")
            //显示的元素
            role: "name"
            //列的宽
            width: 200
        }
    }

    //自定义添加选中
    ItemSelectionModel {
        id: sel
    }

    //树的自定义样式
    Component {
        id: treeViewStyle

        TreeViewStyle {
            //节点间隔
            indentation: 30

            //节点的展开标记图
            branchDelegate: Image {
                id:image
                source: styleData.isExpanded ? "qrc:/pic/pic/collapse.png" : "qrc:/pic/pic/expansion.png"
                width: 9
                height: 15
                anchors.top: parent.top
                anchors.topMargin: 2
            }

            //行代理
            rowDelegate: Rectangle {
                height: 20
                //这里决定了选中的颜色和背景颜色
                color: styleData.selected? "red" : root.color
            }

        }
    }
}

【Qt】QML基本使用_第1张图片

参考了福优学苑qml课程

Flickable

Provides a surface that can be “flicked”.

The Flickable item places its children on a surface that can be dragged and flicked, causing the view onto the child items to scroll. This behavior forms the basis of Items that are designed to show large numbers of child items, such as ListView and GridView.
In traditional user interfaces, views can be scrolled using standard controls, such as scroll bars and arrow buttons. In some situations, it is also possible to drag the view directly by pressing and holding a mouse button while moving the cursor. In touch-based user interfaces, this dragging action is often complemented with a flicking action, where scrolling continues after the user has stopped touching the view.
Flickable does not automatically clip its contents. If it is not used as a full-screen item, you should consider setting the clip property to true.

下面的代码是为了加上滚动条;
【Qt】QML基本使用_第2张图片

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    Flickable{
        id: flick

        width: parent.width

        height: parent.height

        contentWidth: flick.width
        contentHeight: rect1.height + rect2.height + rect3.height

        //ScrollBar.vertical: ScrollBar { }

        Rectangle{
            id: rect1

            anchors.top: parent.top
            anchors.topMargin: 10

            width: parent.width - 50

            anchors.leftMargin: 50 / 2
            anchors.rightMargin: 50 / 2

            height: 100

            color:  "red"
        }

        Rectangle{
            id: rect2

            anchors.top: rect1.bottom
            anchors.topMargin: 10

            width: parent.width - 50

            anchors.leftMargin: 50 / 2
            anchors.rightMargin: 50 /2

            height: 300

            color:  "green"
        }

        Rectangle{
            id: rect3

            anchors.top: rect2.bottom
            anchors.topMargin: 10

            width: parent.width - 50

            anchors.leftMargin: 50 / 2
            anchors.rightMargin: 50 /2

            height: 400

            color:  "blue"
        }
    }

    //添加一个滚动条按钮
    Rectangle
    {
        id: rect;
        width: 10;
        anchors.right: flick.right;
        color: "#6D665C";

        //flick.visibleArea.heightRatio = flick.height / flick.contentHeight
        height: flick.visibleArea.heightRatio * flick.height;

        //滚动条会随着图片移动而移动,因为图片和滚动条应该都是等比例下降,所以换算如下
        y: flick.visibleArea.yPosition * flick.height;

        //给滚动条按钮添加槽函数,可以拖动按钮移动图片
        MouseArea
        {
            id: mouseA;
            anchors.fill: rect;
            //让滚动条可以在垂直方向拖动
            drag.target: rect;
            drag.axis: Drag.YAxis;
            drag.minimumY: 0;
            drag.maximumY: flick.height - rect.height;

            onPressed:
            {
               rect.color = "#A4D3EE";
            }

            onReleased:
            {
               rect.color = "#6D665C";
            }

            //不可以用flick.visibleArea.yPosition,因为是只读属性
            onMouseYChanged:
            {
               flick.contentY = rect.y / flick.height * flick.contentHeight;
            }
        }

        Component.onCompleted:
        {
            console.log("QML MouseArea\'s C++ type - ", mouseA);
        }
    }
}

参考https://blog.csdn.net/weixin_41849730/article/details/108278378

ScrollBar

简化了上面的部分代码,直接使用自带的滚动条

【Qt】QML基本使用_第3张图片

ScrollBar is an interactive bar that can be used to scroll to a specific position. A scroll bar can be either vertical or horizontal, and can be attached to any Flickable, such as ListView and GridView.
从官方文档中可以了解到,只有继承与Flickable的控件才可以使用滚动条

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    //color: "gray"

    Flickable{
        id: flick

        ScrollBar.vertical: ScrollBar{}

        anchors.horizontalCenter: parent.horizontalCenter

        anchors.fill: parent

		//必须设置contectWidth和contentHeight,否则,滚动条不生效
        contentWidth: rectRoot.width
        contentHeight: rectRoot.height

        Rectangle{
            id: rectRoot

            width: parent.width - 20


            anchors.top: parent.top
            anchors.topMargin: 10

            height: 1000

            //color: "gray"

            Rectangle{
                id: rect1

                width: parent.width

                height: 100

                color:  "red"
            }

            Rectangle{
                id: rect2

                anchors.top: rect1.bottom
                anchors.topMargin: 10

                width: parent.width

                height: 300

                color:  "green"
            }

            Rectangle{
                id: rect3

                anchors.top: rect2.bottom
                anchors.topMargin: 10

                width: parent.width

                height: 400

                color:  "blue"
            }
        }
    }
}

增加留白

改进一下,将Rectangle两边留出空白区域,并将其居中;
【Qt】QML基本使用_第4张图片

尝试了一下,Flickable的子项中使用anchors.horizontalCenter: parent.horizontalCenter无效,所以我往下放了一下

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    Flickable{
        id: flick

        ScrollBar.vertical: ScrollBar{}

        anchors.fill: parent

        contentWidth: rectRoot.width
        contentHeight: rectRoot.height

        Rectangle{
            id: rectRoot

            width: parent.width - 10

            anchors.top: parent.top
            anchors.topMargin: 10

            //anchors.horizontalCenter: parent.horizontalCenter

            height: 1000

            color: "gray"

            Rectangle{
                id: rect1

                width: parent.width - 20

                anchors.horizontalCenter: parent.horizontalCenter

                height: 100

                color:  "red"
            }

            Rectangle{
                id: rect2

                anchors.top: rect1.bottom
                anchors.topMargin: 10

                anchors.horizontalCenter: parent.horizontalCenter

                width: parent.width - 20

                height: 300

                color:  "green"
            }

            Rectangle{
                id: rect3

                anchors.top: rect2.bottom
                anchors.topMargin: 10

                width: parent.width - 20
                anchors.horizontalCenter: parent.horizontalCenter

                height: 400

                color:  "blue"
            }
        }
    }
}

ColumnLayout

列布局

【Qt】QML基本使用_第5张图片

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    Flickable{
        id: flick

        ScrollBar.vertical: ScrollBar{}

        anchors.fill: parent

        contentWidth: rectRoot.width
        contentHeight: rectRoot.height


        ColumnLayout{
            id: rectRoot

            width: parent.width - 10

            height: 1000

            spacing: 2

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "red"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "blue"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "green"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }
        }
    }
}

GridLayout

表格布局
Provides a way of dynamically arranging items in a grid.
与定位器Grid的不同在于,布局管理器GridLayout可以自动调节控件的尺寸以适应页面的大小

【Qt】QML基本使用_第6张图片

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    Flickable{
        id: flick

        ScrollBar.vertical: ScrollBar{}

        anchors.fill: parent

        contentWidth: rectRoot.width
        contentHeight: rectRoot.height


        ColumnLayout{
            id: rectRoot

            width: parent.width - 10

            height: 1000

            spacing: 2

            GridLayout {
                Layout.alignment: Qt.AlignCenter
                //color: "red"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300

                columns: 4
                rows: 2

                flow: GridLayout.LeftToRight

                //id
                Text{
                    text: "id"
                    Layout.alignment: Qt.AlignCenter
                }

                TextField{
                    Layout.alignment: Qt.AlignCenter
                }

                //标志
                Text{
                    text: "flag"
                    Layout.alignment: Qt.AlignCenter
                }

                ComboBox{
                    Layout.alignment: Qt.AlignCenter
                }

                //描述
                Text{
                    text: "描述"
                    Layout.alignment: Qt.AlignCenter
                }

                Rectangle{
                    width: 500
                    height: 200

                    Layout.columnSpan: 3
                    Layout.rowSpan: 2

                    Layout.alignment: Qt.AlignTop | Qt.AlignLeft
                    border.color: "gray"
                    border.width: 1

                    TextArea{
                        anchors.fill: parent
                        width: parent.width


                        TextArea.flickable : TextArea

                    }
                }
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "blue"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "green"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }
        }
    }
}

解决居中的问题

【Qt】QML基本使用_第7张图片

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    Flickable{
        id: flick

        ScrollBar.vertical: ScrollBar{}

        //anchors.fill: parent

        width: 450
        height: parent.height

        contentWidth: rectRoot.width
        contentHeight: rectRoot.height

        anchors.horizontalCenter: parent.horizontalCenter

        ColumnLayout{
            id: rectRoot

            width: parent.width - 10

            height: 1000

            spacing: 2

            GridLayout {
                Layout.alignment: Qt.AlignCenter
                //color: "red"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300

                columns: 4
                rows: 2

                flow: GridLayout.LeftToRight

                //
                Text{
                    text: "id"
                    Layout.alignment: Qt.AlignCenter
                }

                TextField{
                    Layout.alignment: Qt.AlignCenter
                }

                //标志
                Text{
                    text: "flag"
                    Layout.alignment: Qt.AlignCenter
                }

                ComboBox{
                    Layout.alignment: Qt.AlignCenter
                }

                //描述
                Text{
                    text: "描述"
                    Layout.alignment: Qt.AlignCenter
                }

                Rectangle{
                    width: rectRoot.width - 100
                    height: 200

                    Layout.columnSpan: 3
                    Layout.rowSpan: 2

                    Layout.alignment: Qt.AlignTop | Qt.AlignLeft
                    border.color: "gray"
                    border.width: 1

                    TextArea{
                        anchors.fill: parent
                        width: parent.width

                        TextArea.flickable : TextArea
                    }
                }
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "blue"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "green"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }
        }
    }
}

这里是限制了Flickable的宽度,将Flickable控件居中,实现了页面的居中显示;但是相应地,滚动条也被移动到屏幕中间了,这不是我想要的;如果需要修改的话,估计需要手工编写滚动条;

嵌套RowLayout

【Qt】QML基本使用_第8张图片

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14

Rectangle{
    anchors.fill: parent

    Flickable{
        id: flick

        ScrollBar.vertical: ScrollBar{}

        width: 450
        height: parent.height

        contentWidth: rectRoot.width
        contentHeight: rectRoot.height

        anchors.horizontalCenter: parent.horizontalCenter

        ColumnLayout{
            id: rectRoot

            width: parent.width - 10

            height: 1000

            spacing: 2

            GridLayout {
                Layout.alignment: Qt.AlignCenter
                //color: "red"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300

                columns: 2
                rows: 2

                flow: GridLayout.LeftToRight

                RowLayout{
                    Layout.alignment: Qt.AlignLeft
                    spacing: 5
                    //
                    Text{
                        text: "id"

                    }

                    TextField{

                    }
                }


                RowLayout{
                    Layout.alignment: Qt.AlignLeft
                    spacing: 5

                    //标志
                    Text{
                        text: "flag"
                    }

                    ComboBox{
                        model: ["First", "Second", "Third"]
                    }
                }

                RowLayout{
                    Layout.alignment: Qt.AlignLeft
                    spacing: 5

                    Layout.columnSpan: 2

                    //描述
                    Text{
                        text: "描述"
                        Layout.alignment: Qt.AlignLeft | Qt.AlignTop
                    }

                    Rectangle{
                        Layout.fillWidth: true //自动填充剩余的宽度
                        height: 200

                        Layout.alignment: Qt.AlignLeft
                        border.color: "gray"
                        border.width: 1

                        TextArea{
                            anchors.fill: parent
                        }
                    }
                }
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "blue"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }

            Rectangle {
                Layout.alignment: Qt.AlignCenter
                color: "green"
                Layout.preferredWidth: parent.width - 20
                Layout.preferredHeight: 300
            }
        }
    }
}

TableView

表格控件
Provides a list view with scroll bars, styling and header sections.

【Qt】QML基本使用_第9张图片

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4

TableView{
    anchors.fill: parent

    model: ListModel {
        id: libraryModel
        ListElement {
            title: "A Masterpiece"
            author: "Gabriel"
        }
        ListElement {
            title: "Brilliance"
            author: "Jens"
        }
        ListElement {
            title: "Outstanding"
            author: "Frederik"
        }
    }

    property var columnWidth: parent.width / 2

    TableViewColumn {
        role: "title"
        title: "Title"
        width: columnWidth
    }

    TableViewColumn {
        role: "author"
        title: "Author"
        width: columnWidth
    }
}

增加删除按钮

【Qt】QML基本使用_第10张图片
增加一列,在delegate中添加Button,在Button中写clicked()信号的槽函数,删除选中的列

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4

TableView{
    anchors.fill: parent

    model: ListModel {
        id: libraryModel
        ListElement {
            title: "A Masterpiece"
            author: "Gabriel"
        }
        ListElement {
            title: "Brilliance"
            author: "Jens"
        }
        ListElement {
            title: "Outstanding"
            author: "Frederik"
        }
    }

    property var columnWidth: parent.width / 3

    TableViewColumn {
        role: "title"
        title: "Title"
        width: columnWidth
    }

    TableViewColumn {
        role: "author"
        title: "Author"
        width: columnWidth
    }


    TableViewColumn {
        title: "delete";
        width: columnWidth

        delegate: Button{

            text: "删除"

            onClicked: {

                console.log("Row: "+styleData.row + " Column: " + styleData.column)

                libraryModel.remove(styleData.row)
            }
        }
    }
}

布局

Flow布局

来自官方文档

import QtQuick 2.0

Flow {
      anchors.fill: parent
      anchors.margins: 4
      spacing: 10

      Text { text: "Text"; font.pixelSize: 40 }
      Text { text: "items"; font.pixelSize: 40 }
      Text { text: "flowing"; font.pixelSize: 40 }
      Text { text: "inside"; font.pixelSize: 40 }
      Text { text: "a"; font.pixelSize: 40 }
      Text { text: "Flow"; font.pixelSize: 40 }
      Text { text: "item"; font.pixelSize: 40 }
  }

【Qt】QML基本使用_第11张图片

区域折叠

思路:动态设置两块区域的宽度,折叠的时候将宽度置0,显示的时候重新设置为默认值

【Qt】QML基本使用_第12张图片

import QtQuick 2.0

Rectangle{
    id: root

    anchors.fill: parent


    Rectangle{
        id: rectLeft

        anchors.left: parent.left

        width: parent.width / 5

        height: parent.height

        color: "blue"
    }

    property var flag: true

    Rectangle{
        id: rectRight

        anchors.left: rectLeft.right

        width: parent.width - rectLeft.width

        height: parent.height

        color: "red"

        MouseArea{
            anchors.fill: parent

            onClicked: {
                //anchors.fill = parent
                if(flag == true){
                    rectLeft.width = 0

                    rectRight.width = root.width

                    flag = false
                }
                else{
                    rectLeft.width = root.width / 5

                    rectRight.width = root.width - rectLeft.width

                    flag = true
                }

                console.log("单击")
            }
        }
    }
}

加入过渡动画

思路:width变化采用线性动画;

使用Behavior

这样做存在一个问题,就是启动的时候也有动画

import QtQuick 2.0

//区域折叠
Rectangle{
    id: root

    anchors.fill: parent

    property var completed: false
    property var hided: false

    Rectangle {
        id: rectLeft

        anchors.left: parent.left

        width: parent.width / 5

        height: parent.height

        color: "blue"

        Behavior on width {
            NumberAnimation { duration: 300 }
        }
    }

    Rectangle {
        id: rectRight

        anchors.left: rectLeft.right

        width: parent.width - rectLeft.width

        height: parent.height

        color: "red"

        Behavior on width{
            NumberAnimation { duration: 300 }
        }

        MouseArea{
            anchors.fill: parent

            onClicked: {
                //anchors.fill = parent
                if(hided == false){
                    rectLeft.width = 0

                    rectRight.width = root.width

                    hided = true
                }
                else{
                    rectLeft.width = root.width / 5

                    rectRight.width = root.width - rectLeft.width

                    hided = false
                }

                console.log("点击")
            }
        }
    }
}
自定义信号

下面的代码没有实现动画效果,只是记录一下自定义信号的使用

signal <signalName>[([<type> <parameter name>[,]])]
function <functionName>([<parameterName>[, ...]]) { <body> }
import QtQuick 2.0

//区域折叠
Rectangle{
    id: root

    anchors.fill: parent

    property var completed: false
    property var hided: false

    //宽度改变
    signal userWidthChanged()

    Rectangle {
        id: rectLeft

        anchors.left: parent.left

        width: parent.width / 5

        height: parent.height

        color: "blue"

//        Behavior on width {
//            NumberAnimation { duration: 500 }
//        }
    }

    Connections{
        target: rectLeft

        onWidthChanged: {
            console.log("widthChanged")
        }
    }

    Rectangle {
        id: rectRight

        anchors.left: rectLeft.right

        width: parent.width - rectLeft.width

        height: parent.height

        color: "red"

//        Behavior on width{
//            NumberAnimation { duration: 500 }
//        }

        MouseArea{
            anchors.fill: parent

            onClicked: {
                //anchors.fill = parent
                if(hided == false){
                    rectLeft.width = 0

                    rectRight.width = root.width

                    hided = true
                }
                else{
                    rectLeft.width = root.width / 5

                    rectRight.width = root.width - rectLeft.width

                    hided = false
                }

                root.userWidthChanged() //抛出信号

                console.log("点击")
            }
        }
    }

    Component.onCompleted: {
        completed = true
    }
}

状态机

主要是通过Item中的state属性实现

左右两个色块,点击切换左边色块的visible属性

showall
【Qt】QML基本使用_第13张图片
hideleft
【Qt】QML基本使用_第14张图片

import QtQuick 2.14

Rectangle{
    id: root

    anchors.fill: parent

    state: "showall"

    Rectangle{
        id: leftRect
        anchors.left: parent.left
        height: parent.height
        width: parent.width / 2

    }

    Rectangle{
        id: rightRect

        anchors.left: leftRect.right
        width: parent.width - leftRect.width
        height: parent.height
        color: "red"
    }

    MouseArea{
        //z: 1

        anchors.fill: parent
		
		//状态切换
        onClicked: {
            console.log("点击")

            if(root.state === "showall")
                root.state = "hideleft"
            else
                root.state = "showall"
        }
    }

	//状态描述
    states: [
        State {
            name: "showall"
            PropertyChanges {
                target: leftRect
                color: "green"
                visible: true
            }
            PropertyChanges {
                target: rightRect
                color: "red"
                visible: true
            }
        },

        State {
            name: "hideleft"

            PropertyChanges {
                target: leftRect
                visible: false
            }
            PropertyChanges {
                target: rightRect
                color: "red"
                visible: true
            }
        }
    ]
}

绑定

import QtQuick 2.12
import QtQuick.Controls 2.14

Column{
    //visual: true
    spacing: 5

    TextField{
        id: text1
        text: text2.text

        width: 60
        height: 50
    }

    TextField{
        id: text2

        width: 60
        height: 50

        text: text1.text
    }
}

上面的代码可以实现简单的双向绑定

也可以绑定到自定义属性上,更灵活一些

import QtQuick 2.12
import QtQuick.Controls 2.14

Column{
    id: root
    //visual: true
    spacing: 5

    property var myText: "test"

    TextField{
        id: text1
        text: myText

        width: 60
        height: 50
    }

    TextField{
        id: text2

        width: 60
        height: 50

        text: myText
    }

    Button{
        id: button1

        width: 60
        height: 50

        text: "点击"

        onClicked: {
            if(root.myText === "test")
                root.myText = "test2"
            else
                root.myText = "test"
            console.log(root.myText)
        }
    }
}

通信

基本代码

QML中调用Https接口会出现qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed 错误,暂时还没有找到解决办法

ComTools.js

// 提供基于Rest的数据访问服务
// url: 访问地址
// callBack: 回调函数
function request(url, callBack) {

    print('request:')
    var xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
        //return xhr.responseText
    }

    xhr.open("POST", url);
    xhr.send()
}

function requestWithOutCallBack(url) {

    print('request:')
    var xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
        //return xhr.responseText
    }

    xhr.open("POST", url, true);
    xhr.send()
}

ComTestPage.qml

import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import "./ComTools.js" as ComTools

Rectangle{
    id: comTestPage

    anchors.fill: parent

    Button{
        width: 60
        height: 50

        text: "访问"

        onClicked: ComTools.requestWithOutCallBack("http://xxx") //这里是自己做的一个rest api,可以替换成开源的http接口
    }
}

DataAccess.js

通过webapi访问数据库

//查询全表
function selectList(serverAddress, tableName, callBack){
    var operation = "selectList"

    print(operation + ': ')
    var xhr = new XMLHttpRequest()

    //组合成地址
    var url = serverAddress + tableName + "/" + operation

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.send()
}
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import "./ComTools.js" as ComTools
import "./DataAccess.js" as DataAccess

Rectangle{
    id: comTestPage

    anchors.fill: parent

    Button{
        id: button1

        width: 60
        height: 50

        text: "访问"

        onClicked: {
            DataAccess.selectList("http://xxx:8081/", "tableName", function setText(text){ text1.text = text})
    }

    Rectangle{

        anchors.left: button1.right
        anchors.leftMargin: 5

        width: 100
        height: 50

        Text{
            id: text1
            text: "123"
            anchors.fill: parent
        }
    }
}

【Qt】QML基本使用_第15张图片

//下面这种写法等价
		onClicked: {
		            DataAccess.selectList("http://xxx:8081/", "yyy", setText)
		        }

        function setText(text){ text1.text = text}
        }

把函数提出来也是可以的;

crud

访问后端服务的基本代码

//查询全表
function selectList(serverAddress, tableName, callBack){
    let operation = "selectList"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + tableName + "/" + operation

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.send()
}

//条件查询
//key--数据库的字段名字, value--字段的值
function selectByMap(serverAddress, tableName, key, value, callBack){
    let operation = "selectByMap"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + tableName + "/" + operation + "/" + key + "/" + value

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.send()
}

//按id查询
function selectById(serverAddress, tableName, id, callBack){
    let operation = "selectById"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + tableName + "/" + operation + "/" + id

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.send()
}

//按id删除
//userName--用户名, userPwd--密码
function deleteById(serverAddress, tableName, id, userName, userPwd, callBack){
    let operation = "deleteById"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + tableName + "/" + operation + "/" + id

    //发送内容
    let userRequest = {
        className: tableName,
        cmd: operation,
        user: userName,
        password: userPwd,
        data: {}
    }

    //console.log(userRequest.className)

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.send(JSON.stringify(userRequest))
}

//按id更新
//userRequest--用户请求的json对象
function updateById(serverAddress, userRequest, callBack){
    let operation = "updateById"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + userRequest.className + "/" + operation

    //console.log(userRequest.className)

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.send(JSON.stringify(userRequest))
}

//插入
function insert(serverAddress, userRequest, callBack){
    let operation = "insert"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + userRequest.className + "/" + operation

    //console.log(userRequest.className)

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.send(JSON.stringify(userRequest))
}

//得到最大的id值
function getMaxId(serverAddress, tableName, callBack){
    let operation = "getMaxId"

    print(operation + ': ')

    //组合成地址
    let url = serverAddress + "/" + tableName + "/" + operation

    let xhr = new XMLHttpRequest()

    // 第二种请求方式 使用onload
    xhr.onload = function(){
        console.log(xhr.responseText)
        callBack(xhr.responseText)
    }

    xhr.open("POST", url)
    xhr.send()
}

应用打包

本质上就是把依赖的动态库放到你指定的文件夹里面去;这样在其他电脑上运行时,就不需要再安装qt的开发环境了;

【Qt】QML基本使用_第16张图片

  • 在qt creator中使用Release编译;
  • 然后打开qt命令行工具,注意不是windows自带的命令行工具,如下所示;
    在这里插入图片描述
  • 切换到Release路径下,示例如下:
cd /d D:/xxx/xxx/release
  • 在qt命令行中输入下列命令,xxx替换成你自己的exe名字:
windeployqt xxx.exe
  • 如果是qt quick项目,还需要输入下列命令,路径需要替换成自己pc的qml路径:
windeployqt xxx.exe -qmldir C:\Qt\Qt5.14.2\5.14.2\mingw73_32\qml
  • 经过以上的步骤,release文件夹中的就包含了这个qt项目需要的运行环境了;如果还有第三方的dll,可能需要手动copy,某些情况下,qml自带的一些dll也需要手动copy
    【Qt】QML基本使用_第17张图片

你可能感兴趣的:(C/C++,qt,qml)