Технология создания форм для работы со справочниками

Для начинающих, да и не только, программистов зачастую большой проблемой является создание однотипных форм для работы со справочниками и тому подобных задач. По крайней мере большое количество вопросов как на форумах так и лично, об этом свидетельствует. Поэтому и по просьбе одного из своих друзей я решил посвятить отдельную статью вопросу организации работыс формами — и в частности организации работы с однотипными формами. Ведь в крупных проектах не редкость огромное количество различных справочников — таких как справочники должностей, справочники подразделений, справочники типов выплат и несть им числа. Мало того, в процессе эксплуатации программы не редкость дополнительный рост числа справочников — что само по себе доставляет. Как же с этим бороться? Ответ на это я попытаюсь дать в своей статье. Хочу обратить внимание, что приведенные рекомендации основываются на личном практическом опыте разработки крупных программных систем. В статье будут рассмотрены две основные темы

  1. Использование в работе шаблон проектирования Модель-Представление-Контроллер (MVС)
  2. Динамическое создание форм, как это делаю я.

Эти две темы практически не отделимы друг от друга — по одной простой причине — если вы не пользуетесь MVС, рано или поздно, в сравнительно большом проекте, вы начнете писать жуткий «макаронный» код, в котором будете путаться сами. Что бы этого избежать — используйте хорошие шаблоны программирования.

Все примеры, которые приведены в данной статье опираются на средства разработки компании Borland — Borland Builder C++, но по сути могу применяться в любой системе вне зависимости от языка программирования. BCB используется здесь только потому, что на нем пишет человек, который попросил написать данную статью.

И так MVС. Что это такое, с чем ее едят и почему именно вам надо этот шаблон использовать? Все очень просто. основной принцип использования MVС заключается в разделении кода — если вы разделяете код, который получает данные из базы, от кода коорый эти данные обрабатывает и отдельно формируете код, который результат этой обработки показывает — поздравляю — вы используете MVС. Большинство удачных программных инструментов спроектировано таким образом, что следовать этому шаблону в них достаточно просто, легко и интуитивно понятно. Но как раз таки BCB и Delphi к таким системам не относятся (за что, я думаю, их создатели будут гореть в Аду — в специально для них созданном 10 круге), потому что в них чрезвычайно просто и легко писать «макаронный» быдлокод — когда все получение данных, их обработка и вывод запихивается в один единственный класс единственной формы, на которую горе программист накидал под сотню контролов — и весь этот, с позволения сказать, код размером в несколько тысяч строк компилируется в исполнимый файл и гордо называется «продуктом».

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

Как реализовать все это на практике? Спроектируем небольшой модуль системы — предположим наш модуль будет реализовать следующую функциональность:

  1. Вывод и отображение записей различных справочников.
  2. Добавление, редактирование и удаление выбранных записей.
  3. Предоставление единого интерфейса для получения записей справочников (списки, деревья и пр.) в любой точке приложения.

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

Как это сделать с точки зрения MVС? Необходимо спроектировать четыре класса — класс-контроллер, класс доступа к данным, класс-форма основная и класс-форма добавления редактирования. Так как справочник по-английски Reference то соответственно классы лично я именую следующим образом:

  1. TRefernceController — класс контроллера, наследует от общего класса контроллера (если нужно);
  2. TmfMain — класс основной формы, наследует от TForm (BCB);
  3. TfmAddEdit — класс вспомогательной формы, наследует от TForm (BCB);
  4. TdmMain — модуль данных, наследует от TDataModule (BCB).

Естественно, наследование классов зависит от вашего конкретного приложения — я взял простейший вариант, что бы не слишком углубляться и не путаться. Название модулей — тоже зависит от того, какая система наименований принята у вас. Например, TmfMain — у меня имеет название любая основная форма модуля, но впрочем, это детали и я оставляю это на ваше рассмотрение.

Объевление класса контроллера может выглядеть примерно таким образом:

Код:

#ifndef loginH
#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

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

Класс модуля данных, в свою очередь, обеспечивает подключение к БД, получение необходимых данных, операции создания, редактирования и удаления данных в таблицах.

Его объявление может выглядеть примерно таким образом:

Код:

#ifndef maindmH
#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

В описании форм нет ничего особенного, поэтому код приводить считаю тут излишним.

Общий алгоритм выглядит следующим образом:

  1. Приложение инициализирует модуль;
  2. Создает объект класса TReferenceController, путем вызова его конструктора и передав ему указатель на открытое соединение с БД;
  3. В качестве параметров конструктора создается объект модуля данных;
  4. Если установлен флаг создания формы — создаем форму.

Пример кода конструктора контроллера:

Код:

__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) и соответственно там располагаются все компоненты для работы с БД. Формы в свою очередь реализуют отображение и обработку ввода данных.

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

Код:

CREATE TABLE [dbo].[Spr_Podr](
    [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, настраиваем нужные параметры. В принципе ничего особо сложного в этом нет — поэтому все дальнейшее я буду иллюстрировать кодом.

Код основной формы:

Код:

// .h
#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/

Читать комменты и комментировать

Добавить комментарий / отзыв



Защитный код
Обновить

Технология создания форм для работы со справочниками | | 2013-10-03 22:33:06 | | Программирование | | Для начинающих, да и не только, программистов зачастую большой проблемой является создание однотипных форм для работы со справочниками и тому подобных задач. По крайней мере большое количество | РэдЛайн, создание сайта, заказать сайт, разработка сайтов, реклама в Интернете, продвижение, маркетинговые исследования, дизайн студия, веб дизайн, раскрутка сайта, создать сайт компании, сделать сайт, создание сайтов, изготовление сайта, обслуживание сайтов, изготовление сайтов, заказать интернет сайт, создать сайт, изготовить сайт, разработка сайта, web студия, создание веб сайта, поддержка сайта, сайт на заказ, сопровождение сайта, дизайн сайта, сайт под ключ, заказ сайта, реклама сайта, хостинг, регистрация доменов, хабаровск, краснодар, москва, комсомольск |
 
Поделиться с друзьями: