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

    width:
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

    width:
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

    width:
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

        width:
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";
width: 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

    width:
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---

时间: 2024-10-25 20:38:26

QtQuick桌面应用程序开发指导 3)达到UI而功能_B 4)动态管理Note物_A的相关文章

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; 基

我为什么要录制Java Swing桌面应用程序开发课程

首先在我从事Swing编程的几年中我听到过各种奇谈怪论.大致意思就是Swing桌面软件不合适.我只能呵呵.一个人能力有高低.对事物的看法有不同,都可以接受.但是把无知当个性只能说你太勇敢了.尽管甲骨文如今力推JavaFX.但是Swing还是目前Java桌面开发的主流技术. 本人从事Java语言编程超过11年.其中有五年左右的时间是在从事Java Swing桌面应用程序开发.其它几年是在从事J2EE与spring3 MVC开发,结合自身实践感慨颇多,感觉自己技术进步最大最快的几年恰恰是从事J2SE

Electron 构建桌面应用程序开发资料整理

Electron 是什么? Electron 是一个程序库,基于Electron库我们可以使用HTML.CSS.JS来开发跨平台桌面应用程序(building cross-platform desktop applications with HTML, CSS, and JavaScript.) 学习资源  Electron 官网文档  https://electron.atom.io/docs/ Electron 实现原理 ? Electron 结合了 Chromium 开源浏览器和带有一系列

QtQuick桌面应用开发指导 4)动态管理Note对象_B 5)外观加强 6)更多改进

4.2.2 Stateless(状态无关的)JavaScript库 为了让开发轻松点, 使用一个JavaScript接口来和数据库交互是个好主意, 它在QML中提供了方便的方法; 在QtCreator中创建一个新的JavaScript文件 noteDB.js, 保证选择了 State Library选项; 这样使得noteDB.js用起来像一个库, 提供了stateless的helper方法; 这样,在每个QML组件 import noteDB.js以及使用它的时候, 就只有一份实例被加载和使用

Electron与WEB桌面应用程序开发及其它

这几天在构思项目,研究了一下Electron,记录下来. 说起WEB桌面程序,当前最火的就是Electron了. Electron的架构用一句话总结,就是一个main.js进程加上一个或数个chrome窗口,每个窗口都包含一个独立的Node.js. 这样的架构,使得这种桌面应用必须是一个(或数个)单页面应用(SPA),而这个SPA还拥有访问本地API的能力(Node.js). 一方面,程序对前端框架的依赖必然加强,想再JQuery打天下就不那么容易了:另一方面也大大加强了前端框架的能力与版图.

mpvue小程序开发之 集成第三方UI框架Vant Weapp UI

集成进第三方的UI框架其实很简单 这里把vant-weapp的dist目录重命名为vant-weapp放在项目根目录的static文件夹下: 在src文件夹下,即我们写vue代码的位置,正在编写的页面中添加main.json文件(vue代码编译成小程序代码时会直接使用这个文件) { "usingComponents": { "van-button": "/static/vant-weapp/button/index" //这个路径是指根目录下st

微信小程序开发(3) 热门电影

在这篇微信小程序开发教程中,我们将介绍如何使用微信小程序开发热门电影及预览功能. 本文主要分为两个部分,小程序主体部分及计算器业务页面部分 一.小程序主体部分 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下: 1. 小程序逻辑 App({ onLaunch: function() { // Do something initial when launch. }, onShow: function() { // Do something when show. }, onHide: f

QtQuick桌面应用开发指导 1)关于教程 2)原型和设计 3)实现UI和功能_A

Release1.0 http://qt-project.org/wiki/developer-guides Qt Quick Application Developer Guide for Desktop 这个教程的目的是让你熟悉使用QtQuick构建QML程序的最佳编程实践方法; 先决条件: 对QML有相当的理解, 相关阅读: <qtquick/qtquick-applicationdevelopers.html>; 本教程会涉及QML开发最佳实践的各个方面, 以及在典型的桌面环境下部署应

QtQuick桌面应用开发指导 7)创建应用 8)扩展

CHAPTER7 部署NotApp应用 现在我们要让程序在典型的桌面环境中可用, 可部署; 如第一章所描述, 我们在QtCreator中使用QtQuick UI项目开发NoteApp程序; 这意味着qmlscene用来加载main.qml, 随之让NoteApp运行; 首先, 让NoteApp可用的最简单方案是创建一个package(包)将所有qml文件, qmlscense和一个可以运行qmlscense加载main.qml的简单脚本bundle(捆扎)起来; 你需要参考每一个桌面平台, 了解