Пример 2.8. Приложение «Счетчик». Генерация нового сигнала.

В качестве примера разработаем приложение, которое считает отдельные нажатия на кнопку и серии по пять нажатий (рисунок 2.8).

Рисунок 2.8 – Внешний вид счетчика нажатий

На рисунке 2.9 приведена диаграмма взаимодействия объектов приложения в процессе работы посредством генерации и обработки сигналов (сообщения создания/уничтожения объектов и изменения их размеров в процессе компоновки не показаны, чтобы не усложнять рисунок).

Таким образом оба объекта счетчиков должны уметь увеличивать свое содержимое на единицу, т.е. включать соответствующий метод – слот. А первый счетчик еще и должен генерировать сигнал по достижении пяти нажатий.

 

Рисунок 2.9 – Сигналы в приложении

Оба счетчика будем строить на базе одного класса Counter, наследуемого от класса QLineEdit. В производном классе предусмотрим соответствующие сигнал tick_signal() и слот add_one() (рисунок 2.10).

Рисунок 2.10 – Структура класса Counter

Описание класса Counter можно поместить в отдельный файл, но для простоты чтения программы включим его в файл win.h вместе с описанием класса окна:

#ifndef win_h

#define win_h

#include <QLineEdit>

#include <QString>

#include <QLabel>

#include <QPushButton>

 

class Counter:public QLineEdit

{

Q_OBJECT

public:

Counter(const QString & contents, QWidget *parent=0):

QLineEdit(contents,parent){}

signals:

void tick_signal();

public slots:

void add_one()

{

QString str=text();

int r=str.toInt();

if (r!=0 && r%5 ==0) emit tick_signal();

r++;

str.setNum(r);

setText(str);

}

};

class Win: public QWidget

{

Q_OBJECT

protected:

QTextCodec *codec;

QLabel *label1,*label2;

Counter *edit1,*edit2;

QPushButton *calcbutton;

QPushButton *exitbutton;

public:

Win(QWidget *parent = 0);

};

#endif

Файл win.cpp в этом случае содержит только описание конструктора класса окна:

#include "win.h"

#include <QTextCodec>

#include <QHBoxLayout>

Win::Win(QWidget *parent):QWidget(parent)

{

codec = QTextCodec::codecForName("Windows-1251");

this->setWindowTitle(codec->toUnicode("Счетчик"));

label1 = new QLabel(codec->toUnicode("Cчет по 1"),this);

label2 = new QLabel(codec->toUnicode("Cчет по 5"),this);

edit1 = new Counter("0",this);

edit2 = new Counter("0",this);

calcbutton=new QPushButton("+1",this);

exitbutton=new QPushButton(codec->toUnicode("Выход"),this);

 

QHBoxLayout *layout1 = new QHBoxLayout();

layout1->addWidget(label1);

layout1->addWidget(label2);

 

QHBoxLayout *layout2 = new QHBoxLayout();

layout2->addWidget(edit1);

layout2->addWidget(edit2);

 

QHBoxLayout *layout3 = new QHBoxLayout();

layout3->addWidget(calcbutton);

layout3->addWidget(exitbutton);

 

QVBoxLayout *layout4 = new QVBoxLayout(this);

layout4->addLayout(layout1);

layout4->addLayout(layout2);

layout4->addLayout(layout3);

 

// связь сигнала нажатия кнопки и слота закрытия окна

connect(calcbutton,SIGNAL(clicked(bool)),

edit1,SLOT(add_one()));

connect(edit1,SIGNAL(tick_signal()),

edit2,SLOT(add_one()));

connect(exitbutton,SIGNAL(clicked(bool)),

this,SLOT(close()));

}

Файл main . cpp не отличается от соответствующих файлов предыдущих программ:

#include "win.h"

#include <QApplication>

 

int main(int argc, char *argv[])

{

QApplication app(argc, argv);

Win win(0);

win.show();

return app.exec();

}

Связь «сигнал-слот» устанавливается между объектами на этапе выполнения, поэтому она может быть разорвана при использовании операции disconnect () . Однако необходимость рассоединять объекты обычно не возникает, поскольку связь автоматически разрывается при уничтожении любого из объектов в ней участвующих.

2.4 Обработка событий. Рисование. События таймера

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

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

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

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

Как правило, необходимости в обработке событий при работе с виджетами не возникает: обработка большинства событий уже выполняется методами виджетов, и в результате этой обработки формируются необходимые сигналы. Так при щелчке пользователя по кнопке QPushButton виджет кнопки обрабатывает событие Нажатие на Кнопку и формирует сигнал clicked(). Но при необходимости возможно создание нестандартных обработчиков событий.

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

Рисование в простейшем варианте выполняется с помощью объекта класса QPainter. Объект этого класса получает доступ к фрагменту экрана, отведенному под окно, в котором выполняется рисование.

Само рисование программируют в обработчике события перерисовки paintEvent(), тогда каждый раз при перерисовке окна (например, когда окно появляется из-за других окон) рисунок возобновляется. Кроме того, при использовании этого события необходимость в стирании рисунка отпадает. Вместо этого следует обеспечить перерисовку окна при изменении положения фигур. Такую перерисовку обеспечивают методы QWidget update() и repaint(). Использование update() предпочтительно, так как метод сам определяет целесообразность немедленной перерисовки, приспосабливаясь к скорости изменения рисунка (при слишком большой частоте перерисовки она будет выполняться не каждый раз).

При создании движущегося изображения сигнал на перерисовку смещенного изображения целесообразно получать от таймера. Приложение, использующее средства Qt, может создавать произвольное количество таймеров с разными временными интервалами. Для этого используется функция класса QObject:

int startTimer(<Временной интервал в мс>) .

Число, возвращаемое функцией – номер таймера. Этот номер необходимо проверить, когда активизируется событие timerEvent(), чтобы быть уверенным, что обрабатывается сигнал от нужного таймера.

Для прекращения работы таймера используют функцию того же класса.

void killTimer(<Номер таймера>)

Пример 2.9. Создание движущихся изображений. Обработка события от таймера, событий визуализации и сокрытия окна, а также события перерисовки окна.

Пусть необходимо создать приложение, в окне которого вращаются вокруг своих геометрических центров линия и квадрат (рисунок 2.11).

Рисунок 2.11 – Внешний вид окна приложения

Приложение будет состоять из шести объектов: Окно, Кнопка, Холст, Таймер, Линия и Квадрат, не считая объекта самого приложения (рисунок 2.12). При этом объект Окно будет отвечать за создание своих компонентов: Холст (поля рисования) и Кнопки, их визуализацию и уничтожение. Основное назначение Кнопки – инициировать закрытие приложения. Холст будет отвечать за создание и уничтожение Таймера, а также за рисование пошагово перемещаемых фигур Линия и Квадрат при обработке сигналов Таймера.

Рисунок 2.12 – Фрагмент объектной декомпозиции приложения (без сообщений создания и уничтожения объектов)

Запускать таймер будем при визуализации Холста в обработчике события showEvent(), а выключать – при его сокрытии (в обработчике hideEvent()). Таким образом всего обработываем четыре типа событий:

· showEvent() – включение таймера;

· timerEvent() – инициация перерисовки Холста;

· paintEvent() – рисование пошагово перемещающихся фигур;

· hideEvent() – выключение таймера.

Для уточнения взаимодействия объектов построим диаграмму последовательности действий (рисунок 2.13).

Рисунок 2.13 – Диаграмма последовательности действий приложения

На рисунке 2.14 показана диаграмма классов приложения. Классы MyLine и MyRect наследуем от абстрактного класса Figura. При этом используем сложный полиморфизм, поскольку переопределяемый в иерархии метод рисовая draw() будет вызываться из метода move() базового класса.

 

Рисунок 2.14 – Диаграмма классов приложения

В соответствии с рекомендациями С++ приложение декомпозируем на 7 файлов, зависимость между которыми представлена на рисунке 2.15. Шесть из семи файлов образуют три модуля, хранящие описания отдельных классов или групп классов. А седьмой файл source.cpp содержит основную программу.

Рисунок 2.15 – Диаграмма компоновки приложения

Файл figura.h содержит описания классов Figura, MyLine и MyRect:

#ifndef figura_h

#define figura_h

#include <QPainter>

 

class Figura

{

protected:

int x,y,halflen,dx,dy,r;

virtual void draw(QPainter *Painter)=0;

public:

Figura(int X,int Y,int Halflen):

x(X),y(Y),halflen(Halflen){}

void move(float Alpha,QPainter *Painter);

};

class MyLine:public Figura

{

protected:

void draw(QPainter *Painter);

public:

MyLine(int x,int y,int halflen):Figura(x,y,halflen){}

};

class MyRect:public Figura

{

protected:

void draw(QPainter *Painter);

public:

MyRect(int x,int y,int halflen):Figura(x,y,halflen){}

};

#endif

Файл figura.cpp содержит описание методов этих же классов:

#include "figura.h"

#include <math.h>

 

void Figura::move(float Alpha,QPainter *Painter)

{

dx=halflen*cos(Alpha);

dy=halflen*sin(Alpha);

draw(Painter);

}

void MyLine::draw(QPainter *Painter)

{

Painter->drawLine(x+dx,y+dy,x-dx,y-dy);

}

void MyRect::draw(QPainter *Painter)

{

Painter->drawLine(x+dx,y+dy,x+dy,y-dx);

Painter->drawLine(x+dy,y-dx,x-dx,y-dy);

Painter->drawLine(x-dx,y-dy,x-dy,y+dx);

Painter->drawLine(x-dy,y+dx,x+dx,y+dy);

}

Файл area.h содержит описание класса Area:

#include "figura.h"

#include <QWidget>

 

class Area : public QWidget

{

int myTimer; // идентификатор таймера

float alpha; // угол поворота

public:

Area(QWidget *parent = 0);

~Area();

MyLine *myline;

MyRect *myrect;

protected:

// обработчики событий

void paintEvent(QPaintEvent *event);

void timerEvent(QTimerEvent *event);

void showEvent(QShowEvent *event);

void hideEvent(QHideEvent *event);

};

#endif

Файл area.cpp содержит описание методов класса Area, включая обработчики событий:

#include "area.h"

#include <QTimerEvent>

Area::Area(QWidget *parent):QWidget(parent)

{

setFixedSize(QSize(300,200));

myline=new MyLine(80,100,50);

myrect=new MyRect(220,100,50);

alpha=0;

}

void Area::showEvent(QShowEvent *)

{

myTimer=startTimer(50); // создать таймер

}

void Area::paintEvent(QPaintEvent *)

{

QPainter painter(this);

painter.setPen(Qt::red);

myline->move(alpha,&painter);

myrect->move(alpha*(-0.5),&painter);

}

void Area::timerEvent(QTimerEvent *event)

{

if (event->timerId() == myTimer) // если наш таймер

{

alpha=alpha+0.2;

update(); // обновить внешний вид

}

else

QWidget::timerEvent(event); // иначе передать для стандартной

// обработки

}

void Area::hideEvent(QHideEvent *)

{

killTimer(myTimer); // уничтожить таймер

}

Area::~Area()

{

delete myline;

delete myrect;

}

Файл window.h содержит описание класса окна:

#ifndef window_h

#define window_h

#include "area.h"

#include <QPushButton>

class Window : public QWidget

{

protected:

QTextCodec *codec;

Area * area; // область отображения рисунка

QPushButton * btn;

public:

Window();

};

#endif

Файл window.cpp содержит описание конструктора класса окна:

#include "window.h"

#include <QVBoxLayout>

#include <QTextCodec>

Window::Window()

{

codec = QTextCodec::codecForName("Windows-1251");

this->setWindowTitle(codec->toUnicode("Обработка событий"));

area = new Area( this );

btn = new QPushButton(codec->toUnicode("Завершить"),this );

QVBoxLayout *layout = new QVBoxLayout(this);

layout->addWidget(area);

layout->addWidget(btn);

connect(btn, SIGNAL(clicked(bool)),this,SLOT(close()));

};

И, наконец, файл main.cpp содержит основную программу:

#include "window.h"

#include <QApplication>

int main(int argc, char *argv[])

{

QApplication appl(argc, argv);

Window win;

win.show();

return appl.exec();

}

Для создания проекта был использован файл .pro следующего вида:

TEMPLATE = app

TARGET = Ex02_09

QT += gui widgets

CONFIG += release

 

# Input

SOURCES += Source.cpp Window.cpp Area.cpp Figura.cpp

HEADERS += Window.h Area.h Figura.h

Этот файла позволяет получать проект Visual Studio при наличии в среде специального плагина или напрямую используется для создания проекта Qt Creator.

2.5 Вывод данных на экран в виде таблиц. Строки, массивы строк и файлы

Как уже упоминалось ранее библиотека Qt – профессиональная библиотека, включающая не только классы для создания виджетов. Она также содержит большое количество классов для реализации сложных структур данных, таких как списки и деревья, классы для работы с базами данных, классы для работы с файлами и т. п.

Описывать все множество классов Qt в рамках данного пособия нецелесообразно, поскольку с этими описаниями можно ознакомиться, используя Qt Assistant, специальные сайты в Интернете или соответствующую литературу [1]. Вместо этого использование перечисленных в заголовке параграфа классов продемонстрируем на примере создания несложного приложения «Записная книжка».

Пример 2.10. Записная книжка. Пусть необходимо разработать приложение, которое обеспечивает хранение в файле фамилии и имена абонентов, а также их телефоны. Приложение также должно осуществлять вывод всех хранимых данных на экран и поиск телефона конкретного абонента по фамилии, по имени, а также по имени и фамилии одновременно.

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

На рисунке 2.16 показаны основные формы интерфейса создаваемого приложения.

Рисунок 2.16 - Формы интерфейса приложения Записная книжка:

а – главная форма приложения; б – форма добавления данных; в – форма запроса на поиск данных; г – форма вывода результатов поиска или всего содержимого файла

Диаграмма состояний интерфейса должна предусмотреть вывод сообщения при отсутствии записей о телефоне конкретного абонента (рисунок 2.17).

Условные обозначения:

1 – нажатие кнопки Добавить;

2 – нажатие кнопки Показать;

3 – нажатие кнопки Найти;

4 – нажатие кнопки Выход;

5 – нажатие кнопки Назад:

6 – нажатие кнопки Найти после ввода ключей [Записи найдены];

7 – нажатие кнопки Найти после ввода ключей [Записи не найдены];

8 - нажатие кнопки OK

Рисунок 2.17 - Диаграмма состояний интерфейса приложения Записная книжка

При выполнении объектной декомпозиции учтем, что каждое окно – отдельный объект. Классы каждого окна должны обеспечивать реакцию на все виды действий пользователя. На диаграмме объектов (рисунок 2.18) покажем сообщения, которыми эти объекты в первом приближении должны обмениваться. В дальнейшем в процессе реализации классов возможно выявление уточняющих сообщений и дополнительных объектов.

Рисунок 2.18 - Объектная декомпозиция приложения Записная книжка

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

Следует обратить внимание, что объект Файл от Формы поиска получает два сообщения: Искать первую запись и Искать следующую запись. Такая реализация упрощает организацию поиска многих записей, удовлетворяющих заданным данным: сначала ищем первую запись, и, если она найдена, то организуем поиск всех последующих с того места в файле, где очередная запись была найдена.

В соответствии с внешним видом форм приложения, изображенных на рис. 9.16 объекты Форма Вывод всех записей и Форма Вывод результатов поиска можно проектировать как объекты одного класса. Таким образом всего необходимо разработать 5 классов:

· класс главной формы;

· класс формы добавления записей;

· класс формы вывода записей на экран;

· класс формы поиска;

· класс данных.

Как и ранее при разработке оконных приложений на языке С++ при компоновке приложения поместим описание каждого класса в свой модуль, состоящий из двух файлов:

· файла с расширением .h, содержащего объявление класса;

· файла с расширение .cpp, содержащего описание методов класса.

Класс Главная форма. На рисунке 2.19 показан вид Главной формы на экране и приведена диаграмма класса Главная форма.

Согласно своему визуальному представлению класс должен включать виджеты кнопок и обеспечивать реакцию на нажатие последних, соответственно необходимы конструктор Window() и слоты:

ShowAdd () – метод Показать форму добавления записей;

ShowPrint () – метод Показать форму вывода всех записей;

ShowFind() – метод Показать форму поиска записей.

Рисунок 2.19 - Диаграмма классов Главной формы

Объявление класса Главной формы поместим в заголовочный файл mainForm.h:

 

#ifndef mainForm_h

#define mainForm_h

#include <QWidget>

#include <QPushButton>

#include <QTextCodec>

#define RUS(str) codec->toUnicode(str)

#include "addForm.h"

#include "findForm.h"

#include "printForm.h"

 

class Window : public QWidget

{

Q_OBJECT

QPushButton * btnAdd,* btnPrint,

* btnFind, * btnExit;

addForm winAdd; // форма Добавления

printForm winPrint; // форма Отображение всех

findForm winFind; // форма Поиска

public:

Window(); // конструктор

public slots:

void showAdd(); // показать форму Добавления

void showPrint(); // показать форму Отображения всех

void showFind(); // показать форму Поиска

};

#endif

 

Соответственно файл mainForm.cpp должен содержать описание методов класса Windows:

 

