Продолжение статьи “Три главных кнопки”

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

Редактирование parentID

My Visual Database не имеет стандартного компонента для редактирования поля, которое ссылается на родительский элемент, поэтом для этой функции используется связка двух визуальных компонентов: выпадающий список для визуализации выбранного значения и редактирования и поле текстового ввода для записи/чтения значения средствами автоматизации MVDB.

Рассмотрим подробней реализацию на примере формы редактирования задачи efmTask (формы редактирования функций efmFuncProc и классов efmClassType работают аналогично).

Так как выпадающий список должен отображать данные из той же таблицы, которую мы редактируем (в структуре данных нет ссылки на внешнюю таблицу), то при настройке поля ForeingKey (1) необходимо вручную ввести значение task, а затем выбрать название отображаемого поля name. В случае с задачами родительским элементом может быть только группа, поэтому в поле Filter (2) необходимо добавить выражение isGroup = 1.

Для поля редактирования свойствам привязки к полям базы данных (1) задаем соответствующие значения, а сам элемент делаем невидимым (2).

При настройке кнопки сохранения комбобокс остаётся не задействованным (1), а поле ввода помещаем в список компонентов, участвующих в сохранении данных (2).

Осталось синхронизировать работу этих двух элементов интерфейса. При открытии формы будем настраивать значение в выпадающем списке в соответствии со значением в поле редактирования, а при сохранении – наоборот. Для этого нам понадобится три процедуры – обработчика событий: на отображение формы редактирования (efmTask.onShow), на изменение значения в выпадающем списке (efmTask.cmbParent.OnChange) и на нажатие кнопки сохранения данных (efmTask.btnSave.OnClick).

procedure efmTask_OnShow (Sender: TObject; Action: string);
begin
  if Action = 'NewRecord' then
  begin
    // если открыто дерево, то добавлять родителя
    if frmMain.pgcTaskView.ActivePage = frmMain.tshTaskTree then
      efmTask.edtParentID.Value := frmTask_Tree.trvMain.dbItemID;
  end;
  // обновить визуализацию значения
  efmTask.cmbParent.dbItemID := trunc( efmTask.edtParentID.Value );
  efmTask.edtName.SetFocus;
end;
 
procedure efmTask_cmbParent_OnChange (Sender: TObject);
begin
  // перенести значение в сохраняемое поле
  efmTask.edtParentID.Value := efmTask.cmbParent.dbItemID;
end;
 
procedure efmTask_btnSave_OnClick (Sender: TObject; var Cancel: boolean);
begin
  // чтобы вместо -1 сохранился NULL
  if efmTask.edtParentID.Text = '-1' then
    efmTask.edtParentID.Text := '';
end;

Добавление нового примера

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

procedure UserApp_btnAddExample_OnClick (Sender: TObject; var Cancel: boolean);
var
  tmpForm: TAForm;
  tmpCombo: TdbComboBox;
begin
  CForm(Sender,tmpForm);
  // найти выпадающий список по имени
  FindC(tmpForm,'cmbExample',tmpCombo);
  efmExample.NewRecord('example');
  // установить добавленное значние
  tmpCombo.dbItemID := efmExample.btnSave.Tag;
end;
Примечание. В работе данной процедуры использовано соглашение по наименованию компонентов, а именно: выпадающий список должен называться cmbExaple. В более общем виде соглашение звучит так: название элемента на форме редактирования должно состоять из трёхсимвольного префикса класса элемента и названия поля, для редактирования которого он предназначен.

Определение активного элемента

В программе имеется глобальная переменная ActiveGrid, в которой хранится ссылка на таблицу или дерево, с которым в настоящий момент работает пользователь: просматривает или редактирует. Такой элемент визуально отличается от остальных рамкой (точнее, её отсутствием), что заметно при любом стиле оформления приложения. Установка значения ActiveGrid ведется через процедуру UserApp_SetActiveGrid(), которая используется в качестве обработчика события onClick для всех таблиц и деревьев.

procedure UserApp_SetActiveGrid(Sender: TObject);
// установка активного грида
// используется в качестве обработчика OnClick для таблицы и дерева
var
  tmpGrid: TdbStringGridEx;
begin
  tmpGrid := TdbStringGridEx(Sender);
  // если грид поменялся, то
  if ActiveGrid <> tmpGrid then
  begin
    // если ранее был установлен грид, то
    if ActiveGrid <> nil then
      // снять выделение - вернуть стандарный вид
      ActiveGrid.BorderStyle := bsSingle;
    ActiveGrid := tmpGrid;
    // если устанавливается грид, то
    if ActiveGrid <> nil then
      // выделить его, измени его рамку
      ActiveGrid.BorderStyle := bsNone;
  end;
end;

Обновление детализации и описания

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

procedure UserApp_UpdateDD(Sender: TObject; ACol, ARow: Integer);
// обновить описание (description) и детализацию (detail)
// используется в качестве обработчика события onCellClick
var
  tmpGrid:TdbStringGridEx;
  tmpTableName: string;
  tmpID: string;
  tmpSQL: string;
  tmpDesk: string;
  tmpForm: TAForm;
begin
  CForm(Sender,tmpForm);
  tmpGrid := TdbStringGridEx(Sender);
  tmpTableName := Grid_GetTableName(tmpGrid);
  tmpID := IntToStr(tmpGrid.dbIndexToID(ARow));
  // обновление описания
  tmpSQL := 'SELECT  name || " - " || description FROM '+tmpTableName+' WHERE id = '+tmpID;
  tmpDesk := SQLExecute(tmpSQL);
  // если открыта навигация по классам
  if frmmain.pgcNavigation.ActivePage = frmMain.tshClass then
  case tmpTableName of
  'property','funcProc','classEvent': begin
    // для свойства, метода и события
    if frmMain.pgcClassView.ActivePage = frmMain.tshClassList then
      tmpGrid := frmClassType_List.tgrMain
    else
      tmpGrid := frmClassType_Tree.trvMain;
    // добавляем имя класса
    tmpDesk := tmpGrid.Cells[0,tmpGrid.SelectedRow]+'.'+tmpDesk;
  end;
  end;
  frmMain.labDesсription.Caption := tmpDesk;
  // обновление делализации
  case tmpForm of
  frmFuncProc_FunctionList,frmFuncProc_FunctionList : Form_UpdateData( frmFuncProcParam_Function );
  frmFuncProc_Method : Form_UpdateData( frmFuncProcParam_Method );
  frmClassType_List, frmClassType_Tree: begin
    Form_UpdateData( frmProperty );
    Form_UpdateData( frmFuncProc_Method );
    Form_UpdateData( frmClassEvent );
  end;
  end;
end;
 
procedure UserApp_Grid_OnKeyUp (Sender: TObject; var Key: Word; Shift, Alt, Ctrl: boolean);
// обновление при навигации с помощью клавиш
begin
  UserApp_UpdateDD(Sender, 0, TdbStringGridEx(Sender).SelectedRow);
end;

Загрузка примера из базы

Процедура, загружающая пример на основании сведений, содержащихся в переменной ActiveGrid.

procedure UserApp_LoadExample;
// загрузка примера из базы
var
  tmpData:string;
  tmpTableName: string;
  tmpTableID: string;
begin
  frmExampleView.edtCaption.Text := '';
  frmExampleView.redExample.Clear;
  tmpTableName := Grid_GetTableName( ActiveGrid );
  if tmpTableName <> '' then
  begin
    tmpTableID := IntToStr(ActiveGrid.dbItemID);
    try
      // получить существующий ID
      IDExample := SQLExecute('SELECT id_example FROM '+tmpTableName+' WHERE id = '+tmpTableID );
    except // а если ID не существует, то будет -1
      IDExample := -1;
    end;
    // загрузить форматироавнный текст
    frmExampleView.redExample.TextRTF := SQLExecute('SELECT detail FROM example WHERE id = '+IntToStr(IDExample));
    // загрущить название
    frmExampleView.edtCaption.Text := SQLExecute('SELECT caption FROM example WHERE id = '+IntToStr(IDExample));
  end;
end;

Названия процедур

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

  • App_ – универсальные процедуры, актуальные для любых приложений
  • Grid_ – процедуры, дополняющие функциональность табличного представления
  • Form_ – процедуры, относящиеся к формам
  • Tree_ – процедуры и функции, расширяющие функциональность деревьев
  • Splitter_ – процедуры для создания и обработки разделителя
  • IniFile_ – процедуры и функции для работы с файлом инициализации
  • UserApp_ – общие процедуры и функции, относящиеся в данному приложению

В связи с этим, названия некоторых процедур в приведенных выше листингах могут не совпадать с названиями этих же процедур в конечном приложении. В дальнейшем я планирую разнести процедуры по разным модулям, так как размер исходного текста составил более 1100 сток. Подробнее об использовании модулей написано в моей книге “Визуальное программирование”, в главе 4. Наводим порядок.

Результат

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

Ссылки

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *