скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Hello, MiniProg



Автор: Иванов Петр ака Brodia@a
Специально для Королевства Delphi

Я не знаю, к какой области "Королевства" отнести эту статью. В принципе, данная публикация подготавливалась для раздела "Hello, World".

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

Первоначальная идея была проста, написать несложную программу, исходный текст которой можно было бы использовать как некий шаблон, с реализованной функциональностью, отвечающей наиболее часто выдвигаемым требованиям. Следует помнить о данной публикации то, что она навеяна темой форума "Delphi Kingdom VCL :)".

Желающие могут присоединиться: покритиковать, дополнить, исправить; и если изменения или найденные ошибки будут существенны, то будет написана новая статья.

Начнем, с требований, которым должна соответствовать программа:

 1. Избегать использования компонентов сторонних производителей, стараться написать программу с помощью стандартных, для текущей версии Delphi, функций и процедур.

 2. Исходный текст программы необходимо снабдить системой автоматической проверки корректности программного кода.

 3. Интерфейс - SDI (MDI хорош для приложений вроде Word или Exceel).

 4. Желательно предусмотреть возможность масштабирования размеров окон, а также размеров и положения всех визуальных элементов, расположенных на ней, после изменения размеров экранного шрифта. Размеры окон и визуальных компонентов не должны меняться при изменении разрешения экрана.

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

 6. Программа должна иметь возможность запуска с командной строки, с использованием управляющих ключей.

 7. Программа должна иметь возможность запуска, как в режиме консоли, так и в режиме с графическим интерфейсом.

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

 9. Управляющие ключи командной строки, должны поддерживать, как минимум ключ "?" и/или "help"- вывод краткого пояснения о программе, и подсказки о доступных ключах, в режиме консоли. Ключ "concole" - запуск в режиме консоли. Ключ "nologo" - отключение показа заставки. Ключ "logo " c параметром, определяющим время показа заставки.

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

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

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

Ну что же, первый шаг, создание директории проекта, назовем его MiniProg, в которой расположим поддиректории:

  • DCU - откомпилированные dcu (такова моя привычка :),
  • DOC - поместим текст данной статьи,
  • DUNIT - система автоматического тестирования от SourceForge,
  • IMAGE - для картинок и иконок,
  • SOURCE - исходные тексты самой программы,
  • TEST - исходные тексты тестирующих файлов.

Будем считать, что основная директория, MiniProg, предназначена для размещения в ней откомпилированной программы, файлов конфигурации и языковых настроек, а так же, для откомпилированной тестовой программы.

Создаем проект, главную форму называем просто и незатейливо - FMain. Cохраняем как файл Main.pas в поддиректории SOURCE. Проект сохраняем как MiniProg.dpr, там же :). Открываем меню Project | Options, переходим на страницу Directories/Conditionals, заносим в Output directory и в Unit output directory соответствующие пути. В нашем случай это будут "..\..\MiniProg" и "..\..\MiniProg\DCU". Можно и короче записать, но так нагляднее. Если есть иконка для программы, то устанавливаем её на странице Application, через Load Icon. Создадим новый unit, сохраним под именем Appl.pas. Зачем? Как задел на будущее, будем размещать в нем функции и процедуры, реализующие наши требования.

Теперь начнем выполнять пункт 2 наших требований, т.е. создавать тестирующую программу. В подкаталоге DUNIT расположены некоторые необходимые нам файлы, взятые из оригинального DUNIT , версии от 2002/01/17. И так, создаем новый проект, закрываем Unit1.pas, отказываемся от сохранения, проект назовем, без особой фантазии, testMiniProg.dpr и сохраняем в TEST. Удаляем всё из этого файла и помещаем в него такой код:


program testMiniProg;

uses
  Forms,
  TestFrameWork,
  GUITestRunner;

{$R *.res}

begin
  Application.Initialize;
  GUITestRunner.RunRegisteredTests;
end.

В настройках проекта, на странице Directories/Conditionals, заполняем поля Output directory и Unit output directory, так же, как и у проекта MiniProg. Дополнительно пропишем в Search path поддиректорий SOURCE и DUNIT. Вот теперь, можно создать новый unit с названием (как бы вы думали?) testAppl.pas и следующим содержанием:


unit testAppl;

interface

uses
  TestFramework, SysUtils, Controls, Forms, Appl;

type
  TTestUnitAppl = class(TTestCase)
  published
  end;

implementation

initialization
  TestFramework.RegisterTest(TTestUnitAppl.Suite);
end.

Можно откомпилировать testMiniProg и посмотреть на внешний вид нашей тестирующей программы. В дереве просмотра, с именем Test Hierarchy, будут заноситься наши тесты, серые квадратики, при успешном прохождении теста, будут окрашиваться зеленым цветов, иначе - красным или розовым (цвет может быть и синим). Тесты можно отключать галочками. В окнах, расположенных ниже, можно будет наблюдать сообщение о всякой всячине, в том числе и некоторое пояснение о крахе теста. Да, кстати, тесты запускаются кнопочкой, с изображением зеленого треугольника, но пока он окрашен в серый цвет, так как ни одного реального теста у нас нет. Вот, вкратце и всё, что пока нужно знать о DUNIT. Товарищи, желающие узнать о DUNIT больше, а так же патологические "ХочуВсёЗнайки", могут самостоятельно поискать дополнительную информацию. Хочу только заметить, что данная система проверки является портом с JUNIT, и создавалась для применения в проектах с использованием Xtreem Programming (сокращенно XP). Одной из отличительных особенностей данной методологии является глубокая неприязнь к ведению документации :). Конечно же, возможности DUNIT гораздо шире, чем это будет показано в данном материале.

Попытаемся разобраться с проблемой масштабирования форм. Проведя то, что обычно называется предварительным расследованием; покопавшись в интернет, заглянув в хелп, почитав книги, спросив товарищей (нужное подчеркнуть); выяснилось что, можно принудить форму автоматически масштабировать собственные размеры, а так же размеры и положение размещенных на ней визуальных компонентов, при изменении размера экранного шрифта. Для этого необходимо проверить и если нужно установить свойства формы, в нашем случае FMain, ParentFont = False, Scaled = True, AutoScroll = False и PixelsPerInch равный PixelsPerInch текущего экрана. Данное утверждение верно для форм созданных с помощью Delphi 6.2, для более ранних версий не проверялось. Но, судя по количеству воплей на различных форумах - у некоторых такая проблема была. Впрочем, помнится, еще у М. Канту в "Delphi 2 for Windows95/NT" существовала небольшая глава, освещающая именно такой подход. После рассмотрения исходных кодов VCL Delphi выяснилось, что существует другая проблема, связанная с масштабированием. Дело в том, что свойства Constraints компонентов, к большому сожалению, не масштабируются. Придется заняться этим отдельно, иначе может нарушиться внешний вид формы.
Что делает программа, когда создает форму? Если у формы установлены свойства как было указано выше, то в зависимости от того, отличается PixelsPerInch (сокращенно PPI) формы от PPI экрана или нет, происходит умножение значений местоположения компонентов на "новый" PPI и деление на "старый" PPI (в действительности, конечно, всё сложнее, но на первых порах и такого понимания достаточно). Будем называть эту функцию ScaleValue.
Откроем проект testMiniProg, и откроем в нем файлы testAppl.pas и Appl.pas из поддиректории SOURCE. Теперь самое странное: в testAppl.pas создаем процедуру проверки TestScaleValue, и объявляем её в published свойствах TTestUnitAppl:


unit testAppl;

interface

uses
  TestFramework, SysUtils, Controls, Forms, Appl;

type
  TTestUnitAppl = class(TTestCase)
  published
    procedure TestScaleValue;
  end;

implementation

procedure TTestUnitAppl.TestScaleValue;
var
  Test: integer;
begin
  Test := ScaleValue(120,96,120);
  Check( Test = 96, Format('return wrong %d',[Test]));
end;

initialization
  TestFramework.RegisterTest(TTestUnitAppl.Suite);
end.

Главное действие в этом unit, происходит в теле процедуры TestScaleValue, по вызову функции Check, в которой проходит проверки первого параметра, и если он False, то тест считается неудачным. Второй параметр функции Check - сообщение, в котором можно написать, в краткой форме, всё, что вы думаете об отрицательном результате тестирования :). Почему, при заданных значениях входных параметров, в результате должно получиться именно 96? - можно понять в результате несложных математических преобразований исходной формулы. Менее успешные математики могут проверить на калькуляторе :). Что же, мы создали тестирующую процедуру, которая проверит корректность работы нашей функции, при чем сделает это автоматически, стоит лишь запустить тесты. Следует сказать, что проверяться функция будет при каждом запуске тестовой программы, т.е. если вы впоследствии поменяете текст функции, и сделаете это некорректно, то программа тут же сообщит вам об этом. Еще одним положительным свойством такого тестирования, является то, что в саму программу не вносится ни каких посторонних тестирующих и проверяющих функций. Далее, в файле Appl.pas, создаем саму функцию:


function ScaleValue(Value, NewPPI, OldPPI: integer): integer;
begin
  Result := MulDiv(Value, NewPPI, OldPPI);
end;

Компилируем, запускаем программу, нажимаем на зеленый треугольник - всё зеленое! Замечательно, первый и пока единственный тест пройден. Если кто-то не заметил, то поясню, что сначала была создана тестирующая процедура, проверяющая результат функции, и только потом создавалась сама функция. Несколько необычно, но именно такой порядок рекомендует методология XP. Вообще, если призадуматься, то в этом можно узреть глубокий смысл, который заключен в том, что до создания функции мы ДОЛЖНЫ хорошо себе представлять результат :). Вроде бы тривиальная мысль, но многих ошибок в программах не было бы, если бы кодеры всегда следовали этому правилу. Подход, продемонстрированный выше, просто вынуждает поступать именно так. Другим положительным моментом предварительного создание тестовых функций является то что, в конечном счете, изначально "большие" функции будут разбиты на более мелкие и легко тестируемые, что то же неплохо. Кстати, XP настоятельно рекомендует заниматься рефакторингом, по-простому - переписыванием исходного текста, с целью его улучшения. Правда, в отличие от банального исправления ошибок и внесения уточнений, рефакторить рекомендуется только тогда, когда в этом действительно возникла необходимость. Но вообще, код пишется исключительно в требованиях текущего момента, т.е. даже если вы знаете, что какая то дополнительная функциональность вам обязательно понадобиться в дальнейшем - не прилагайте ни малейшего усилия, для её реализации. На этапе рефакторинга всегда можно вернуться к этому, если конечно понадобиться :).

Очевидно, что нам нужны функции, которые бы возвращали значения PPI как времени создания, так и времени исполнения программы, назовем их RtmPPI и DsgnPPI. Напишем тест. Подумав, решаем, что RtmPPI и DsgnPPI должны быть равны по значениям, если разработка программы и тестирование происходит при одних и тех же режима экрана:


procedure TTestUnitAppl.TestDsgnVsRtmPPI;
begin
  Check( DsgnPPI = RtmPPI, Format('Design time PixelsPerInch not %d DPI',
    [RtmPPI]));
end;

По крайней мере, такой тест напомнит вам, что при тестировании значение DsgnPPI должно быть равно PPI вашего экрана. Один совет, связанный с масштабированием форм - старайтесь создавать все свои формы при одном и том же PPI, это убережет вас от неприятных эффектов в дальнейшем, либо вам придется написать специальный тест, который будет проверять значения PPI всех форм, а это часто очень утомительно :). Кстати, этот тест наводит на мысль о том, что не плохо было бы завести функцию, которая бы сообщала, изменилось PPI или нет, и она нам нужна именно сейчас, что бы включить в тест. Сам текст функций выглядит следующим образом:


function RtmPPI: integer;
begin
  Result := Screen.PixelsPerInch;
end;

function DsgnPPI: integer;
begin
  Result := 120;
end;

function IsChangePPI: boolean;
begin
  Result := DsgnPPI <> RtmPPI;
end;

К сожалению, функция DsgnPPI возвращает результат, просто используя константу, которая выставляется в зависимости от конкретного PPI, используемого при дизайне (у меня это 120, у вас может быть и другое значение). Несмотря на то, что в хелп указано TForm.PixelsPerInch как свойство, хранящее значение времени создания, проверка показала, что это не так. Рассмотрение исходных текстов подтвердило факт изменения значения TForm.PixelsPerInch при масштабирование формы, во время исполнения. Так как простого и надежного решения данной проблемы у меня ПОКА нет, то поступим в соответствии с принципами Экстремального Программирования - "Если есть что-то что можно отложить на завтра - отложите это". Прошу прощение, у адептов XP, за столь вольную трактовку принципа.

Пришло время заняться процедурой, которая будет масштабировать Constraints компонентов. Собственно говоря, это свойство наследуется от TControl, по этому, будем обращаться именно к нему. Подумаем, как тестировать изменение Constraints. Первое, что приходит в голову, это создать специальную тестовую форму. Конечно, такой путь несколько сложноват, однако эта форма, скорее всего, пригодиться и в дальнейшем. Выбираем меню File | New | Form, даем название testForm и сохраняем как testUnit в поддиректории TEST, если Delphi предложит сохранить еще и проект, смело откажитесь. Не забудьте установить свойства формы так, как было описано ранее. Добавьте, в uses Appl. Проверьте, в меню Project | Options, новая форма должна располагаться в Available Forms, то есть не должна создаваться автоматически, при запуске приложения. Создайте в Events формы событие OnClose:


procedure TtestForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

Это заставит удалиться форму из памяти самостоятельно, после закрытия. Не забудьте, выполнить, для testAppl.pas, дополнение через File | Use Unit: Вот, теперь создадим TestChangeConstraints. Что бы легче было тестировать, и избежать неоднозначности, воспользуемся опытом тестирования ScaleValue и зададим размеры формы кратные 120, например 480, после масштабирования должно получиться 384. Так как, отдельные числа используются в unit более чем один раз, то вынесем их в константы.


const
  testOldPPI = 120;
  testNewPPI = 96;
...
procedure TTestUnitAppl.TestChangeConstraints;
var
  OK1, OK2: boolean;
  Size1, Size2: integer;
begin
  OK1 := False;
  OK2 := False;
  Size1 := testOldPPI * 4;
  Size2 := ScaleValue(Size1, testNewPPI, testOldPPI);
  testForm := TtestForm.Create(Application);
  try
    testForm.Constraints.MaxHeight := Size1;
    testForm.Constraints.MinHeight := Size1;
    testForm.Constraints.MaxWidth := 0;
    testForm.Constraints.MinWidth := Size1;
    ChangeConstraints(testForm as TControl, testNewPPI, testOldPPI);
    OK1 := (testForm.Constraints.MaxHeight = Size2) and
      (testForm.Constraints.MinHeight = Size2) and
      (testForm.Constraints.MaxWidth = 0) and
      (testForm.Constraints.MinWidth = Size2);
    ChangeConstraints(testForm as TControl, testOldPPI, testNewPPI);
    OK2 := (testForm.Constraints.MaxHeight = Size1) and
      (testForm.Constraints.MinHeight = Size1) and
      (testForm.Constraints.MaxWidth = 0) and
      (testForm.Constraints.MinWidth = Size1);
  finally
    testForm.Close;
    Check(OK1 and OK2, 'failed test');
  end;
end;

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


procedure ChangeConstraints(Control: TControl; NewPPI, OldPPI: integer);
begin
  with Control.Constraints do
  begin
    if MaxHeight > 0 then MaxHeight := ScaleValue(MaxHeight, NewPPI, OldPPI);
    if MinHeight > 0 then MinHeight := ScaleValue(MinHeight, NewPPI, OldPPI);
    if MaxWidth > 0 then MaxWidth := ScaleValue(MaxWidth, NewPPI, OldPPI);
    if MinWidth > 0 then MinWidth := ScaleValue(MinWidth, NewPPI, OldPPI);
  end;
end;

Запускаем тест - "Шеф!!! Всё пропало!!!" - в чем же дело? А дело в том, что Constraints для минимальных и максимальных значений взаимозависимы. Максимальное значение не может быть меньше минимального и наоборот, и если происходит присвоение некорректного, с этой точки зрения значения, то оно изменяется в нужную сторону. Такое поведение весьма логично, но нас оно не всегда устраивает, по тому что, нам бы хотелось, что бы такое выравнивание сработало после наших изменений. Кстати, вот вам и первый пойманный баг, и довольно хитрый :). Поспешный поиск дихлофоса от Borland, среди методов TControl, напоминавших по духу, что-то вроде DisabledAlign ничего не дал. Пришлось воспользоваться простым дедовским антитараканным средством - типа "тапочек":


procedure ChangeConstraints(Control: TControl; NewPPI, OldPPI: integer);
begin
  with Control.Constraints do
  begin
    if NewPPI > OldPPI then
    begin
      if MaxHeight > 0 then MaxHeight := ScaleValue(MaxHeight, NewPPI, OldPPI);
      if MinHeight > 0 then MinHeight := ScaleValue(MinHeight, NewPPI, OldPPI);
      if MaxWidth > 0 then MaxWidth := ScaleValue(MaxWidth, NewPPI, OldPPI);
      if MinWidth > 0 then MinWidth := ScaleValue(MinWidth, NewPPI, OldPPI);
    end
    else
    begin
      if MinHeight > 0 then MinHeight := ScaleValue(MinHeight, NewPPI, OldPPI);
      if MaxHeight > 0 then MaxHeight := ScaleValue(MaxHeight, NewPPI, OldPPI);
      if MinWidth > 0 then MinWidth := ScaleValue(MinWidth, NewPPI, OldPPI);
      if MaxWidth > 0 then MaxWidth := ScaleValue(MaxWidth, NewPPI, OldPPI);
    end;
  end;
end;

Тест, зеленый цвет, "едем" дальше... Дальше? А дальше, расположим на testForm какие-нибудь визуальные компоненты, ...даааа побольше :). В принципе, TestChangeConstraints показал, что процедура работает успешно, с наследником TForm, но не мешало бы, проверить её и с другими компонентами, хотя бы некоторую их часть (нет у нас такого требования - тестировать VCL). Так как предполагаемый процесс тестирования вполне однообразен, то создадим функцию, которой будем передавать компонент, из числа тех, которые расположены на форме, а возвращать она будет - "да" или "нет".


function TTestUnitAppl.TestScaleControl(Control: TControl): boolean;
var
  OK1, OK2: boolean;
  Size1, Size2: integer;
begin
  OK1 := False;
  OK2 := False;
  Size1 := testOldPPI;
  Size2 := ScaleValue(Size1, testNewPPI, testOldPPI);
  testForm := TtestForm.Create(Application);
  try
    Control.Constraints.MaxHeight := Size1;
    Control.Constraints.MinHeight := 0;
    Control.Constraints.MaxWidth := Size1;
    Control.Constraints.MinWidth := Size1;
    ChangeConstraints(Control, testNewPPI, testOldPPI);
    OK1 := (Control.Constraints.MaxHeight = Size2) and
      (Control.Constraints.MinHeight = 0) and
      (Control.Constraints.MaxWidth = Size2) and
      (Control.Constraints.MinWidth = Size2);
    ChangeConstraints(Control, testOldPPI, testNewPPI);
    OK2 := (Control.Constraints.MaxHeight = Size1) and
      (Control.Constraints.MinHeight = 0) and
      (Control.Constraints.MaxWidth = Size1) and
      (Control.Constraints.MinWidth = Size1);
  finally
    testForm.Close;
    Result := OK1 and OK2;
  end;
end;

Тестовая функция, например, для Label1, будет выглядеть так:


procedure TTestUnitAppl.TestScaleLabel;
begin
  Check(TestScaleControl(testForm.Label1 as TControl), 'failed test ');
end;

Если все тесты проходят успешно, то с определенной долей вероятности можно утверждать, что мы теперь знаем, как настроить форму так, что бы она автоматически масштабировались, по крайней мере в пределах, которые обеспечивает Delphi. Так же сможем масштабировать Constraints отдельно взятого контрола окна, при необходимости. Думаю, сфера использования ChangeConstraints довольно ограниченна, но в большинстве случаев результаты, полученные с помощью таких простых средств - вполне удовлетворительные. Можно было бы разработать функцию, которая бы сама изменяла Constraints у всех элементов формы. Желающие могут попробовать свои силы самостоятельно, не забудьте только прислать пример с тестом, и он будет включен в проект. По моему скромному мнению, решить эту проблему кардинально и качественно, можно лишь на уровне изменения исходного кода VCL. Хотя, "неумение" Constraints корректировать свои значения во время масштабирование окна и не является "официальным" багом но, очень хочется надеяться, что авторы Delphi 7 позаботятся об этом. Конечно, всегда можно создать собственный вариант формы, в котором проблема будет решена, но для данного проекта это будет расцениваться как выход за рамки требований (см. пункт.1). Впрочем, повторюсь, если у кого-то есть возможность исправить - пишите.
И так, мы провели некоторые технологические тесты, и убедились в работоспособности функций и процедур основной программы. Пришло время заняться функциональными тестами, то есть тестами, в которых проводится общая проверка на соответствие наших решений требованиям. Наиболее наблюдательные читатели должны были заметить, что к самой программе мы еще и не прикасались, но уже имеем для неё несколько работоспособных функций :). Проводить функциональное тестирование можно по-разному, и в принципе, лучше всего на рабочем приложении. У нас, его пока нет, кроме того, оценить правильность масштабирования можно на любом примере, ведь от нас не требуется реакция (нажатие кнопки, движение мыши и т.д.). Нам нужно просто посмотреть. Так что, воспользуемся testForm и разместим на ней 3 компонента TLabel. В свойство Caption каждой занесем такой текст "0123456789". У Label2 установим Constraints равными Width, у Label3 минимальное и максимальное значения отличающееся не менее чем на 50%, у Label4 минимальное и максимальное значения отличающееся на 5%.


procedure TTestUnitAppl.TestFuncScale;
begin
  if IsChangePPI then
  begin
    testForm := TtestForm.Create(Application);
    try
      testForm.ShowModal;
    finally
      testForm.Close;
    end;
  end;
  Check(True, 'very strange ');
end;

Тест очень прост, создается и визуализируется окно, рассматривается и закрывается. Процедура выполняется при запуске тестовой программы, если установлено иное значение PPI, чем использовалось при создании. И она всегда завершается успешно, что бы не портить общие "показатели" :). Можно откомпилировать тестовую программу, изменить размер шрифта экрана, перезагрузиться, запустить тест. Естественно, наш тест TestDsgnVsRtmPPI не должен пройти. Зато появиться окно testForm, где можно будет видеть результат масштабирования. Скажу прямо, LabeledEdit меня крайне разочаровал, впрочем, я его всегда подозревал и никогда им непользовался. Зато Label'ы вели себя так как им предписано. Закрываем окно, изменяем шрифт экрана, перезагружаемся, запускаем Delphi. Дальнейшие ухищрения в процессе тестирования, уважаемый читатель, может продолжить и самостоятельно.

Продолжение следует ...

Declaimer aka Отмазка.

Я надеюсь, что люди, привыкшие читать академические труды, или слушать классические оперы, не станут осуждать автора, за его простую и незатейливую песнь кочевника. Что делал - о том и пел.
Исходную партитуру и ноты можно взять здесь.

Любые претензии и предложения принимаются в обсуждение и/или мылом.
Предложения будут рассмотрены, претензии - проигнорированы.
С особым вниманием будут рассмотрены уточнения списка требований и новые тесты.

Все копирайты, если они известны, указаны. Иначе, автор не известен или копирайт утерян.

Проект создан в Delphi6 MiniProg.zip (44.3K)






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook