QML Virtual Keyboard 转自Intel开发人员专区

Introduction

QML, also called Qt* Quick, is a declarative language that is built on top of the Qt* libraries for rapid application development. Qt* Quick is a new addition to the family of Qt* technologies that bring together a very fast standards compliant JavaScript engine and the Qt* C++ libraries. While Qt* Quick does emphasize graphical and touch based applications, developers are building all types of rich, interactive components that extend QML and promise to make QML a very wide reaching and interesting programming environment.

In this tutorial, we are going to walk through the creation of a sample QML application that includes a virtual keyboard component. By the end, you should have a good understanding of how QML lets a developer rapidly create an application, how to create components for your application and how to integrate your QML with C++ and compile

For more details on designing MeeGo* applications see the MeeGo* Application Design Guidelines.


Create a new Qt* Quick Application Project

Open Qt* Creator and select File -> New File or Project ->Qt* Quick Project -> Qt* Quick Application

Name this project "VirtualKeyBoard"

A new project with a default main.qml will be created

Override, main.qml with the contents given below

main.qml
import QtQuick 1.0

Rectangle {
    id: window

    width: 600; height: 380
    color: "#282828"
}

This will give us a rectangular gray screen, the canvas for our application.


QML Components for your application

Let's start by setting up our directory structure.

Right click on the QML section of the your new project, select "Add New" and create a new QML file named. Now a new dialogue box should appear that will allow you to create a new QML file. Click on the browse button and create a new directory named "components". Lastly, give the name "LineEdit.qml" to your new file.

Here is the contents of LineEdit.qml.

import QtQuick 1.0

FocusScope {
    id: scope

    //FocusScope needs to bind to visual properties of the children
    property alias color: rectangle.color
    property alias text : displayText.text
    x: rectangle.x
    y: rectangle.y
    width: rectangle.width
    height: rectangle.height

    Rectangle {
        id: rectangle
        //property alias text : displayText.text

        /* set the default values for the LineEdit */

        // when active there should be an orange border
        border.color: "orange"
        border.width: scope.activeFocus ? 5 : 0

        // Properties that are passed in to this Item
        width: parent.width
        height: parent.height

        // Properties that are given a value
        radius: 10
        focus: false

        Text {
            id: displayText
            anchors {
                left: parent.left;
                verticalCenter: parent.verticalCenter;
                margins: 6
            }

            // The width must be set for eliding to work properly
            width: parent.width - 6

            // Text properties
            font.pixelSize: parent.height * .6;  // Text should fit in the the field
            elide: Text.ElideLeft   // Scroll the text to the left when it is longer
            // than the width of the LineEdit
            color: "#343434"
            smooth: true;           // Use font smoothing
            font.bold: true         // font is bold
            font.family: "Times New Roman"
        }
    }
    MouseArea { anchors.fill: parent; onClicked: { scope.focus = true } }
}


Qt* Quick allows you to quickly create QML components simply by creating a file that begins with an uppercase character and contains only one high-level item. This higher level item can be referred to in other QML files simply by referring to the name of the file. You will see how to create and use the LineEdit component later on in our main.qml.


Walk through of the LineEdit.qml file

Line 1: the first line of every QML file should be to import the Qt* Quick libraries.

import QtQuick 1.0

Line 3: on line three, we declare a FocusScope. A FocusScope defines a boundary for a focus.

FocusScope {
    id: scope

Lines 9-12: A focusScope by itself does not have any physical dimensions and must inherit the dimensions of its child element.

    //FocusScope needs to bind to visual properties of the children
    property alias color: rectangle.color
    property alias text : displayText.text
    x: rectangle.x
    y: rectangle.y
    width: rectangle.width
    height: rectangle.height

Line 14: The Rectangle element is one of the most basic of all QML elements. On line 15, we give the Rectangle an indentifier that can be used to reference this Rectangle object in other parts of the code.

  Rectangle {
          id: rectangle

Lines 21 and 22 define that this Rectangle should have an orange border. When it has the focus the width of the border is 5 pixels and when it does not the width is 0 pixels. This border gives a visual indicator that the use has clicked on the LineEdit.

        // when active there should be an orange border
        border.color: "orange"
        border.width: scope.activeFocus ? 5 : 0

Lines 25-30 are setting the width, height, radius and focus properties of this LineEdit. These properties are the default values and can be overridden when a LineEdit is instantiated. Notice that the width and height properties are set to the parents elements width and height. In this case, the parent is the FocusScope and if you examine the FocusScope, it is simply using the value of the rectangle since the FocusScope element is not a visual element, the properties of its children need to be exposed to the FocusScope. Also note that there is no default value specified therefore we must set it when instantiating a LineEdit.

        // Properties that are passed in to this Item
        width: parent.width
        height: parent.height

        // Properties that are given a value
        radius: 10
        focus: false

Line 32-33: On this line, we instantiate a Text element and give it a name.

        Text {
            id: displayText
            anchors {

Line 34-38: Here we describe how the Text element is to be positioned. In QML, visual items are often laid out relatively to each other using anchors. In this example, we have the following lines.

anchors {
                left: parent.left;
                verticalCenter: parent.verticalCenter;
                margins: 6
}

These can be read as:

  1. The left anchor of the Text element is positioned next to it's parent's left anchor, in other words, the text will start on the left side of the Rectangle.
  2. The verticalCenter of the Text element is positioned the same a the parent's vertical center, in other words, text should be in the vertical center of the Rectangle
  3. The top, right, bottom and left sides of the Text element have a margin of 6 pixels.

Lines 41-50: Here the width, size, color, smoothing, eliding, bold and font family properties are all set. Setting the elide property to Text.ElideLeft means that if the text typed fills the width of the Text element the text will begin to scroll to the left, keeping all displayed text inside the boundaries of the Text element.

  // The width must be set for eliding to work properly
            width: parent.width - 6

            // Text properties
            font.pixelSize: parent.height * .6;  // Text should fit in the the field
            elide: Text.ElideLeft   // Scroll the text to the left when it is longer
            // than the width of the LineEdit
            color: "#343434"
            smooth: true;           // Use font smoothing
            font.bold: true         // font is bold
            font.family: "Times New Roman"

Lastly line 53: A MouseArea is defined that fills it's entire parent. When the MouseArea is clicked then the parent receives the focus.

  MouseArea { anchors.fill: parent; onClicked: { scope.focus = true } }



Using QML Components in main.qml

Now that we have a LineEdit component, let's look at our main.qml which declares two of them. The reason for declaring two of them is so later when we attach a virtual keyboard, we can click between them and show that the focus determines which lineEdit receives the typed characters.

Here is the new code for main.qml

import QtQuick 1.0
import "components"
import "components/ops.js" as Ops

Rectangle {
    id: window

    width: 600; height: 380
    color: "#444444"

    property string rotateLeft: "\u2939"
    property string rotateRight: "\u2935"
    property string leftArrow: "\u2190"
    property string upperArrow: "\u2191"
    property string division : "\u00f7"
    property string multiplication : "\u00d7"
    property string squareRoot : "\u221a"
    property string plusminus : "\u00b1"

    // if the background is clicked then other Items lose focus
    MouseArea {
     anchors.fill: parent
            onClicked: { parent.focus = true; vk.state = "inactive"; }
    }

    Column {
        spacing: 8
        anchors { fill: parent; margins: 10 }

    LineEdit {
            id: lineEdit1
            width: parent.width; height: 64
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    vk.state = "active";
                    parent.focus = true
                }
            }
        }

        LineEdit {
            id: lineEdit2
            width: parent.width; height: 64
            MouseArea {
                anchors.fill: parent
                onClicked: { vk.state = "active"; parent.focus = true }
            }
        }
    }

    VirtualKeyboard {
        id: vk;
        onButtonPressed: {
            console.log("Keyboard sent: " + op)
            Ops.doOperation(op)
        }
    }
}


Creating an In-Application Virtual Keyboard: The Button Component

A virtual keyboard is usually created by arranging key buttons in a particular order. So a Button is a natural fit for a new QML component.

Right click on the QML section of the your new project, select "Add New" and create a new QML file named "Button.qml". The new dialogue box should appear that will allow you to create a new QML file. Click on the browse button and create a new directory named "components". Lastly, give the name "Button.qml" to your new file.

import QtQuick 1.0

BorderImage {
    id: button

    property alias text: buttonText.text
    property string op: buttonText.op
    property alias shiftText: buttonShiftText.text
    property string shiftOp: buttonShiftText.op

    signal clicked(string op)

    width: 50
    height: 50

    source: "images/button.png"; clip: true
    border { left: 10; top: 10; right: 10; bottom: 10 }

    Rectangle {
        id: shade
        anchors {fill: button;}
        radius: 10;
        color: "black";
        opacity: 0
    }

    Text {
        id: buttonText
        anchors { centerIn: parent; }
        font.family: "Times New Roman"
        font.pixelSize: parent.width > parent.height ? parent.height * .6 : parent.width * .6
        style: Text.Sunken;
        color: "black";
        styleColor: "black";
        smooth: true
    }

    Text {
        id: buttonShiftText
        anchors {
            top: parent.top
            right: parent.right
            rightMargin: 8
        }

        font.pixelSize: parent.width > parent.height ? parent.height * .25 : parent.width * .25
      // style: Text.Sunken;
        color: "white";
        styleColor: "black";
        smooth: true
    }

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        onClicked: button.clicked(op)
    }

    states: State {
        name: "pressed"; when: mouseArea.pressed == true
        PropertyChanges { target: shade; opacity: .3 }
    }
}


The Button is a more interesting component then the LineEdit for a couple of reasons. First of all, the Button introduces QML Signals. Signals allow QML elements to send messages and data to each other. Secondly, the Button introduces the ImageBorder element which is used to define a background that can scale in size while maintaining particular aspect ratios for the borders.

QML Signal and Handler System

QML is built on top of Qt* and therefore inherits Qt*'s meta-object and signal systems. Signals defined in Qt* C++ can be sent or received in QML.

Signals allow Qt* objects to communicate between each other. For example, the MouseArea clicked signal notifies other elements that the mouse has been clicked within the area.

The Button element defines the clicked signal that sends the character that the user touched. Notice that the event is triggered from the MouseArea's onClicked function.

Creating an In-Application Virtual Keyboard: The Keyboard Component

Here is the source code for VirtualKeyboard.qml

import QtQuick 1.0

Rectangle {
    id: vk

    property string rotateLeft: "\u2939"
    property string rotateRight: "\u2935"
    property string leftArrow: "\u2190"
    property string upperArrow: "\u2191"
    property string division : "\u00f7"
    property string multiplication : "\u00d7"
    property string squareRoot : "\u221a"
    property string plusminus : "\u00b1"

    signal buttonPressed(string op)

    // span the parent's width
    anchors {
        left: parent.left
        right: parent.right
    }

    // A virtual keyboard (VK) is attached the the bottom of an Item
    // and slides up when activated.
    // this item is called the surface
    property Item surface: parent

    // By default the VK's height is at the bottom of the surface
    y: surface.height

    // Depending on the parent's focus, set the initial state
    state: parent.activeFocus ? "active" : "inactive"

    Column {
        id: vkc;

        anchors {
            horizontalCenter: parent.horizontalCenter
            margins: 4
        }

        spacing: 6

        Row {
            spacing: 6
            anchors.horizontalCenter: parent.horizontalCenter
            Button { text: "q"; op: "q"; shiftText: "1"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "w"; op: "w"; shiftText: "2"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "e"; op: "e"; shiftText: "3"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "r"; op: "r"; shiftText: "4"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "t"; op: "t"; shiftText: "5"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "y"; op: "y"; shiftText: "6"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "u"; op: "u"; shiftText: "7"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "i"; op: "i"; shiftText: "8"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "o"; op: "o"; shiftText: "9"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "p"; op: "p"; shiftText: "0"; Component.onCompleted: clicked.connect(buttonPressed)}
        }
        Row {
            spacing: 6
            anchors.horizontalCenter: parent.horizontalCenter
            Button { text: "a"; op: "a"; shiftText: "@"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "s"; op: "s"; shiftText: "#"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "d"; op: "d"; shiftText: "%"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "f"; op: "f"; shiftText: "&"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "g"; op: "g"; shiftText: "*"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "h"; op: "h"; shiftText: "-"; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "j"; op: "j"; shiftText: "+"; shiftOp: "+" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "k"; op: "k"; shiftText: "="; shiftOp: "=" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "l"; op: "l"; shiftText: "/"; shiftOp: "/" ; Component.onCompleted: clicked.connect(buttonPressed)}
        }
        Row {
            spacing: 6
            anchors.horizontalCenter: parent.horizontalCenter
            Button { text: "z"; op: "z"; shiftText: "_"; shiftOp: "_" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "x"; op: "x"; shiftText: "$"; shiftOp: "$" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "c"; op: "c"; shiftText: "^"; shiftOp: "%" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "v"; op: "v"; shiftText: "\""; shiftOp: "&" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "b"; op: "b"; shiftText: "\'"; shiftOp: "*" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "n"; op: "n"; shiftText: ":"; shiftOp: "-" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "m"; op: "m"; shiftText: ";"; shiftOp: "+" ; Component.onCompleted: clicked.connect(buttonPressed)}
        }
        Row {
            spacing: 6
            anchors.horizontalCenter: parent.horizontalCenter
            Button { text: upperArrow; op: upperArrow ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { text: "Alt"; op: "" ; Component.onCompleted: clicked.connect(buttonPressed)}
            Button { width: 200; height: vk.h; text: "Space"; op: " "; Component.onCompleted: clicked.connect(buttonPressed) }
            Button { width: 80; text: ".com"; op: ".com"; Component.onCompleted: clicked.connect(buttonPressed) }
            Button { text: leftArrow; op: leftArrow; Component.onCompleted: clicked.connect(buttonPressed) }
        }
    }

    states: [
        State {
            name: "active"
            PropertyChanges { target: vk; y: (surface.height - vkc.height); }
        },
        State {
            name: "inactive"
            PropertyChanges { target: vk; y: surface.height + 8; }
        }
    ]

    transitions: Transition {
        NumberAnimation {
            target: vk
            property: "y"
            easing.type: "OutQuad"
            duration: 250
        }
    }
}


There are a couple of features to point out here. First of all, the string, "Component.onCompleted: clicked.connect(buttonPressed)}" is repeated on each button. This takes the button's signal clicked and connects it to the Virtual Keyboards buttonPressed signal. This means that every time a button sends the clicked signal, the virtual keyboard will send the buttonPressed signal.

Of course, the virtual keyboard should hide when it's not in use. After the column/row layout of the virtual keyboard is defined, two states active and inactive are created, as well as, a transition which slides the keyboard up and down in a quarter of a second.


Adding Javascript

Arguably, the Javascript file could easily be inlined in one of the QML files. However, it is useful to know how to separate them out. In the case, of this application the main.qml files imports Javascript file with the doOperation() function to allow the actively focused LineEdit to receive the characters typed on the virtual keyboard. The doOperation function also implements the backspace and spacebar operations.

Javascript can be called from QML by including the line:

import "components/ops.js" as Ops.

Here is the file ops.js

function doOperation(op) {
    var t = (lineEdit1.activeFocus ? lineEdit1.text : lineEdit2.text)

    if (op == leftArrow) {
        t = t.toString().slice(0, -1)
        if (t.length == 0) {
            t = ""
        }
    } else {
        t = t + op.toString()
    }

    lineEdit1.activeFocus ? lineEdit1.text = t : lineEdit2.text = t
}


Congratulations

Here is a screenshot of the in-application virtual keyboard.

Exercises for the Reader

At this point, you have a minimal but functional in-application virtual keyboard. There are many ways in which this virtual keyboard can be improved. Here are some suggested improvements that will help you exercise your newly formed QML developer skills.

  1. Currently, the keyboard does not react if a use types on a hardware keyboard. Change the QML so that both keyboard strokes and mouse clicks active the virtual keyboard's buttons.

  2. Implement the SHIFT key. This should include changing the button's text, op and shiftops properties.

  3. Implement a specialized keyboard layout such as a numberpad or a keyboard that is specialized for a particular program or game.

  4. Localize the virtual keyboard for another language.

  5. Implement your own interesting extension to the virtual keyboard.

SAMPLE SOURCE CODE - SUBJECT TO THE TERMS OF SAMPLE CODE LICENSE AGREEMENT,
http://software.intel.com/en-us/articles/intel-sample-source-code-license-agreement/

Copyright 2011 Intel Corporation

THIS FILE IS PROVIDED "AS IS" WITH NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值