Если Вы, изучив предыдущие уроки, получили в свое распоряжение файл Excel, Вам не составит труда вывести его на печать. Но, не всегда такой вариант получения отчета приемлем.
Для конструирования отчетов существует ряд средств. Я не берусь здесь обсуждать их достоинства и недостатки. Так исторически сложилось, что я пользуюсь платной версией Fast Report.
Но, для того, чтобы попробовать, можно найти на сайте производителя бесплатные или демо версии, а так же очень подробную документацию.
Почему именно FastReport? Посмотрите здесь. Не только из гордости за отечественного производителя, еще и за практически безграничные возможности.
Установка компонентов FastReport не должна вызвать никаких трудностей. После установки, на Tool Palette появится ряд вкладок.
Разместите на форме два компонента, с вкладки FastReport:
TfrxDBDataset - источник данных для отчета, TfrxReport - конструктор отчета.
Теперь необходимо немного подготовиться и временно включить:
1. Форма MainFrm: ADOConnection - Connected = True (если не задана строка подключения, задать ее).
2. Форма OborotFrm, компонент CDS - свойство Connection = MainFrm.ADOConnection1 (т.е. связать этот компонент с ADOConnection на главной форме) и указать свойство TableName = Oborot. Собственно, это все проделывается программно, но нам нужно временно для создания отчета.
Не забудьте по окончании создания отчета очистить значения этих свойств.
Настройте компонент TfrxDBDataset, указав его свойство DataSet:
А затем два раза щелкните по компоненту TfrxReport, чтобы перейти в конструктор отчета.
Первое, что необходимо сделать - выбрать меню Report - Data и назначить источник данных для отчета, как показано на рисунке:
Затем - развернуть страницу на Landscape (альбомную) в меню File - Page Settings, т.к. отчет наш "широкий".
Добавить "бэнды" - так называются области для размещения в них различных данных и группировке этих данных, как минимум - два - ReportTitle (заголовок отчета) и MasterData (мастер - для вывода данных отчета), связав последний с источником данных.
Сохраните свой макет под именем Oborot.fr3.
Теперь настало время дать отчету заголовок, нажав на пиктограмму "ab":
Форматируйте надпись подобно тому, как Вы это делали в MS WORD, здесь инструменты очень похожи:
Перетащите одно поле для начала на MasterData (можно добавить еще заголовок страницы (PageHeader), где разместить заголовки колонок отчета):
Опция Create caption удобна, но не в нашей языковой зоне: подписи = имена полей на латинице, так что подписи придется сделать ручками.
Сохраните и закройте конструктор. Теперь нужно в Popup меню формы добавить новый пункт, по которому будет вызываться отчет:
procedure TOborotFrm.N2Click(Sender: TObject);
begin
// Вывод предварительного просмотра отчета
frxReport1.ShowReport(True);
end;
Если все сделано правильно, на этом этапе Вы получите список счетов с заголовком "Оборотная ведомость".
Лично я предпочитаю табличное оформление (с рамочками), чему и Вы быстро научитесь, поскольку самое главное я уже рассказал:
Главное рассказал, а важное и нужное - еще впереди.
Мой дорогой читатель, наверное уже догадался, что в документе не хватает подзаголовка, т.е. периода, за который он составлен.
Далее я покажу один из способов передачи данных посредством переменных в отчет.
Добавьте перед строкой frxReport1.ShowReport(True) ровно такую же строку, какой задавался период при импорте отчета в Excel:
frxReport1.Script.Variables['Period']:='Период: '+FormatDateTime('dd/mm/yy',Date_N.Value)+' - '+FormatDateTime('dd/mm/yy',Date_K.Value);
здесь Period - имя переменной, которую нужно разместить в конструкторе отчета в текстовом поле (memo) в квадратных скобках:
Результат, прямо скажем еще далек от совершенства:
числа нужно сдвинуть вправо, сделать их красивыми с помощью DisplayFormat (два знака после запятой) и подсчитать итоги по страницам (добавить пару бэндов)...
Если не сможете разобраться - пишите (координаты Вы найдете через страницу "Обо мне"), и тогда будем разбираться вместе.
А.В.
Научиться программировать не так сложно, как кажется. Уроки по принципу от простого к сложному. Исходные тексты программ. Видео уроки.
понедельник, 19 марта 2012 г.
пятница, 16 марта 2012 г.
Урок 34. Создание библиотеки (Dll)
"...когда-то давно, когда наша Земля была еще маленькая
и тепленькая, и по ней бегали такие ма-а-а-аленькие...
и с тех пор пошло и пошло, пошло и пошло..."
Мой отец коллекционировал записи А.Райкина, Р.Карцева и В.Ильченко, М.Мироновой и А.Менакера и других... Я любил слушать их вместе с ним, поэтому помню многое наизусть...
Так вот, с тех пор как появилась вычислительная техника в виде камушков (первые счеты - абак) и пошло, и пошло, и пошло... Вот только задачи усложнялись временем... и повторялись...
К чему я это все? Да к тому, что застав в своем развитии вычислительную машину ДВК-2М, я приобрел привычку экономно относиться к памяти. Кто из Вас сейчас, владеющих гигабайтными дисками, способен представить себе пятидюймовую дискету стандартным объемом 360 килобайт? При особом искусстве разметить (форматировать) эту дискету можно было на 720кБ. И, о счастье!, в ДВК было целых два дисковода! Значит, в распоряжении программиста - аж целых 1440 килобайт! Не тут-то было! А операционная система? Значит, на пространстве немногим более одного (!) мегабайта нужно было исхитриться и прожить. Но эта трудность была даже не главной. А крошечное ОЗУ? Вот где была настоящая засада! Вот куда нужно было умудриться запихнуть еще что-то помимо операционной системы! Собственно от этого и пошло то, о чем я сегодня поведу речь...
Небольшие объемы оперативной памяти заставляли программистов делить задачу на кусочки и размещать в памяти только нужный фрагмент, именно тот, который сейчас будет использован в вычислениях, выбрасывая из памяти отработавший свою функцию кусок. Мало кто из современных авторов почитаемых мною сайтов, посвященных программированию, может припомнить BASICD и его замечательный оператор COMMON, дающий возможность загружать из библиотеки необходимую подпрограмму...
Когда читаю на различных сайтах доводы в пользу Dll, у меня невольно они вызывают улыбку:
при современных объемах оперативной памяти нет необходимости задумываться программисту об ее экономии. В принципе, программист может весь код забабахать в один модуль, получить на выходе огромного объема exe-к и радоваться. А что Вы говорите: пользователь будет долго ждать, пока этот exe-к загрузится? Пусть купит себе комп по мощнее...
Так что с точки зрения программиста я особых удовольствий от использования библиотек не вижу. Тем более их нет, если Вы пишете одну программу, чтобы сдать, наконец тот самый ненавистный курсовой...
Совсем другое дело, если у Вас много наработок. Тем более, если десяток программ используют одни и те же функции. Привести пример? Легко: перевод числа в строку прописью, подсчет доходности по ценным бумагам... в нашем случае - экспорт отчета в Excel.
Да-да, именно этот функционал я и предлагаю перетащить в библиотеку, вдруг где-то еще понадобится?
Начать следует с создания модуля библиотеки:
Много вопросов визард не задаст, но сгенерирует вот такое предупреждение:
library Project1;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
SysUtils,
Classes;
{$R *.res}
begin
end.
В двух словах: если функции Вашей библиотеки будут передавать или получать строковые значения, то нужно в Uses на первом месте иметь ссылку на ShareMem, соответственно, в системе должна присутствовать библиотека BORLNDMM.DLL.
Если же, использование этой библиотеки по каким-то причинам невозможно (а такое бывает!), то передаваемые параметры должны быть типов PChar or ShortString.
Способов подключений библиотек два. У каждого есть свои преимущества и недостатки, но этой информации много в сети, не буду на ней останавливаться, оговорюсь лишь, что я буду использовать способ привязки DLL к программе. Моя задача иная: я хочу проиллюстрировать, как из готовой программы извлечь фрагмент и разместить его в библиотеке.
Сохраним проект под именем ProDelphiLyb.
Добавим в Uses ссылки на следующие модули:
ShareMem, // Нас предупреждали!!!
DBGridEh, // Для работы с сеткой
ExcelXP, // Для работы с приложением Excel
DB, // Для работы с закладкой
и далее - текст процедуры, практически полностью совпадающий с аналогичной процедурой из предыдущего урока:
Procedure MyExportToExcel(WorkSheetName: String; PGridEh: TDBGridEh; XLApp: TExcelApplication; RepName: String; RepPeriod: String); stdcall;
// Вывод данных из источника объекта DBGridEh в Excel
var
WorkBk: _WorkBook; // определяем WorkBook
WorkSheet: _WorkSheet; // определяем WorkSheet
I, J: Integer;
SavePlace: TBookmark;
begin
Try
// Установка закладки
SavePlace := PGridEh.DataSource.DataSet.GetBookmark;
// Соединяемся с сервером TExcelApplication
XLApp.Connect;
// Добавляем WorkBooks в ExcelApplication
XLApp.WorkBooks.Add(xlWBatWorkSheet, 0);
// Выбираем первую WorkBook
WorkBk := XLApp.WorkBooks.Item[1];
// Определяем первый WorkSheet
WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet;
// Заполняем свойства WorkSheet
WorkSheet.Name := WorkSheetName;
// Заголовок отчета
J := 1;
Worksheet.Cells.Item[J,1].Value:= RepName;
Worksheet.Cells.Item[J,1].Font.Bold := True;
Worksheet.Cells.Item[J,1].Font.size := 16;
// Указание периода
J := 2;
Worksheet.Cells.Item[J,1].Value:= RepPeriod;
Worksheet.Cells.Item[J,1].Font.Italic := True;
// Заголовки колонок
J := 3;
PGridEh.DataSource.DataSet.First;
with PGridEh.Columns do
begin
for i := 1 to Count do
if Items[I-1].Visible then
begin
Worksheet.Cells.Item[J,I].Value:= Items[I-1].Title.Caption;
Worksheet.Cells.Item[J,I].Font.Bold := True;
Worksheet.Cells.Item[J,I].HorizontalAlignment := xlCenter;
end;
end;
// Собственно вывод данных из источника под сеткой в Excel
J := 4;
with PGridEh.DataSource.DataSet do
begin
First;
while not Eof do
begin
with PGridEh.Columns do
begin
for I := 1 to Count do
if Items[I-1].Visible then
Worksheet.Cells.Item[J,I].Value:=FieldByName(Items[I-1].FieldName).AsVariant;
end;
Inc(J, 1);
next;
end;
end;
WorkSheet.Columns.ColumnWidth := 25;
// Показываем Excel
XLApp.Visible[0] := True;
// Разрываем связь с сервером
XLApp.Disconnect;
// Возврат на закладку
PGridEh.DataSource.DataSet.GotoBookmark(SavePlace);
PGridEh.DataSource.DataSet.FreeBookmark(SavePlace);
Finally
End;
end;
exports
MyExportToExcel
begin
end. // NB точка в конце
Для экспорта процедур и функций из DLL, необходимо использовать ключевое слово export, после которого перечисляются процедуры.
Выполните Compile и Build проекта. В результате последнего действия, в каталоге с проектом появится файл: ProDelphiLyb.dll
Теперь - самое время вернуться к проекту "Расходы".
Найдите в модуле главной формы процедуру ExportTo и замените все ее содержимое (которое переехало в модуль library ProDelphiLyb) вот на это:
Procedure TMainFrm.ExportTo(p_WorkSheetName: String; p_PGridEh: TDBGridEh; p_XLApp: TExcelApplication;
p_RepName: String; p_RepSubTitle: String);
begin
// Экспорт в Эксель
Try
MyExportToExcel(p_WorkSheetName, p_PGridEh, p_XLApp, p_RepName, p_RepSubTitle);
Except
MyMessenger.TitleString:='Проблема';
MyMessenger.MessageString:='Возможно, в Вашем компьютере не установлен MS Excel';
MyMessenger.Buttons:=[mbOk];
MyMessenger.ShowMessage;
end;
end;
А теперь - напишите оператор, подгружающий процедуру MyExportToExcel из вновь созданной библиотеки:
implementation
Uses MOs, Oborot;
{$R *.dfm}
Procedure MyExportToExcel(WorkSheetName: String; PGridEh: TDBGridEh; XLApp: TExcelApplication;
RepName: String; RepPeriod: String); stdcall; external 'ProDelphiLyb.dll';
Если все сделано правильно, то работа программы "Расходы" внешне ни чем не будет отличаться от того, как она работала до сего момента. Но, если Вы попробуете установить эту программу на другой компьютер, у Вас, вполне вероятно, будут неприятности, связанные с использованием BORLNDMM.DLL, точнее - с отсутствием этой библиотеки в компьютере.
Предлагаю воспользоваться предупреждением и избавиться от строковых параметров.
начать нужно с модуля библиотеки, убрав из Uses Sharemem и изменив тип принимаемых параметров со String на PChar:
library ProDelphiLyb;
uses
// ShareMem,
DBGridEh, // Для работы с сеткой
ExcelXP, // Для работы с приложением Excel
DB, // Для работы с закладкой
SysUtils,
Classes;
{$R *.res}
Procedure MyExportToExcel(WorkSheetName: PChar; PGridEh: TDBGridEh; XLApp: TExcelApplication; RepName: PChar; RepPeriod: PChar); stdcall;
далее - в теле самой процедуры выполнить преобразование типов, поскольку ячейки Excel, в которые вписываются заголовок отчета и подзаголовок (период) не могут принять значения типа PChar, им - строки подавай:
Worksheet.Cells.Item[J,1].Value:= StrPas(RepName);
и
Worksheet.Cells.Item[J,1].Value:= StrPas(RepPeriod);
Save, compile, build.
В модуле main проекта "Расходы" в объявлениях процедур тоже внести необходимые поправки:
{ Public declarations }
Procedure ExportTo(p_WorkSheetName: PChar; p_PGridEh: TDBGridEh; p_XLApp: TExcelApplication; p_RepName: PChar; p_RepSubTitle: PChar);
(не забудьте внести аналогичные поправки и в декларации этой процедуры в разделе implementation !!!)
и
{$R *.dfm}
Procedure MyExportToExcel(WorkSheetName: PChar; PGridEh: TDBGridEh; XLApp: TExcelApplication; RepName: PChar; RepPeriod: PChar); stdcall; external 'ProDelphiLyb.dll';
И последнее, в модуле Oborot выполнить преобразование передаваемых параметров из строковых в PChar:
procedure TOborotFrm.N_ExportClick(Sender: TObject);
begin
// Экспорт в Эксель
MainFrm.ExportTo(PChar('ОВ'), DBGridEh1, MainFrm.XLApp, PChar('Оборотная ведомость'), PChar('Период: '+FormatDateTime('dd/mm/yy',Date_N.Value)+' - '+FormatDateTime('dd/mm/yy',Date_K.Value)));
end;
Трудились долго, нудно, но заметных на первый взгляд отличий не получили...
Смотрите на вопрос ширше или ширее, а лучше - глыбже... :-)

среда, 14 марта 2012 г.
Урок 33. Оборотная ведомость - экспорт в Excel
Когда-то давно, в самом начале тревожных 90-х, когда я писал свою первую бухгалтерскую настоящую большую программу, постановщик задачи, мой коллега и друг, задал такую идею: хорошо было бы, если бы программа могла бы из одних и тех же цифр складывать несколько разных главных книг: одну - для налоговой, одну - для акционеров, одну - самую полную и правильную... и одну - для души...
Не смотря на то, что потом было еще много "хочу" разной сложности от постановщиков задач, с тех давних пор эта идеология живет вместе со мной.
Только решать задачку складывания одних и тех же цифр в различные результаты (=отчеты), сейчас стало на много проще. Для этого существуют универсальные программы, например: Excel.
Итак, в предыдущем уроке я показал как составить и вывести на экран оборотную ведомость. В таком виде можно посмотреть только небольшой отчет, пару каких-то конкретных цифр. Работать, а тем более изменять информацию на экране, добавлять оформление и т.п. невозможно.
Для того, чтобы экспортировать данные из экранного представления прикладной программы "Расходы", добавьте для начала контекстное меню для формы OborotFrm с одним пунктом "Экспорт":
Подготовьте обработчик события нажатия на этот пункт меню.
Тут я хочу сделать небольшое отступление, озадачив всех вопросом: где располагать процедуру экспорта: в данной форме или в модуле главной формы? В принципе - решать Вам, но! Если эта процедура будет универсальной, способной выполнять возложенные на нее функции не только для отчета "Оборотная ведомость", то удобнее ее расположить в модуле главной формы. Более того, если эта процедура окажется на столько универсальна и хороша, что сможет обслуживать не только эту программу, но и многие другие, тогда ей место - в отдельно лежащей библиотеке, например: в файле Dll, но об этом - несколько позже.
procedure TOborotFrm.N_ExportClick(Sender: TObject);
begin
// Экспорт в Эксель
MainFrm.ExportTo('ОВ', DBGridEh1, MainFrm.XLApp, 'Оборотная ведомость', 'Период: '+FormatDateTime('dd/mm/yy',Date_N.Value)+' - '+FormatDateTime('dd/mm/yy',Date_K.Value));
end;
Что здесь "что", перечислю параметры: название книги Excel, ссылка на GridEh, ссылка на неизвестный нам пока компонент, который еще предстоит расположить в главной форме, заголовок документа, подзаголовок.
Для начала расположите на ней экземпляр компонента TExcelApplication c вкладки Servers, дав ему имя XLApp.
Затем (внимание!) в секции общедоступных объявлений опишите процедуру:
public
{ Public declarations }
Procedure ExportTo(p_WorkSheetName: String; p_PGridEh: TDBGridEh; p_XLApp: TExcelApplication;
p_RepName: String; p_RepSubTitle: String);
А теперь - собственно цель урока - процедура вывода данных:
var WorkBk: _WorkBook; // определяем WorkBook (книгу) WorkSheet: _WorkSheet; // определяем WorkSheet (лист) I, J: Integer; // Вспомогательные переменные SavePlace: TBookmark; // Закладка begin // Вывод данных из источника объекта DBGridEh в Excel try // Установка закладки (чтобы позже вернуть на нее указатель в сетке) SavePlace := p_PGridEh.DataSource.DataSet.GetBookmark; // Соединяемся с сервером TExcelApplication p_XLApp.Connect; // Добавляем WorkBooks в ExcelApplication p_XLApp.WorkBooks.Add(xlWBatWorkSheet, 0); // Выбираем первую WorkBook WorkBk := p_XLApp.WorkBooks.Item[1]; // Определяем первый WorkSheet WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; // Заполняем свойства WorkSheet WorkSheet.Name := p_WorkSheetName; // Заголовок отчета J := 1; Worksheet.Cells.Item[J,1].Value:= p_RepName; Worksheet.Cells.Item[J,1].Font.Bold := True; Worksheet.Cells.Item[J,1].Font.size := 16; // Указание периода J := 2; Worksheet.Cells.Item[J,1].Value:= p_RepSubTitle; Worksheet.Cells.Item[J,1].Font.Italic := True; // Заголовки колонок J := 3; p_PGridEh.DataSource.DataSet.First; with p_PGridEh.Columns do begin for i := 1 to Count do if Items[I-1].Visible then begin Worksheet.Cells.Item[J,I].Value:= Items[I-1].Title.Caption; Worksheet.Cells.Item[J,I].Font.Bold := True; Worksheet.Cells.Item[J,I].HorizontalAlignment := xlCenter; end; end; // Собственно вывод данных из источника под сеткой в Excel J := 4; with p_PGridEh.DataSource.DataSet do begin First; while not Eof do begin with p_PGridEh.Columns do begin for I := 1 to Count do if Items[I-1].Visible then Worksheet.Cells.Item[J,I].Value:=FieldByName(Items[I-1].FieldName).AsVariant; end; Inc(J, 1); next; end; end; WorkSheet.Columns.ColumnWidth := 25; // Показываем Excel p_XLApp.Visible[0] := True; // Разрываем связь с сервером p_XLApp.Disconnect; // Возврат на закладку p_PGridEh.DataSource.DataSet.GotoBookmark(SavePlace);
// Удаление закладки p_PGridEh.DataSource.DataSet.FreeBookmark(SavePlace); Except MyMessenger.TitleString:='Проблема'; MyMessenger.MessageString:='Возможно, в Вашем компьютере не установлен MS Excel'; MyMessenger.Buttons:=[mbOk]; MyMessenger.ShowMessage; end; end;
Как всегда я достаточно много вписал комментариев в текст процедуры, но,если все же у Вас остались вопросы, и Вы не получили XLS файла, похожего на этот:
мои координаты Вы найдете тут.

вторник, 13 марта 2012 г.
Урок 32. “Уважаемые кроты, а не посчитать ли нам?”
Сегодня я решил поделиться одним из главных секретов, который поведал мне мой гуру однажды: "Хочешь сложный отчет получить - сделай под него таблицу".
Счастье любого бухгалтера - правильно, без ошибок, посчитанная оборотная ведомость. Без лукавства можно утверждать, что "оборотка" - основной документ. Он служит и для составления Главной книги, и для поиска ошибок...
Это - довольно сложный отчет. Составляется он за период. Поэтому включает в себя для каждого счета развернутые (по дебету и кредиту) остатки на начало периода, обороты за период и развернутые остатки на конец периода. Средством первичного контроля правильности составления оборотной ведомости является присутствие в ней всех счетов и по парное совпадение итогов.
Коль скоро нам предстоит получить столь важный и сложный документ, мы должны иметь в базе данных "Расход", о создании которой я рассказал в уроке 18, одну вспомогательную таблицу "Oborot". Чем она существенно отличается от всех остальных? Она не присутствует в схеме данных, поскольку не имеет связей. А связей она не имеет, поскольку в ней нет ключей, в этой таблице не хранится полезная информация. Она нужна только для того, чтобы временно хранить результаты вычислений.
Структура ее проста:
И я надеюсь, не вызовет вопросов при ее создании.
Теперь вернемся к нашему проекту и добавим в него форму Oborot.pas (имя формы OborotFrm).
Все настройки формы мало чем отличаются от настроек формы main, кроме свойства
WindowState, которое нужно установить в wsMaximized, чтобы при создании формы она разворачивалась на весь экран (создаваемый отчет большой, хочется видеть как можно больше информации одновременно).
Не забудьте после добавления формы в проект удалить ее из автоматически создаваемых. Вместо этого в главной форме создайте обработчик пункта меню, предназначенного для создания Оборотной ведомости:
procedure TMainFrm.N3Click(Sender: TObject); // Пункт меню "Оборотная ведомость"
begin
Application.CreateForm(TOborotFrm, OborotFrm); // Создание формы OborotFrm
OborotFrm.ShowModal; // Вывод формы в модальном окне
OborotFrm.Free; // Освобождение памяти после окончания работы с формой
end;
Не забудьте в разделе implementation главной формы добавить ссылку на модуль формы Oborot.pas:
Uses
MOs
, Oborot;
Теперь в коде добавленной формы пропишем сразу, чтобы не забыть:
Uses Main;
Это нужно для того, чтобы компонентам доступа к данным дать возможность использовать настройки компонента ADOConnection, расположенного на главной форме.
Традиционные обработчики при старте, активации, закрытии формы и некоторые другие:
procedure TOborotFrm.FormCreate(Sender: TObject);
begin
// Стартовые значения для дат периода
Date_N.Value:=Now();
Date_K.Value:=Now()+1;
// Подключение компонентов доступа к данным к базе данных
ADOQuery1.Connection:=MainFrm.ADOConnection1;
ADOQuery2.Connection:=MainFrm.ADOConnection1;
ADOCommand1.Connection:=MainFrm.ADOConnection1;
CDS.Connection:=MainFrm.ADOConnection1;
// Назначение таблицы компоненту
CDS.TableName:='Oborot';
end;
procedure TOborotFrm.FormActivate(Sender: TObject);
begin
// Запрос на первичную очистку таблицы
ADOCommand1.CommandText:='DELETE Oborot.* FROM Oborot ';
ADOCommand1.Execute;
end;
procedure TOborotFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:= caFree;
end;
procedure TOborotFrm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// Обработка нажатия клавиши Escape - закрытие формы
case Key of // Start Case
VK_ESCAPE:
begin
close;
end;
else
End; // End case
end;
procedure TOborotFrm.MyOkButton1Click(Sender: TObject);
begin
Close;
end;
А теперь - самое главное - собственно алгоритм вычисления оборотной ведомости, в основу которого положена описанная в уроке 16 формула:
procedure TOborotFrm.CalcOborots(Dn,Dk: TDateTime);
Var
o: Currency; // Свернутый остаток
MyStr: String; // Для хранения строки SQL запроса
begin
// Очистка временной таблицы
ADOCommand1.Execute;
CDS.Active:=True;
CDS.Requery();
// Составление оборотной ведомости
// Заполнение списка счетов и стартовых остатков (для выбранной валюты)
ADOQuery1.Active:=False; // Деактивация запроса
ADOQuery1.SQL.Clear; // Очистка и затем - задание строки SQL
ADOQuery1.SQL.Add('SELECT * FROM Accounts WHERE Val='+IntToStr(MySelect.MySel_IDVal));
ADOQuery1.Active:=True; // Включение запроса
ADOQuery1.First; // Переход на первую запись
While not ADOQuery1.EOF do // Перебор всех значений, пока не будет достигнут конец файла
Begin
CDS.Insert; // Добавление строки во вспомогательную таблицу, далее - заполнение полей
CDS.FieldByName('IDAcc').Value:=ADOQuery1.FieldByName('ID').Value; // ID счета
CDS.FieldByName('NameAcc').Text:=ADOQuery1.FieldByName('Name').Text;// Название счета
o:=ADOQuery1.FieldByName('Ost_D').Value-ADOQuery1.FieldByName('Ost_K').Value; // Вычисление свернутого остатка на момент старта программы
If o>=0
Then
Begin
CDS.FieldByName('OstD_N').Value:=o;
CDS.FieldByName('OstK_N').Value:=0; // Это - ноль
End
else
Begin
CDS.FieldByName('OstD_N').Value:=0; // Это - ноль
CDS.FieldByName('OstK_N').Value:=-o;
End;
// Вычисление остатков на начало по данным до даты Dn
o:=CDS.FieldByName('OstD_N').Value-CDS.FieldByName('OstK_N').Value;
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main WHERE (((Main.MyDate)<#';
MyStr:=MyStr+FormatDateTime('mm/dd/yyyy',Dn);
MyStr:=MyStr+'#) AND ((Main.D)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o+ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0; // Это - ноль
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main WHERE (((Main.MyDate)<#';
MyStr:=MyStr+FormatDateTime('mm/dd/yyyy',Dn);
MyStr:=MyStr+'#) AND ((Main.K)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o-ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0; // Это - ноль
// Запись остатков на начало
If o>=0
Then
Begin
CDS.FieldByName('OstD_N').Value:=o;
CDS.FieldByName('OstK_N').Value:=0;
End
else
Begin
CDS.FieldByName('OstD_N').Value:=0;
CDS.FieldByName('OstK_N').Value:=-o;
End;
// Вычисление и запись оборотов
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main ';
MyStr:=MyStr+'WHERE (((Main.MyDate)>=#'+FormatDateTime('mm/dd/yyyy',Dn)+'#) ';
MyStr:=MyStr+'AND ((Main.MyDate)<#'+FormatDateTime('mm/dd/yyyy',Dk)+'#) ';
MyStr:=MyStr+'AND ((Main.D)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o+ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0;
CDS.FieldByName('ObD').Value:=ADOQuery2.FieldByName('MySumNotNull').Value;
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main ';
MyStr:=MyStr+'WHERE (((Main.MyDate)>=#'+FormatDateTime('mm/dd/yyyy',Dn)+'#) ';
MyStr:=MyStr+'AND ((Main.MyDate)<#'+FormatDateTime('mm/dd/yyyy',Dk)+'#) ';
MyStr:=MyStr+'AND ((Main.K)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o-ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0;
CDS.FieldByName('ObK').Value:=ADOQuery2.FieldByName('MySumNotNull').Value;
// Вычисление и запись остатков на конец
If o>=0
Then
Begin
CDS.FieldByName('OstD_K').Value:=o;
CDS.FieldByName('OstK_K').Value:=0;
End
else
Begin
CDS.FieldByName('OstD_K').Value:=0;
CDS.FieldByName('OstK_K').Value:=-o;
End;
CDS.Post;
ADOQuery1.Next;
End;
CDS.First;
end;
Я постарался дать подробные комментарии. Но, если у Вас возникнут вопросы, я жду их по адресу vs259@mail.ru.
Что остается? Остается только подложить вызов этой процедуры под кнопку:
procedure TOborotFrm.Button1Click(Sender: TObject);
begin
CalcOborots(Date_N.Value,Date_K.Value);
end;
Счастье любого бухгалтера - правильно, без ошибок, посчитанная оборотная ведомость. Без лукавства можно утверждать, что "оборотка" - основной документ. Он служит и для составления Главной книги, и для поиска ошибок...
Это - довольно сложный отчет. Составляется он за период. Поэтому включает в себя для каждого счета развернутые (по дебету и кредиту) остатки на начало периода, обороты за период и развернутые остатки на конец периода. Средством первичного контроля правильности составления оборотной ведомости является присутствие в ней всех счетов и по парное совпадение итогов.
Коль скоро нам предстоит получить столь важный и сложный документ, мы должны иметь в базе данных "Расход", о создании которой я рассказал в уроке 18, одну вспомогательную таблицу "Oborot". Чем она существенно отличается от всех остальных? Она не присутствует в схеме данных, поскольку не имеет связей. А связей она не имеет, поскольку в ней нет ключей, в этой таблице не хранится полезная информация. Она нужна только для того, чтобы временно хранить результаты вычислений.
Структура ее проста:
И я надеюсь, не вызовет вопросов при ее создании.
Теперь вернемся к нашему проекту и добавим в него форму Oborot.pas (имя формы OborotFrm).
Все настройки формы мало чем отличаются от настроек формы main, кроме свойства
WindowState, которое нужно установить в wsMaximized, чтобы при создании формы она разворачивалась на весь экран (создаваемый отчет большой, хочется видеть как можно больше информации одновременно).
Не забудьте после добавления формы в проект удалить ее из автоматически создаваемых. Вместо этого в главной форме создайте обработчик пункта меню, предназначенного для создания Оборотной ведомости:
procedure TMainFrm.N3Click(Sender: TObject); // Пункт меню "Оборотная ведомость"
begin
Application.CreateForm(TOborotFrm, OborotFrm); // Создание формы OborotFrm
OborotFrm.ShowModal; // Вывод формы в модальном окне
OborotFrm.Free; // Освобождение памяти после окончания работы с формой
end;
Не забудьте в разделе implementation главной формы добавить ссылку на модуль формы Oborot.pas:
Uses
MOs
, Oborot;
Теперь в коде добавленной формы пропишем сразу, чтобы не забыть:
Uses Main;
Это нужно для того, чтобы компонентам доступа к данным дать возможность использовать настройки компонента ADOConnection, расположенного на главной форме.
Традиционные обработчики при старте, активации, закрытии формы и некоторые другие:
procedure TOborotFrm.FormCreate(Sender: TObject);
begin
// Стартовые значения для дат периода
Date_N.Value:=Now();
Date_K.Value:=Now()+1;
// Подключение компонентов доступа к данным к базе данных
ADOQuery1.Connection:=MainFrm.ADOConnection1;
ADOQuery2.Connection:=MainFrm.ADOConnection1;
ADOCommand1.Connection:=MainFrm.ADOConnection1;
CDS.Connection:=MainFrm.ADOConnection1;
// Назначение таблицы компоненту
CDS.TableName:='Oborot';
end;
procedure TOborotFrm.FormActivate(Sender: TObject);
begin
// Запрос на первичную очистку таблицы
ADOCommand1.CommandText:='DELETE Oborot.* FROM Oborot ';
ADOCommand1.Execute;
end;
procedure TOborotFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:= caFree;
end;
procedure TOborotFrm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// Обработка нажатия клавиши Escape - закрытие формы
case Key of // Start Case
VK_ESCAPE:
begin
close;
end;
else
End; // End case
end;
procedure TOborotFrm.MyOkButton1Click(Sender: TObject);
begin
Close;
end;
А теперь - самое главное - собственно алгоритм вычисления оборотной ведомости, в основу которого положена описанная в уроке 16 формула:
procedure TOborotFrm.CalcOborots(Dn,Dk: TDateTime);
Var
o: Currency; // Свернутый остаток
MyStr: String; // Для хранения строки SQL запроса
begin
// Очистка временной таблицы
ADOCommand1.Execute;
CDS.Active:=True;
CDS.Requery();
// Составление оборотной ведомости
// Заполнение списка счетов и стартовых остатков (для выбранной валюты)
ADOQuery1.Active:=False; // Деактивация запроса
ADOQuery1.SQL.Clear; // Очистка и затем - задание строки SQL
ADOQuery1.SQL.Add('SELECT * FROM Accounts WHERE Val='+IntToStr(MySelect.MySel_IDVal));
ADOQuery1.Active:=True; // Включение запроса
ADOQuery1.First; // Переход на первую запись
While not ADOQuery1.EOF do // Перебор всех значений, пока не будет достигнут конец файла
Begin
CDS.Insert; // Добавление строки во вспомогательную таблицу, далее - заполнение полей
CDS.FieldByName('IDAcc').Value:=ADOQuery1.FieldByName('ID').Value; // ID счета
CDS.FieldByName('NameAcc').Text:=ADOQuery1.FieldByName('Name').Text;// Название счета
o:=ADOQuery1.FieldByName('Ost_D').Value-ADOQuery1.FieldByName('Ost_K').Value; // Вычисление свернутого остатка на момент старта программы
If o>=0
Then
Begin
CDS.FieldByName('OstD_N').Value:=o;
CDS.FieldByName('OstK_N').Value:=0; // Это - ноль
End
else
Begin
CDS.FieldByName('OstD_N').Value:=0; // Это - ноль
CDS.FieldByName('OstK_N').Value:=-o;
End;
// Вычисление остатков на начало по данным до даты Dn
o:=CDS.FieldByName('OstD_N').Value-CDS.FieldByName('OstK_N').Value;
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main WHERE (((Main.MyDate)<#';
MyStr:=MyStr+FormatDateTime('mm/dd/yyyy',Dn);
MyStr:=MyStr+'#) AND ((Main.D)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o+ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0; // Это - ноль
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main WHERE (((Main.MyDate)<#';
MyStr:=MyStr+FormatDateTime('mm/dd/yyyy',Dn);
MyStr:=MyStr+'#) AND ((Main.K)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o-ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0; // Это - ноль
// Запись остатков на начало
If o>=0
Then
Begin
CDS.FieldByName('OstD_N').Value:=o;
CDS.FieldByName('OstK_N').Value:=0;
End
else
Begin
CDS.FieldByName('OstD_N').Value:=0;
CDS.FieldByName('OstK_N').Value:=-o;
End;
// Вычисление и запись оборотов
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main ';
MyStr:=MyStr+'WHERE (((Main.MyDate)>=#'+FormatDateTime('mm/dd/yyyy',Dn)+'#) ';
MyStr:=MyStr+'AND ((Main.MyDate)<#'+FormatDateTime('mm/dd/yyyy',Dk)+'#) ';
MyStr:=MyStr+'AND ((Main.D)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o+ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0;
CDS.FieldByName('ObD').Value:=ADOQuery2.FieldByName('MySumNotNull').Value;
ADOQuery2.Active:=False;
ADOQuery2.SQL.Clear;
MyStr:='SELECT Sum(Main.Summa) AS [MySum], IIf(IsNull([MySum]),0,[MySum]) AS MySumNotNull FROM Main ';
MyStr:=MyStr+'WHERE (((Main.MyDate)>=#'+FormatDateTime('mm/dd/yyyy',Dn)+'#) ';
MyStr:=MyStr+'AND ((Main.MyDate)<#'+FormatDateTime('mm/dd/yyyy',Dk)+'#) ';
MyStr:=MyStr+'AND ((Main.K)=';
MyStr:=MyStr+IntToStr(CDS.FieldByName('IDAcc').value);
MyStr:=MyStr+'))';
ADOQuery2.SQL.Add(MyStr);
ADOQuery2.Active:=True;
if ADOQuery2.RecordCount>0
then
o:=o-ADOQuery2.FieldByName('MySumNotNull').Value
else
o:=0;
CDS.FieldByName('ObK').Value:=ADOQuery2.FieldByName('MySumNotNull').Value;
// Вычисление и запись остатков на конец
If o>=0
Then
Begin
CDS.FieldByName('OstD_K').Value:=o;
CDS.FieldByName('OstK_K').Value:=0;
End
else
Begin
CDS.FieldByName('OstD_K').Value:=0;
CDS.FieldByName('OstK_K').Value:=-o;
End;
CDS.Post;
ADOQuery1.Next;
End;
CDS.First;
end;
Я постарался дать подробные комментарии. Но, если у Вас возникнут вопросы, я жду их по адресу vs259@mail.ru.
Что остается? Остается только подложить вызов этой процедуры под кнопку:
procedure TOborotFrm.Button1Click(Sender: TObject);
begin
CalcOborots(Date_N.Value,Date_K.Value);
end;
Но, смотреть на экране объемный документ не всегда удобно. Поэтому в следующих уроках я планирую рассказать, как экспортировать данные в Excel и как "прикрутить" к программе бумажный отчет.

Ярлыки:
Оборотная ведомость,
Развернутый остаток,
Свернутый остаток,
ADOCommand,
ADOQuery,
Click,
FormActivate,
FormClose,
FormCreate,
FormKeyDown,
Uses,
WindowState
вторник, 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.
Удачи.
А.В.
Подписаться на:
Сообщения (Atom)