Технология создания форм для работы со справочниками
Для начинающих, да и не только, программистов зачастую большой проблемой является создание однотипных форм для работы со справочниками и тому подобных задач. По крайней мере большое количество вопросов как на форумах так и лично, об этом свидетельствует. Поэтому и по просьбе одного из своих друзей я решил посвятить отдельную статью вопросу организации работыс формами — и в частности организации работы с однотипными формами. Ведь в крупных проектах не редкость огромное количество различных справочников — таких как справочники должностей, справочники подразделений, справочники типов выплат и несть им числа. Мало того, в процессе эксплуатации программы не редкость дополнительный рост числа справочников — что само по себе доставляет. Как же с этим бороться? Ответ на это я попытаюсь дать в своей статье. Хочу обратить внимание, что приведенные рекомендации основываются на личном практическом опыте разработки крупных программных систем. В статье будут рассмотрены две основные темы
- Использование в работе шаблон проектирования Модель-Представление-Контроллер (MVС)
- Динамическое создание форм, как это делаю я.
Эти две темы практически не отделимы друг от друга — по одной простой причине — если вы не пользуетесь MVС, рано или поздно, в сравнительно большом проекте, вы начнете писать жуткий «макаронный» код, в котором будете путаться сами. Что бы этого избежать — используйте хорошие шаблоны программирования.
Все примеры, которые приведены в данной статье опираются на средства разработки компании Borland — Borland Builder C++, но по сути могу применяться в любой системе вне зависимости от языка программирования. BCB используется здесь только потому, что на нем пишет человек, который попросил написать данную статью.
И так MVС. Что это такое, с чем ее едят и почему именно вам надо этот шаблон использовать? Все очень просто. основной принцип использования MVС заключается в разделении кода — если вы разделяете код, который получает данные из базы, от кода коорый эти данные обрабатывает и отдельно формируете код, который результат этой обработки показывает — поздравляю — вы используете MVС. Большинство удачных программных инструментов спроектировано таким образом, что следовать этому шаблону в них достаточно просто, легко и интуитивно понятно. Но как раз таки BCB и Delphi к таким системам не относятся (за что, я думаю, их создатели будут гореть в Аду — в специально для них созданном 10 круге), потому что в них чрезвычайно просто и легко писать «макаронный» быдлокод — когда все получение данных, их обработка и вывод запихивается в один единственный класс единственной формы, на которую горе программист накидал под сотню контролов — и весь этот, с позволения сказать, код размером в несколько тысяч строк компилируется в исполнимый файл и гордо называется «продуктом».
В принципе ничего конечно страшного в этом нет — если конечно не вам это все в последствии обслуживать и поддерживать. Потому что, в процессе обслуживания и поддержки работающей программы так или иначе приходится ее расширять, дорабатывать и… и вот тут то наступает армагедец. Потому что, через несколько месяцев уже плохо помниться зачем была написана та функция — которая вызывается по 20 раз в разных местах, и почему в некоторых местах в место этой функции вызывается совершенно другой код…ну и так далее. Те, кому приходилось поддерживать «продукты» жизнедеятельности таких вот «программистов» меня поймут.
Как реализовать все это на практике? Спроектируем небольшой модуль системы — предположим наш модуль будет реализовать следующую функциональность:
- Вывод и отображение записей различных справочников.
- Добавление, редактирование и удаление выбранных записей.
- Предоставление единого интерфейса для получения записей справочников (списки, деревья и пр.) в любой точке приложения.
Для начала вполне достаточно. Если использовать стандартный подход — то обычно создается форма, туда запихиваются нужные контролы и компоненты — потом создаются еще форма(а зачастую и не одна) для редактирования и добавления. Выборка данных из справочников выполняется в каждой точке приложения независимо.
Как это сделать с точки зрения MVС? Необходимо спроектировать четыре класса — класс-контроллер, класс доступа к данным, класс-форма основная и класс-форма добавления редактирования. Так как справочник по-английски Reference то соответственно классы лично я именую следующим образом:
- TRefernceController — класс контроллера, наследует от общего класса контроллера (если нужно);
- TmfMain — класс основной формы, наследует от TForm (BCB);
- TfmAddEdit — класс вспомогательной формы, наследует от TForm (BCB);
- TdmMain — модуль данных, наследует от TDataModule (BCB).
Естественно, наследование классов зависит от вашего конкретного приложения — я взял простейший вариант, что бы не слишком углубляться и не путаться. Название модулей — тоже зависит от того, какая система наименований принята у вас. Например, TmfMain — у меня имеет название любая основная форма модуля, но впрочем, это детали и я оставляю это на ваше рассмотрение.
Объевление класса контроллера может выглядеть примерно таким образом:
Код:
#define loginH
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
#include <Buttons.hpp>
#include <ExtCtrls.hpp>
#include "main.h" //подключаем заголовочные файлы форм
#include "addedit.h"
class TReferenceController :public TGlobalController //если нужно, наследуем от своего общего класса
{
TfmMain *fmMain;
TfmMain *dmMain;
...//другие закрытые объявления
public:
__fastcall TRefernceController(TConnection* db);
void __fastcall getList(TStrings *list, constint index);
...//другие открытые методы
};
#endif
Как видим ничего особо сложного тут нет. Конкретная реализация контроллера зависит от задач приложения, но суть от этого не меняется — задача класса — обеспечивать всю работу с объектами, скрывая от пользователя-приложения все детали и тонкости. Основной момент — в конструктор класса я передаю указатель на существующее в приложении подключение — что бы было чем инициализировать модуль подключения к БД.
Класс модуля данных, в свою очередь, обеспечивает подключение к БД, получение необходимых данных, операции создания, редактирования и удаления данных в таблицах.
Его объявление может выглядеть примерно таким образом:
Код:
#define maindmH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ADODB.hpp>
#include <DB.hpp>
#include <Provider.hpp>
#include <DBClient.hpp>
//---------------------------------------------------------------------------
class TdmMain :public TDataModule
{
__published: // IDE-managed Components
TADOConnection *adoConnect;
TDataSource *dsListOrder;
TADOQuery *adoListOrder;
private: // User declarations
int UserId;
//другие закрытые методы и свойства
public: // User declarations
__fastcall TdmMain(TComponent* Owner, constbool visibleForm =true);
__fastcall TdmMain::TdmMain(TComponent* Owner, TConnection *db);
void __fastcall getListReference(TStrings *list);
//другие открытые методы
};
//---------------------------------------------------------------------------
extern PACKAGE TdmMain *dmMain;
//---------------------------------------------------------------------------
#endif
В описании форм нет ничего особенного, поэтому код приводить считаю тут излишним.
Общий алгоритм выглядит следующим образом:
- Приложение инициализирует модуль;
- Создает объект класса TReferenceController, путем вызова его конструктора и передав ему указатель на открытое соединение с БД;
- В качестве параметров конструктора создается объект модуля данных;
- Если установлен флаг создания формы — создаем форму.
Пример кода конструктора контроллера:
Код:
__fastcall TReferenceController::TReferenceController(TConnection *db, constbool visibleform):dmMain(NULL,db)
{
//если нужно - создаем форму, заполняем и показываем ее модально
if(visibleForm)
{
fmMain =new TfmMain(NULL);
dmMain->getListTab(fmMain->PageContorl,0);//загружаем табы справочников и устанавливаем индекс
fmMain->ShowModal();
}
}
Думаю основная идея вполне понятна. Естественно, использование MVC немного усложняет жизнь на первом этапе — но зато значительно упрощает ее в дальнейшем.
Если же, идея с использованием контроллера все же вам кажется чересчур сложной — вы можете использовать упрощенный вариант — две формы и модуль данных. В таком случае получение, обработка и запись данных реализуется в модуле данных (dmMain) и соответственно там располагаются все компоненты для работы с БД. Формы в свою очередь реализуют отображение и обработку ввода данных.
И здесь основная проблема у начинающих возникает с тем, как создать динамически форму и передать ей нужные данные. Делается это следующим образом. Предположим что у нас в базе содержатся таблицы-справочники следующего вида:
Код:
[Kod][int] IDENTITY (1, 1) NOT NULL , автоинкрементный
[KodMR][int]NULL , код места работы
[MR][nvarchar](150) COLLATE Cyrillic_General_CI_AS NULL , название места работы
[prMR][nvarchar](250) COLLATE Cyrillic_General_CI_AS NULL , название места работы для формирования приказа
[arh][int] NOT NULL признак записи в архиве 0 или 1
) ON [PRIMARY]
CREATE TABLE [dbo].[Spr_Slugb](
[Kod][int] IDENTITY (1, 1) NOT NULL , автоинкрементный
[KodPodr][int]NULL , код службы (отдела)
[Podr][nvarchar](250) COLLATE Cyrillic_General_CI_AS NULL , название службы (отдела)
[arh][int] NOT NULL признак записи в архиве 0 или 1
) ON [PRIMARY]
CREATE TABLE [dbo].[Spr_PodPodr](
[Kod][int] IDENTITY (1, 1) NOT NULL , автоинкрементный
[KodPPodr][int]NULL , код подотдела
[PodPodr][nvarchar](255) COLLATE Cyrillic_General_CI_AS NULL , название подотдела
[arh][int] NOT NULL признак записи в архиве 0 или 1
) ON [PRIMARY]
CREATE TABLE [dbo].[SprDolg](
[id][int] IDENTITY (1, 1) NOT NULL , автоинкрементный
[Kod][int]NULL , код должности
[Dolg][nvarchar](150) COLLATE Cyrillic_General_CI_AS NULL , название должности
[Dolg_K][nvarchar](150) COLLATE Cyrillic_General_CI_AS NULL , название должности для карточки
[K_go][nvarchar](150) COLLATE Cyrillic_General_CI_AS NULL , название должности - кого
[K_mu][nvarchar](150) COLLATE Cyrillic_General_CI_AS NULL , название должности - кому
[K_m][nvarchar](150) COLLATE Cyrillic_General_CI_AS NULL название должности - ким
[arh][int] NOT NULL признак записи в архиве 0 или 1
) ON [PRIMARY]
как видно, в целом справочники похожи — они отличаются только количеством текстовых полей, которые записываются и читаются в БД, т.е. три поля во всех справочниках однотипны — это поля id (Kod), Kod*, arh — первые два — числовые значения, последнее — может содержать либо 0 либо 1. Поле id автоинкремент — т.е. поле ввода ему не нужно, оно может только отображаться. Хотя бывают задачи, когда нужно иметь возможность устанавливать его вручную, тогда нужно реализовывать отдельную операцию — но в данном случае нет такой необходимости. Поле Kod пользователь должен иметь возможность устанавливать руками — т.е. для него мы предусматриваем отдельное текстовое поле ввода. Для поля arh нам достаточно чекбокса — так как оно может иметь только два значения. И как минимум, в каждом справочнике должно быть хотя бы одно текстовое поле. Структура конечно так себе, тут ничего не скажешь. Человек который ее проектировал — сложной судьбы человек, мягко говоря. :) Но тут я не буду останавливаться на принципах проектирования БД, скажу только, что если вы реализуете подобное — старайтесь соблюдать единообразие. Если во всех справочниках присутствует однотипные поля — используйте для них и одинаковые названия. Не должно в одной из таблиц ключевое поле в одном случае Kod а во втором id. Не усложняйте сами себе жизнь.
Что бы нормально работать со справочниками - я бы рекомендовал добавить в БД две дополнительные таблицы, задача которых — хранить метаданные для отображения. В первой из них храните идентификатор справочника, название таблицы справочника, возможно название справочника для отображения (если система одноязычная, либо название для языка по умолчанию) и другие параметры, которые возможно вам понадобятся, а во второй таблице соответственно идентификатор записи, идентификатор справочника, имя поля и название поля для отображения. Ну и другие параметры, которые помогут вам настраивать отображение каждого из полей.Таблицы естественно должны быть связаны по идентификатору справочника. Если вы сердцем чуете что естественные ключи лучше чем искусственные — можете использовать в качестве ключа название таблицы справочника. Это на любителя.
В таком случае для каждого справочника задача упрощается максимально. После того, как таблицы созданы и заполнены — следующий шаг — создание основной формы. Помните, выше мы говорили о контроллере — так вот проще всего делать это через него. Если же эта модель вам кажется слишком сложной — то можно обойтись и без этого. В первую очередь определяемся — как будет происходить загрузка наших данных. Так как справочников может быть один и больше — используем TPageControl. Располагаем его на форме, называем pcMain, настраиваем нужные параметры. В принципе ничего особо сложного в этом нет — поэтому все дальнейшее я буду иллюстрировать кодом.
Код основной формы:
Код:
#ifndef mainfmH
#define mainfmH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
#include "maindm.h"
#include <DBGrids.hpp>
#include <Grids.hpp>
//---------------------------------------------------------------------------
class TfmMain :public TForm
{
__published: // IDE-managed Components
TPageControl *pgMain;
void __fastcall pgMainChange(TObject *Sender);
void __fastcall FormDblClick(TObject *Sender);
private: // User declarations
TdmMain *dmMain;
TDBGrid * __fastcall getCurrentGrid();
public: // User declarations
__fastcall TfmMain(TComponent* Owner);
__fastcall TfmMain(TComponent* Owner, TADOConnection *db);
};
//---------------------------------------------------------------------------
extern PACKAGE TfmMain *fmMain;
//---------------------------------------------------------------------------
#endif
// .cpp
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "mainfm.h"
#include "addedit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfmMain *fmMain;
//---------------------------------------------------------------------------
__fastcall TfmMain::TfmMain(TComponent* Owner)
: TForm(Owner),dmMain(Owner)
{
//тут пишете код, в котором инициализируете подключение к БД. Если БД уже подключена - используется другой конструктор
}
__fastcall TfmMain::TfmMain(TComponent* Owner, TADOConnection *db)
: TForm(Owner),dmMain(Owner,db)
{
dmMain->loadPages(this->pgMain);
dmMain->loadReference(getCurrentGrid());
}
//---------------------------------------------------------------------------
TDBGrid * __fastcall TfmMain::getCurrentGrid()
{
if(pgMain->ActivePageIndex ==-1)return;
TDBGrid* db =NULL;
for(int i =0; i < this->ComponentCount;++i){
if(this->Components->ClassName()!="TDBGrid")continue;
db =dynamic_cast<"TDBGrid">(this->Components);
if(db->Parent == pgMain->ActivePage)break;
}
return db;
}
void __fastcall TfmMain::pgMainChange(TObject *Sender)
{
dmMain->loadReference(getCurrentGrid());
}
//---------------------------------------------------------------------------
void __fastcall TfmMain::FormDblClick(TObject *Sender)
{
TStringList *ls;
TfmAddEdit *fm;
try{
fm =new TfmAddEdit(NULL);
//получаем перевичный ключ
fm->id = dmMain->adoQuery->Fields->FieldByNumber(0)->AsInteger;
//тут получаем параметры наших полей из доптаблицы в список
ls =new TStringList;
dmMain->loadReferenceFields(ls);
//тут обрабатываем наш список - как создавать объекты и инициализировать их параметры
// показано выше. не забывайте проверять высоту формы и высоту объектов
...
}
__finally{
if(fm)delete fm;
if(ls)delete ls;
}
}
//---------------------------------------------------------------------------
Код модуля данных:
// .h
//---------------------------------------------------------------------------
#ifndef maindmH
#define maindmH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ADODB.hpp>
#include <DB.hpp>
#include <DBGrids.hpp>
#include <Grids.hpp>
//---------------------------------------------------------------------------
class TdmMain :public TDataModule
{
__published: // IDE-managed Components
TADOConnection *adoConnect;
TADOQuery *adoQuery;
TDataSource *dsQuery;
TADOQuery *adoExecute;
private: // User declarations
public: // User declarations
__fastcall TdmMain(TComponent* Owner);
__fastcall TdmMain(TComponent *Owner, TADOConnection *db);
void __fastcall loadPages(TPageControl* pg,constint index =0);
void __fastcall loadReference(TDBGrid *gb);
void __fastcall loadReferenceFields(TStrings *ls);
};
//---------------------------------------------------------------------------
extern PACKAGE TdmMain *dmMain;
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "maindm.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
#include "mainfm.h"
TdmMain *dmMain;
//---------------------------------------------------------------------------
__fastcall TdmMain::TdmMain(TComponent* Owner)
: TDataModule(Owner)
{
}
void __fastcall TdmMain::loadPages(TPageControl *pg, constint index)
{
TTabSheet *tab;
adoExecute->SQL->LoadFromFile("loadreferense.sql");
adoExecute->Active =true;
while(!adoExecute->Eof){
tab =new TTabSheet(pg);
tab->Name = adoExecute->FieldByName("tablename")->AsString;
//так же настраиваем другие параметры если надо
TDBGrid *grid =new TDBGrid(tab);
grid->Name ="gd"+tab->Name;
grid->Parent = tab;
grid->Tag = adoExecute->FieldByName("referenceid")->AsInteger;
grid->Align = alClient;
grid->OnDblClick = fmMain->OnDblClick;//устанавливаем обработчик двойного клика
adoExecute->Next();
}
if(pg->PageCount > index) pg->ActivePageIndex = index;
else pg->ActivePageIndex =0;
}
void __fastcall TdmMain::loadReference(TDBGrid *gb)
{
if(!gb)return;
adoQuery->Active =false;
adoQuery->LoadFromFile(gb->Name+".sql");
adoQuery->Active =true;
gb->DataSource = dsQuery;
}
//---------------------------------------------------------------------------
Как видно в коде, практически все компоненты создаются динамически, и в процессе работы я им присваиваю необходимые обработчики. Значительная часть кода опущена, так как она строится по тем же принципам, что и приведенный. Естественно в модуле данных надо реализовать сохранение изменений и т.п., но я думаю что вы с этим сможете справится сами. Ничего сложного. Кроме того — если вы обратили внимание — запросы к БД загружаются из файлов. Я считаю подобный подход более практичным — когда запросы хранятся отдельно от приложения. Хотя конечно все еще зависит от конкретных задач и от ваших предпочтений.
Надеюсь что этот материал оказался полезным — если есть вопросы, задавайте в комментариях.
Источник: http://feedproxy.google.com/~r/codenet/read/~3/pZsB9aZJlLw/
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-11-26 » Капитан грузового судна, или Как начать использовать Docker в своих проектах
- 2024-11-26 » Обеспечение безопасности ваших веб-приложений с помощью PHP OOP и PDO
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
- 2024-06-11 » Скорость загрузки сайта: почему это важно и как влияет на ранжирование
Несчастен тот человек, у которого есть любимый ресторан и нет любимого автора. Он нашел любимое место, где можно накормить тело, но не нашел любимого места, где можно накормить свой ум Рон Джим - выдающийся американский бизнес-тренер и мотиватор, разрабатывал стратегию работы компаний I.B.M., Coca-Cola, Xerox, General Motors и др. |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.