#include "mainForm.h"

#include <QVBoxLayout>

 

Window::Window()

{

QTextCodec *codec =

QTextCodec::codecForName("Windows-1251");

this->setWindowTitle(RUS("Записная книжка"));

// создаем кнопки

btnAdd = new QPushButton(RUS("Добавить"),this);

btnFind = new QPushButton(RUS("Найти"),this);

btnPrint = new QPushButton(RUS("Показать"),this);

btnExit = new QPushButton(RUS("Выход"),this);

// создаем компоновщик и передаем ему кнопки

QVBoxLayout *layout = new QVBoxLayout(this);

layout->addWidget(btnAdd);

layout->addWidget(btnFind);

layout->addWidget(btnPrint);

layout->addWidget(btnExit);

// устанавливаем размеры окна

resize(180,150);

// связываем сигналы от нажатия кнопок со слотами

connect(btnExit, SIGNAL(clicked(bool)),

this,SLOT(close()));

connect(btnAdd,SIGNAL(clicked(bool)),

this,SLOT(showAdd()));

connect(btnFind,SIGNAL(clicked(bool)),

this,SLOT(showFind()));

connect(btnPrint, SIGNAL(clicked(bool)),

this,SLOT(showPrint()));

}

void Window::showAdd()

{ winAdd.show(); }

void Window::showFind()

{ winFind.show(); }

void Window::showPrint()

{ winPrint.showAll(); }

 

Класс данных. Как указано выше, класс данных должен инкапсулировать связь с файлом и все операции над хранимыми в файле данными. С учетом того набора операций с файлом, который был выявлен при объектной декомпозиции приложения, получаем диаграмму классов для этого класса, изображенную на рисунке 2.20.

Рисунок 2.20 - Диаграмма классов класса bookFile

Кроме операций над данными на диаграмме показано, что класс данных должен включать адрес объекта специального класса QFile, который предназначен в Qt для работы с текстовыми или двоичными файлами. Так класс QFile содержит методы проверки существования файла, его открытия и закрытия файла, а также методы чтения и записи блоков данных заданной длины.

Согласно заданию в файле должны храниться имена и фамилии абонентов на русском языке в кодировке Unicode, т. е. именно в том виде, в котором они были введены посредством виджетов ввода. Соответственно для представления данных в программе целообразно использовать объекты-строки класса QString, которые также предназначены для работы с кодировкой Unicode. Для удобства обработки три объекта-строки: для хранения имени, фамилии и телефона – целесообразно собрать в структуру recType (структура на диаграмме классов показана как класс) и предусмотреть в объекте соответствующий буфер r для хранения данных в процессе поиска.

Кроме этого класс данных должен включать поля для размещения булевских ключей поиска данных:

· k1 – в запросе присутствует фамилия;

· k2 – в запросе присутствует имя;

· k3 – найдена фамилия;

· k4 – найдено имя;

· ff – найдена запись.

Полностью формируем объявление класса в файле bookFile.h:

#ifndef bookFile_h

#define bookFile_h

#include <QFile>

 

struct recType // структура записи для одного абонента

{ QString fam,name,nom; };

 

class bookFile

{

bool k1,k2,k3,k4,ff; // ключи поиска

public:

QFile * f; // указатель на объект класса QFile

recType r; // буфер ввода-вывода

bookFile(); // конструктор

~bookFile(); // деструктор

bool addRec(recType r); // добавление записи в файл

bool readRec(); // чтение записи из файда

bool findFirst(const recType r1); // поиск первой

// записи, удовлетворяющей условию

bool findNext(const recType r1);

// поиск следующей записи

};

#endif

 

Класс QFile библиотеки Qt обеспечивает чтение из файла и запись в файл указанного количества байтов, но при работе со сложными типами хранимых данных использование методов класса QFile требует программирования дополнительных действий по преобразованию данных в требуемый формат.

Гораздо эффективнее при выполнении операций ввода-вывода применять потоки – объекты класса QDataStream – для двоичных файлов и QTextStream – для текстовых файлов.

Поток типа QTextStream предназначен для работы с данными, хранимыми в файле в текстовом виде. При этом данные текстовых типов, таких как Char, QChar, QString, QByteArray, записываются в файл и читаются из него без изменений, а данные базовых типов языка С++, таких как short, int, long, float, double, при вводе из файла и выводе в файл преобразуются из текстового представления во внутреннее и обратно.

Поток типа QDataStream также обеспечивает выполнение операций ввода вывода с данными базовых типов С++, однако кроме этого он позволяет также вводить и выводить объекты многих классов библиотеки Qt, таких как QBrush, QColor, QDateTime, QFont, QPixmap, QString, QVariant. Формат внутреннего представления данных в файле отличен от формата двоичного файла С++. В процессе разработки классов библиотеки этот формат многократно менялся, но при этом он не требует преобразования числовых данных в текстовое представление и основан на внутреннем двоичном формате данных.

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

Ниже представлено описание методов класса, которое размещаем в файле bookFile.cpp:

 

#include "bookFile.h"

#include "mainForm.h"

#include <QMessageBox>

 

bookFile::bookFile() // конструктор

{

QTextCodec *codec =

QTextCodec::codecForName("Windows-1251");

f=new QFile("book.txt");// создаем объект-файл

if(!f->exists()) // если файл не существует, то

{ // формирмируем сообщение

QMessageBox msg(QMessageBox::Critical,

RUS("Файл не найден"),

RUS("Файл book.txt создан"),

QMessageBox::Ok,0);

msg.exec();// выводим сообщение на экран

}

f->open(QFile::ReadWrite); // открываем файл

// для ввода-вывода

}

bookFile::~bookFile() // деструктор

{

f->close(); // закрываем файл

delete f; // освобождаем память

}

bool bookFile::addRec(recType r)

{

f->seek(f->size()); // переходим на конец файла

QDataStream out(f); // связываем с файлом поток вывода

out<<r.fam<<r.name<<r.nom;// выводим данные в файл

return true;

}

bool bookFile::readRec()

{

QDataStream in(f); // связываем с файлом поток ввода

if (in.atEnd())return false;

else

{

in>>r.fam>>r.name>>r.nom;

return true;

}

}

bool bookFile::findFirst(const recType r1)

{

k1=(r1.fam==""); // устанавливаем два ключа поиска

k2=(r1.name=="");

ff=false; // устанавливаем ключ поиска «запись не найдена»

f->reset();

bool fff = readRec();

while(fff &&(!ff))

{

k3=(r1.fam==r.fam); //строим еще два ключа поиска

k4=(r1.name==r.name);

if ((!k1 && !k2 && k3 && k4)||

(!k1 && k2 && k3)||

(k1 && !k2 && k4))

ff=true; // ключ поиска «запись найдена»

else fff=readRec();

}

return ff; // возвращаем ключ поиска

}

bool bookFile::findNext(const recType r1)

{

ff=false; // ключ поиска «запись не найдена»

bool fff = readRec();

while((!ff) && fff)

{

k3=(r1.fam==r.fam);//строим еще два ключа поиска

k4=(r1.name==r.name);

if ((!k1 && !k2 && k3 && k4)||

(!k1 && k2 && k3)||

(k1 && !k2 && k4))

ff=true; // ключ поиска «запись найдена»

else fff=readRec();

}

return ff; // возвращаем ключ поиска

}

 

Класс формы добавления записей. Внешний вид окна формы представлен на рисунке 2.21, а . Для реализации формы необходимо создать интерфейсный класс addForm, который должен включать виджеты кнопок, меток и элементов ввода (рисунок 2.21).

Рисунок 2.21 - Внешний вид (а) и диаграмма класса (б) окна добавления записей

Объявление класса поместим в файл addForm.h:

 

#ifndef addForm_h

#define addForm_h

#include <QWidget>

#include <QLabel>

#include <QLineEdit>

#include <QPushButton>

 

class addForm : public QWidget

{

Q_OBJECT

QLabel * family,* name,* nomer;

QLineEdit * familyEdit,* nameEdit,* nomerEdit;

QPushButton * btnAdd, * btnExit;

public:

addForm();

public slots:

void addRecord();

};

#endif

 

Реализацию методов соответственно помещаем в файл addForm.cpp:

 

#include "addForm.h"

#include "bookFile.h"

#include "mainForm.h"

#include <QVBoxLayout>

 

addForm::addForm()

{

QTextCodec *codec =

QTextCodec::codecForName("Windows-1251");

this->setWindowTitle(RUS("Добавление записей"));

QVBoxLayout *layoutV1 = new QVBoxLayout();

family=new QLabel(RUS("Фамилия"), this);

name=new QLabel(RUS("Имя"), this);

nomer=new QLabel(RUS("Телефон"), this);

layoutV1->addWidget(family);

layoutV1->addWidget(name);

layoutV1->addWidget(nomer);

QVBoxLayout *layoutV2 = new QVBoxLayout();

familyEdit=new QLineEdit(RUS(""), this);

nameEdit=new QLineEdit(RUS(""), this);

nomerEdit=new QLineEdit(RUS(""), this);

layoutV2->addWidget(familyEdit);

layoutV2->addWidget(nameEdit);

layoutV2->addWidget(nomerEdit);

QHBoxLayout *layoutG1 = new QHBoxLayout();

layoutG1->addLayout(layoutV1);

layoutG1->addLayout(layoutV2);

QHBoxLayout *layoutG2 = new QHBoxLayout();

btnAdd=new QPushButton(RUS("Добавить"), this);

btnExit=new QPushButton(RUS("Назад"),this);

layoutG2->addWidget(btnAdd);

layoutG2->addWidget(btnExit);

QVBoxLayout *layout = new QVBoxLayout(this);

layout->addLayout(layoutG1);

layout->addLayout(layoutG2);

connect(btnAdd, SIGNAL(clicked(bool)),

this,SLOT(addRecord()));

connect(btnExit, SIGNAL(clicked(bool)),

this,SLOT(close()));

}

void addForm::addRecord()

{

bookFile book; recType r;

r.fam=familyEdit->text();

r.name=nameEdit->text();

r.nom=nomerEdit->text();

familyEdit->clear();

nameEdit->clear();

nomerEdit->clear();

book.addRec(r);

}

 

Класс формы вывода записей на экран. При создании интерфейсов достаточно часто возникает необходимость вывода на экран табличных данных. Библиотека Qt с этой целью предоставляет специальный класс QTableWidget. Объект этого класса – таблица, каждая ячейка которой – объект класса QTableWidgetItem (рисунок 2.22).

Рисунок 2.22 - Внешний вид (а) и диаграмма классов (б) формы вывода записей на экран

Оба класса предлагают большое количество методов, позволяющих создавать на экране таблицы с текстовой и графической информацией, а также выводить и вводить из них данные [21]. В рассматриваемом примере таблица используется только для вывода результатов, поэтому объекты класса ячейки создаются с флагами Qt::NoItemFlags, запрещающими не только изменение, но и выделение ячеек.

Описание класса помещаем в файл printForm.h:

#ifndef printForm_h

#define printForm_h

#include "bookFile.h"

#include <QTableWidget>

#include <QPushButton>

 

class printForm : public QWidget

{

QTextCodec *codec;

QTableWidget *table; // таблица

QPushButton *btnExit;

void showRow(int i,recType r);// вывод строки таблицы

public:

printForm(); // конструктор

void showAll(); // показать все записи

void showResults(recType r1); // показать результаты поиска

};

#endif

 

Реализацию методов помещаем в файл printForm.cpp:

 

#include "printForm.h"

#include "mainForm.h"

#include <QHBoxLayout>

#include <QMessageBox>

printForm::printForm()

{

codec = QTextCodec::codecForName("Windows-1251");

this->setWindowTitle(RUS("Результат"));

QStringList strlist; // объект Список строк

strlist << RUS("Фамилия")<< RUS("Имя")

<< RUS("Телефон"); // записываем строки заголовка

table = new QTableWidget(10,3,this); // создаем таблицу

table->setHorizontalHeaderLabels(strlist);// включаем

// заголовок

QHBoxLayout *layoutG2 = new QHBoxLayout();

btnExit=new QPushButton(RUS("Назад"), this);

layoutG2->addWidget(btnExit);

QVBoxLayout *layout = new QVBoxLayout(this);

layout->addWidget(table);

layout->addLayout(layoutG2);

connect(btnExit, SIGNAL(clicked(bool)),

this,SLOT(close()));

}

void printForm::showRow(int i,recType r)

{

QTableWidgetItem *item; // элемент таблицы

item = new QTableWidgetItem(); // создаем элемент

item->setFlags(Qt::NoItemFlags);//запрещаем

// выделение

item->setText(r.fam); // записываем текст

table->setItem(i,0,item);// привязываем элемент

// к таблице

item = new QTableWidgetItem();// создаем элемент

item->setFlags(Qt::NoItemFlags); //запрещаем

// выделение

item->setText(r.name);

table->setItem(i,1,item); // привязываем элемент

item = new QTableWidgetItem();// создаем элемент

item->setFlags(Qt::NoItemFlags); //запрещаем

// выделение

item->setText(r.nom); // записываем текст

table->setItem(i,2,item); // привязываем элемент

}

void printForm::showAll()

{

bookFile book;

if (!book.readRec())

{ // если файл пустой , то создаем сообщение

QMessageBox msg(QMessageBox::Critical,

RUS("Нет данных"),

RUS("База пуста"),

QMessageBox::Ok,0);

msg.exec(); // выводим сообщение

}

else

{ // иначе - выводим таблицу по строкам

showRow(0,book.r);

int i=0;

while (book.readRec())

showRow(++i,book.r);

table->setRowCount(i+1);

resize(350,330);

show();

}

}

void printForm::showResults(recType r1)

{

bookFile book;

book.f->reset();

if (!book.findFirst(r1))

{ // если данные не найдены, то создаем сообщение

QMessageBox msg(QMessageBox::Critical,

RUS("Нет данных"),

RUS("Данные не найдены"),

QMessageBox::Ok,0);

msg.exec();

}

else

{ // иначе - выводим результаты по строкам

showRow(0,book.r);

int i=0;

while (book.findNext(r1))

showRow(++i,book.r);

table->setRowCount(i+1);

resize(350,200);

show();

}

}

 

Класс формы поиска. По структуре форма поиска аналогична форме ввода, поэтому их классы имеют одинаковую структуру (рисунок 2.23).

Рисунок 2.23. Внешний вид и диаграмма классов формы поиска

Описание класса поместим в файл findForm.h:

 

#ifndef findForm_h

#define findForm_h

#include <QLabel>

#include "printForm.h"

 

class findForm : public QWidget

{

Q_OBJECT

QLabel * family,* name;

QLineEdit * familyEdit,* nameEdit;

QPushButton * btnFind, * btnExit;

printForm winPrint;

public:

findForm(); // конструктор

public slots:

void findRecs(); // метод поиска записей

};

#endif

 

Реализацию методов помещаем в файл findForm.сpp:

 

#include "findForm.h"

#include "bookFile.h"

#include <QVBoxLayout>

#include <QLineEdit>

#include <QTextCodec>

#define RUS(str) codec->toUnicode(str)

 

findForm::findForm()

{

QTextCodec *codec =

QTextCodec::codecForName("Windows-1251");

this->setWindowTitle(RUS("Поиск записей"));

QVBoxLayout *layoutV1 = new QVBoxLayout();

family=new QLabel(RUS("Фамилия"), this);

name=new QLabel(RUS("Имя"), this);

layoutV1->addWidget(family);

layoutV1->addWidget(name);

QVBoxLayout *layoutV2 = new QVBoxLayout();

familyEdit=new QLineEdit(RUS(""), this);

nameEdit=new QLineEdit(RUS(""), this);

layoutV2->addWidget(familyEdit);

layoutV2->addWidget(nameEdit);

QHBoxLayout *layoutG1 = new QHBoxLayout();

layoutG1->addLayout(layoutV1);

layoutG1->addLayout(layoutV2);

QHBoxLayout *layoutG2 = new QHBoxLayout();

btnFind=new QPushButton(RUS("Найти"), this);

btnExit=new QPushButton(RUS("Назад"), this);

layoutG2->addWidget(btnFind);

layoutG2->addWidget(btnExit);

QVBoxLayout *layout = new QVBoxLayout(this);

layout->addLayout(layoutG1);

layout->addLayout(layoutG2);

connect(btnFind, SIGNAL(clicked(bool)),

this,SLOT(findRecs()));

connect(btnExit, SIGNAL(clicked(bool)),

this,SLOT(close()));

}

void findForm::findRecs()

{

recType r; // параметры поиска

r.fam=familyEdit->text();

r.name=nameEdit->text();

winPrint.showResults(r);

}

 

Основная программа. Основная программа данного приложения создает приложение, визуализирует главное окно и передает передает управление циклу обработки сообщений:

#include "mainForm.h"

#include <QApplication>

 

int main(int argc, char *argv[])

{

QApplication appl(argc,argv);

Window win;

win.show();

return appl.exec();

}

Сборка приложения может осуществляться как в консольном режиме, так и в средах Qt Creator или Visual Studio.

 

 

Литература

1. Шлее М. Qt 5.3. Профессиональное программирование на С++. – СПб.: БХВ-Петербург, 2015.