понедельник, 19 марта 2012 г.

Урок 35. Оборотная ведомость. Печать отчета

Если Вы, изучив предыдущие уроки, получили в свое распоряжение файл 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 (два знака после запятой) и подсчитать итоги по страницам (добавить пару бэндов)...

Если не сможете разобраться - пишите (координаты Вы найдете через страницу "Обо мне"), и тогда будем разбираться вместе.

А.В.






пятница, 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, но об этом - несколько позже.

А пока - добавьте в форме OborotFRM Вот такой обработчик:



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, ссылка на неизвестный нам пока компонент, который еще предстоит расположить в главной форме, заголовок документа, подзаголовок.

На этом форму Oborot можно пока закрыть, поскольку все дальнейшие манипуляции будут происходить в форме MainFrm.


Для начала расположите на ней экземпляр компонента TExcelApplication c вкладки Servers, дав ему имя XLApp.


Затем (внимание!) в секции общедоступных объявлений опишите процедуру:



  public
    { Public declarations }
    Procedure ExportTo(p_WorkSheetName: String;  p_PGridEh: TDBGridEh;  p_XLApp: TExcelApplication;
                          p_RepName: String; p_RepSubTitle: String);


А теперь - собственно цель урока - процедура вывода данных:

Procedure TMainFrm.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;



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
















вторник, 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.

Удачи.
А.В.