Проверка маски на полноту ввода в FMX Delphi — часть 1

Проверка даты на полноту ввода в FMX Delphi

В статье «Компонент маски в FMX Delphi» мы рассмотрели способы организации маски ввода для фреймворка FMX. Но в той статье был описан именно процесс организации маски ввода. А вот как проконтролировать правильность ввода маски и каким образом организовать ввод по этой маске и проверку правильности ввода в поле, привязанном к набору данных в той статье не рассказывается. Обо всем этом речь пойдет в этой статье.

Все дело в том, что у поля набора данных есть такое свойство EditMask. Оно замечательно работает в VCL, но не работает в FMX. Но или я пока не нашел способ заставить его заработать. Если кто-то из читающих этот пост знает, как заставить это свойство работать в FMX, пожалуйста отпишитесь в комментариях. А пока рассмотрим такой способ.

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

Давайте начнем свое исследование с проверки правильности ввода данных. Рассматривать будем на примере ввода даты. Я сразу оговорюсь, что цель статьи — разобраться в вопросе создания маски ввода. Дело в том, что организовать ввод даты в Delphi FMX не является проблемой. Для этого есть специальный компонент под названием TDateEdit. А если вам нужно ввести время, то есть компонент TTimeEdit. И эти же самые компоненты встроены в тот же TStringGrid и простой TGrid.

Для использования маски даты в TStringGrid, после того, как вы подключите его к данным, нужно вызвать контекстное меню TStringGrid и выбрать и з него пункт Column Editor... Затем, как и у TDBGrid из VCL просто добавить столбцы набора данных. Затем выбрать нужный вам столбец, в котором отображается дата и в окне свойств перейти на свойство ColumnStyle. В нем выбрать значение DateColumn (в данном случае). После этого при редактировании данных в ячейке таблицы, где у вас дата, будет появляться календарь. Также вы можете редактировать дату и в ручную с клавиатуры. Здесь проверять правильность ввода маски не нужно, поскольку несуществующую даты вы не введете. Но меня интересует не эти компоненты, а возможность организовывать именно маску, подобную TEditMask и ее практическое применение, поскольку кроме даты, маска может быть использована для ввода других данных, скажем СНИЛС или паспортных данных и т.п.

В статье Компонент маски в FMX Delphi у нас рассмотрен процесс формирования такой маски на примере маски даты. Давайте вернемся к проверке корректности ввода. Представим, что пользователь ввел ввел в наше маску значение: «45.02.2022». Как вы видите, 45-го дня в месяце не бывает. Или же пользователь ввел значение значение: «18.0_.2025» — случайно пропустил цифру. Введенное значение также не является датой. И вот этот процесс неверного ввода данных нам нужно проконтролировать.

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

Это можно организовать следующим образом. У нас есть наш объект TEdit, в который вводится маска. В это объекте вызывается наша процедура, которую мы описали в предыдущей статье:

procedure TfrmActions.txtFilterDate1KeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
begin
   MaskDate(TEdit(Sender), Key, KeyChar);
end;

Эта процедура у нас превращает компонент TEdit в некое подобие TMaskEdit, который был в VCL. И вот мы ввели значение даты в нашу маску. Есть у нас также кнопка, по нажатию на которую мы хотели бы, чтобы наша введенная дата применилась к какому-либо фильтру запроса. Какой это запрос совершенно не важно. Гипотетически к любому запросу на выборку.

Но перед тем, как дата будет применена, нам нужно проверить ее правильность в этой же кнопке, так как неправильная дата вызовет ошибку в запросе. Проверку будем осуществлять с помощью функции, которую мы назовем IsDateСheckMask и, которую мы разместим в том же модуле, в котором у нас размещена процедура MaskDate.

Итак:

