ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 12.04.2021

Просмотров: 1866

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

!

!

П

ра
кт

ич

ес

ко

е

за

ня

ти

е

12

П

ри
ме

р

ра

зр

аб
от

ки

пр
ил

ож

ен

ия

дл

я

Л

И
Н

Т
Е

Р

с

ис

по

ль

зо

ва

ни

ем

Qt

!

81

 
 

E-

mai

l: 

ma

rke

t@

rele

x.r

u

классе

  CSsqlDS 

инкапсулируется

большинство

работы

нашего

примера

с

СУБД

ЛИНТЕР

Используются

обычные

вызовы

 Call-

интерфейса

уже

изученные

нами

.  

Для

связи

элементов

графического

интерфейса

с

данными

из

БД

необходимо

модифицировать

стандартные

элементы

управления

так

чтобы

они

работали

через

CDataSource.

Однострочный

элемент

должен

выглядеть

примерно

так

class CMyEdit : public QLineEdit 

... 

  public: 

    void setDS(CDataSource *ds); 

    void setField(int f) {m_field =  f;} 

  public slots: 

    void onDsError(long); 

    void onDsFullChange(); 

    void onCurRowChange(long oldrow); 

 ... 

}; 

Для

многострочного

элемента

необходимы

 (

на

примере

с

 'QListBox') 

а

Функции

конвертации

номера

строки

в

базе

и

в

элементе

     long rowToDbRow(int); 
     int  dbRowToRow(long); 

б

Синхронизация

   

изменения

положения

курсора

в

источнике

данных

при

изменении

строки

в

управляющем

элементе

Для

этого

подписываемся

на

сигнал

connect(this, SIGNAL(highlighted(int)), this, 
SLOT(slot_highlighted(int))); 

и

пишем

следующий

код

void CMyListBox::slot_highlighted(int) 

   m_ds->setCurRow(rowToDbRow(currentItem())); 

в

Работу

многострочного

элемента

можно

построить

так

что

он

не

будет

содержать

полную

копию

всех

строчек

выборки

а

только

видимую

часть

И

только

при

перемещении

по

строчкам

по

необходимости

брать

записи

из

источника

данных

В

нашем

простом

примере

это

не

рассматривается

.

Указания

к

практической

работе

1.

Просмотрите

исходные

тексты

  1-

го

этапа

  (

директория

  step1). 

В

директории

datasource 

находятся

 h-

файлы

с

описанием

интерфейсных

классов

и

 cpp-

файлы


background image

!

82

П

ра
кт

ич

ес

ко

е

за

ня

ти

е

12

П

ри
ме

р

ра

зр

аб
от

ки

пр
ил

ож

ен

ия

дл

я

Л

И
Н

Т
Е

Р

с

ис

по

ль

зо

ва

ни

ем

Qt

!

!

 
 

E-

mai

l: 

ma

rke

t@

rele

x.r

u

с

реализацией

классов

источника

данных

В

директории

 ctrl 

находятся

описания

и

реализация

классов

визуальных

элементов

управления

расширенных

до

возможности

работы

с

источниками

данных

Кроме

того

там

описан

и

реализован

класс

формы

нашего

тестового

приложения

В

файле

  main.cpp 

содержится

код

основной

программы

которая

инициализирует

приложение

создает

соединение

с

БД

ЛИНТЕР

 (

используются

жестко

закодированные

имя

и

пароль

  “SYSTEM/MANAGER”), 

инициализирует

источник

данных

и

создает

графическую

форму

Обратите

внимание

на

  makefile. 

Там

необходимо

переопределить

переменные

  LINTER  (

на

путь

в

котором

установлена

СУБД

ЛИНТЕР

и

 QTDIR (

путь

к

установке

 Qt). 

20.

Скомпилируйте

исполняемый

файл

  (

команда

  make). 

Обратите

внимание

что

программа

компилируется

с

отладочной

информацией

 (

ключ

 –g). 

21.

Запустите

программу

и

просмотрите

содержимое

таблицы

  AUTO 

в

форме

приложения

22.

Запустите

программу

в

отладчике

  gdb. 

Поставьте

точки

останова

в

методах

реализующих

работу

источника

данных

с

СУБД

ЛИНТЕР

например

в

методах

CSqlDS::retrieve 

и

  CSqlDS::getField. 

Понаблюдайте

за

исполнением

методов

включая

вызовы

ЛИНТЕР

по

шагам

12.2. 

Второй

этап

Добавим

в

наш

пример

возможность

обработки

сообщений

от

СУБД

ЛИНТЕР

Для

приложения

интересны

асинхронный

прием

сообщения

и

синхронная

обработка

Асинхронная

обработка

имеет

ряд

системных

ограничений

и

не

все

вызовы

из

нее

можно

делать

 (

в

 POSIX 

стандарте

есть

список

разрешенных

вызовов

).  

Разработаем

по

нашей

проблеме

класс

AsyncManager

Наш

класс

содержит

три

статические

функции

AsyncManager::addNewAsync  - 

регистрация

синхронного

обработчика

для

асинхронного

запроса

по

определенному

каналу

AsyncManager::removeAsync  - 

снятие

регистрации

синхронного

обработчика

для

асинхронного

запроса

AsyncManager::onAsyncWork  - 

универсальный

асинхронный

обработчик

передается

как

аргумент

вызова

 inter. 

Для

синхронизации

используем

системный

  (Qt-1.4.x) 

цикл

обработки

сообщений

В

таком

цикле

обязательно

стоит

вызов

типа

  'select(.....)', 

который

проверяет

состояние

по

указанным

  

дескрипторам

ввода

-

вывода

в

 Qt 

есть

класс

 QSocketNotifier, 

который

позволяет

зарегистрировать

дескриптор

ввода

-

вывода

на

такую

проверку

.   

Мы

воспользуемся

им

Создадим

 'pipe' 

и

будем

его

использовать

как

флаг

наличия

синхронного

обработчика

 (

для

асинхронного

запроса

в

очереди

Для

этого

переводим

  'pipe' 

в

режим

неблокирующей

работы

и

регистрируем

дескриптор

чтения

 (

на

прослушивание

в

 'select') 

таким

образом

m_qNotifier = new QSocketNotifier(m_pipe[0], 
QSocketNotifier::Read, this); 

connect(m_qNotifier, SIGNAL(activated(int)), this, 
SLOT(SyncProc(int))); 

В

нашем

объекте

будем

вести

два

списка

объектов

типа

  AsyncObject, 

которые

содержат

информацию

о

канале

по

которому

был

подан

запрос

и

синхронной

функции

-

обработчике

Первый

список

    m_targetList  - 

список

поставленных

на

обработку

второй

m _ r a i s e L i s t  - 

с п и с о к

   

гото в ы х

к

с и н х р о н н о й

о б р а б от к е

В

о б р а б от ч и к е

AsyncManager::onAsyncWork 

производится

перевод

из

 m_targetList 

в

 m_raiseList 

список

Для

активизации

 'QSocketNotifier' 

в

 'pipe' 

записывается

 1 

байт

таким

образом

while(write(m_pipe[1], &event, sizeof(event)) < 0) 

  if (errno != EINTR) 


background image

!

!

П

ра
кт

ич

ес

ко

е

за

ня

ти

е

12

П

ри
ме

р

ра

зр

аб
от

ки

пр
ил

ож

ен

ия

дл

я

Л

И
Н

Т
Е

Р

с

ис

по

ль

зо

ва

ни

ем

Qt

!

83

 
 

E-

mai

l: 

ma

rke

t@

rele

x.r

u

  { 

    errno = 0; 

    break; 

  } 

Цикл

 'while' 

здесь

применяется

для

гарантированной

записи

в

 'pipe'. 

Если

 ‘pipe’ 

уже

заполнен

то

запись

пропускается

  (

т

.

к

любой

системный

вызов

может

быть

прерван

обработкой

по

сигналу

см

. POSIX).  

Все

функции

класса

которые

модифицируют

списки

  m_targetList 

и

  m_raiseList 

при

с в о е й

р а б о т е

д о л ж н ы

б л о к и р о в а т ь

а с и н х р о н н у ю

о б р а б о т к у

   

т

.

к

AsyncManager::onAsyncWork 

также

модифицируют

эти

списки

Блокировать

необходимо

сигналы

  SIGIO 

и

  SIGUSR1  - 

эти

сигналы

могут

быть

использованы

для

асинхронной

обработки

на

клиентской

части

СУБД

ЛИНТЕР

.  

Универсальный

синхронный

обработчик

  AsyncManager::SyncProc 

вызывается

из

цикла

обработки

очереди

сообщений

при

активизации

класса

  QSocketNotifier. 

В

нем

из

списка

  m_raiseList 

активизируется

синхронные

обработчики

по

каждому

каналу

Список

m_raiseList 

и

  'pipe' 

очищается

Цикл

  ‘while(read(m_pipe[0],  &event,  sizeof(event))  >  0)’ 

применяется

для

гарантированной

очистки

 'pipe', 

как

и

в

случае

с

 write.  

В

приложении

создается

один

объект

класса

  AsyncManager, 

его

конструктор

содержит

код

инициализации

 - 

создание

 'pipe' 

и

 QSocketNotifier.  

Класс

CDbEvent

непосредственно

связывается

с

ожидаемым

событием

Линтера

в

его

конструктор

передается

имя

события

Активизация

события

в

программе

осуществляется

через

метод

-

сигнал

  onRaise, 

интересующиеся

объекты

   

должны

подписаться

на

этот

сигнал

 (

здесь

говорится

о

сигнале

как

о

термине

 Qt).  

Вся

работа

по

обработки

асинхронного

запроса

производится

через

функциональность

выше

описанного

класса

синхронный

обработчик

 - 

статическая

функция

класса

 CDbEvent::onSelect. 

Объект

может

находиться

в

 3-

х

состояниях

: Empty/Busy/Ready. 

В

состояние

  Busy  (

ожидания

события

объект

переводится

вызовом

метода

  retrieve, 

т

.

е

.  - 

асинхронно

выполняется

запрос

  

wait event auto_change; 

Внутри

метода

 retrieve 

открывается

новый

курсор

чтобы

освободить

соединение

для

другой

работы

Метод

 clear 

выполняет

запрос

clear event auto_change;  

Метод

 destroy 

переводит

объект

в

состояние

 'Empty'.

Указания

к

практической

работе

1.

Просмотрите

исходные

тексты

  2-

го

этапа

  (

директория

  step2). 

По

сравнению

с

первым

этапом

во

-

первых

добавлена

директория

  async, 

в

которой

находится

реализация

универсального

менеджера

асинхронной

обработки

Во

-

вторых

в

директории

  datasource 

добавлены

исходные

тексты

класса

  CDbEvent. 

В

файле

main.cpp 

добавлена

инициализация

события

23.

Скомпилируйте

исполняемый

файл

 (

команда

 make).  

24.

Запустите

программу

и

просмотрите

содержимое

таблицы

  AUTO 

в

форме

приложения

Затем

измените

содержимое

таблицы

  AUTO 

из

другой

сессии

например

подав

запрос

из

программы

  INL. 

Приложение

должно

автоматически

отобразить

изменения

25.

Запустите

программу

в

отладчике

  gdb. 

Посмотрите

по

шагам

как

в

программе

выполняется

обработка

события

БД

12.3. 

Третий

этап

На

этом

этапе

мы

добавим

асинхронную

обработку

запросов

в

классе

 CSqlDS.  


background image

!

84

П

ра
кт

ич

ес

ко

е

за

ня

ти

е

12

П

ри
ме

р

ра

зр

аб
от

ки

пр
ил

ож

ен

ия

дл

я

Л

И
Н

Т
Е

Р

с

ис

по

ль

зо

ва

ни

ем

Qt

!

!

 
 

E-

mai

l: 

ma

rke

t@

rele

x.r

u

В

интерфейсе

класса

 CDataSource 

никаких

изменений

нет

но

объект

теперь

может

находиться

в

 3-

х

состояниях

: Empty/Busy/Ready. 

Новое

состояние

 'Busy', 

как

и

для

объекта

класса

  CDbEvent, 

характеризует

объект

находящийся

в

процессе

ожидания

ответа

на

запрос

к

СУБД

ЛИНТЕР

.  

Добавлен

новый

метод

-

сигнал

  onState  - 

вызывается

при

изменении

состояния

объекта

Метод

 retrieve 

изменен

открывается

новый

канал

для

работы

 (

как

в

 CDbEvent) 

и

запрос

посылается

асинхронно

с

переводом

в

состояние

  'Busy'. 

При

получении

ответа

вызывается

синхронный

обработчик

  - 

статическая

функция

класса

  CSqlDS::onSelect  - 

который

переводит

объект

в

состояние

  'Ready'  (

вызов

метода

-

сигнала

  onState) 

или

вызывается

метод

-

сигнал

  onError. 

При

изменении

состояния

объекта

  CSqlDS   

в

интерфейсном

классе

  CDataSource 

вызывается

метод

-

сигнал

  onFullChange 

и

переустанавливается

положение

курсора

.

Указания

к

практической

работе

1.

Просмотрите

исходные

тексты

 3-

го

этапа

 (

директория

 step3). 

Имеются

изменения

в

директории

 ctrl. 

Вместо

списка

в

форму

добавлен

табличный

элемент

 – 

класс

CMyTable, 

который

реализует

работу

с

источником

данных

простого

табличного

визуального

элемента

позаимствованного

из

стандартных

примеров

  Qt 

(

исходные

тексты

этого

элемента

находятся

в

поддиректории

 table; 

они

взяты

из

примеров

  Qt 

как

есть

). 

Есть

изменения

также

и

в

директории

  datasource: 

они

касаются

в

основном

асинхронной

обработки

Соответствующим

образом

модифицирован

и

файл

 main.cpp. 

26.

Скомпилируйте

исполняемый

файл

 (

команда

 make).  

27.

Запустите

программу

Обратите

внимание

что

запрос

на

выборку

данных

теперь

выполняется

долго

  (

мы

специально

вставили

перемножение

таблицы

  AUTO, 

чтобы

время

работы

запроса

было

велико

). 

Но

наше

приложение

при

этом

продолжает

работать

оно

выдает

сообщения

о

состоянии

реагирует

на

события

от

клавиш

и

мыши

и

т

.

д

Это

возможно

благодаря

асинхронной

обработки

запроса

12.4. 

Задание

для

самостоятельной

работы

В

качестве

самостоятельной

работы

дополним

наш

пример

функциями

модификации

  (

возможность

удаления

модификации

и

добавления

записи

в

источник

данных

).  

Для

этого

в

форме

уже

предусмотрены

кнопки

  "

Обновить

",  "

Вставить

",  "

Удалить

". 

Необходимо

добавить

обработчики

нажатия

на

эти

кнопки

которые

будут

формировать

и

исполнять

запросы

 UPDATE, UNSERT, DELETE 

для

таблицы

 AUTO. 

При

этом

данные

для

этих

запросов

можно

взять

из

четырех

однострочных

полей

ввода

имеющихся

в

форме

Непосредственные

вызовы

  Call-

интерфейса

ЛИНТЕР

можно

скрыть

в

классе

  CSqlDS, 

например

добавить

в

него

метод

  modify, 

который

будет

в

качестве

параметра

принимать

текст

запроса

и

выполнять

этот

запрос

при

помощи

команды

 Call-

интерфейса

 “    “ (

четыре

пробела

). 

Тогда

задача

обработчиков

кнопок

будет

состоять

в

том

чтобы

сформировать

требуемый

текст

запроса

  (

например

функцией

  sprintf, 

используя

тексты

однострочных

полей

и

передать

его

методу

 modify.  

Отметим

что

после

модификации

таблицы

старая

выборка

сделанная

запросом

SELECT, 

перестает

отображать

реальные

данные

Простейший

способ

избежать

этого

  – 

выбрать

данные

заново

после

их

модификации

 (

это

позволяет

в

том

числе

использовать

для

  INSERT,DELETE,UPDATE 

тот

же

канал

ЛИНТЕР

т

.

к

результаты

старой

выборки

SELECT 

нам

все

равно

уже

больше

не

понадобятся

). 

Заметим

что

в

нашем

примере

данные

автоматически

будут

перечитываться

после

любой

операции

модификации

благодаря

отслеживанию

события

БД

Учащимся

необходимо

самостоятельно

реализовать

и

отладить

описанную

функциональность

Она

является

самым

простым

способом

обработки

модификаций

в

БД

Более

сложный

подход

описан

в

следующем

разделе

Однако

он

требует

уже

более

серьезного

программирования

и

в

рамках

данной

практики

не

реализуется

.  

12.5. 

Пути

развития

класса

 – 

источника

данных

Что

еще

не

хватает

в

разработанном

классе

 CDataSource?  


background image

!

!

П

ра
кт

ич

ес

ко

е

за

ня

ти

е

12

П

ри
ме

р

ра

зр

аб
от

ки

пр
ил

ож

ен

ия

дл

я

Л

И
Н

Т
Е

Р

с

ис

по

ль

зо

ва

ни

ем

Qt

!

85

 
 

E-

mai

l: 

ma

rke

t@

rele

x.r

u

Очевидно

буферизации

данных

Буферизация

необходима

для

того

чтобы

исключить

лишние

обращения

к

базе

данных

и

производить

модификацию

данных

не

перечитывая

данные

из

базы

а

также

поднять

производительность

обмена

между

ядром

СУБД

ЛИНТЕР

и

клиентом

за

счет

использования

команд

пакетной

загрузки

и

выгрузки

данных

 GETM, PUTM.  

Не

хватает

также

управлением

транзакциями

 RollBack/Commit.  

Кроме

того

источники

данных

можно

делать

связанными

в

соотношении

  '

Главный

-

подчиненный

', 

организовать

фильтрацию

/

сортировку

в

памяти

работу

с

выборкой

с

полной

буферизацией

на

клиенте

быстрый

поиск

записи

в

выборке

и

многое

другое

.  

В

 AsyncManager 

можно

произвести

оптимизацию

по

вызовам

 new/delete, 

если

ввести

список

свободных

объектов

и

использовать

его

как

буфер

свободных

объектов

.