Показаны сообщения с ярлыком Action. Показать все сообщения
Показаны сообщения с ярлыком Action. Показать все сообщения

вторник, 20 марта 2012 г.

Урок 36. Продолжаем разговор...

Именно так любил приговаривать очаровательный мультяшный герой по имени Карслон, который живет на крыше...

Прежде, чем углубиться дальше в лес Delphi и не только,  я бы хотел оживить некоторые пункты меню проекта "Расходы".

Шаг 1. Пункт меню "О программе"


Когда-то, в уроке №13, я рассказал о том, как сделать компонент "О программе". Вам придется его немного подправить :-) и разместить на форме MainFrm, а затем назначить соответствующему пункту меню вот такой простой обработчик:


procedure TMainFrm.N_AboutClick(Sender: TObject);
begin
  PrDAboutBoxDlg1.Execute;
end;


Наделите компонент следующими свойствами:



Щаг 2. Никто не обратил внимания, что кнопка Cancel (PrDCancelButton1) на верхней панельке осталась без обработчика. Добавьте обработку действий, когда пользователь ничего не ввел в поля новой записи или передумал добавлять новую запись:


procedure TMainFrm.PrDCancelButton1Click(Sender: TObject);
begin


  // Отказ от редактирования
  ADOTableMain.Cancel;
  DBGridEh1.Enabled:=True;                                   // Переключение грида в режим доступности
  Panel3.Visible:=False;                                            // Скрытие верней панели


end;

Шаг 3. Новый Action Data_Insert, обрабатывающий процесс добавления новой записи, необходимо добавить в список ActionList и подложить его под соответствующие пункты главного меню и PopupMenu:


Обработчик события выполнения экшена:


procedure TMainFrm.Data_InsertExecute(Sender: TObject);
begin


  // Новая запись
  ADOTableMain.Insert;


end;



Внимание! Не забыть вернуться к процедуре:

procedure TMainFrm.Data_SelectMOExecute(Sender: TObject);    // Соответствует N_EditClick
begin
...
  //   Сделать доступным именно Action, а пункты меню станут доступными автоматически
  Data_Insert.Enabled:=True;


Шаг 4. Выбор базы данных

Добавьте Action в категорию Service


Создайте обработчик Action:




procedure TMainFrm.Service_Select_DiskExecute(Sender: TObject);
begin


  // Выбор диска с базой данных
  SetDisk;
  MainConnecting;
  TableActive(True);


  // Инфо на статусбаре
  StatusBarUpdate;


end;

Установите соответствие пункта меню и нового действия.
Данный пункт меню позволит Вам выбрать для просмотра и работы другую базу данных (например, по прошествии года, после архивации базы данных, сохранения ее в другом каталоге).

На этом пока все.
В следующем уроке я предполагаю показать как "перевести" интерфейс программы на другой язык.



вторник, 6 марта 2012 г.

Урок 31. Работа с ресурсами. Создание компонентов - кнопок.



Доброго времени суток.

Чтобы продолжить создание программы "Расходы", мне понадобится пара кнопок.

Продвинутый программер Pascal/Delphi скажет, что кнопка есть на панели Standart, есть BitBtn на панели Additional, чего еще остается желать? Отвечу. Если Вы создаете одну программу, тогда можно пользоваться стандартными компонентами, наделяя их нужными свойствами. А мне по долгу службы иногда приходилось создавать по две-три формы в день, располагая на каждой по паре кнопок: "OK" и "Cancel". Ну и что, казалось бы? Но для каждой кнопки приходилось загружать рисунок, настраивать свойства привязки (якоря), убирать надпись и т.п. Ленивому как я человеку это быстро надоедает. И тогда на ум приходит идея создания собственных компонентов кнопок, свойства которых уже установлены в таком виде, в каком они чаще всего используются.


Итак, начнем.

Прежде всего, если у Вас установлена библиотека компонентов Pro Delphi Lib, деинсталлируйте ее.








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



Этот файл я назвал Set_16_16.bmp.



Второй файл я назвал Cancel_16_16.bmp







Рисунки имеют размеры 16 на 32 пикселя. Обязательно сохраните их в формате bmp. Это важно.

Фактически, можно было бы обойтись одним изображением 16 на 16, как это уже реализовано в библиотеке Pro Delphi Lib, но одним рисунком можно обходиться, если кнопки всегда доступны. Если же свойство Enabled установить в False, то на месте кнопки появится неприглядный квадратик без надписи и какого бы ни было изображения. Это не есть гуд...

Далее, при помощи блокнота создаем файл OK_Cancel_New.rc (файл OK_Cancel без New не годится, такой уже был). Обратите внимание на расширение "rc".

В этом файле пишем две строки:

OK Bitmap "Set_16_16.bmp"
CANCEL Bitmap "Cancel_16_16.bmp"

где OK и CANCEL - названия (имена, без них как обратиться к нужному?) будущих ресурсов, Bitmap - тип ресурса и далее - имена подготовленных файлов с изображениями.

Сохраните этот файл.

В папку с этим файлом и файлами рисунков скопируйте файл brcc32.exe (его можно найти в папке с делфёй ($Delphi\bin)).

Облегчим себе жизнь. Тут я и позавидую и порадуюсь одновременно за тех, кто не работал в DOS-е с командной строкой... Создадим еще один файл в той же рабочей папке с помощью того же блокнота, но, чтобы он стал исполняемым операционной системой, дадим ему расширение "bat", например: runme.bat, следующего содержания:

brcc32.exe OK_Cancel_New.rc

Эта строка запустит программу brcc32.exe, которая в качестве параметра получит ссылку на файл OK_Cancel_New.rc (еще раз подчеркиваю, что все должно быть в одной папке, чтобы не прописывать абсолютных путей и не искать потом: куда же делся результат?).

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

OK_Cancel_New.RES

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



Открываем оболочкой Delphi файл PrDButtons.pas, содержащийся в библиотеке Pro Delphi Lib.




Внесем в него необходимые изменения:
вместо строки

{$R *.res}

запишем новую директиву компиллятору - ссылку на созданный нами файл ресурсов:

{$R OK_Cancel_New.RES}

Общие свойства, как вы можете видеть, задаются для компонента TPrDBUTTON в его конструкторе (от этого компонента наследуются новые кнопки TPrDOkButton = class( TPrDBUTTON ) и TPrDCancelButton = class( TPrDBUTTON ) ):



constructor TPrDBUTTON.Create( AOwner: TComponent );
begin
  inherited Create(AOwner);
  Width:=26;
  Height:=26;
  ControlStyle := ControlStyle - [ csSetCaption ];
  ShowHint:=True;
  Anchors:=[akRight]+[akBottom];  // Изменение свойств привязки к правому нижнему углу
end;





Далее - можно добавить в published разделы для кнопок свойства:



  TPrDOkButton = class( TPrDBUTTON )
  public
    constructor Create( AOwner: TComponent ); override;
  published
    property Default default True;
    property ModalResult default mrOK;
    property NumGlyphs;                             
  end;


  TPrDCancelButton = class( TPrDBUTTON )
  public
    constructor Create( AOwner: TComponent ); override;
  published
    property Cancel default True;
    property ModalResult default mrCancel;

    property NumGlyphs;                             

  end;


А в конструкторе компонентов задать значение этого свойства, равное 2, т.к. на каждую кнопку теперь полагается два рисунка:



constructor TPrDOkButton.Create( AOwner: TComponent );
begin
  inherited Create(AOwner);
  Default:= True;
  ModalResult:= mrOK;
  Hint:='Кнопка подтверждения (ОК)';
  Glyph.LoadFromResourceName(HInstance, 'OK');
  NumGlyphs:=2;                        
end;


constructor TPrDCancelButton.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  Cancel := True;
  ModalResult := mrCancel;
  Hint:='Кнопка отмены (Cancel)';
  Glyph.LoadFromResourceName(HInstance, 'CANCEL');
  NumGlyphs:=2;                        
end;


Установите вновь библиотеку компонентов Pro Delphi Lib.


Грех - не применить модернизированные компоненты на практике. Если Вы следите за разработкой программы "Расходы", то, возможно, обратили внимание на то, что после редактирования строки нет возможности ее сохранить...


Добавьте на верхнюю панельку пару кнопок.


А дальше - по проторенной ранее дорожке: нужно добавить в список действий новую операцию:


Data_OK (внимание: свойство Caption нужно оставить пустым):


В секции частных объявлений дать декларацию новой процедуры, которую необходимо указать в обработчике onExecute новой операции:



  private
    { Private declarations }
    procedure SetPost();


Процедура SetPost выполнит следующие действия:

перепишет введенные в элементы управления данные в поля таблицы и выполнит сохранение данных, т.е. - отправку их в источник, затем - вернет форму к исходному виду.


procedure TMainFrm.SetPost();
begin

  ADOTableMain.Edit;                                                // Перевод таблицы в режим редактирования
  ADOTableMain.FieldByName('MyDate').Value:=DataEdit.Value;         // Передача значений из
  ADOTableMain.FieldByName('D').Value:=DEdit.KeyValue;              // элементов управления формы
  ADOTableMain.FieldByName('K').Value:=KEdit.KeyValue;              // в поля таблицы
  ADOTableMain.FieldByName('Summa').Value:=SEdit.Value;
  ADOTableMain.FieldByName('Rem').Text:=PEdit.Text;

  Try
    ADOTableMain.Post;                                              // Попытка записи
  Except
    ADOTableMain.Cancel;                                            // Сброс в случае ошибки
    MyMessenger.TitleString:='Ошибка';                              // Сообщение оператору об ошибке
    MyMessenger.MessageString:='При попытке записать данные в базу возникла проблема.';
    MyMessenger.Buttons:=[mbOK];
    MyMessenger.ShowMessage;
  end;

  DBGridEh1.Enabled:=True;                                          // Переключение грида в режим доступности
  Panel3.Visible:=False;                                            // Скрытие верней панели

end;

Не забудьте для кнопки "OK" указать Action.

Удачи.
А.В.





среда, 22 февраля 2012 г.

Урок 30. Поиск и фильтрация

"Давненько я не брал в руки шашек...", - говорил Гоголевский Ноздрёв.

Давненько я ничего не писал в моем блоге. Не буду приносить никаких извинений и писать оправдательных фраз. Достаточно одного слова: "Работа..." А посему - за дело...

Вряд ли какая-то программа обходится без алгоритма поиска чего либо. А интерфейс, предоставляющий пользователю возможность работы с цифрами, тем более не может обойтись без таких полезных функций как поиск и фильтрация записей. Я утверждаю это со 100% уверенностью как пользователь собственных творений. А причина тому проста: чаще всего оператору не нужен полный набор всех записей. Как правило, его интересует ограниченное количество строк или вообще одна конкретная запись.

Если кто-то из Вас, мои дорогие читатели, работал с подобными программами, то мог сталкиваться с таким удобством: щелкнешь по заголовку какой либо колонки в таблице, и порядок сортировки строк таблицы изменился... Волшебство? Нет, уверяю Вас. Предлагаю снабдить сетку на главной форме подобными функциями. Это возможно, так как grid на форме main из библиотеки EhLib.

Не помню, описывал я ранее или нет, но для удобства пользователя (а об этом в очередной раз призываю не забывать никогда!) нужно установить такой порядок сортировки записей в главной форме при ее старте, чтобы самые свежие строки оказались вверху. Много для этого не нужно, укажите лишь в инспекторе объектов для ADOTableMain имя поля, по которому должна произойти сортировка, не забыв при этом указать обратный порядок (DESC):





Затем нужно обратиться к свойствам сетки:


SortLocal=True (по умолчанию стоит False).

Затем в событии onShow формы написать следующий обработчик:


procedure TMainFrm.FormShow(Sender: TObject);

Var
  i: Integer;
begin

  // Настройка заголовков колонок
  For i:=0 to DbGridEh1.Columns.Count-1 do begin
    DbGridEh1.Columns[i].Title.TitleButton := TRUE;                  //заголовок колонки становится "кнопкой"
  end;

// Настройка свойств сетки (добавляются свойства)
  DbGridEh1.OptionsEh := DbGridEh1.OptionsEh + [dghAutoSortMarking]; //автоматическая сортировка
  DbGridEh1.OptionsEh := DbGridEh1.OptionsEh + [dghMultiSortMarking]; //сортировка по нескольким колонкам

end;

А для клика по заголовку сетки создать обработчик вида:


procedure TMainFrm.DBGridEh1TitleBtnClick(Sender: TObject; ACol: Integer;

  Column: TColumnEh);
begin

If DBGridEh1.Columns[ACol].Title.SortMarker=smDownEh
then
  ADOTableMain.IndexFieldNames:=DBGridEh1.Columns[ACol].FieldName
else
  ADOTableMain.IndexFieldNames:=DBGridEh1.Columns[ACol].FieldName+' DESC';

end;,

здесь сначала проверяется, какой маркер сортировки установлен в колонке с номером ACol (напомню, что нумерация ведется от нуля), а затем назначается индекс по этой колонке для сортировки в прямом или обратном (DESC) порядке.

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




связанная с тем, что эти поля в сетке не простые, а с подстановкой (на экране отображается содержимое поля D_Name, а на самом деле ключевую роль (KeyField) выполняет поле с именем D). Решений у этой задачки несколько, но я полагаю, что останавливаться на этом не стоит.

Ну, а чтобы все работало без ошибок пока Вы думаете, исключите обработку этих полей, написав в начале последней процедуры:

If (DBGridEh1.Columns[ACol].FieldName='D_Name') OR (DBGridEh1.Columns[ACol].FieldName='K_Name')
then
  exit;

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

Для этого понадобится текстовое поле, где будет вводиться искомое значение и кнопка. Назовем их FindEdit и BitBtnFind.

Напишите следующий обработчик события нажатия кнопки:

procedure TMainFrm.BitBtnFindClick(Sender: TObject);
begin

  // Поиск первого вхождения
  ADOTableMain.First;
  While not ADOTableMain.Eof do
  begin
    If Copy(ADOTableMain.FieldByName(DBGridEh1.Columns[DBGridEh1.SelectedIndex].FieldName).AsString,1,Length(FindEdit.Text))=FindEdit.Text
    then
      Break;
    ADOTableMain.Next;
  end;

  // Сообщение при неудачном поиске
  If ADOTableMain.eof
  then
  begin
    MyMessenger.MessageString:='Попробуйте: '
                              +CR
                              +'1. Изменить искомое слово.'
                              +CR
                              +'2. Проверить регистр.'
                              +CR
                              +'3. Установить курсор в одно из полей <Куда> или <Откуда>';
    MyMessenger.TitleString:='Ничего не найдено';
    MyMessenger.MessageType:=mtInformation;
    MyMessenger.Buttons:=[mbOK];

    MyMessenger.ShowMessage;

    // Установка фокуса в поле ввода
    If FindEdit.CanFocus
    then
      FindEdit.SetFocus;

  end;

end;

Самое интересное на мой взгляд место в этом коде - функция Copy, которая выделяет подстроку в поле, начиная с первой позиции. Длина этой подстроки берется равной длине введенного в поле поиска значения.

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

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

Приступим к организации фильтра.

Прежде всего, создайте экшн в категории Data под данное действие (на предыдущем шаге создавать Action особого смысла не было, так как выполняемое по кнопке действие вряд ли имеет смысл подкладывать под какой-то другой элемент управления или пункт меню - действие связано с содержимым поля ввода и не работает само по себе).


Обработчик события onExecute напишите следующий:

procedure TMainFrm.Data_FilterExecute(Sender: TObject);
begin

  // Фильтр
  If MySelect.MySel_Field='MyDate'
  then
    NewFilter:='('+OldFilter+') AND (MyDate='+ADOTableMain.FieldByName('MyDate').AsString+')';

  If MySelect.MySel_Field='D_Name'
  then
    NewFilter:='('+OldFilter+') AND (D='+ADOTableMain.FieldByName('D').AsString+')';

  If MySelect.MySel_Field='K_Name'
  then
    NewFilter:='('+OldFilter+') AND (K='+ADOTableMain.FieldByName('K').AsString+')';

  If MySelect.MySel_Field='Summa'
  then
    NewFilter:='('+OldFilter+') AND (Summa='+ADOTableMain.FieldByName('Summa').AsString+')';

  ADOTableMain.Filtered:=False;
  ADOTableMain.Filter:=NewFilter;
  ADOTableMain.Filtered:=True;

  StatusBar1.Panels[0].Text:='Записей (фильтр): '+IntToStr(ADOTableMain.RecordCount);

end;

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

Установите этот Action под соответствующий пункт меню.
Тестирование будет состоять в следующем: 

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

Но, вот беда: как вернуть все записи? Как снять фильтр? Как отказаться от него?

Понадобится еще одно действие в категории Data вот с таким обработчиком события "выполнить":

procedure TMainFrm.Data_NoFilterExecute(Sender: TObject);
begin

  // Снятие фильтра
  ADOTableMain.Filtered:=False;
  ADOTableMain.Filter:=OldFilter;
  ADOTableMain.Filtered:=True;
  StatusBarUpdate;

end;

Для отмены действия вновь наложенного фильтра назначьте это действие пункту меню или кнопке.

На этом на сегодня все.

Жду Ваших вопросов.



понедельник, 26 декабря 2011 г.

Урок 26. Обнуление базы данных


Предыдущий урок был посвящен вопросам подключения к базе данных, расположенной на сменном носителе.
Сегодня я хочу показать, как с использованием компонента TADOCommand вносить изменения в базу данных. Мои не случайные читатели, которые следят за ходом событий, спросят: "Что это ты, Александр Львович, все вокруг да около ходишь? Когда же, наконец, мы двинемся дальше?" А скоро, очень скоро мы продолжим разработку основного алгоритма программы (кстати, Вы не забыли, как она называется и для чего, собственно, она предназначена? :-) ). Я стараюсь сохранить интригу. Да и не интересно будет рассказывать о разных мелочах настройки интерфейса и т.п., когда основной материал останется позади. Так что: терпение, мой друг, терпение...
А сейчас нужно добавить новый Action, создав новую категорию Service. Его свойства показаны на рисунке.
Затем положить на форму компонент TADOCommand с вкладки dbGo панели компонентов. Этот компонент как раз предназначен для того, чтобы выполнять запросы к базе данных, не требующие ответов, т.е. такие, которые видоизменяют информацию в базе данных, но не возвращают пользователю никакого набора строк. В нашем случае, например, по окончании календарного года старые записи становятся не очень-то актуальны. Хранить их нужно (мало ли что?!), а вот таскать везде с собой - вовсе не обязательно. Поэтому и существует необходимость почистить наше хранилище.
И последнее, что нужно сделать - написать обработчик события onExecute, не забыв при этом дать объявление процедуре и "подложить" этот Action под один из пунктов меню "Настройки".
Итак, текст обработчика события onExecute:
procedure TMainFrm.Service_ClearExecute(Sender: TObject);
begin
// Предупреждающий диалог с оператором
MyMessenger.MessageString:='Будет произведена полная очистка базы данных'+CR+'Выполнить?';
MyMessenger.TitleString:='Обслуживание...';
MyMessenger.MessageType:=mtConfirmation;
MyMessenger.Buttons:=[mbYes]+[mbNo];
If MyMessenger.ShowMessage=6
Then
Begin
// Выбор БД и подключение (случае, если необходимые данные не вводились и работа программы только началась)
SetDisk;
MainConnecting;
TableActive(True);
// Подготовка
With ADOCommand1 do
begin
CommandTimeout := 45;
Connection := ADOConnection1;
Prepared := True;
MyMessenger.MessageString:='Можно вычислить остатки на начало или обнулить их'
+CR
+CR
+'Вычислить и запомнить остатки на начало?';
If MyMessenger.ShowMessage=6
Then
Begin                              // Вычисление остатков (заготовка на будущее)
// Подсчет оборотной ведомости и перенос вычисленных остатков - зона нашего ближайшего развития
End
Else                               // Обнуление остатков
begin
// Очистка остатков (значения полей Ost_D и Ost_K таблицы  Accounts будут установлены в ноль)
CommandText:= 'UPDATE Accounts SET Accounts.Ost_D = 0, Accounts.Ost_K = 0';
Execute;
end;
// Очистка главной таблицы (удаление всех записей из таблицы)
CommandText:= 'DELETE FROM Main';
Execute;
// Очистка списка МО (удаление всех записей из таблицы)
CommandText:= 'DELETE FROM MOs';
Execute;// Очистка оборотки (разберем на одном из следующих уроков)
//CommandText:= 'DELETE FROM Oborot';
//Execute;


// Сообщение оператору
MyMessenger.MessageString:='Операция завершена успешно';
MyMessenger.MessageType:=mtInformation;
MyMessenger.Buttons:=[mbOK];
MyMessenger.ShowMessage;
end;
End;
end;

Как видно из текста этой несложной процедуры: один компонент TADOCommand выполняет последовательно несколько запросов: один - на изменение данных в таблице Accouts, остальные - на удаление записей из таблиц. Почему записи не следует удалять из таблицы Accounts? Очень просто: эта таблица содержит план счетов, говоря бухгалтерским языком. Или попросту - условно постоянный набор признаков, который можно использовать из года в год.


В следующем уроке я расскажу о подготовке главной формы к полноценному отображению данных с помощью DBGridEh, а так же поговорим о полях LookUp.