Основная программа

Класс формы поиска

Класс формы вывода записей на экран. Создание таблицы

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

Класс доступа к данным

Класс Главная форма

Разработка классов приложения

Компоновка приложения

Объектная декомпозиция приложения

Разработка диаграммы состояний интерфейса

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Файл основной программы соответственно назовем main.cpp.

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

На рисунке 5 показан вид Главной формы на экране и приведена диаграмма класса Главная форма. Согласно своему визуальному представлению класс должен включать виджеты кнопок и обеспечивать реакцию на нажатие последних.

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

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

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

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

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

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

 

#ifndef mainForm_h

#define mainForm_h

#include <QtGui>

#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"

Window::Window()

{

QTextCodec *codec =

QTextCodec::codecForName("UTF-8");

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(); }

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

Рисунок 6 – Диаграмма классов класса 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

{

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

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

public:

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"

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

{

QTextCodec *codec =

QTextCodec::codecForName("UTF-8");

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; // возвращаем ключ поиска

}

 

Примечание – Сложные условия поиска составлены на основе специальной таблицы [1].

 

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

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

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

 

#ifndef addForm_h

#define addForm_h

#include <QtGui>

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"

addForm::addForm()

{

QTextCodec *codec =

QTextCodec::codecForName("UTF-8");

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 (рисунок 7).

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

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

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

#ifndef printForm_h

#define printForm_h

#include "bookFile.h"

#include <QtGui>

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"

printForm::printForm()

{

codec = QTextCodec::codecForName("UTF-8");

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.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();

}

}

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

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

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

 

#ifndef findForm_h

#define findForm_h

#include <QtGui>

#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"

findForm::findForm()

{

QTextCodec *codec =

QTextCodec::codecForName("UTF-8");

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"

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

{

QApplication appl(argc,argv);// создать приложение

Window win; // создать Главное окно

win.show(); // показать на экране Главное окно

return appl.exec(); // запуск цикла обработки сообщений

}

Литература

1. Г.С. Иванова. Программирование: Учеб. для вузов. – М.: Из-во Кнорус, 2013.

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