среда, 11 апреля 2012 г.

Урок 39. "Товары", сопутствующие Insert

Наверное, те, кто пытается следить за ходом моих публикаций, уже имеют более ни менее работоспособную программу "Расходы", но иногда натыкаются на разные неожиданные неприятности, не позволяющие полноценно использовать ее в качестве инструмента учета Например, прошел месяц март, наступил апрель, Вы пытаетесь добавить новую строчку в список мемориальных ордеров, но...

при попытке сохранения пустой строчки происходит ошибка...


написали "Апрель", но не ввели номер месяца - другая...


Если пользователь - Вы сам, то беды большой нет, поскольку Вы знаете в чем тут дело и как поправить. Но я стараюсь как можно чаще давать понять моему читателю, что пугать неподготовленного пользователя служебными сообщениями есть моветон. Как плохой манерой является и исполнение маневров, подобных тем, что исполняют в МТС.

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

Найдите на форме компонент ADOTABLEMOS и напишите обработчик свойства AfterInsert:

procedure TMainFrm.ADOTableMOsAfterInsert(DataSet: TDataSet);
var
  year, month, day: Word;
  monthName:array[1..12] of string[8];       // Объявление одномерного массива фиксированного размера
begin

  // Присвоение начальных значений элементам массива
  monthName[1]:='Январь';
  monthName[2]:='Февраль';
  monthName[3]:='Март';
  monthName[4]:='Апрель';
  monthName[5]:='Май';
  monthName[6]:='Июнь';
  monthName[7]:='Июль';
  monthName[8]:='Август';
  monthName[9]:='Сентябрь';
  monthName[10]:='Октябрь';
  monthName[11]:='Ноябрь';
  monthName[12]:='Декабрь';

  // Декодирование текущей даты  Date() функцией  DecodeDate(). Результат - в переменных year, month, day
  DecodeDate(Date(), year, month, day);

  // Добавление значений по умолчанию в список мемориальных ордеров
  ADOTableMOs.FieldByName('Mes').Value:= IntToStr(month);
  ADOTableMOs.FieldByName('Name').Value:= monthName[month];  // Значение из массива по номеру месяца

end;


Теперь при добавлении новой записи программа поможет оператору:


Вы можете смело продолжить работу, а я попутно хочу заметить
1. Массив monthName можно объявить не в этой процедуре, а выше - на уровне модуля, сделав его не локальным, а глобальным по отношению к данному модулю, если его значения  необходимы в других процедурах. Тогда объявление массива можно было бы совместить с присвоением его элементам значений:

monthName: array[1..12] of String[8]=
('Январь','Февраль','Март',и т.д. до,'Декабрь');



2. Алгоритм "угадывания" названия месяца по его номеру можно вынести в DLL в виде отдельной функции, так как он может понадобится не только в этой программе, но и в других.

3. На основе этого несложного алгоритма  можно сделать компонент, на входе которого будет номер месяца, а на выходе - его название.

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

Ладно бы стер... он может нажать кнопку "Добавить" пару раз, и в справочнике легко появятся два, а то и три апреля...



Помните, я говорил в уроке 38, что обработчик ошибок нужен при выполнении Post? Давайте поправим ситуацию, пока не поздно.

Займемся этим оператором. А точнее - событием, предшествующим сохранению введенных в Grid значений в источнике, под сеткой подложенной таблице. Событие это носит название BeforePost. Именно в этот момент, перед выполнением операции сохранения данных, уместно сделать проверку на предмет: "Подходят ли нам введенные значения?"

Для этого напишем вот такой обработчик события BeforePost

procedure TMainFrm.ADOTableMOsBeforePost(DataSet: TDataSet);

  function TheTestMos(): Boolean;
  var
    MyStr: String;
    p: boolean;
  begin

    p:= True;            // Признак успешности проверки

    MyStr:= ADOTableMos.FieldByName('Name').AsString;
    if (MyStr='')
    then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Пустые значения в названии не допустимы';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      abort;            // Не показывать пользователю стандартное сообщение об ошибке
    end;

    Result:=p;
  end;

begin

  If not TheTestMos
  then
    ADOTableMOs.Cancel;
end;


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

Но это - так... лирика. Смоделировав некорректный ввод пользователя, мы получим на экран сообщение:


Первая часть проверки (попытка ввести мемориальный ордер без названия) успешно сработала. 

Аналогично нужно сделать проверку введенного значения в поле Mes.

А еще необходимо как-то решить вопрос с двумя апрелями, что на мой взгляд интереснее.

Для этого понадобится запрос TADOQuery, который необходимо разместить на форме. Я назову его TempQuery.


И дополню функцию проверки: 


    // 1-я проверка на отсутствие названия мемориального ордера
    MyStr:= ADOTableMos.FieldByName('Name').AsString;
    if (MyStr='')
    then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Пустые значения в названии не допустимы';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      abort;            // Не показывать пользователю стандартное сообщение об ошибке
    end;

    // 2-я проверка на отсутствие номера месяца
    MyStr:= ADOTableMos.FieldByName('Mes').AsString;
    if (MyStr='')
    then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Пустые значения в поле Месяц не допустимы';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      abort;            // Не показывать пользователю стандартное сообщение об ошибке
    end;

    // 3-я проверка на ввод повторной записи
    TempQuery.Active:=False;
    TempQuery.SQL.Clear;
    MyStr:='SELECT * FROM MOs WHERE (Name='''+MyStr+''') AND (Mes='+ADOTableMos.FieldByName('Mes').AsString+')';
    TempQuery.SQL.Add(MyStr);
    TempQuery.Active:=True;
    If not TempQuery.Eof
    Then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Запись с такими значениями уже имеется в базе данных...';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      Abort;
    end;
    TempQuery.Active:=False;


Запустив программу, после нажатия кнопки "Добавить" мемориальный ордер, ничего не меняя нажму "Сохранить" и опять увижу два апреля. Почему?

Вот тут я готов начать разговор о средствах отладки.
Простейшее из имеющегося богатого набора - это точка останова. Щелкните мышкой на сером поле в строке №967 ( TempQuery.SQL.Add(MyStr);). Появившаяся красная точка - точка, где программа остановится в процессе выполнения.

Если теперь навести курсор на переменную MyStr, то в хинте будет выведено значение, которое эта переменная имеет на данный момент:


В название месяца из той же переменной MyStr подставляется номер (4), полученный ею на этапе предыдущей проверки. Выход прост: переставьте местами 1-ю и 2-ю проверку:



Вот теперь, если оператор будет пытаться сделать некорректные действия, программа постарается его направить в мирное русло:


PS Точку останова нужно всегда ставить на следующей строчке, чтобы оператор присвоения значения (:=) уже был выполнен.

PPS Можно ли было вместо запроса использовать Locate? 





Комментариев нет:

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