function IsDateСheckMask(oEdit: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;

Наша функция будет возвращать логическое значение. Если дата верная, то True, иначе False. А в качестве параметров мы будем передавать ей объект TEdit, то есть тот, в который мы ввели дату, а также дополнительные параметры, такие как координаты и габариты текущего окна. Эти параметры нужны для того, чтобы в случае неправильного ввода даты вызвать свое пользовательское окно с сообщением в центре по отношению к окну, в котором ввели дату. Эти параметры не обязательные и вы можете адаптировать функцию под себя, убрав их.

Смотрим саму функцию:

function IsDateСheckMask(oEdit: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;
var
    dDate: TDateTime;
    bDate: boolean;
begin
    if (oEdit.text = '__.__.____')  then
    begin
       bDate:=false;
       MessageCreate(Left,Top,Height,Width,'Укажите дату!');
       oEdit.SetFocus;
       abort;
    end else
    begin
        if oEdit.text <> '__.__.____' then
        begin
            if not tryStrToDate(oEdit.text, dDate) then
            begin
               bDate:=false;
               MessageCreate(Left,Top,Height,Width,'Не верная дата!');
               oEdit.SetFocus;
               abort;
            end else bDate:=true;
        end else
        begin
           bDate:=false;
           MessageCreate(Left,Top,Height,Width,'Укажите дату');
           oEdit.SetFocus;
           abort;
        end;
        Result:=bDate;
    end;
end;

Функция не большая. Смотрите, вы можете из аргументов функции убрать все вот эти параметры: Left: integer; Top: integer; Height: integer; Width: integer. Но тогда вам нужно будет внутри тела функции вместо моей процедуры

MessageCreate(Left,Top,Height,Width,'Укажите дату');

вызвать стандартную встроенную процедуру ShowMessage... во всех трех местах. Но я бы не рекомендовал вам такой подход, потому что в этом случае вы получите простое стандартное модальное окно, которое будет сильно отличаться от стилистике вашего приложения. Вместо этого лучше сделайте четыре заготовки окон как показано в статье Организация диалоговых окон в Delphi и они вам уже пригодятся практически во всех создаваемых вами приложениях в будущем. Причем это одинаково работает как в VCL, так и в FMX. Сделайте, не поленитесь, работы всего на час и двигайтесь дальше по этой статье.

Теперь в этому же модуле my_proc давайте разместим еще одну функцию, которая будет у нас проверять сразу две даты. Она нам пригодится, если мы будем вводить диапазон дат в двух разных полях TEdit:

function IsDatesСhecksMasks(oEdit1: TEdit; oEdit2: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;

Она очень похожа на предыдущую, с той разницей, что здесь мы добавили еще один параметр oEdit типа TEdit:

function IsDatesСhecksMasks(oEdit1: TEdit; oEdit2: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;
var
    dDate: TDateTime;
    bDate: boolean;
begin
    if (oEdit1.text = '__.__.____') and (oEdit2.text = '__.__.____')  then
    begin
       bDate:=false;
       MessageCreate(Left,Top,Height,Width,'Укажите начальную и конечную даты!');
       oEdit1.SetFocus;
       abort;
    end else
    begin
        if oEdit1.text <> '__.__.____' then
        begin
            if not tryStrToDate(oEdit1.text, dDate) then
            begin
               bDate:=false;
               MessageCreate(Left,Top,Height,Width,'Не верная начальная дата!');
               oEdit1.SetFocus;
               abort;
            end else bDate:=true;
        end else
        begin
           bDate:=false;
           MessageCreate(Left,Top,Height,Width,'Укажите начальную дату');
           oEdit1.SetFocus;
           abort;
        end;
        if oEdit2.text <> '__.__.____' then
        begin
            if not tryStrToDate(oEdit2.text, dDate) then
            begin
               bDate:=false;
               MessageCreate(Left,Top,Height,Width,'Не верная конечная дата!');
               oEdit2.SetFocus;
               abort;
            end else bDate:=true;
        end else
        begin
           bDate:=false;
           MessageCreate(Left,Top,Height,Width,'Укажите конечную дату!');
           oEdit2.SetFocus;
           abort;
        end;
        Result:=bDate;
    end;
end;

oEdit1 и oEdit2 — это наши объекты, которые передаются в нашу функцию. Я знаю, что возможно немного запутано, но вам нужно шаг за шагом начать пробовать и все получится. Давайте теперь приведу полный код модуля my_proc. В конце-концов, если не хотите разбираться во всем этом, вы просто можете взять целиком код, а разобраться уже по ходу того, как у вас появится в этом необходимость:

unit my_proc;
interface
uses
  SysUtils, VCL.Mask, dialogs_, FMX.Edit;

  procedure MaskDate(oEdit: TEdit; Key: Word; KeyChar: WideChar);
  function IsDateСheckMask(oEdit: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;
  function IsDatesСhecksMasks(oEdit1: TEdit; oEdit2: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;

implementation

uses actions, racks;

procedure MaskDate(oEdit: TEdit; Key: Word; KeyChar: WideChar);
var
   s: string;//Значение текстового поля
   sSelect: string;//Текст выделенного фрагмента
   d: integer;//Длина выделенного текста
   i: integer;//Счетчик
   iStart: integer;//Начальная позиция
   iLen: integer;//Длина выделенного текста
begin
     //Сначала осуществляем действия, если выделено сразу несколько любых
     //символов и нажат Delete или Backspace. При этом выделенные символы
     //обязаны удалиться, а вместо них должны остаться символы заполнители
     //и разделительные символы.
     //Итак, если нажата Backspace или Delete и при этом выделенных символов больше 0, то
     if ((Key=8) or (Key=46)) and (oEdit.SelLength>0) then
     begin
         s:=oEdit.Text; //Присваиваем текст компоненте TEdit
         sSelect:=oEdit.SelText; //Присваиваем выделенный текст из TEdit
         iStart:=oEdit.SelStart+1;//Двигаем курсов на единицу вперед
         d:=Length(sSelect);//Запоминаем длину выделенного текста
         i:=1; //Присваиваем счетчику единицу
         while i<=d do //Если счетчик меньше, либо равен длине выделенного текста
         begin //то проверяем выделенный текст посимвольно
            if sSelect[i]<>'.' then sSelect[i]:='_'; //Если символ не точка, то заменяем его на подчеркивание
            i:=i+1; //Передвигаемся к следующему символу для проверки
         end;
         Delete(s, iStart, d); //Удаляем выделенный кусок текста из переменной
         Insert(sSelect, s, iStart); //Вставляем измененный кусок в переменную s на место удаленного текста
         oEdit.Text:=s; //Присваиваем TEdit новое значение переменной s
         oEdit.SelStart:=iStart-1; //Ставим курсор в прежнее положение
         abort; //Останавливаем выполнение программы
     end else //Иначе (если нет выделенных символов) выполняем код ниже.
     begin
         if not ((Key=37) or (Key=39)) then //Если не нажаты клавиши "Назад" или "Вперед" клавиатуры, то
         begin  //проверяем посимвольно
            case oEdit.SelStart of
                0: //Перед первым символом дня месяца
                begin
                     if not ((Key=8) or (Key=46)) then //Если не нажаты Backspace или Delete
                     begin //то
                       //Контролируем тип вводимых данных в первый символ
                       //Если нажат любой символ, кроме цифр или Backspace, то прерываем ввод
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin //иначе (если нажата цифра)
                          s:=oEdit.Text;//Присваиваем переменной s значение всего текста
                          Delete(s, 1, 1); //Удаляем из переменной s первый символ
                          oEdit.Text:=s; //Присваиваем TEdit переменную s с удаленным первым символом
                          oEdit.SelStart:=0; //Устанавливаем указатель перед первым символом
                       end; //Теперь на это место будет добавлен введенный символ
                     end;
                     //Если нажата Backspace, то ничего не делаем, так как это первый символ
                     if Key=8 then
                     begin
                        abort; //то ничего не делаем, так как слева нет символов
                     end;
                     //Если нажата Delete, то меняем символ на "_" справа
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 1, 1);
                        Insert('_', s, 1);
                        oEdit.Text:=s;
                        oEdit.SelStart:=0;
                        abort;
                     end;
                end;
                1: //Перед вторым символом дня месяца
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                       //Контролируем тип вводимых данных во второй символ
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin
                          s:=oEdit.Text;
                          Delete(s, 2, 1);
                          oEdit.Text:=s;
                          oEdit.SelStart:=1;
                       end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 1, 1);
                        Insert('_', s, 1);
                        oEdit.Text:=s;
                        oEdit.SelStart:=0;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 2, 1);
                        Insert('_', s, 2);
                        oEdit.Text:=s;
                        oEdit.SelStart:=1;
                        abort;
                     end;
                end;
                2: //Перед точкой
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                        //Контролируем тип вводимых данных
                        if not (KeyChar in ['0'..'9']) then Abort else
                        begin
                           //И, если это была цифра, то сдвигаемся на один символ
                           //вправо, так как перед нами точка
                           oEdit.SelStart:=3;
                           //Далее удаляем прежний символ, чтобы вместо него
                           //разместился тот, который нажали
                           s:=oEdit.Text;
                           Delete(s, 4, 1);
                           oEdit.Text:=s;
                           oEdit.SelStart:=3;
                        end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 2, 1);
                        Insert('_', s, 2);
                        oEdit.Text:=s;
                        oEdit.SelStart:=1;
                        abort;
                     end;
                     //Если нажата Backspace,
                     if Key=46 then
                     begin
                        //Ничего не делаем, потому что сразу справа находится точка
                        abort;
                     end;
                end;
                3: //Перед первым символом месяца
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                       //Контролируем тип вводимых данных в четвертую позицию
                       //Третью позицию перескочили, так как там точка
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin
                          s:=oEdit.Text;
                          Delete(s, 4, 1);
                          oEdit.Text:=s;
                          oEdit.SelStart:=3;
                       end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        oEdit.SelStart:=2;
                        s:=oEdit.Text;
                        Delete(s, 2, 1);
                        Insert('_', s, 2);
                        oEdit.Text:=s;
                        oEdit.SelStart:=1;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 4, 1);
                        Insert('_', s, 4);
                        oEdit.Text:=s;
                        oEdit.SelStart:=3;
                        abort;
                     end;
                end;
                4: //Перед вторым символом месяца
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                       //Контролируем тип вводимых данных в пятую позицию
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin
                          s:=oEdit.Text;
                          Delete(s, 5, 1);
                          oEdit.Text:=s;
                          oEdit.SelStart:=4;
                       end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 4, 1);
                        Insert('_', s, 4);
                        oEdit.Text:=s;
                        oEdit.SelStart:=3;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 5, 1);
                        Insert('_', s, 5);
                        oEdit.Text:=s;
                        oEdit.SelStart:=4;
                        abort;
                     end;
                end;
                5: //Перед точкой
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                        //Контролируем тип вводимых данных
                        if not (KeyChar in ['0'..'9']) then Abort else
                        begin
                           //И, если это была цифра, то сдвигаемся на один символ
                           //вправо, так как перед нами точка
                           oEdit.SelStart:=6;
                           //Далее удаляем прежний символ, чтобы вместо него
                           //разместился тот, который нажали
                           s:=oEdit.Text;
                           Delete(s, 7, 1);
                           oEdit.Text:=s;
                           oEdit.SelStart:=6;
                        end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 5, 1);
                        Insert('_', s, 5);
                        oEdit.Text:=s;
                        oEdit.SelStart:=4;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        //Ничего не делаем, потому что сразу справа находится точка
                        abort;
                     end;
                end;
                6: //Перед первым символом года
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                        //Контролируем тип вводимых данных
                        if not (KeyChar in ['0'..'9']) then Abort else
                        begin
                           s:=oEdit.Text;
                           Delete(s, 7, 1);
                           oEdit.Text:=s;
                           oEdit.SelStart:=6;
                        end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        oEdit.SelStart:=5;
                        s:=oEdit.Text;
                        Delete(s, 5, 1);
                        Insert('_', s, 5);
                        oEdit.Text:=s;
                        oEdit.SelStart:=4;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 7, 1);
                        Insert('_', s, 7);
                        oEdit.Text:=s;
                        oEdit.SelStart:=6;
                        abort;
                     end;
                end;
                7: //Перед вторым символом года
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                       //Контролируем тип вводимых данных
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin
                          s:=oEdit.Text;
                          Delete(s, 8, 1);
                          oEdit.Text:=s;
                          oEdit.SelStart:=7;
                       end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 7, 1);
                        Insert('_', s, 7);
                        oEdit.Text:=s;
                        oEdit.SelStart:=6;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 8, 1);
                        Insert('_', s, 8);
                        oEdit.Text:=s;
                        oEdit.SelStart:=7;
                        abort;
                     end;
                end;
                8: //Перед третьим символом года
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                       //Контролируем тип вводимых данных
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin
                          s:=oEdit.Text;
                          Delete(s, 9, 1);
                          oEdit.Text:=s;
                          oEdit.SelStart:=8;
                       end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 8, 1);
                        Insert('_', s, 8);
                        oEdit.Text:=s;
                        oEdit.SelStart:=7;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 9, 1);
                        Insert('_', s, 9);
                        oEdit.Text:=s;
                        oEdit.SelStart:=8;
                        abort;
                     end;
                end;
                9: //Перед четвертым символом года
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                       //Контролируем тип вводимых данных
                       if not (KeyChar in ['0'..'9']) then Abort else
                       begin
                          s:=oEdit.Text;
                          Delete(s, 10, 1);
                          oEdit.Text:=s;
                          oEdit.SelStart:=9;
                       end;
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 9, 1);
                        Insert('_', s, 9);
                        oEdit.Text:=s;
                        oEdit.SelStart:=8;
                        abort;
                     end;
                     //Если нажата Delete
                     if Key=46 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 10, 1);
                        Insert('_', s, 10);
                        oEdit.Text:=s;
                        oEdit.SelStart:=9;
                        abort;
                     end;
                end;
                10: //После четвертого символа года
                begin
                     if not ((Key=8) or (Key=46)) then
                     begin
                        abort;//Так как год закончился
                     end;
                     //Если нажата Backspace
                     if Key=8 then
                     begin
                        s:=oEdit.Text;
                        Delete(s, 10, 1);
                        Insert('_', s, 10);
                        oEdit.Text:=s;
                        oEdit.SelStart:=9;
                        abort;
                     end;
                     //Если нажата Delete, то
                     if Key=46 then
                     begin
                        abort;
                     end;
                end;
            end;
         end;
     end;
end;

function IsDateСheckMask(oEdit: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;
var
    dDate: TDateTime;
    bDate: boolean;
begin
    if (oEdit.text = '__.__.____')  then
    begin
       bDate:=false;
       MessageCreate(Left,Top,Height,Width,'Укажите дату!');
       oEdit.SetFocus;
       abort;
    end else
    begin
        if oEdit.text <> '__.__.____' then
        begin
            if not tryStrToDate(oEdit.text, dDate) then
            begin
               bDate:=false;
               MessageCreate(Left,Top,Height,Width,'Не верная дата!');
               oEdit.SetFocus;
               abort;
            end else bDate:=true;
        end else
        begin
           bDate:=false;
           MessageCreate(Left,Top,Height,Width,'Укажите дату');
           oEdit.SetFocus;
           abort;
        end;
        Result:=bDate;
    end;
end;

function IsDatesСhecksMasks(oEdit1: TEdit; oEdit2: TEdit; Left: integer; Top: integer; Height: integer; Width: integer): boolean;
var
    dDate: TDateTime;
    bDate: boolean;
begin
    if (oEdit1.text = '__.__.____') and (oEdit2.text = '__.__.____')  then
    begin
       bDate:=false;
       MessageCreate(Left,Top,Height,Width,'Укажите начальную и конечную даты!');
       oEdit1.SetFocus;
       abort;
    end else
    begin
        if oEdit1.text <> '__.__.____' then
        begin
            if not tryStrToDate(oEdit1.text, dDate) then
            begin
               bDate:=false;
               MessageCreate(Left,Top,Height,Width,'Не верная начальная дата!');
               oEdit1.SetFocus;
               abort;
            end else bDate:=true;
        end else
        begin
           bDate:=false;
           MessageCreate(Left,Top,Height,Width,'Укажите начальную дату');
           oEdit1.SetFocus;
           abort;
        end;
        if oEdit2.text <> '__.__.____' then
        begin
            if not tryStrToDate(oEdit2.text, dDate) then
            begin
               bDate:=false;
               MessageCreate(Left,Top,Height,Width,'Не верная конечная дата!');
               oEdit2.SetFocus;
               abort;
            end else bDate:=true;
        end else
        begin
           bDate:=false;
           MessageCreate(Left,Top,Height,Width,'Укажите конечную дату!');
           oEdit2.SetFocus;
           abort;
        end;
        Result:=bDate;
    end;
end;
end.

Здесь одна процедура, которая уже была описана ранее и две функции, которые работают в связке.

Организация взаимодействия маски ввода с полем набора данных

Итак, у нас есть наш объект TEdit с именем txtFilterDate1 и обычная кнопка TButton.

В текстовом поле с именем txtFilterDate1 в событии onKeyDown пишем:

procedure TfrmActions.txtFilterDate1KeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
begin
   MaskDate(TEdit(Sender), Key, KeyChar);
end;

А в кнопке пишем:

procedure TfrmActions.Button1Click(Sender: TObject);
begin
   IsDateСheckMask(txtFilterDate1, Left, Top, Height, Width);
end;

Теперь, после нажатия на кнопку, если дата будет введена не корректно или вообще пустая, то вы получите соответствующее сообщение:

Проверка корректности даты

Если у нас будет два поля. Пусть одно поле называется txtFilterDate1, а второе txtFilterDate2, то в каждом поле мы пишем:

procedure TfrmActions.txtFilterDate1KeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
begin
   MaskDate(TEdit(Sender), Key, KeyChar);
end;

и

procedure TfrmActions.txtFilterDate2KeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
begin
   MaskDate(TEdit(Sender), Key, KeyChar);
end;

А в кнопке мы напишем:

procedure TfrmActions.Button1Click(Sender: TObject);
begin
   IsDatesСhecksMasks(txtFilterDate1, txtFilterDate2, Left, Top, Height, Width);
end;

Теперь, если пользователь, допустим, ввел неправильную дату в второе поле, то мы получим сообщение:

Проверка корректности двух дат

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

procedure TfrmActions.Button1Click(Sender: TObject);
begin
   if IsDatesСhecksMasks(txtFilterDate1, txtFilterDate2, Left, Top, Height, Width) then  //Если функция возвращает истину, то
   begin
      //Здесь выполняем код, например, выборку
   end;
end;

Теперь давайте рассмотрим ситуацию, когда у нас TEdit привязан к полю набора данных.

Сначала посмотрим, как организовать маску ввода. Для этого, как и в прошлый раз, мы вызываем процедуру MaskEdit, описанную в этой статье.

procedure TfrmRacks.txtDateInstallKeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
begin
   if fdRacks.State=dsBrowse then fdRacks.Edit;
   MaskDate(TEdit(Sender), Key, KeyChar);
end;

В этой процедуре есть еще одна строка:

if fdRacks.State=dsBrowse then fdRacks.Edit;

Она нужна для того, чтобы при нажатии клавиши в поле ввода даты набор данных переходил в режим редактирования. Обычно это происходит автоматически, когда мы начинаем править значение текстового поля, привязанного к набору данных. Но в данном случае, если принудительно не перевести набор данных в режим редактирования, то при удалении даты вида, например, «12.12.2000» из текстового поля, мы получим вид «__.__.____». При этом набор данных не перейдет в режим редактирования (как ни странно). То есть, последующий вызов метода Post не сохранит изменение. Если же дату «12.12.2000» (или любую другую) поменять на другую дату, например, на «05.06.1997», то набор данных перейдет в режим редактирования. Эту загадку мне угадать не удалось. И там и там я нажимаю клавишу на клавиатуре, только в одном случае цифру, а в другом Delete. И этим нажатием я вношу изменение, но увы — результат разный. Видимо Delphi считает, что в случае нажатия Delete изменения вносятся не в поле набора данных, а компонент TEdit. Но опять таки — странное представление у FMX. Поэтому оставим нашу строку на месте:

if fdRacks.State=dsBrowse then fdRacks.Edit;

Дальше... Привяжите к набору данных компонент TDataSource и пропишите в его событии onDataChange код:

procedure TfrmRacks.dsRacksDataChange(Sender: TObject; Field: TField);
begin
    if txtDateInstall.Text='' then txtDateInstall.Text:='__.__.____';
end;

Дело в том, что при переходе от одной записи набора данных к другой, нужно проверять, не пустая ли дата. Если дата пустая, то без этой строки наш TEdit будет выглядеть пустым, а нам нужно, чтобы в нем показывались символы-заполнители с разделителями типа «__.__.____».

Если в VCL компоненты к набору данных подключаются через TDataSource, то в FMX — через TBindSource. TDataSource тоже можно использовать для разных целей. Если вы не используете TDataSource, то в событии OnAfterPost набора данных пишем либо так:

procedure TfrmRacks.fdRacksAfterPost(DataSet: TDataSet);
begin
    if fdRacks.FieldByName('Дата_установки').isNull then txtDateInstall.Text:='__.__.____';
end;

либо так:

procedure TfrmRacks.fdRacksAfterPost(DataSet: TDataSet);
begin
    if txtDateInstall.Text='' then txtDateInstall.Text:='__.__.____';
end;

Если вы не используете TDataSource, то в OnAfterInsert пишем:

procedure TfrmRacks.fdRacksAfterInsert(DataSet: TDataSet);
begin
   txtDateInstall.Text:='__.__.____';
end;

Если используете, то не пишем, поскольку в OnDataChange компонента TDataSource мы выше писали код, который выполняет нужную функцию.

Также, если вы не используете TDataSource, то в наборе данных в событии OnAfterEdit набора данных пишем:

procedure TfrmRacks.fdRacksAfterEdit(DataSet: TDataSet);
begin
   if txtDateInstall.Text='' then txtDateInstall.Text:='__.__.____';
end;

Ну и в событии OnAfterOpen, если вы не используете TDataSource нужно записать либо так:

procedure TfrmRacks.fdRacksAfterOpen(DataSet: TDataSet);
begin
    if fdRacks.FieldByName('Дата_установки').isNull then txtDateInstall.Text:='__.__.____';
end;

либо:

procedure TfrmRacks.fdRacksAfterOpen(DataSet: TDataSet);
begin
    if txtDateInstall.Text='' then txtDateInstall.Text:='__.__.____';
end;

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

  • onAfterOpen;
  • onAfterInsert;
  • onAfterEdit;
  • onAfterPost;
  • onAfterDelete.

Выгоднее использовать TDataSource.

В событии OnBeforePost пишем код:

procedure TfrmRacks.fdRacksBeforePost(DataSet: TDataSet);
begin
    if txtDateInstall.Text='__.__.____' then fdRacks.FieldByName('Дата_установки').Clear;
end;

Этот код нужен обязательно. Если мы удалим дату, то наше поле заполнится символами-заполнителями и разделителями и при выполнении метода Post это значение попытается записаться в поле типа даты набора данных, привязанное к нашему TEdit. Как вы понимаете, это приведет к ошибке. Поэтому поле TEdit мы должны оставить с символами-заполнителями, а привязанное к нему поле набора данных нужно очистить перед сохранением.

Теперь откройте Fields Editor набора данных, выделите поле с датой и откройте событие onSetText. Запишите в него следующий код:

procedure TfrmRacks.fdRacksДата_установкиSetText(Sender: TField;
  const Text: string);
var
    dDate: TDateTime;
begin
    if Text <> '__.__.____' then
    begin
        if not tryStrToDate(Text, dDate) then
        begin
           MessageCreate(Left,Top,Height,Width,'Не верная дата установки стойки!');
           abort;
        end else
           Tfield(Sender).Value:=StrToDate(Text);
    end else
           Tfield(Sender).Clear;
end;

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

Давайте подытожим. Если мы используем TDataSource, то мы заполняем событие onDataChange этого объекта, событие onBeforePost набора данных и событие onKeyDown объекта TEdit. То есть, всего три коротких процедуры буквально в одну строку каждая.

Если же мы не используем TDataSource, то тогда вместо onDataChange мы заполняем для набора данных все вот эти события:

  • onAfterOpen;
  • onAfterInsert;
  • onAfterEdit;
  • onAfterPost;
  • onAfterDelete.

Собственно, тоже по одной строке кода.

ok, мы подробно рассмотрели организацию работы с маской для поля TEdit, привязанного к набору данных. Теперь рассмотрим как это организовать маску ввода в TStringGrid.

Здесь я знаю два подхода. Один заключается все в том же использовании события OnKeyDown, только уже объекта TStringGrid, другой в том, чтобы использовать обычный TEdit, использование которого мы только что рассмотрели, но каждый раз подставлять его поверх активной ячейки TStringGrid. Давайте рассмотрим оба подхода. Но в этой статье мы рассмотрим метод подстановки TEdit поверх TStringGrid, а второй метод — в следующей статье.

Путем подстановки TEdit поверх TStringGrid

Это способ заключается в том, чтобы мы подставили заранее подготовленный TEdit прямо над редактируемой ячейкой TStringGrid. На мой взгляд — тоже довольно удобный способ.

Разместите на форме TStringGrid, а затем TEdit и сделайте из TEdit маску как описано в статье «Компонент маски в FMX Delphi». Назовем наш TEdit как txtEditDateInstall, а TStringGrid как StringGridAcion.

Далее нам понадобится событие OnKeyDown нашей сетки.

Сразу нужно сказать, что в FMX у объекта TStringGrid нет обработчика события OnKeyDown . Скажем так, что не то, чтобы его совсем нет. Он есть, но он по каким то причинам не вынесен в окно диспетчера свойств и методов. То есть, в режиме разработки вы его не найдете. Но это не значит, что мы его не можем создать. Давайте это сделаем. Это достаточно просто. Зная о том, что FMX — это еще не совсем зрелая платформа и в ней много ошибок, а самое главное то, что она от версии к версии постоянно видоизменяется и старый код может не работать, поэтому я стараюсь использовать какие то общие для все версий методы, свойства и подходы.

Допустим, что наш объект TStringGrid Мы назовем как StringGridAction, а форму как frmActions Объявим процедуру в классе формы:

procedure StringGridActionKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);

