ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 12.04.2021
Просмотров: 1866
Скачиваний: 3

!
!
П
ра
кт
ич
ес
ко
е
за
ня
ти
е
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-
файлы

!
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)

!
!
П
ра
кт
ич
ес
ко
е
за
ня
ти
е
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.

!
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?

!
!
П
ра
кт
ич
ес
ко
е
за
ня
ти
е
12
П
ри
ме
р
ра
зр
аб
от
ки
пр
ил
ож
ен
ия
дл
я
Л
И
Н
Т
Е
Р
с
ис
по
ль
зо
ва
ни
ем
Qt
!
85
E-
mai
l:
ma
rke
t@
rele
x.r
u
Очевидно
,
буферизации
данных
.
Буферизация
необходима
для
того
,
чтобы
исключить
лишние
обращения
к
базе
данных
и
производить
модификацию
данных
,
не
перечитывая
данные
из
базы
,
а
также
поднять
производительность
обмена
между
ядром
СУБД
ЛИНТЕР
и
клиентом
за
счет
использования
команд
пакетной
загрузки
и
выгрузки
данных
GETM, PUTM.
Не
хватает
также
управлением
транзакциями
RollBack/Commit.
Кроме
того
,
источники
данных
можно
делать
связанными
в
соотношении
'
Главный
-
подчиненный
',
организовать
фильтрацию
/
сортировку
в
памяти
,
работу
с
выборкой
с
полной
буферизацией
на
клиенте
,
быстрый
поиск
записи
в
выборке
и
многое
другое
.
В
AsyncManager
можно
произвести
оптимизацию
по
вызовам
new/delete,
если
ввести
список
свободных
объектов
и
использовать
его
как
буфер
свободных
объектов
.