zoukankan      html  css  js  c++  java
  • QtQuick桌面应用程序开发指导 3)达到UI而功能_B 4)动态管理Note物_A

    3.2 把Page Item和Marker Item绑定

    之前我们实现了PagePanel组件, 使用了三个state来切换Page组件的opacity属性; 这一步我们会使用Marker和MarkerPanel组件来实现页面导航; 

    在原型阶段, MarkerPanel组件十分简单, 没有不论什么功能; 它使用了Repeater类型来产生三个QML Item以及Marker组件作为delegate; 

    MarkerPanel应该存储当前激活的marker(标记), 即那个被用户点击的marker; 基于MarkerPanel中激活的marker, PagePanel会更新它的state属性; 我们须要将PagePanel的state属性和MarkerPanel新的属性--持有当前激活marker的属性绑定起来;

    在MarkerPanel中定义一个string属性--activeMarker;

    // MarkerPanel.qml

    1
    2
    3
    4
    5
    6
    7
    Item {
        id: root
         150;    height: 450
        // a property of type string to hold
        // the value of the current active marker
        property string activeMarker: "personal"
    //...

    我们能够把一个markerid值存储起来, 用来唯一地识别marker item; 这样, activeMarker会持实用户所点击的marker item的markerid的值, 

    依据model, Repeater元素能够产生三个marker item, 因此我们能够使用一个model来存储markerid值, 然后在Repeater中使用;

    // MarkerPanel.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    Item {
        id: root
         150;    height: 450
        // a property of type string to hold
        // the value of the current active marker
        property string activeMarker: "personal"
     
        // a list for holding respective data for a Marker item.
        property variant markerData: [
            { markerid: "personal" },
            { markerid: "fun" },
            { markerid: "work" }
        ]
     
        Column {
            id: layout
            anchors.fill: parent
            spacing: 5
            Repeater {
                // using the defined list as our model
                model: markerData
                delegate: Marker {
                    id: marker
                    // handling the clicked signal of the Marker item,
                    // setting the currentMarker property
                    // of MarkerPanel based on the clicked Marker
                    //MouseArea {
                        //anchors.fill: parent
                        onClicked: root.activeMarker = modelData.markerid
                    //}
                }
            }
        }
    }

    上述代码中我们在onClicked signal handler中设置了 activeMarker属性; 这意味着我们已经在Marker组件中定义了一个clicked() signal来通知用户的鼠标点击事件; 

    // Marker.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Item {
        id: root
         50; height: 90
        signal clicked()
        MouseArea {
            id: mouseArea
            anchors.fill: parent
            // emitting the clicked() signal Marker item
            onClicked: root.clicked()
        }
    }

    眼下我们以后有了PagePanel组件使用state属性来管理page, 让MarkPanel组件能够识别激活的marker, 因此, 切换各个page的可见性能够通过改变page的opacity属性来做到;

    来看看如何使用 activeMarker属性来相应地更新PagePanel的state;

    在main.qml里面, 已经有了Page item和 MarkerPanel定位好了, 我们会创建以及使用PagePanel item而不是各自使用anchor定位;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
        // creating a MarkerPanel item
        MarkerPanel {
            id: markerPanel
             50
            anchors.topMargin: 20
            anchors {
                right: window.right
                top: window.top
                bottom: window.bottom
            }
        }
    //...
        // creating a PagePanel item
        PagePanel {
            id: pagePanel
            // binding the state of PagePanel to the
            // activeMarker property of MarkerPanel
            state: markerPanel.activeMarker
            anchors {
                right: markerPanel.left
                left: toolbar.right
                top: parent.top
                bottom: parent.bottom
                leftMargin: 1
                rightMargin: -50
                topMargin: 3
                bottomMargin: 15
            }
        }

    上面代码中, 我们能够看到QML的 property binding特性, 能够把state属性和activeMarker属性绑定起来; 这样不论activeMarker通过用户操作获得了什么值, 同样的值会被分配给PagePanel的state属性, 这样就能开关各个page的可见性了;

    下一步

    给出如何使用使用图形来强化UI的细节;


    3.3 加入graphics(图形)

    由于QML的特性, 开发和设计全然能够一起紧密工作, 贯彻整个开发生命期; 现在, 使用graphics让用户体验有了非常大的不同, 这也是程序让用户感受到的地方;

    QML鼓舞在UI实现过程中尽可能地使用graphics; 使用QML让图形设计和开发之间的协作更easy, 设计能够立马在主要的UI元素上測试graphics; 这帮助设计来理解在开发新的组件时, 程序猿会须要什么, 这也让程序的UI更有吸引力并且某种程度上更易维护;

    3.3.1 给组件设置背景图片

    BorderImage类型推荐使用的情况是: 在你想要把一个图片scale(按比例拉伸), 但它的border(边界)保持不变的时候; 这样的类型的一个好的用例(use case)是在QML item上有阴影效果的背景图片; 你的item可能会在某些时刻scale可是须要保持corners(四角)不变; 

    来看下如何在组件中将BorderImage设置成背景;

    // PagePanel.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //...
        BorderImage {
            id: background
            // filling the entire PagePanel
            anchors.fill: parent
            source: "images/page.png"
            // specifying the border margins for each corner,
            // this info should be given by the designer
            border.left: 68; border.top: 69
            border.right: 40; border.bottom: 80
        }

    // Note.qml

    1
    2
    3
    4
    5
    6
    7
    BorderImage {
        id: noteImage
        anchors { fill: parent}
        source: "images/personal_note.png"
        border.left: 20; border.top: 20
        border.right: 20; border.bottom: 20
    }

    Warning: 注意BorderImage类型要以正确的次序在组件里面使用, 由于实现的次序定义了显示的顺序; 具有同样 z值的item显示的次序是按它们被声明的次序决定的; 很多其它细节參考stack ordering of items-- z property;

    当这些item都在MarkerPanel组件中创建的时候, 如何才是给Marker item设置背景的最佳方案--这个方法已经在MarkerPanel中展现了;

    这里有个markerData list, 把它作为model给Repeater来创建Marker item, 当一个marker item被点击的时候, 设置markerid作为activeMarker; 我们能够扩展markerData, 存储一个图像的的url路径, 使用Image类型作为Marker组件的顶层类型;

    // Marker.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // The Image type as top level is convenient
    // as the Marker component simply is a graphical
    // UI with a clicked() signal.
    Image {
        id: root
        // declaring the clicked() signal to be used in the MarkerPanel
        signal clicked()
        // creating a MouseArea type to intercept the mouse click
        MouseArea {
            id: mouseArea
            anchors.fill: parent
            // emitting the clicked() signal Marker item
            onClicked: root.clicked()
        }
    }

    这样就能够增强MarkerPanel组件;

    // MarkerPanel.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //...
        // for the markerData, we add the img value pointing to the image url
        property variant markerData: [
            { img: "images/personalmarker.png", markerid: "personal" },
            { img: "images/funmarker.png", markerid: "fun" },
            { img: "images/workmarker.png", markerid: "work" }
        ]
     
        Column {
            id: layout
            anchors.fill: parent
            spacing: 5
            Repeater {
                // using the defined list as our model
                model: markerData
                delegate: Marker {
                    id: marker
                    // binding the source property of Marker to that
                    // of the modelData' s img value.
                    // note that the Marker is an Image element
                    source: modelData.img
                    // handling the clicked signal of the Marker item,
                    // setting the currentMarker property
                    // of MarkerPanel based on the clicked Marker
                    onClicked: root.activeMarker = modelData.markerid
                }
            }
        }

    上述代码中, 能够看到Marker item的source属性是怎样绑定到markerData model的image值的;

    我们使用了BorderImage类型来为NoteToolbar组件设置背景, 也作为main.qml的顶层类型;

    Note 关于图像的border margins, 以及图像的怎样anchor和align(对齐), 要和graphics设计讨论清楚;

    MarkerPanel组件看起来是这种:

    然后来看看如何在原型阶段使用graphics依照设计来增强toolbar;

    3.3.2 创建Tool组件

    基于代码重用考虑, 定义一个新组件给toolbar中的New Note和Clear All工具使用; 这是为什么我们已经实现了一个Tool组件, 使用Image类型作为顶层类型, 处理鼠标点击事件;

    Image类型经经常使用作UI元素自身, 不论是静态的或是动绘图像; 它会按像素布局, 能够非常好地依照设计需求来定义;

    // Tool.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Use Image as the top level type
    Image {
        id: root
        // defining the clicked signal
        signal clicked()
        // using a MouseArea type to capture
        // the mouse click of the user
        MouseArea {
            anchors.fill: parent
            // emitting the clicked() signal of the root item
            onClicked: root.clicked()
        }
    }

    如今用Tool组件来创建toolbar; 我们从原型阶段改动代码, 用Tool item取代Rectangle元素;

    //main.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //...
        // toolbar background
        Rectangle {
            anchors.fill: toolbar
            color: "white"
            opacity: 0.15
            radius: 16
            border { color: "#600"; 4 }
        }
     
        // using a Column element to layout
        // the Tool items vertically
        Column { // sidebar toolbar
            id: toolbar
            spacing: 16
            anchors {
                top: window.top
                left: window.left
                bottom: window.bottom
                topMargin: 50
                bottomMargin: 50
                leftMargin: 8
            }
            // new note tool
            Tool {
                id: newNoteTool
                source: "images/add.png"
            }
            // clear page tool
            Tool {
                id: clearAllTool
                source: "images/clear.png"
            }
        }

    如今我们给我们的组件设置了全部的graphics, 程序应该有了更吸引人的外观和很多其它定义好的UI了;

    下一步

    下一章具体介绍怎样动态地创建和管理Note item以及怎样在本地数据库存储它们;

    ---3End---


    CHAPTER4 动态管理Note对象

    我们眼下看到的QML是一个很强大的声明性(declarative)语言, 和JavaScript组合使用让它更强大; QML不仅提供了 inline JavaScript, 并且还能够把整个JavaScript库导入到文件里;

    NoteApp的核心功能是能够让用户去创建, 改动和删除note, 可是程序应该也要自己主动存储note, 无需提示;

    这一章会指导怎样使用JavaScript来给QML代码加入逻辑, 实现本地存储--Qt Quick Local Storage

    本章主要主题:

    - 使用JavaScript实现动态对象管理的功能;

    - 怎样使用 Qt Quick Database API 进行本地数据存储;


    4.1 创建和管理Note Item

    用户应该随时能够创建和删除note, 这意味着我们的代码应该能够动态地创建和删除Note item; 有多种方式创建和管理QML对象; 其实, 我们已经使用一种--Repeater类型; 创建一个QML对象意味着在创建组件的实例之前, 组件必需要被创建和载入起来;

    QML对象能够通过 createObject(Item parent, object properties) JavaScript方法在组件上创建; 很多其它细节參考 Dynamic Object Management in QML http://qt-project.org/doc/qt-5/qtqml-javascript-dynamicobjectcreation.html  

    4.1.1 Note对象的动态创建

    我们知道一个Note item是属于Page组件的, 它负责note对象的创建, 也会从数据库中读取笔记; 

    如前面所说, 首先载入Note组件:

    // Page.qml

    1
    2
    3
    4
    5
    6
    //...
        // loading the Note Component
        Component {
            id: noteComponent
            Note { }
        }

    如今我们来定义一个Javascript方法, 创建QML Note对象; 创建QML对象的时候, 必须保证一个參数是这个对象的parent; 在Page组件中持有一个Note item容器(container)是个管理note对象的好主意, 这样我们能够在数据库中保存这些note;

    // Page.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //...
        // creating an Item element that will be used as a note container
        Item { id: container }
     
        // a Javascript helper function for creating QML Note objects
        function newNoteObject(args) {
            // calling the createObject() function on noteComponent item
            // and the container item will be the parent of the new
            // object and args as the set of arguments
            var note = noteComponent.createObject(container, args)
            if(note == null)
                console.log("note object failed to be created!")
        }

    在前面显示的代码中, 我们看到一个新的 note item对象是如何在 newNoteObject()方法中创建的; 新建的 note对象隶属于container item;

    如今我们要在toolbar上的new note tool被按下的时候调用这种方法, toolbar在main.qml中; 因为PagePanel组件知道当前可见的page item, 我们能够在PagePanel中创建一个新的属性来存储那个page;

    // PagePanel.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    //...
        // this property holds the current visible page
        property Page currentPage: personalpage
     
        // creating the list of states
        states: [
            // creating a state item with its corresponding name
            State {
                name: "personal"
                PropertyChanges {
                    target: personalpage
                    opacity:1.0
                    restoreEntryValues: true
                }
                PropertyChanges {
                    target: root
                    currentPage: personalpage
                    explicit: true
                }
            },
            State {
                name: "fun"
                PropertyChanges {
                    target: funpage
                    opacity:1.0
                    restoreEntryValues: true
                }
                PropertyChanges {
                    target: root
                    currentPage: funpage
                    explicit: true
                }
            },
            State {
                name: "work"
                PropertyChanges {
                    target: workpage
                    opacity:1.0
                    restoreEntryValues: true
                }
                PropertyChanges {
                    target: root
                    currentPage: workpage
                    explicit: true
                }
            }
        ]

    我们改动了三个state来给currentPage属性设置合适的值; 

    在main.qml中, 看看在new note tool被点击的时候如何调用方法来创建新的note对象;

    // main.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // using a Column element to layout the Tool items vertically
    Column {
        id: toolbar
        spacing: 16
        anchors {
            top: window.top; left: window.left; bottom: window.bottom
            topMargin: 50; bottomMargin: 50; leftMargin: 8
        }
        // new note tool, also known as the plus icon
        Tool {
            id: newNoteTool
            source: "images/add.png"
            // using the currentPage property of PagePanel and
            // calling newNoteObject() function without any arguments.
            onClicked: pagePanel.currentPage.newNoteObject()
        }

    4.1.2 删除Note对象

    删除Note对象是个更直接的过程, 由于QML item类型提供了一个JavaScipt方法--destroy() http://qt-project.org/doc/qt-5/qtqml-javascript-dynamicobjectcreation.html#deleting-objects-dynamically  ; 由于我们已经有一个container item的children是Note item, 我们能够简单地对children逐个地调用 destroy; 

    在Page组件上, 定义一个方法来为我们运行操作:

    // Page.qml

    1
    2
    3
    4
    5
    6
    7
    //...
        // a JavaScript helper function for iterating through the children elements of the
        // container item and calls destroy() for deleting them
        function clear() {
            for(var i=0; i<container.children.length; ++i)
                container.children[i].destroy()
        }

    在main.qml文件里, 我们在clear tool被按下时调用 clear()方法:

    1
    2
    3
    4
    5
    6
    7
    //...
            // the clear tool
            Tool {
                id: clearAllTool
                source: "images/clear.png"
                onClicked: pagePanel.currentPage.clear()
            }

    为了让用户能够独立地删除每个note, 我们在NoteToolbar组件中为Note组件加入了一个tool; 能够使用签名实现的Tool组件:

    // Note.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //...
        // creating a NoteToolbar that will be anchored to its parent
        NoteToolbar {
            id: toolbar
            height: 40
            anchors { top: root.top; left: root.left; right: root.right }
            // using the drag property alias to set the drag.target to our Note item.
            drag.target: root
            // creating the delete  tool for deleting the note item
            Tool {
                id: deleteItem
                source: "images/delete.png"
                onClicked: root.destroy()
            }
        }

    下一步

    关于怎样在本地数据库存储note item的具体步骤;


    4.2 从数据库存储和读取数据

    眼下我们实现了NoteApp的功能: 实时地创建和管理note item;
    这里我们会了解在本地数据库存储note的具体实现; QML提供了一个简单的 Qt Quick Local Storage API, 使用SQLite数据库来实现我们想要的功能;

    首选的方式是在NoteApp程序启动的时候从数据库读取note, 然后在程序关闭的是保存它们; 用户不会收到提示;

    4.2.1 定义数据库

    NoteApp的数据库非常easy; 它仅仅有一个table--note table, 包括我们所保存的note的信息;

    看一下Table的定义, 让我们了解下Note组件的哪些属性或哪些新的数据应该被引入:

    x和y是每一个QML item都有的几何属性; 从Note item获得这些值非常easy; 这些值会用来粗糙你note在page中的位置; noteText是note的实际文字, 我们能够从Note组件中的Text元素中获取它们, 我们应该定义一个alias(别名_)--noteText; noteId和markerId是每一个note item都该有的标识符; noteId是一个唯一的标识符, 数据库须要用到, markerId用来标识note item属于哪一个page; 因此我们会在Note组件里面加入两个新的属性;

    // Note.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Item {
        id: root
         200; height: 200
     
        property string markerId;
        property int  noteId;
        property alias noteText: editArea.text
    //...
        // creating a TextEdit
        TextEdit {
            id: editArea
    //...

    考虑到Page组件负责创建Note item, 它应该也知道Note和哪个markerId相关联; 当一个新的Note item创建出来(不是从数据库读取), Page应该设置好Note的markerId属性;

    使用一个JavaScript的 helper方法:

    // Page.qml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Item {
        id: root
     
        // this property is held for helping store
        // the note items in the database
        property string markerId
        // this Javascript helper function is used to create,
        // Note items not loaded from database so that it will set
        // the markerId property of the note.
        function newNote() {
            // calling the newNoteObject and passing the a set of
            // arguments where the markerId is set.
            newNoteObject( { "markerId": root.markerId } )
        }
    //...

    先前在main.qml中, 我们使用了 newNoteObject()方法, 但就如前面解释的, 我们须要用newNote()方法取代它来达到目的;

    如今每一个Page组件有个markerId属性, 能够在Note item被创建的时候设置markerId; 我们必须保证page的markerId属性在Page item在PagePanel组件中创建的时候就被设置了;

    // PagePanel.qml

    1
    2
    3
    4
    5
    //...
    // creating three Page items that are anchored to fill the parent
    Page { id: personalpage; anchors.fill: parent; markerId: "personal" }
    Page { id: funpage; anchors.fill: parent; markerId: "fun" }
    Page { id: workpage; anchors.fill: parent; markerId: "work" }

    眼下我们保证的:

    - 从相应的关系数据库[relational database http://en.wikipedia.org/wiki/Relational_database ]得到的note和page之间的关系是正确的;

    - 每一个Note item有一个唯一的ID, ID属于page, 能够识别marker ID;

    - 这些属性值要被正确设置;

    以下来读取和存储笔记;

    ---TBC---


  • 相关阅读:
    leetcode二叉树相同的树
    leetcode二叉树中序遍历
    leetcode二叉树前序遍历
    leetcode数组中级Lc287.寻找重复数
    概要设计说明书
    leetcode二叉树对称二叉树
    小数点处理详解:切舍、切上、四舍五入
    C++多态的两种使用方式
    让Ogre的资源管理器为我们服务
    地形纹理Splatting技术(翻译)
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/4840398.html
Copyright © 2011-2022 走看看