1 (08.10.2012 20:34:08 отредактировано nbvehbq)

Тема: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

[size=5]Аннотация[/size]

Эта статья рассказывает о разработке приложения, использующего механизм доступа к базе данных SQLite, для MeeGo Harmattan устройств на QML и Quick Components.
Часто сдавая экзамен на категорию, сертификат, etc Вы решаете тесты, которые представляют собой вопросы и список ответов на них, Вам всего лишь надо указать правильный. Вот если бы у Вас была программка, содержащая вопросы и ответы, то возможно она могла бы дать Вам небольшое преимущество на экзамене, нужно только незаметно воспользоваться смартфоном..., но это уже другая история.
Итак база данных представляет собой сборник вопросов (тестов) и вариантов ответов на них с указанием правильного. Доступ к базе будет осуществляться с использованием Offline Storage API.


[size=5]Логика[/size]

700x316

У пользователя есть возможность набрать в строке поиска часть вопроса (searchStr). Нажав на кнопку поиска отправляем searchStr в предложение Like SQL запроса:

Select * from quiestions where SomeField like %searchStr%

где quiestions наша таблица с вопросами, на выходе получаем набор записей состоящих из вопросов, содержащих searchStrig. Поместим набор записей в ListView, тем самым позволив пользователю уточнить свой выбор.
Выбирая нужный вопрос пользователь инициализирует отправку SQL запроса:

Select * from answers where id = idQuestion

где answers наша таблица с ответами, а idQuestion идентификатор вопроса. Получив ответы выводим их в ListView, выделив правильный каким нибудь интерфейсным элементом.

700x405

Интерфейс состоит из двух окон Pages в терминологии QML(MainPage{}, AnswersPage{}).
Основное окно (MainPage{}) содержит:

  • заголовок (Header рекомендуется создавать у каждого окна, соглассно UI Guidelines);

  • поле для ввода текста (TextField{}) и кнопка поиска (разместим её прямо в поле ввода);

  • список вопросов (ListView{});

  • ToolBar{} обязательный элемент, позволяющий нам перемещаться между окнами, вызывать меню, etc.

Окно ответов (AnswersPage{}) содержит:

  • заголовок (Header{});

  • выбранный вопрос (Text{});

  • список ответов с индикатором (ListView{});

  • ToolBar{}.


[size=5]Программная часть[/size]

Логику мы обсудили, интерфейс спроектировали, пора заняться программированием.
Запустим Qt Creator, на первом шаге выберем шаблон Qt Quick Project->Qt Quick Application, на втором зададим имя проекта, я думаю qaTools (Question Answer Tools) будет в самый раз, на третьем шаге укажем тип приложения Qt Quick Components for MeeGo/Harmattan, следующие шаги оставим без изменения.
Давайте посмотрим, что для нас сделал Qt Creator.

Нативная часть кода содержится в main.cpp, где создается экземпляр класса QmlApplicationViewer viewer, который загружает декларативную часть,

viewer.setMainQmlFile(QLatin1String("qml/qaTools/main.qml"))

.
Каталог qaTools/qml/qaToosl/ как раз и содержит декларативную часть кода. На данный момент у нас там два файла main.qml и MainPage.qml. main.qml это основной файл нашего приложения:

import QtQuick 1.1
import com.nokia.meego 1.0

PageStackWindow {
    id: appWindow

    initialPage: mainPage

    MainPage {
        id: mainPage
    }

    ToolBarLayout {
        id: commonTools
        visible: true
        ToolIcon {
            platformIconId: "toolbar-view-menu"
            anchors.right: (parent === undefined) ? undefined : parent.right
            onClicked: (myMenu.status === DialogStatus.Closed) ? myMenu.open() : myMenu.close()
        }
    }

    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem { text: qsTr("Sample menu item") }
        }
    }
}

Центральное понятие в QML - элемент. Элементы представляют собой базовые строительные блоки, из которых формируется программа на QML. Большинство элементов могут быть контейнерами для других элементов. У разработчика есть возможность создавать (описывать) собственные QML типы.
Основная идея мобильных приложений на QML это переключение экранов в стеке. В терминах Qt компонентов экраны называются страницами (Pages{}), а главный контейнер окном (Window{}). В нашем приложении основной элемент это PageStackWindow{}, который является наследником Window{} и содержит в себе компонент pageStack. У pageStack есть методы которые позволяют перемещать страницы в стеке (pop(), push()).
initialPage - это свойство  устанавливает начальную страницу в стеке (pageStack), в нашем случае это mainPage.
PageStackWindow{} является контейнером для элементов MainPage{}, ToolBarLayout{}, Menu{}.
ToolBarLayout{} предоставляет макет для элементов размещенных в контейнере ToolBar{}. Сам ToolBar{} явно не объявлен, а является свойством pageStack, который в свою очередь является свойством PageStackWindow{}.

Давайте сразу следовать UI Guidelines от Nokia, согласно которому навигация между страницами может осуществляться тремя способами: Tab Bar Navigation, Drill Down Navigation, Tab Bar + Back Navigation. Мы будем использовать Drill Down Navigation, в этом случае для каждой страницы, кроме основной у ToolBar{} должна быть кнопка “назад”. На основной она тоже может присутствовать, но должна быть неактивна. Что ж одна кнопка (ToolIcon{}) у нас уже есть, она прижата вправо и отвечает за вызов меню, добавим кнопку back и прижмем её влево:

...
ToolIcon {
            enabled: (pageStack.currentPage === mainPage) ? false : true
            platformIconId: (pageStack.currentPage === mainPage) ? "toolbar-back-dimmed" : "toolbar-back"
            anchors.left: (parent === undefined) ? undefined : parent.left
            onClicked: pageStack.pop()
}
...

Код достаточно прост, если активное окно mainPage, тогда кнопка неактивна. Событие onClicked кнопки вытягивает из стека предыдущее окно. Это и есть Drill Down Navigation смысл которого состоит в том, чтобы возвращать пользователя на предыдущее окно, до тех пор пока не появится основное.
Еще один элемент в файле main.qml это Menu{}, что тут можно сказать меню есть меню, давайте просто заменим текст “Sample menu item” на “About”.
Последний элемент это MainPage{}, который как раз является типом, описываемым разработчиком самостоятельно. Для описания собственных типов достаточно создать файл с именем типа с Большой буквы с расширением qml. В нашем случае Qt Creator это сделал за нас. Файл MainPage.qml:

import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    tools: commonTools

    Label {
        id: label
        anchors.centerIn: parent
        text: qsTr("Hello world!")
        visible: false
    }

    Button{
        anchors {
            horizontalCenter: parent.horizontalCenter
            top: label.bottom
            topMargin: 10
        }
        text: qsTr("Click here!")
        onClicked: label.visible = true
    }
}

Основным элементом - контейнером здесь является Page{} (страница). У страницы есть свойство tools, благодаря которому мы можем использовать на странице элемент ToolBarLayout{} определенный в main.qml. Дочерние элементы это Label{} - позволяет разместить тестовую информацию и Button{} (кнопка), позволяет выполнить какое либо действие при нажатии на неё. Элемент Label{} нам здесь не нужен, просто удалим его определение из файла, так же удалим свойство top у кнопки, вместо него добавим

verticalCenter: parent.verticalCenter

, а на обработчик onClicked повесим следующий код:

onClicked: pageStack.push(answersPage)

Обработчик проталкивает в стек и одновременно выводит на экран страницу с идентификатором answersPage. Страницы с таким идентификатором у нас нет, давайте создадим её.
В Qt Creatore выберете New File or Project...->QML->QML File. Ввведите имя AnswersPage.qml, не забудте указать путь  qaTools/qml/qaTools. Давайте добавим в файл описание страницы:

import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    tools: commonTools

}

Пока  у нас только голая страница с тулбаром. Для того чтобы pageStack знал о существовании нашей страницы добавим её объявление в main.qml:

...
MainPage {
        id: mainPage
}

AnswersPage {
    id: answersPage
}
...

Таким образом приложение сразу загрузит наши страницы в память, что немного не корректно, т.к. рекомендуется загружать страницы динамически по мере необходимости, но так как в нашем приложении всего две страницы то можно сделать и так.

В соответствии с нашим макетом, каждое окошко должно иметь заголовок. Стандартного элемента QML для этих целей не существует, поэтому опишем его сами. Создадим в Qt Creator новый файл Header.qml:


import QtQuick 1.1
import com.nokia.meego 1.0

Rectangle {
    id: header

    property alias text: title.text

    color: "yellow"
    height: 72 
    width: parent.width
    anchors.top: parent.top

    Label {
        id: title
        font.weight: Font.Light
        font.pixelSize: 32
        anchors {
            left: parent.left; leftMargin: 20
            verticalCenter: parent.verticalCenter
        }
    }
}

Тут все достаточно просто, рисуем прямоугольник, внутри которого размещаем Label{}. Обратите внимание на следующую строчку:

property alias text: header.Text

Этим определением мы создаем у элемента свойство text и указываем, что оно является алиасом для свойства text элемента Label.
Ну что ж, давайте добавим на наши странички заголовок:
MainPage.qml

...
 tools: commonTool

 Header {
        id: header
        text: "Вопросы"
 }
...

AnswersPage.qml

...
 tools: commonTool

 Header {
        id: header
        text: "Ответы"
 }
...

Теперь мы можем запустить наше приложение в симуляторе и посмотреть как все выглядит, и как переключаются странички.

Добавим в MainPage.qml недостающие элементы (определение Button{} удалите, больше он нам не нужен).
Строка поиска:

...
TextField {
        id: searchField
        anchors {top: header.bottom; left: parent.left; right: parent.right; margins: 7}
        placeholderText: "Введите текст"
        width: parent.width
        platformStyle: TextFieldStyle { paddingRight: searchIcon.width }

        ToolIcon {
            id: searchIcon
            platformIconId: "toolbar-search"
            anchors { top: parent.top; right: parent.right }
            height: parent.height; width: parent.height
        }
    }
...

TextField{} элемент специально предназначен для ввода текста. Элементы в QML крепятся друг к другу с помощью якорей (anchors), к нижней части нашего заголовка мы и прикрепим его (TextField{}).
placeholderText  это подсказка для пользователя.
platformStyle: TextFieldStyle { paddingRight: searchIcon.width } позволяет нам создать отступ  для текста, ведь справа прямо в строке поиска мы разместим кнопку поиска (ToolIcon{}).

Список ListView с найденными вопросами.
Элемент ListView предназначен для отображения списков. То какие это данные и как они будут выглядеть определяют два свойства model и delegate. model это структура и хранилище данных, а delegate определяет как будет выглядеть отдельный элемент ListView. В составе пакета com.nokia.extras есть готовый элемент ListDelegate{}, однако для наших целей он не подходит. Давайте создадим собственный. Добавьте в проект файл с именем QuestionsDelegate.qml:

import QtQuick 1.1
import com.nokia.meego 1.0

Item {
    id: q

    property int idQ: 0
    property alias text: textField.text
    signal clicked

    height: textField.height + 7
    width: parent.width
    anchors {left: parent.left; right: parent.right; margins: 10}

    Text {
        id: textField
        width: parent.width
        font.pointSize: 6
        wrapMode: TextEdit.Wrap
    }

    Rectangle {
        id: devider
        anchors.top: textField.bottom
        anchors.topMargin: 5
        height: 2; width: parent.width
        color: "gray"
        opacity: 0.7
    }

    Rectangle {
        anchors.top: devider.bottom
        height: 1; width: parent.width
        color: "white"
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            q.clicked();
        }
    }
}

Контейнером для всех элементов здесь является Item, в  QML Item самый простой визуальный элемент, все остальные визуальные элементы наследуются от него. Хотя Item не имеет визуального представления, у него есть такие свойства как x, y, width, height. Идеология его использования состоит в группировке дочерних элементов.
Item содержит Text{} - элемент в который мы поместим текст вопроса, вопросы могут быть длинными, так что установим свойство

wrapMode: TextEdit.Wrap

, чтобы текст переносился на другую строчку, свойство не будет работать без width, что логично, не зная ширину текста невозможно разбить строки. Это кстати один из важных моментов в QML, многие свойства зависят от других, еще один пример margins установив его значение вы получите отступы только у тех свойств, который “заякарили” (anchors {left: parent.left; right: parent.right})
Для визуального разделения теста добавим две тоненьких линии серого и белого цвета (Rectangle{}).
Последний элемент это MouseArea{} он занимает всю площадь Item{}, добавили мы его для того чтобы можно было обрабатывать события касания к экрану у нашего Item{}.
Свойству height элемента Item{} присвоим значение textField.height + 7. Что такое 7? Это отступ, т.к. мы не можем “заякорить” наш элемент снизу, то просто увеличим высоту.
Мы определили у Item{} два свойства IdQ и text и один сигнал clicked. С text  все понятно, у него такая же роль как и у ранее созданного нами Header{}. Разница в определении состоит в том, что для свойств без ключевого слова alias система резервирует память. IdQ будет хранить id вопроса и понадобится нам в дальнейшем для обращения к базе данных. У MouseArea{} есть событие onClicked, в котором мы и  вызываем сигнал clicked, описанный нами у Item{}.
Теперь мы можем описать ListView{}, после определения TextField{} в MainPage.qml добавьте следующий код:

...
    ListModel {
        id: itemModel
    }

    ListView {
        id: view
        anchors {
            left: parent.left; right:  parent.right;
            top: searchField.bottom; bottom: parent.bottom;
            topMargin: 7;
        }

        model: itemModel
        clip: true

        delegate: QuestionsDelegate{
            text: model.text
            idQ: model.idQ
            onClicked: {
                 pageStack.push(answersPage, {idQ: model.idQ});
            }
        }
    }
...

Здесь мы описываем простую модель ListModel{} для нашего ListView{}, которую в дальнейшем будем заполнять из базы данных, соединяем вместе модель и делегат. Свойства IdQ и text делегата заполняем из модели, а на событие onClicked делегата повесим вызов answersPage и передадим ей (странице) параметр idQ.
Свойство clip у ListView{} отвечает за красивую обрезку элементов, если они не помещаются на странице, без этого свойства все элементы сбились бы в некрасивую кучку smile.
Добавим к ListView{} скролбар.

ScrollDecorator { flickableItem: view }

На этом пока закончим с QuestionPage и перейдем к AnswersPage. Пока что у нас там только Header{}. Раз уж мы передаем параметр idQ  из MainPage{}, то нам его надо где то хранить. Для этих целей опишем у AnswersPage{} свойство:

Page {
   id: answers
   property int idQ: 0
   tools: commonTools
...

И добавим элементы из нашего макета:

...
Text {
        id: q
        width: parent.width
        anchors {top: header.bottom; left: parent.left;
            right: parent.right; margins: 10}
        font.pointSize: 6
        font.italic: true
        wrapMode: TextEdit.Wrap;
    }

    ListModel {
        id: itemModel
    }

    ListView {
        id: view
        anchors {
            left: parent.left; right:  parent.right;
            top: q.bottom; bottom: parent.bottom;
            topMargin: 7;
        }

        model: itemModel
        clip: true

        delegate: AnswersDelegate{
            text: model.text
            color: model.color
        }
    }
    ScrollDecorator { flickableItem: view } 
...

Во первых это элемент Text{} который содержит текст вопроса выбранного пользователем. А во вторых ListView{} с ответами. Тут мы тоже используем собственный делегат - AnswersDelegate. Давайте опишем его в файле AnswersDelegate.qml:

import QtQuick 1.1
import com.nokia.meego 1.0

Item {
    id: a

    property alias text: textField.text
    property alias color: indicator.color

    height: row.height + 12
    width: parent.width
    anchors {left: parent.left; right: parent.right; margins: 10}

    Row{
        id: row
        width: parent.width
        spacing: 7

        Rectangle {
            id: indicator
            width: 4
            height: textField.height
        }

        Text {
            id: textField
            font.pointSize: 6
            width: parent.width - indicator.width - row.spacing
            wrapMode: TextEdit.Wrap;
        }
    }

    Rectangle {
      id: devider
      anchors.top: row.bottom
      anchors.topMargin: 5
      height: 2; width: parent.width
      color: "gray"
      opacity: 0.7
    }
    Rectangle {
      anchors.top: devider.bottom
      height: 1; width: parent.width
      color: "white"
    }
}

Отличие от QuestionsDelegate  в том, что здесь мы размещаем прямоугольник (indicator) шириной 4 пикселя перед текстом ответа. Свойство color прямоугольника выносим в property, если ответ правильный то будем закрашивать его зеленым, иначе красным цветом. Прямоугольник с ответом помещаем в элемент row{}, чтобы сгруппировать их относительно других элементов.


[size=5]Доступ к данным[/size]

Для доступа к данным из QML приложений необходимо использовать Offline Storage API, который предоставляет нам возможность взаимодействовать с локальной базой данных SQLite. Сама база по умолчанию храниться в подкаталоге Databases, каталога, который нам возвратит вызов функции

QDeclarativeEngine::offlineStoragePath()

Вызов API осуществляется из JavaScript функций в QML файле.
db = openDatabaseSync(identifier, version, description, estimated_size, callback(db))
Эта функция вернет нам идентификатор открытой базы данных.
Давайте подробно поговорим о параметре identifier. В документации о нем упоминается вскользь в одной фразе: Returns the database identified by identifier. Т.е подразумевается что identifier это имя базы данных. На самом деле физически файл базы данных имеет следующее имя Qt.md5(identifier).sqlite. Т.е модель использования Offline Storage API подразумеваемая разработчиками следующая: программист в коде создает базу (openDatabaseSync() создаст файл если его не существует), заполняет её данными и потом использует. О том с каким именем сохраняется файл ему знать необязательно smile. Очевидно, что нам это не подходит, т.к. у нас уже есть готовая база и все что нам нужно это разместить эту базу в нужном месте и с нужным именем.

О том как деплоить базу вместе с приложением поговорим позже, а сейчас давайте разместим наш файл в том месте файловой системы , где его сможет найти симулятор.
Если Вы работаете под Windows, то это: %APPDATA%\Nokia\QtSimulator\data\QML\OfflineStorage\.
Если под Linux, то Вам необходимо добавить в main.cpp следующие строки:

...
#include <QtGui>
#include <QDeclarativeEngine>
...
qDebug() << viewer.engine()->offlineStoragePath();
...

что позволит вам узнать, где по мнению симулятора находиться offlineStoragePath.

В файл MainPage.qml добавим следующую функцию (должна находится в границах определения Page{}):

Component.onCompleted: {
   var db = openDatabaseSync("Answers", "1.0", "The Answers SQL database", 1000000);
}

Метод Component.onCompleted выполнится когда страница будет полностью создана. Первый параметр мы уже обсудили, второй version  - версия нашей базы (у нас всегда 1.0), третий и четвертый это описание и размер, в текущей версии API не используется. Запустим в симуляторе наше приложение, в результате в каталоге %APPDATA%\Nokia\QtSimulator\data\QML\OfflineStorage\Database появятся два файла:
7d5a6969802bb5e1d931b510a8fdb3ba.sqlite,
7d5a6969802bb5e1d931b510a8fdb3ba.ini.

Как я уже говорил openDatabaseSync() создает файл базы данных, если он не существует. Второй файл 7d5a6969802bb5e1d931b510a8fdb3ba.ini создается один раз в момент создания базы данных и содержит её характеристики.
Вот таким хитрым путем мы и получили имя файла, в котором должна лежать наша база. Теперь мы можем переместить нашу базу данных на место 7d5a6969802bb5e1d931b510a8fdb3ba.sqlite. В дальнейшем openDatabaseSync() будет использовать именно его. Кстати имя фала мы могли бы получить выполнив следующий код console.log(Qt.md5(identifier)), как я уже упоминалось ранее имя файла это md5 хэш от identifier. Обработчик события Component.onCompleted удалите, оно больше нам не нужно.
Итак, в соответствии с логикой работы нашего приложения пользователь должен отправить запрос к базе данных, нажав на кнопку поиска. Добавим обработчик onClicked в файле MainPage.qml для ToolIcon{id: searchIcon}:

...
height: parent.height; width: parent.height
            onClicked: {
                findQ(itemModel, searchField.text)
                searchField.platformCloseSoftwareInputPanel()
            } 
...

В обработчике мы вызываем функцию findQ() и передаем ей два параметра модель нашего ListView{} и текст из поля searchField. Также мы закрываем SIP (при получении фокуса поле ввода открывает SoftwareInputPanel, позволяя пользователю ввести текст).
Функция findQ() у нас не определена, давайте напишем её:

function findQ(model, str) {
    model.clear()
    var db = openDatabaseSync("Answers", "1.0", "The Answers SQL database", 1000000);
    db.readTransaction(
        function(tx) {
            var rs = tx.executeSql('Select id, q from questions where q like ?', "%" + str + "%");
            for (var i=0; i< rs.rows.length; i++) {
                model.append({“idQ”: rs.rows.item(i).id, “text”: rs.rows.item(i).q});
            }
        }
    )
}

В первую очередь очистим наш ListView{}, путем удаления данных из модели - model.clear(). C openDataBaseSync() мы уже знакомы. Метод readTransaction(callback(tx)) создает транзакцию для чтения и передает ее в callback(tx), в которой мы и обращаемся к нашей базе данных, отправляя ей SQL запрос через вызов метода executeSql(). Далее в цикле мы добавляем в модель полученные данные, используя метод ListModel.append().

На этом этапе можно запустить приложение в симуляторе, набрать запрос в строке поиска и увидеть результат.

Согласно разработанной логике пользователь уточняет вопрос, путем выбора его из ListView{}. Событие onClicked{} у нас описано и в нем мы вызываем AnswersPage{}, передавая ему id вопроса. В текущем варианте уточняя вопрос мы переходим на пустую страницу с ответами, давайте исправим это.
Добавим в описание AnswersPage{} следующее событие:

...
onStatusChanged: {
       if(status === PageStatus.Activating) {
           getAnswers(itemModel, answers.idQ);
       }
   }
...

Событие возникает при смене статуса страницы, всего их бывает четыре (Inactive, Active, Activating,  Deactivating), нас интересует Activating, происходит перед тем как страница станет активна. Это как раз тот момент, когда мы хотим получить данные и разместить их на странице. За это у нас отвечает функция getAnswers(). Ей мы передаем два параметра, модель и id вопроса, который мы определили как свойство страницы.

function getAnswers(model, idq){
       var db = openDatabaseSync("Answers", "1.0", "Answers Database!", 1000000);
       db.readTransaction(
           function(tx) {
               var rs = tx.executeSql('select q, a from Questions q where id = ?', idq);
               q.text = rs.rows.item(0).q;
               var answerNum = rs.rows.item(0).a

               model.clear()
               rs = tx.executeSql('select a, num from answers a where id_questions = ?', idq);
               for (var i=0; i< rs.rows.length; i++) {
                   model.append({"text": rs.rows.item(i).a,
                                 "color": (rs.rows.item(i).num === answerNum) ? "green" : "red"});
               }
            }
       )
   }

Здесь мы выполняем два  запроса к базе данных. В первом мы получаем вопрос и правильный ответ. Вопрос выводим в элемент Text{id:q},  а номер правильного ответа записываем в переменную answerNum. Во втором запросе мы получаем список ответов, зная правильный ответ, мы можем выделить его зеленым цветом, в момент добавления в модель.
Ну вот теперь можно запустить приложение в эмуляторе и проверить его функциональность. Вроде бы все хорошо, но если пользователь наберет в строке поиска что нибудь, чего нет у нас в базе данных, то... ничего не происходит, давайте покажем ему сообщение, в котором укажем ему на это. Для этих целей существует элемент InfoBanner{}. Элемент содержится в пакете com.nokia.extras 1.1. Добавим его описание в MainPage.qml:

...
import com.nokia.extras 1.1
...
InfoBanner {
       id: banner
}
...

Добавим функцию для показа банера:

function showError(str){
       banner.text = str;
       banner.show();
}

Функцию showError() будем вызывать из findQ() следующим образом:

...
var rs = tx.executeSql('Select id, q from questions where q like ?', "%" + str + "%");
if (rs.rows.length === 0){
        showError("Не найденно вопросов содержащих '" + str + "'");
}

for (var i=0; i< rs.rows.length; i++) {
...

[size=5]“Развертывание” на устройстве[/size]

Мы должны загрузить на устройство нашу базу данных. Сделать это можно, поместив файлы в deb пакет нашего приложения. Откройте в Qt Creator файл qaTools.pro, добавьте в конец следующие строчки:

   data.path = /opt/qaTools/data/Databases/
   data.files += data/*
   INSTALLS += data

Создайте в каталоге проекта папку data и поместите туда файлы:
7d5a6969802bb5e1d931b510a8fdb3ba.sqlite,
7d5a6969802bb5e1d931b510a8fdb3ba.ini
, и после установки пакета на устройстве, база окажется в каталоге /opt/qaTools/data/Databases/

Куда бы мы не поместили, базу а openDataBaseSync()  будет её искать в подкаталоге Databases, каталога , который нам вернет метод offlineStoragePath(). Благо у QDeclarativeEngine, есть метод setOfflineStoragePath(), которому мы и передадим путь к каталогу в котором у нас будет лежать база данных. Добавим в main.cpp следующую строчку:

...
#include <QDeclarativeEngine>
...
viewer.engine()->setOfflineStoragePath("/opt/qaTools/data/");
...

Итак компилируем запускаем на реальном устройстве, или эмуляторе и видим, что размер шрифта вопросов и ответов очень маленький (по сравнению с симулятором). Выставьте размер шрифта у элементов Text{} в QuestionsDelegate.qml,  AnswersDelegate.qml, AnswersPage.qml, какой вам больше нравится, я поставил 16.

Осталось нарисовать иконку, в стиле MeeGo Harmattan, скачайте отсюда http://harmattan-dev.nokia.com/docs/ux/ … pment.html NokiaN9_Icon_Templates.zip в нем есть шаблоны и инструкция, как сделать иконку, у меня получилось так:

80x80

Готовое приложение выглядит так:

700x414


[size=5]Заключение[/size]

Если вы дочитали до этого момента, значит вы написали приложение smile (может первое, может n-ное, но надеюсь не последнее). Конечно мы сделали только основные вещи, возможно где то есть ошибки, например если нажать на кнопку поиска без ввода текста, то в Select передастся простая строка, и он вернет, все строки из таблицы. Хотя может это не баг, а фича smile. Можно вынести обращения к базе данных в отдельный модуль. Необходимо как то реагировать если пользователь меняет темы (thems), в конце концов добавить окно About smile. В общем есть место для творчества.
Надеюсь наше приложение поможет кому нибудь получить сертификат, а статья написать много разных программ!


Исходник приложения



P.S. Приложение достаточно легко портировать под платформу Symbian^3

Post's attachments

qatools_1.0.0_armel.deb 269.51 kb, 12 загрузок с 2012-10-07 

У Вас недостаточно прав для загрузки файлов, прикрепленных к этому сообщению.

2

Re: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

я понял что я ничего не понял но в любом случяе было интересно! таких бы спецов в нокию или самсунг вот это было бы дело!

Поделиться

3

Re: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

predator пишет:

я понял что я ничего не понял но в любом случяе было интересно! таких бы спецов в нокию или самсунг вот это было бы дело!

Приятно слышать!

На самом деле это моё первое приложение для MeeGo, но я начинал баловаться еще с PalmOS, так что опыт программирования под мобильные операционки имелся :)

На самом деле документация достаточно подробна, но разнесена, часть на nokia, часть на digia и не всегда совпадает :)

Поделиться

4

Re: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

офигеть как круто.
Еще бы сюда добавить синхронизацию с сервером вообще было бы сказочно.

Поделиться

5

Re: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

IDis пишет:

офигеть как круто.
Еще бы сюда добавить синхронизацию с сервером вообще было бы сказочно.

Можно и синхронизацию прикрутить, был бы сервер smile

Можно реализовать например такую модель:

1. При старте приложение запрашивает версию базы у сервера, если она отличается, то отправляет запрос о готовности принять данные.
2. Сервер отправляет данные, которые отсутствуют в базе.
3. Приложение принимает данные, вносит их в базу и изменяет версию, чтоб соответствовать серверной базе.

Осталось только реализовать сервер, а протокол обмена данными у нас уже есть smile

Поделиться

6

Re: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

Доброго времени!
Форумчане, подскажите кто сталкивался, если необходимо в базе хранить картинки и извлекать их,
то возможно ли это реализовать в связке QML+SQLITE или такой вариант не пройдет, картинки хранить просто в каталоге?

Поделиться

7

Re: Доступ к SQLite из приложения на QML, для MeeGo Harmattan устройств.

я осилил) Спасибо, отличная статья smile , не хватает скриншотов по ходу текста, а так, всё понятно и доступно объяснено

Поделиться