Давайте сделаем TEdit невидимым изначально. Но его будет не удобно редактировать, поэтому лучше это сделать при создании формы в событии onCreate. Заодно и назначим обработчик для нашего события onKeyDown:

txtEditDateInstall.Visible:=false;
StringGridAction.OnKeyDown := StringGridActionKeyDown;

ok. Еще наверное для нашего TEdit можно изменить стиль отображения. Создайте стиль, который вам нравится и примените к этому объекту. Этот пункт не связан с программированием, он чисто дизайнерский, поэтому является не обязательным. Но я все же убрал бы хотя бы рамку, чтобы подстановка смотрелась реалистичнее.

В нашей форме мы объявим три глобальные переменные:

...
var
   frmActions: TfrmActions;
   //Ниже наши три переменные
   iRow,iCol: integer;//Номер строки и столбца
   CurrentCell: TRectF;//Текущая выбранная ячейка
   PreviousCell: TRectF;//Предыдущая выбранная ячейка
implementation
...

Данные переменные можно объявить и как часть класса формы — не принципиально.

Далее в форме мы объявим две пользовательские процедуры. Первую процедуру назовем EditPosition. Она у нас будет позиционировать наш TEdit относительно ячейки, а вторую назовем CellActive. Она у нас будет контролировать количество нажатий на одну и ту же ячейку. Дело в том, что TStringGrid входит в редактирование ячейки, либо после второго одинарного щелчка мышью на ячейке, либо после двойного щелчка. Сделаем симуляцию этого процесса так, чтобы наш TEdit появлялся либо после второго одиночного щелчка по ячейке, либо после двойного щелчка.

...
procedure EditPosition(StringGrid: TStringGrid; Edit: TEdit; ACol: Integer; ARow: Integer);
procedure CellActive;
...

А затем мы опишем наши процедуры. Сначала опишем EditPosition:

procedure TfrmActions.EditPosition(StringGrid: TStringGrid; Edit: TEdit; ACol: Integer; ARow: Integer);
var
    CellRect: TRectF;//Наша активная ячейка
    AbsoluteCellRect: TRectF;//Абсолютные координаты ячейки
    ViewportPosition: TPointF;
begin
    //if ACol=8 then
    //begin
    //Выбранная ячейка находится в столбце ACol и в строке ARow
    CellRect:=StringGrid.CellRect(ACol, ARow);

    //Текущие координаты смещения окна просмотра
    ViewportPosition:=StringGrid.ViewportPosition;

    //Преобразуем относительные координаты ячейки в абсолютные
    AbsoluteCellRect.TopLeft:=
      StringGrid.AbsoluteRect.TopLeft +
      PointF(CellRect.Left - ViewportPosition.X, CellRect.Top - ViewportPosition.Y);

    AbsoluteCellRect.BottomRight:=
      StringGrid.AbsoluteRect.TopLeft +
      PointF(CellRect.Right - ViewportPosition.X, CellRect.Bottom - ViewportPosition.Y);

    //Настраиваем размеры TEdit равными размеру ячейки
    Edit.Width:=Round(AbsoluteCellRect.Width);
    Edit.Height:=Round(AbsoluteCellRect.Height);

    //Размещаем TEdit
    //Edit.Position.Point:=AbsoluteCellRect.TopLeft;//Можно так
    //А можно в две строки
    Edit.Position.X:=AbsoluteCellRect.Left+7;//Здесь можно добавить корректирующие пиксели с учетом смещения
    Edit.Position.Y:=AbsoluteCellRect.Top+7;
    Edit.BringToFront;
    //Показываем TEdit
    Edit.Text:=StringGrid.Cells[ACol, ARow];
    Edit.Visible:=True;
    if txtEditDateInstall.Text='' then txtEditDateInstall.Text:='__.__.____';
    Edit.SetFocus;
    abort;
end;

Здесь мы поясним вопросы по координатам. Во-первых, ACol равен 8 означает, что мы работаем с девятым столбцом. Именно к этому столбцу мы хотим применить нашу маску, то есть, разместить над ячейкой этого столбца наш TEdit. Во-вторых, в FMX есть три типа координат для объектов:

  • локальные — координаты в системе координат элемента управления;
  • абсолютные — координаты в системе координат клиентской части формы;
  • экранные — координаты в системе координат экрана.

Одна система координат может быть сконвертирована в другую посредством функций:

Локальные -> Абсолютные
TControl.LocalToAbsolute(TPointF): TPointF

Абсолютные -> Локальные
TControl.AbsoluteToLocal(TPointF): TPointF

Абсолютные -> Экранные
TControl.Scene.LocalToScreen(TPointF): TPointF;
Экранные -> Локальные
TControl.Scene.ScreenToLocal(TPointF): TPointF;

Ну это к нам сейчас не относится, информация просто предоставлена для расширения кругозора.

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

Еще один момент, требующий объяснения заключается в позиционировании TEdit, Его можно сделать в одну строку, а можно в две, как показано. В две мне удобнее, потому что можно добавить несколько пунктов, которые скорректируют позицию нашего TEdit. Корректировка может понадобиться, когда у нас есть какие то отступы самого TStringGrid.

Теперь нам нужно как то организовать само появление нашего TEdit. Если мы посмотрим на то, как ведет себя TStringGrid, то увидим, что у него есть как бы две фазы, о которых я уже писал. Первая фаза устанавливает фокус, а вторая (второй клик) — переводит ячейку в режим редактирования. Поэтому опишем нашу процедуру CellActive следующим образом:

procedure TfrmActions.CellActive;
begin
    //Получаем активные строку и столбец
    iRow := StringGridAction.Row;
    iCol := StringGridAction.Col;
    //Получаем текущую ячейку
    CurrentCell := StringGridAction.CellRect(iCol, iRow);
    //Проверяем, изменилась ли выбранная ячейка
    if (CurrentCell = PreviousCell) then
        if iCol=8 then EditPosition(StringGridAction, txtEditDateInstall, iCol, iRow);
    //Получаем предыдущую ячейку
    PreviousCell := StringGridAction.CellRect(iCol, iRow);
    abort;
end;

iCol равен 8 означает все тоже самое, что и в случае с ACol. Далее, в событии OnCellClick нашего TStringGrid мы вызовем нашу последнюю процедуру:

procedure TfrmActions.StringGridActionCellClick(const Column: TColumn;
  const Row: Integer);
begin
   CellActive;
end;

И тоже самое мы сделаем в событии OnCellDblClick:

procedure TfrmActions.StringGridActionCellDblClick(const Column: TColumn;
  const Row: Integer);
begin
   CellActive;
end;

Но мы же будем вызывать наш TEdit не только по щелчку мыши, но и по нажатию на Enter, когда ячейка находится в нашем столбце ( в нашем примере — это восьмой столбец). Поэтому опишем нашу процедуру onKeyDown нашей сетки:

procedure TfrmActions.StringGridActionKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
var
    CellRect: TRectF;//Наша активная ячейка
    AbsoluteCellRect: TRectF;//Абсолютные координаты ячейки
    ViewportPosition: TPointF;
begin
    iRow := StringGridAction.Row;
    iCol := StringGridAction.Col;
    if iCol = 8 then
    begin
        if not ((key=37) or (key=38) or (key=39) or (key=40) or (key=27)) then
        begin
           EditPosition(StringGridAction, txtEditDateInstall, iCol, iRow);
        end;
    end;
end;

В событии OnKeyDown нашего подставного TEdit с именем txtEditDateInstall пишем такой код:

procedure TfrmActions.txtEditDateInstallKeyDown(Sender: TObject; var Key: Word;
  var KeyChar: WideChar; Shift: TShiftState);
begin
    case key  of
       9,27,33,34,38,40,145:
                            begin
                               if (fdActions.State=dsInsert) and  (key=27) then fdActions.Cancel;
                               TEdit(Sender).Visible:=false;
                               StringGridAction.SetFocus;
                               StringGridAction.SelectCell(iCol, iRow);//Выделяем ячейку
                            end;
                         13:
                            begin
                               if IsDateСheckMask(TEdit(Sender),Left,Top,Height,Width) then
                               begin
                                  fdActions.FieldByName('Дата').value:=FormatDateTime('yyyy-mm-dd',strtodate(txtEditDateInstall.Text));
                                  txtEditDateInstall.Visible:=false;
                               end;
                            end;
       else MaskDate(TEdit(Sender), Key, KeyChar);
    end;
end;

Также запишем в событии OnSelectCell нашего TStringGrid следующий код:

procedure TfrmActions.StringGridActionSelectCell(Sender: TObject; const ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
   txtEditDateInstall.Visible:=false;
end;

И последнее, что нужно сделать — это в событии onChange нашего txtEditDateInstall написать написать такой код:

procedure TfrmActions.txtEditDateInstallChange(Sender: TObject);
begin
    if (fdActions.state<>dsInsert) or (fdActions.state<>dsEdit) then fdActions.Edit;
end;

где fdActions — имя нашего набора данных, привязанного к сетке.

Итак, вроде бы больше ничего не забыл описать. Теперь поведение нашего TEdit полностью соответствует ожидаемому. Далее рассмотрим второй упомянутый способ реализации маски с помощью onKeyDown нашей сетки.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: