Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Последние темы форума

Показать новые сообщения »

Почтовая рассылка

Подписчиков: 11855
Последний выпуск: 19.06.2015

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

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

  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, const int 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, const bool 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, const bool 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,const int 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, const int 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;
}
//---------------------------------------------------------------------------

Как видно в коде, практически все компоненты создаются динамически, и в процессе работы я им присваиваю необходимые обработчики. Значительная часть кода опущена, так как она строится по тем же принципам, что и приведенный. Естественно в модуле данных надо реализовать сохранение изменений и т.п., но я думаю что вы с этим сможете справится сами. Ничего сложного. Кроме того — если вы обратили внимание — запросы к БД загружаются из файлов. Я считаю подобный подход более практичным — когда запросы хранятся отдельно от приложения. Хотя конечно все еще зависит от конкретных задач и от ваших предпочтений.

Надеюсь что этот материал оказался полезным — если есть вопросы, задавайте в комментариях.

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
97K
04 июня 2016 года
Александр Шерстнюк
0 / / 04.06.2016
+1 / -0
Мне нравитсяМне не нравится
4 июня 2016, 19:56:31
Хороший материал по базам данных. Вот тут тоже есть готовый пример базы данных
2.
108
19 мая 2015 года
brider
0 / / 19.05.2015
+1 / -0
Мне нравитсяМне не нравится
19 мая 2015, 10:48:06
спасибо,пригодилось.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог