среда, 19 октября 2011 г.

Урок 23. Очень важная последовательность действий



Сначала я назвал этот урок “Форма выбора мемориального ордера”. Но, поскольку, ниже поведу речь о добавлении в проект новой формы, подключении ее к источнику данных, о том, как организовать выбор в форме и передать выбранные данные дальше, как их оценить и обработать, показать оператору и т.п. О многом из того, что будет представлено в этом уроке, я уже говорил ранее. Более того: в будущем действия, о которых пойдет здесь речь, станут привычной рутиной. Поэтому мне хотелось бы очень подробно остановиться на этом вопросе, чтобы потом только ссылаться на него не повторяясь. Всегда интереснее обсуждать что-то новое, чем талдычить об одном и том же, правда? Поэтому, дорогой читатель, наберись терпения. Урок будет длинный, более 20 пунктов...
Цель: добавить в проект новую форму, в которой должна отображаться какая либо справочная таблица, чтобы пользователь мог выбрать одну из записей, предварительно добавив ее, если нужной записи нет.
Озадачимся вопросом: что нужно знать программе, т.е. что должен пользователь уже определить, прежде, чем начнет вводить записи о расходах в наш реестр?
Валюту? Правильно, но пока я принял валюту по умолчанию - рубли.
Пользователь должен определиться, как он будет группировать информацию. Опытный бухгалтер на крупном предприятии обычно не ограничивается распределением информации только по месяцам года, он дробит ее на более мелкие группы. Но, я думаю, что для бытовых целей, достаточно группировать записи о расходах по месяцам. Поэтому в дальнейшем под мемориальным ордером будем понимать часть записей базы данных, относящихся к определенному месяцу. Отсюда и перечень мемориальных ордеров будет состоять из 12 строк по числу месяцев в году.
Нам понадобится очень многое из предыдущих уроков, поэтому, если Вы с ними не ознакомились по какой либо причине, то лучше это сделать сейчас.
Приступим (подробности - см. в соответствующем уроку видео материале).
1. Добавьте на форму Main компоненты TADOTable и TDataSource с постфиксами MOs в названиях. Назначьте ADOTableMOs свойство Connection=ADOConnection1 (ранее выполнено подключение к базе данных). Определите свойство TableName=MOs (имя таблицы базы данных “Расход”, содержащей перечень мемориальных ордеров). Компоненту DataSourceMOs назначьте DataSet=ADOTableMOs (этот компонент будет иметь связь с таблицей, чтобы выполнить визуализацию - вывод на экран - данных, хранящихся в ней). Установите свойство AutoEdit этого компонента в False.
2. Добавьте  в проект новую форму с именем Name=MOs_Frm, назвав модуль формы Unit=MOs. Задайте некоторые свойства формы: заголовок - Caption=’Список мемориальных ордеров’, KeyPreview=True, чтобы форма реагировала на нажатие клавишей (понадобится в дальнейшем для обработки нажатия функциональных кнопок и таких как “Escape” или “Enter”), Position=poScreenCenter, чтобы форма выводилась в центре экрана.
3. Добавьте пару панелей из библиотеки компонентов ProDelphi с соответствующими опциями Align (одна панель должна располагаться внизу, вторая - быть клиентом).
4. С вкладки компонентов библиотеки EhLib, установленной ранее, добавьте компонент сетку - TDBGridEh на панель-клиент.
5. Установите на нижнюю панель разработанный ранее навигатор (вы можете использовать навигатор, поставляемый с средой разработки, работать он будет аналогично, отличия лишь во внешнем виде).
6. Пропишите взаимно формы в Uses в разделе implementation (в модуле формы Main: Uses MOs; в модуле формы MOs: Uses Main). Сие описание необходимо, чтобы можно было в одной форме использовать переменные и процедуры с глобальной областью видимости из другой формы.
7. Аналогично тому, как мы это делали в уроке 4, создайте в ActionList действие, назвав его Name=Data_SelectMO, с подсказкой - Hint=’Выбор мемориального ордера’, отнеся его к категории Category=Data с подписью Caption=’&Начать ввод’. Надеюсь, что Вы понимаете, для чего в Caption использован знак амперсанда. Создайте заготовку (достаточно ввести один комментарий, чтобы среда разработки не удалила пустышку при сохранении) OnExecute.
8. Свяжите пункт меню (Начать ввод, Name=N_Edit) с созданным действием Data_SelectMO, аналогично тому, как это делалось в уроке 4.
9. Добавьте  поля в ADOTableMOs (правой кнопкой по компоненту - Fields Editor, правой кнопкой по окну редактора полей - Add All Fields...)
10. Уберите форму из автодоступных в модуле проекта (как это сделать, см урок 12).
11. Создайте процедуру StatusBarUpdate, дав ее объявление в области Private, т.к. нет необходимости вызывать ее из других форм, отображающую в статус баре выбор оператора с помощью следующих команд:
StatusBar1.Panels[1].Text:='Валюта: '+ MySelect.MySel_ValName;
StatusBar1.Panels[2].Text:='МО: '+ MySelect.MySel_MOName;
StatusBar1.Panels[3].Text:='Счет: '+MySelect.MySel_AccName;
StatusBar1.Panels[4].Text:='Каталог: '+ MySelect.MySel_Dir;
12. Создайте Private процедуру ShowMOs, позволяющую выводить форму MOs_Frm на экран:
Application.CreateForm(TMOs_Frm, MOs_Frm); // Создание формы
MOs_Frm.ShowMoDal;                                          // Вывод формы на экран
MOs_Frm.Free;                                                      // Освобождение памяти
StatusBarUpdate;                                                   // Визуализация выбора
13. Добавьте на форму MOs_Frm кнопку выбора (экземпляр компонента TButton). Для этой кнопки достаточно изменить только подпись Caption=’Выбрать’, поскольку в дальнейшем я планирую заменить ее компонентом собственного производства. Не стоит создавать действие в ActionList, так как действие этой кнопки не будет продублировано в меню или где-то еще.
14. Напишите обработчик нажатия кнопки выбора:
// Выбор сделан
MyPSelect();
где MyPSelect() - Private процедура:
procedure TMOs_Frm.MyPSelect();
begin
// Обработка выбора
case ModalResult of
1:
begin
if MainFrm.ADOTableMOs.FieldByName('ID').Value>0
then
Begin
MySelect.MySel_IDMO:=MainFrm.ADOTableMOs.FieldByName('ID').Value;
MySelect.MySel_MOName:=MainFrm.ADOTableMOs.FieldByName('Name').Text;
MySelect.MySel_Mes:=MainFrm.ADOTableMOs.FieldByName('Mes').Value;
Close;
end;
end;
2:
Begin
Close;
end;
end;
end;
Обратите внимание, что переменная MySelect типа запись объявлена в форме Main!
15. Добавьте в обработчик создания формы операторы присвоения начальных значений переменным:
procedure TMOs_Frm.FormCreate(Sender: TObject);
begin
MySelect.MySel_IDMO:=0;
MySelect.MySel_MOName:='';
MySelect.MySel_Mes:=0;
end;
16. Чтобы сделать таблицу доступной (активировать), создайте в главной форме Private подпрограмму:
procedure TMainFrm.TableActive(p: boolean);
begin
// Активация (деактивация таблиц)
ADOTableMOs.Active:=p;
end;
которая так же может быть использована в целях отключения таблиц (подобных объектов в будущем будет несколько).
17. В конструкторе формы MOs_Frm назначте источник для DBGridEh: DataSource=MainFrm.DataSourceMOs. Обратите внимание, что средства подключения к базе данных и компоненты для работы с ней расположены на главной форме.
18. Теперь пришло время “облагородить” сетку, задав удобоваримые заголовки колонок (столбцов). Для этого кликните два раза по изображению грида на форме MOs_Frm, а затем по кнопке Add All Fields - добавить все поля. Перейдите в инспектор объектов. Выбирая последовательно поля, в свойстве Title-Caption задайте названия. Закройте окно Editing.DBGridEh.Colums.
19. Для сетки, отображающей таблицу из трех полей (колонок), удобно установить свойство AutoFitColWidths в значение True, чтобы они все уместились на экране, чтобы избежать появления горизонтальной полосы прокрутки.
20. Назначьте источник для навигатора аналогично тому, как Вы сделали это для DBGridEh.
21. Создайте обработчик события onKeyDown формы MOs_Frm дважды щелкнув по одноименной строке в инспекторе объектов:
procedure TMOs_Frm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// Обработка нажатия клавиши
case Key of            // Start Case
VK_ESCAPE:
begin
if MessageDlg('Закрыть?',mtConfirmation, [mbYes]+[mbNo], 0)=6
then
Close;
end;
else
End;                   // End case
end;
Это нужно для того, чтобы пользователь мог закрыть форму клавишей Esc.
Вывод на экран окна диалога с вопросом “Закрыть?” можно оформить с помощью находящегося на главной форме компонента MyMessenger.
22. Для пользователей, которые не привыкли кликать по кнопкам, а предпочитают делать выбор двойным щелчком, оформите обработку соответствующего события для DBGridEh:
procedure TMOs_Frm.DBGridEh1DblClick(Sender: TObject);
begin
// Реакция на двойной клик
ModalResult:=1;
MyPSelect();
end;
23. И последнее, что необходимо сделать - обработать выбор после закрытия вспомогательной формы (если ничего не выбрано, зачем показывать пустую таблицу пользователю?!) и научить программу показывать необходимые в данную минуту пункты меню:
procedure TMainFrm.Data_SelectMOExecute(Sender: TObject);
begin
// Обработка выбора МО
// Инфо на статусбаре
StatusBarUpdate;
// Вывод формы выбора МО
ShowMOs;
// Обработка выбора
if MySelect.MySel_IDMO=0
then
PrDPanel2.Visible:=False
else
Begin
PrDPanel2.Visible:=True;
end;
// Открыть недоступные пункты меню
N12_Ins.Enabled:=True;
N10_Filter.Enabled:=True;
N_Credit.Enabled:=True;
N_ExportToMDB.Enabled:=True;
N_Kodifikators.Enabled:=True;
end;
Что же дальше?
Короткий разговор о сортировке записей в таблице-источнике.

Урок 22. Компонент навигатор


На предыдущем уроке мы начали наполнять главную форму программы "Расходы" контейнерами и элементами управления. На этом я предлагаю временно оставить наш проект. Я постараюсь объяснить почему. Какое основное действие будет производить оператор, запустив программу, как Вы думаете? Чаще всего он будет вводить новые данные. Что ему нужно для этого? Следуя логике, заложенной в структуре базы данных, следуя фазам луны :-) и правилам бухгалтерского учета, учетные записи лучше группировать. Группировка в журнально-ордерной системе (это я сейчас не ругнулся, поверьте) имеет одни принципы. Эта система была создана до появления вычислительных машин. С появлением же компьютеров, надобность в группировке как таковой отпала вовсе: в базу данных можно валить все навалом, потом разберемся, когда будем из нее данные извлекать. И все же, во многих программах присутствует вид группировки записей в мемориальные ордера (опять не ругнулся). Под этим каждый волен понимать все, что хочет,  а я предлагаю не мучиться (все же не большую систему пишем) и принять один уровень группировки равным одному календарному месяцу. В крупных системах мемориальные ордера группируют по дебетовому или кредитовому принципу, а мы сопоставим мемориальному ордеру месяц. Вряд ли в нашем учете накопится огромное количество записей среди которых потребуется отыскать ошибку - это основная причина настоящего упрощения. Хотя и в этой программе пользователь может применить иные принципы. Наше дело - написать такой софт, который его в его творчестве не будет сдерживать и ограничивать.
Поэтому, первое, что должна наша программа спросить пользователя - это: в какой мемориальный ордер желаете запись добавить?
Как программа может это сделать? Соответствующую форму на экран вывести.
Что в той форме будет видеть пользователь? Пользователь в той форме должен увидеть список всех заведенных им мемориальных ордеров - МО (с учетом нашего упрощения - месяцев).
- А что, если наступил новый месяц, спросят меня любознательные? Как быть пользователю?
Справедливый вопрос. Стало быть, в форме, позволяющей просматривать список МО, и выбирать нужный из этого списка, должна быть предусмотрена возможность добавления новой записи. Ох, чувствуете, куда нас занесло?
А занесло нас в область управления (добавления, редактирования и удаления - AED от Add, Edit, Delete) записями в таблице базы данных.
Вообще-то, для этого существует стандартный компонент, поставляемый с средой разработки. Но...
Однажды шеф сказал: "Хочу, чтобы на кнопках были надписи вместо непонятных пиктограмм!"
И действительно, домашнюю хозяйку, решившую с помощью программы "Расходы" поучитывать слегка свои затраты на губную помаду, лучше не пугать пиктограммами и, не дай Бог, иностранными словами.
Увы, такого компонента в стандартной поставке нет, только TDBNavigator.
Пришлось яндекс погуглить :-)
После долгих мытарств отыскал, наконец, на Delphi World 6 статью NavigationBotton. Отдельное спасибо автору!
Потом были долгие поиски контейнера для набора кнопок. Суть проблемы, с которой сталкивались многие до меня, но никто не поделился результатом ее решения, заключается в том, что при изменении размера контейнера компонента хотелось, чтобы пропорционально (в конструкторе) менялись бы и размеры кнопок.
Для любознательных скажу пару слов: на основе добытой NavigationBotton и стандартного контейнера TPanel, я сделал собственный компонент, поменяв надписи на кириллические, исключив кнопки перехода по записям, добавив свойство источника данных DataSource и написав подпрограмму пересчета размеров. Исходный код, который, признаюсь, далек от идеала, каждый любознательный может скачать вот здесь. Ну, а для тех, кто не захочет заморачиваться и разбираться в чужих писульках - добро пожаловать на страничку с библиотекой компонентов Pro-Delphi (кстати, для успешной дальнейшей работы она должна уже быть установлена в Вашей среде разработки).

Что же дальше? Попробуем организовать работу с одной из таблиц базы данных. Пора уже, не правда ли?

вторник, 18 октября 2011 г.

Урок 21. Контейнеры и элементы управления главной формы


Упомянув однажды в новостях о Java , я оставил эту модную среду разработки в стороне. Для работы с базами данных она годится, но не очень. Подключение к БД Access так и не вызвало у меня восторга, но мало того: для таблиц нужно "сочинять" модель поведения. Не то, чтобы лениво... Вряд ли стоит этим заниматься, когда стоит цель - сделать быстро. Но, не влюбившись в Java, не оставлю ее навсегда. Как в мультике про Карлсона: "Он улетел, но он обещал вернуться..." Я обещаю вернуться.
Итак, в предыдущем уроке я рассказал о подключении к базе данных MS Access. Но, давайте сейчас вернемся к оставленному нами Delphi проекту программы "Расходы" и наполним нашу основную форму контейнерами и элементами управления.
Я не располагаю элементы управления непосредственно на форме. Такой способ приемлем и не является ошибкой. Просто для управления интерфейсом (переключения его в различные режимы: добавления данных, редактирования и т.п.) удобнее располагать элементы управления, размещая их в контейнерах.
Стандартным (привычным) набором я считаю три панели и строку состояния.
Вы можете использовать любые панели, а я размещу на форме свое "детище" из библиотеки Pro-Delphi. Строку состояния Вы найдете на панели компонентов, на вкладке "Win 32".
Первой панели установите свойство Align в alTop, чтобы она была всегда только сверху,  а вторую - сделайте клиентом (она будет изменять свой размер пропорционально изменению размера формы) - alClient. Третью расположите на предыдущей (можно использовать окно Structure) - и установите ее свойство Align в alBottom.
Для первых двух панелей отключите свойство Visible, установив его в False. Третью панель можно оставить видимой, так как она будет показываться или отключаться вместе с контейнером, в котором она размещена, т.е. с панелью 2.
Несколько слов о назначении этих панелек.
На верхней панельке я обычно располагаю элементы управления, предназначенные для редактирования выбранной записи. Кому-то может показаться такой способ не вполне удобным... Что ж: вольному - воля, Вы вправе добавить в проект лишнюю форму. Поэтому при старте показывать эту панель не имеет смысла.
Панель-клиент - основное поле действия. Здесь, как правило, располагается большая таблица, позволяющая обозревать "оперативный простор", производить выбор какой либо строки или как-то отфильтровывать лишнее. Совсем не лишнее использовать ее для показа, например, результатов поиска.
На последней панельке я располагаю обычно элементы управления, предназначенные для перехода по записям таблицы (навигатор), средства поиска и отбора записей.
Теперь щелкните два раза по StatusBar, чтобы вызвать на экран редактор. Добавьте в семейство Panels пять элементов (счет ведется от нуля):
0    Text=Записей   Width=130
1    Text=Валюта     Width=150
2    Text=МО             Width=200
3    Text=Счет         Width=200
4    Text=Каталог   Width=255
(ширина подобрана опытным путем :-) ). На этих Panels будут отображаться поясняющие выбор оператора надписи.
Теперь вспомним о том, что когда-то давно, на уроке №5 я рассказывал о замечательной библиотеке компонентов EhLib. Настало время ее полноценного использования. Добавьте на панель-клиент экземпляр компонента TDBGridEh. Я сознательно сейчас не переименовываю добавляемые компоненты, чтобы не усложнять рассказ.
На самую верхнюю нашу панельку (1) добавьте следующие компоненты из библиотеки EhLib, переименовав их (здесь это действие необходимо, так как эти элементы будут использоваться в программном коде, и будет просто удобнее ,если они получат осмысленные имена хотя бы на смеси кириллицы и латиницы :-) ):
TDBDateTimeEditEh           Name=DataEdit,
два элемента TDBLookUpComboBoxEh с именами Name=DEdit, и Name=KEdit,
TDBNumberEditEh с именем Name=SEdit
и один элемент TEdit с вкладки "Стандартные", дав ему имя PEdit.
Как многие уже догадались, эти элементы управления предназначены для редактирования выбранной записи, а точнее полей: даты, счетов дебета и кредита, суммы и примечания.
В результате всех этих действий, должна получиться вот такая картина:
При запуске проекта на выполнение, вы не должны увидеть ничего нового: форма и строка меню. Если Вы видите панели с элементами управления, значит Вы не сделали их невидимыми на момент старта программы. Исправьте эту ошибку.







Что же дальше?
Придется оставить не на долго наш проект, чтобы подготовить один полезный компонент.


вторник, 11 октября 2011 г.

Урок 20. Подключение к базе данных


Продолжаем разговор о создании программы "Расходы". 


На прошлом уроке я позволил себе лирическое отступление, рассказав о создании заставки к программе.
Но сегодня, мой дорогой читатель,  наступил долгожданный момент, когда речь пойдет о подключении к созданной нами ранее в среде MS Access базе данных Rashod.mdb.
Но, для начала, предлагаю ввести одну переменную типа записи и одну полезную константу в разделе interface:
const CR=#13#10;                   // Константа перевода каретки
Type  TMySelect = record
-  объявление типа запись,  на основе которого будет объявлена переменная,  предназначенная для хранения  выбираемых в процессе работы значений
MySel_ID: Integer;             // Идентификатор выбранной строки
MySel_IDAcc: Integer;          // Идентификатор счета
MySel_AccName: String;         // Наименование счета
MySel_IDMO: Integer;           // Идентификатор мемориального ордера
MySel_MOName: String;          // Наименование мемориального ордера
MySel_Mes: Integer;            // Номер месяца
MySel_IDVal: Integer;          // Идентификатор валюты
MySel_ValName: String;         // Название валюты
MySel_Dir: String;             // Выбранная рабочая папка
MySel_Field: String;           // Имя поля
end;

И пусть это пока не смущает... Пригодится.
Не забудьте добавить объявление переменной:
var  MySelect: TMySelect;
Теперь настало время открыть форму в режиме конструктора и разместить на ней компонент TADOConnection с вкладки dbGO панели инструментов. Этот компонент не единственный, который позволяет связать приложение с базой данных. Есть ряд компонентов сторонних производителей, выполняющий аналогичные функции. Корифеи начнут упрекать меня за выбор этого компонента, но я оправдаюсь следующим:
  • данный компонент входит в стандартную поставку среды разработки (т.е. Вам не придется искать и покупать аналоги сторонних производителей),
  • работа с ним проста и удобна,
  • компонент работает надежно и стабильно.
Измените свойство Login Prompt экземпляра ADOConnection1 компонента TADOConnection на False. Это нужно для того, чтобы при попытке подключения к базе данных не запрашивался пароль и логин. В других задачах Вы можете использовать иные настройки.
Затем, как показано на иллюстрации:
  1. щелкните по кнопке с многоточием в поле ConnectionString, чтобы вызвать редактор строки подключения,
  2. нажмите кнопку "Build" - построить,
  3. выберите драйвер MS Jet, позволяющий коннектиться к базам данных Access,
  4. нажмите кнопку "Далее"
  5. выберите базу данных Rashod.mdb,
  6. нажмите "Проверить соединение",
  7. нажмите "ОК".
Полученную с помощью мастера строку подключения скопируйте целиком в соответствующую строку файла Rashod.ini (см. видео):
[General]
ConnectionString=Provider=Microsoft.Jet.OLEDB.4.0;Data Source=X:\Lessons\InternetBusiness\! VideoLessons\Les 20\Расходы\Rashod.mdb;Persist Security Info=False

Правильно сформированная строка не вызовет ошибки, если свойство Connected элемента ADOConnection1 изменить на True. Если у Вас все получилось, верните этому свойству первоначальное значение.
ВНИМАНИЕ! В финале работы над проектом Вы обязательно удалите строку подключения из свойства ConnectionString!
Но, не сейчас. Она нам еще понадобится, когда мы продолжим разработку и будем наполнять проект таблицами и запросами.
Теперь нам необходимо осуществить программное подключение.
В описании класса формы, в разделе Private, добавим одну процедуру и одну функцию:
private    { Private declarations }
Procedure MainConnecting();
Function TestConnection(): Boolean;
Назначение функции - дать ответ на вопрос: возможно ли подключение к базе данных по заданной в ini файле строке подключения.
Процедура же, после тестирования производит необходимые действия по подключению к базе данных.
Текст функции:
Function TMainFrm.TestConnection(): Boolean;
var  ini: TIniFile;
ConnectionString: String;
begin
Result:=True;                     // Если не произойдет "плохого", то функция вернет это значение
// Подключение и считывание из файла настроек
ini  ini:=TiniFile.Create(GetCurrentDir + '\Rashod.ini');
ConnectionString:=Ini.ReadString('General','ConnectionString','');
ini.Free;
// Проверка, имеется ли подключение? Если да, то - сброс
If ADOConnection1.Connected=True  then    ADOConnection1.Connected:=False;
// Назначение компоненту считанной из файла настроек строки подключения
ADOConnection1.ConnectionString:=ConnectionString;
// Подключение
Try
ADOConnection1.Connected:=true;
Except
ADOConnection1.Connected:=False;     // Если неудача
Result:=False;
End;
end;

Процедура:
Procedure TMainFrm.MainConnecting();
var  ini: TIniFile;
ConnectionString: String;
begin
ini:=TiniFile.Create(GetCurrentDir + '\Rashod.ini');
ConnectionString:=Ini.ReadString('General','ConnectionString','');
ini.Free;
If ADOConnection1.Connected=True  then    ADOConnection1.Connected:=False;
ADOConnection1.ConnectionString:=ConnectionString;
ADOConnection1.Connected:=true;
end;

А теперь, задав для события создания формы (onCreate) процедуру его обработки как показано в прилагаемом видео, запишем в нее следующий текст:
procedure TMainFrm.FormCreate(Sender: TObject);
begin
// Стартовые значения переменным
MySelect.MySel_ID:=0;
MySelect.MySel_IDAcc:=0;
MySelect.MySel_IDMO:=0;
MySelect.MySel_AccName:='';
MySelect.MySel_MOName:='';
MySelect.MySel_Mes:=0;
MySelect.MySel_IDVal:=1;
MySelect.MySel_ValName:='Руб';
If Not TestConnection  then    {некие действия с помощью процедуры SetDisk, разговор о которой в будущем};
MainConnecting;
// далее будет произведено подключение таблиц TableActive(True);
end;
Если Вы скомпилировали проект, и при этом не возникло ошибок, на этом на сегодня все.

Что же дальше?
Короткий разговор о контейнерах и элементах управления главной формы.