Професійна розробка програм за допомогою Delphi 5: частина 3, Комерція, Різне, статті

У цій публікації мова піде про правила, які бажано дотримуватися при написанні Windows-додатків, що працюють з документами (повний список цих правил міститься в документах Microsoft, що входять до складу Platform SDK для відповідної версії Windows. – Прим. ред. ). В принципі, ці правила дотримуватися не обов'язково – можна створити цілком працездатні програми, які їм не підкоряються. Проте це ускладнює розуміння користувачами графічного інтерфейсу додатків і ускладнює роботу з ним. Природно, при першій же можливості користувач відмовиться від такого застосування. У цій публікації викладені лише основні розділи, які викликають найбільшу труднощі у програмістів.


Реєстрація розширень файлів і піктограми документа в системному реєстрі


Реєстрація піктограм документів обмовляється Microsoft як обов'язкова умова для додатків, що претендують на отримання логотипу Win95 Compatible. При цьому встановлено, що необхідно реєструвати піктограми розміром і 32 * 32, і 16 * 16 пікселів. На практиці досить використовувати піктограми 32 * 32. Відомий один тип програм, яким реально потрібні піктограми 16 * 16, – це програми, що відображають піктограму зазначеного розміру у правому нижньому кутку панелі задач (tray icon).


У Delphi є модуль Registry, який забезпечує доступ до системного реєстру. Наведений нижче фрагмент коду дозволяє зареєструвати розширення файлів *. mfe:

Uses Registry;

procedure TMainForm.FormCreate;
var
Reg:TRegistry;
begin
Reg:=nil;
try {Register icon}
Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CLASSES_ROOT;
Reg.OpenKey(“.mfe”,True);
Reg.WriteString(“”, “MainFormData”);
Reg.CloseKey;
Reg.OpenKey(“MainFormData”,True);
Reg.WriteString(“”, “My private datafiles”);
Reg.CloseKey;
Reg.OpenKey(“MainFormDataShellOpenCommand”,True);
Reg.WriteString(“”,ParamStr(0)+” %1″);
Reg.CloseKey;
Reg.OpenKey(“MainFormDataDefaultIcon”,True);
Reg.WriteString(“”,ParamStr(0)+”, 1″);
Reg.CloseKey;
Reg.Free;
except
if Assigned(Reg) then Reg.Free;
end;
end;


В принципі, цю процедуру досить викликати один раз при інсталяції програми. Однак згідно з правилами Microsoft додаток при запуску має перевіряти відповідні інші секції системного реєстру і при наявності недоброякісної інформації – виправляти її. Найпростіше це досягається повторної реєстрацією.


Об'єкт TRegistry не має власника, і тому всі маніпуляції з ним повинні проводитися в захищеному блоці. Необхідно використовувати блок try … except … end без повторного порушення виключення оператором raise у секції except … end. Якщо відбувається виключення, то додаток продовжить роботу без повідомлення користувачу будь-якої інформації. Це в даному випадку виправдано, оскільки процедура реєстрації відноситься тільки до сервісу і не впливає на роботу самого додатка. При повторному порушенні вилучення (чи використання блоку try … finally … end), якщо виникне виняткова ситуація, то головна форма не буде створена і додаток не буде запущено. Виняткова ситуація гарантовано виникне при запуску програми в Windows NT, якщо проект був скомпільований в Delphi 3.0 або в більш ранньої версії, а користувач увійшов не під ім'ям системного адміністратора. У Delphi 3.01 цей недолік вже усунуто. Якщо все ж необхідно повідомити користувачеві про проблеми з системним реєстром, то рекомендується використовувати метод типу MessageDlg в секції except … end, але не генерувати виняток.


Даний фрагмент коду створює в системному реєстрі дві секції:. Mfe, яка просто посилається на іншу секцію – MainFormData. Інформаційна рядок 'My private datafiles' буде видно в Windows Explorer при виборі режиму перегляду вмісту каталогів у вигляді таблиці поряд з кожним файлом із зареєстрованим розширенням. Крім того, в цій секції також прописуються піктограма для документа (яка в принципі може збігатися з піктограмою програми, але так краще не робити), а також команда, яку необхідно виконати, якщо користувач двічі клацнув мишею в Windows Explorer на ім'я файлу з зареєстрованим розширенням.


Обидві ці команди вимагають, щоб в реєстрі був прописаний повний шлях до додатка. Можна його прописати відразу ж в явному вигляді, наприклад: C: MyDirMyProject.exe, але так робити не рекомендується. Справа в тому, що користувачі мають звичку перейменовувати каталоги, при цьому шлях до додатка буде загублений. Якщо використовувати результат, що повертається функцією ParamStr (0), – повний шлях і ім'я програми, то при одноразовому запуску з перейменованого каталогу значуща інформація буде відновлена ​​в системному реєстрі.


Крім посилання на файл *. exe або *. dll, для реєстрації піктограми необхідно вказати її індекс. Він починається з нуля – головної піктограми програми. Очевидно, що в ресурсах програми необхідно мати як мінімум дві піктограми: для позначення документів і самого додатка. Це досягається за допомогою створення окремого *. res-файл і включенням директиви {$ R filename.res} в *. pas-файл. Хоча будь-яке додаток має *. res-файл, що співпадає з ім'ям проекту, включати туди другій піктограмі (і взагалі будь-які інші ресурси) абсолютно безглуздо – цей файл повністю переписується при виклику команди Project / Options в середовищі розробки. Зазначу також, що всі піктограми, що завантажуються в форми при використанні властивості Icon, не зберігаються у вигляді зрозумілих Windows ресурсів, і посилатися на них за індексами безглуздо.


Рядок %1 в реєстрації команди, яка буде викликатися при подвійному клацанні на іменах файлів документів, означає підстановку повного шляху і назви файлу документа замість параметра %1. Тому додаток при старті зобов'язана перевіряти результат, що повертається функцією ParamCount. Якщо він більше нуля і ParamStr (1) повертає легальне ім'я файлу, то документ необхідно завантажити автоматично після старту програми. Тут є велика різниця для додатків SDI (Single Document Interface) і MDI (Multiply Document Interface).


У SDI-додатках можна проаналізувати значення ParamCount в обробнику події OnCreate головної форми і там же виконати всі необхідні процедури щодо завантаження документа. У MDI-додатках необхідно виконати наступну послідовність дій:



  1. При запуску програми перевірити, чи працює вже його копія. Найбільш просто в середовищі Windows 95/NT це завдання вирішується за допомогою мьютекса. При наявності працює копії MDI-додаток зобов'язана звернутися до неї для відновлення її на екрані (вона може бути перш мінімізована користувачем) і підняття вікна на верхній рівень (вона може бути перекрита іншими вікнами). Це досягається посилкою повідомлення методом PostMessage, причому параметр типу HWND може бути знайдений викликом методу FindWindow. Після цього програма має закритися без показу головної форми на екрані. Але перед закриттям необхідно проаналізувати ParamCount, ParamStr (1) і при наявності легального файлу документа передати його назву і шлях в працюючу копію. Для передачі даних можна використовувати Clipboard. Нижче наводиться фрагмент коду, що ілюструє сказане:
program OneInst;

uses
Forms,
Windows,
ClipBrd,
UMain in “UMain.pas” {MainForm};

{$R *.RES}

var
HM:THandle=0;

function CheckForInstance:boolean;
var
HW:THandle;
N:integer;
begin
N:=0;
HM:=OpenMutex(MUTEX_ALL_ACCESS,False,”MyMutex”);
Result:=True;
if HM<>0 then begin
HW:=FindWindow(“TMainForm”,”MainForm”);
if HW<>0 then begin
if ParamCount>0 then begin
Clipboard.AsText:=ParamStr(1);
N:=1;
end;
PostMessage(HW,WM_RESTOREMESSAGE,N,0);
end;
Result:=False;
HM:=0;
end else HM:=CreateMutex(nil,False,”MyMutex”);
end;

begin
if CheckForInstance then begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
if HM<>0 then ReleaseMutex(HM);
end;
end.


Константа WM_RESTOREMESSAGE і обробник події визначені в одиниці UMain:

Const
WM_RESTOREMESSAGE=WM_USER+3245;


procedure TMainForm.WMRestoreMessage(var Message:TMessage);
var
S:string;
begin
Application.Restore;
SetForegroundWindow(Handle);
S:=””;
if (Message.wParam<>0)
and Clipboard.HasFormat(CF_TEXT) then begin
S:=Clipboard.AsText;
Clipboard.AsText:=””;
end;
if length (S)> 0 then if FileExists (S) then CreateMDIChild (S);
end;


Якщо працююча копія MDI-програми не було, то, як і у випадку SDI-додатки, одразу після старту необхідно завантажити відповідний документ. Якщо спробувати це зробити в обробнику події OnCreate головної форми, то додаток зупиниться з повідомленням про помилку. Створювати дочірнє MDI-вікно необхідно в обробнику події OnShow.


Таким чином, в результаті реєстрації в системному реєстрі піктограми і команди відкриття документа візуальний інтерфейс виглядає наступним чином:



Обробка повідомлення WM_DROPFILES


Повідомлення WM_DROPFILES виникає, коли користувач відкриває Windows Explorer, зазначає один або кілька файлів і, натиснувши ліву кнопку миші, переміщує її покажчик на будь-яку форму (технологія drag-and-drop). Реалізація цього інтерфейсу починається з виклику методу DragAcceptFiles (Handle, True) в обробнику події OnCreate головної форми і закінчується викликом цього ж методу, але з параметром False в обробнику події OnDestroy. DragAcceptFiles визначена в одиниці ShellAPI. Виклик цього методу (коли ще не додано обробник події WM_DROPFILES) призводить до появи «дозволяє» курсору, коли перетягуються файли з Windows Explorer.


Приклад обробника події WM_DROPFILES (для MDI-приложения) наведено нижче:

procedure TMainForm.WMDropFiles(var Message:TWMDropFiles);
var
HF:THandle;
S,SMessage:string;
C:array[0..MaxPathLength] of char;
I,Count:integer;
begin
HF:=Message.Drop;
Count:=DragQueryFile(HF,$FFFFFFFF,nil,0);
SMessage:= “”;
if Count>0 then for I:=0 to Count-1 do begin
DragQueryFile(HF,I,C,MaxPathLength);
S:=StrPas(C);
if not CreateMDIChild (S) then SMessage: = SMessage + # 13 + # 10 + S;
end;
DragFinish(HF);
if length(SMessage)>0 then
MessageDlg(Format(“Next files can not be loaded: %s’,
[SMessage]),mtError,[mbOK],0);
end;

Метод DragQueryFile з параметром $ FFFFFFFF повертає загальне число файлів, вибране користувачем у Windows Explorer. Цей же метод копіює шлях і ім'я файлу в буфер C при легальному значенні лічильника I . Далі MDI-додаток намагається створити вікно і завантажити вибраний документ. Не рекомендується перевіряти розширення файлу для визначення того, чи містить файл документ потрібного формату. По-перше, користувач може перейменувати файл з документом, а по-друге, в файл з потрібним розширенням він може помістити «сторонні» дані. При невдалої завантаженні документа функція CreateMDIChild не створює дочірнє вікно і повертає False без показу користувачеві можливих помилок. Список файлів, які не можуть бути завантажені (якщо такі є), наводиться в одному діалозі в кінці виконання команди. На закінчення обов'язково повинен викликатися метод DragFinish – він звільняє пам'ять, яку виділив Windows Explorer для збереження імен та шляхів вибраних файлів.


У SDI-додатку даний цикл необхідно зупинити після першого успішного зчитування документа. Якщо раніше вже був відкритий документ і в нього були внесені зміни, то необхідно повідомити про це користувачеві, дозволивши вибрати або збереження старого документа, або відмова від збереження, або ігнорування завантаження нового документа.


Якщо програма працює зі складовими документами і є діалоги для редакції окремих його частин, логічно реалізувати WM_DROPFILES для окремих діалогів. Наприклад, якщо в документ входить растрове графічне зображення і є діалог для його редагування, то розумно дозволити в ньому завантаження *. bmp-файлів.


Все вищесказане про реалізацію WM_DROPFILES абсолютно незастосовно до діалоговим панелям – тільки головна форма програми здатна отримувати повідомлення WM_DROPFILES. Тут дуже до речі згадати, що реалізація ShellAPI базується на технології COM (Component Object Model). OLE-реалізація інтерфейсу drag-and-drop успішно працює і для діалогових панелей. Чудовий приклад і вихідні коди цього інтерфейсу наведені в книзі Тейлора (Don Taylor, Jim Mischel, John Penman, Terence Goggin, John Shemitz. High Performance Delphi 3 Programming. Coriolis Group Book, 1997, російський переклад цієї книги, що вийшов у видавництві «Питер» в 1998 році, відомий під назвою «Delphi 3: Бібліотека програміста ». – Прим. ред.)


Обробка повідомлення WM_GETMINMAXINFO


У Delphi 4 і 5 клас TForm має властивість Constraints типу TSizeConstraints. Використовуючи цю властивість, програміст може задати мінімальну і максимальну ширину і висоту форми. Відповідно, якщо користувач під час виконання програми спробує змінити зазначену ширину або висоту форми, то програма не дозволить зробити цього. Подія WM_GETMINMAXINFO якраз і призначене для того, щоб повідомити системі про можливі діапазонах зміни меж елемента керування. Однак з незрозумілих міркувань властивість Constraints в Delphi 5 використовується не в обробнику події WM_GETMINMAXINFO, а в обробнику події WM_SIZE. Така «крива» реалізація призводить до некоректного поводження форми при зміні її розмірів: користувач може зробити її дуже маленькою або дуже великий, і тільки після відпускання кнопки миші використовується інформація з властивості Constraints, відповідно до якої форма знову змінює свої розміри. Точно так само некоректно реалізовано подія OnConstrainedResize, в якому динамічно можна заповнити структуру TSizeConstraints. У зв'язку з цим в Delphi 5, як і в попередніх версіях Delphi, необхідно створювати обробник повідомлення WM_GETMINMAXINFO.


При обробці цього повідомлення необхідно задати мінімальні і максимальні розміри форми, а також початкові координати верхнього лівого кута. Як правило, даний обробник події використовують тільки для завдання мінімальної ширини і висоти форми. При їх визначенні виходять з того, що всі елементи управління на формі мають бути завжди доступні користувачеві. Відповідно розробник ніколи не повинен створювати форми розміром більше 640 * 480 пікселів (а ще краще – 600 * 450). Не слід брати до уваги можливе збільшення розміру форми при збільшенні розмірів системного шрифту – системні шрифти з великим розміром, як правило, використовуються при високому графічному дозволі екрану.


Типовий приклад обробника WM_GETMINMAXINFO наведено нижче:

procedure TMainForm.WMGetMinMaxInfo (var Message: TWMGetMinMaxInfo);
begin
if csLoading in ComponentState then Exit;
with Message.MinMaxInfo^ do begin
ptMinTrackSize.X:=
ScreenToClient (BitExport.ClientToScreen (Point (BitExport.Width, 0))). X
+200;
ptMinTrackSize.Y:=
ScreenToClient (BitExport.ClientToScreen (Point (0, BitExport.Height))). Y
+2*GetSystemMetrics(SM_CYFRAME)
+GetSystemMetrics(SM_CYCAPTION)+4;
end;
end;

Зверніть увагу на перевірку того, завантажені чи вже всі ресурси (if csLoading in ComponentState). При відсутності такої перевірки виникає виняткова ситуація при старті програми.


Обробник цієї події не дозволить зробити висоту форми менше нижнього краю елемента управління BitExport, а мінімальна ширина форми буде на 200 пікселів більше, ніж правий край цього елементу управління (В даному прикладі на формі розміщується елемент, розміри якого змінюються при зміні розміру форми).


Не можна використовувати координати в пікселях при завданні значень ptMinTrackSize.X і ptMintrackSize.Y. По-перше, при зміні положень елементів управління на формі в процесі розробки будуть потрібні і відповідні виправлення в коді. По-друге, елементи управління можуть змінювати розміри і положення при перенесенні готового програми на комп'ютер з іншим розміром шрифту системного (про це буде розказано в наступному розділі).


І нарешті, рекомендується використовувати системну метрику для визначення величини кордонів некліентской області форми. Використання системної метрики і квітів є вимогою Microsoft. У вищенаведеному прикладі звернення до системної метриці здійснюється викликом функції GetSystemMetrics.Масштабірованіе форми при зміні розміру системного шрифту


При перенесенні програми з одного комп'ютера на інший часто відбуваються небажані спотворення форм, наприклад частина заголовків елементів управління перестає бути видимою, елементи управління перекривають один одного і т.д. Це трапляється при зміні розміру системного шрифту, після чого програма намагається змінити розміри і положення елементів керування з метою розміщення на них написів в тому ж масштабі.


Можна відзначити три способи вирішення цієї проблеми:



  1. Додаток встановлює новий системний шрифт. Від програм такого роду слід по можливості позбуватися, оскільки незрозуміло його поведінку під час запуску другого аналогічного додатка, що знову захоче змінити системний шрифт для себе.

  2. Додаток не змінює позицій і розмірів елементів управління при зміні величини системного шрифту, однак розміри самих елементів управління досить великі, щоб розмістити на них написи при збільшенні розмірів системного шрифту. Форми в таких додатках часто роблять погане враження при малих розмірах системного шрифту. Крім того, якщо користувач встановлює системний шрифт більше стандартного Large Font Windows високого графічного дозволу, написи часто все одно не вміщаються на елементах управління.

  3. Нарешті, у ряді програм величина і розміри елементів управління змінюються пропорційно розміру системного шрифту. Це, на мій погляд, найбільш коректний спосіб масштабування, саме про нього і піде мова нижче.

Форма має дві властивості, які регулюють масштабування: Scaled і PixelsPerInch. Якщо встановити властивість Scaled рівним False, то при зміні системного шрифту форма масштабуватися не буде. Якщо при цьому заздалегідь зробити елементи управління великими, вийде той самий результат, що в пункті 2. Отже, значення властивості Scaled має дорівнювати True.


Незрозуміло, навіщо властивість PixelsPerInch доступно в інспекторі об'єктів. Перед показом форми ця властивість перераховується відповідно до реальним розміром системного шрифту, а його первинне значення, встановлене в інспекторі об'єктів, замінюється результатом цього розрахунку. Але для коректного масштабування форми важливо аналізувати саме перелічене значення.


Якщо форма має «товсті» кордону (її розмір у цьому випадку може змінюватися користувачем під час виконання програми), або якщо формі дозволено мати смуги прокрутки, то розмір клієнтської області форми залишиться таким же, що був на етапі розробки. При цьому частина елементів управління опиниться за межами клієнтської області і користувач змушений буде використовувати смуги прокрутки або ж переміщати межі форми, а це призводить до зайвих операціям.


Тому перед показом форми іноді необхідно писати код для перерахунку її ширини і висоти. Це робиться в обробнику події OnShow. Використання OnShow гарантує, що всі ресурси (в тому числі і розміри елементів управління) вже завантажені.

procedure TDialogForm.FormShow(Sender: TObject);
var
I,XMax,YMax:integer;
PT:TPoint;
begin
if not FFirstRun then Exit;
{Resizing of form}
if (Screen.PixelsPerInch <> 96) and (ComponentCount> 0) then begin
XMax:=0;
YMax:=0;
for I:=0 to ComponentCount-1 do
if Components[I] is TControl
then with Components[I] as TControl do begin
PT: = Self.ScreenToClient (ClientToScreen (Point (Width, Height)));
if PT.X>XMax then XMax:=PT.X;
if PT.Y>YMax then YMax:=PT.Y;
end;
XMax:=XMax+2*GetSystemMetrics(SM_CXDLGFRAME)+4;
YMax:=YMax+2*GetSystemMetrics(SM_CYDLGFRAME)
+GetSystemMetrics(SM_CYCAPTION)+4;
Width:=XMax;
Height:=YMax;
end;
FFirstRun:=False;
end;

Даний код можна використовувати в більшості форм. Винятком є ​​компонент TScrollBox – його «дітей» не треба враховувати для визначення розмірів форми. Класова мінлива FFirstRun використовується для одноразового запуску даного коду.


Якщо елементи керування створюються динамічно (під час виконання, а не на етапі розробки), то при установці меж елементів управління не можна користуватися абсолютними координатами – тільки відносними! Наприклад, якщо елемент керування MyControl повинен розташовуватися під кнопкою BitOK, мати таку ж ширину, а по висоті на 4 піксели не доходити до краю форми, то слід писати код: MyControl.SetBounds (BitOK.Left, BitOK.Top + BitOK.Height +4, BitOK.Width, ClientWidth-BitOK.Top-BitOK.Height-8); і ні в якому разі не писати, наприклад, так: MyControl.SetBounds (10,60,70,180);


Все сказане вище про масштабування працює тільки при збігу шрифтів форми і встановлених на ній елементів управління. Тому не рекомендується визначати для елементів управління окремі шрифти. Якщо ж це необхідно, розміри таких елементів управління повинні бути перераховані в явному вигляді перед показом форми. При цьому для розрахунку масштабного коефіцієнта не можна користуватися властивістю Height (або Size) шрифту. Замість цього потрібно викликати функцію Windows API GetTextMetrics і використовувати поле tmHeight структури TTextMetric.


Створення додатків з невидимою головною формою і використання Tray Icon


У деяких спеціальних типах додатків (наприклад, MIDAS-серверів) потрібно, щоб при його старті головна форма не показувалася на екрані. Спроба змінити властивість Visible головної форми ні до чого хорошого не приводить. Щоб приховати головної форми в момент старту програми необхідно встановити властивість Application.ShowMainForm в False. Це звичайно досягається додаванням одного рядка коду в *. dpr-файл:

begin
Application.Initialize;
Application.ShowMainForm:=False;
Application.CreateForm(TForm1, Form1);
Application.Run;
FNID:TNotifyIconData;
end.

У додатках такого типу не тільки не показується форма, а й на панелі завдань (taskbar) відсутня кнопка для показу або приховування головної форми програми. Тому, якщо все ж на вимогу користувача необхідно показувати головну форму, це досягається приміщенням невеликий піктограми (tray icon) у правому куті панелі задач (taskbar). Подібним чином це реалізовано, наприклад, в Microsoft Volume Control і ряді інших додатків.


Створення tray-піктограми необхідно описувати в конструкторі класу форми. Для цього пошлемося на модуль ShellAPI та секції private класу TForm1 оголосимо змінну:


Далі, скориставшись додаються до Delphi графічним редактором Image Editor, створимо новий ресурсний файл з розширенням *. res, а в нього помістимо піктограму, розміри якої повинні складати 16 * 16 (піктограми інших розмірів не можуть бути поміщені на панель задач!). Назвемо цю піктограму MICON і збережемо файл ресурсів під ім'ям Unit1.res. Подальші зміни виробляємо у файлі Form1.pas. Насамперед вважаємо ресурси з файлу Unit1.res. Для цього додамо команду:

{$R *.RES}

Потім оголосимо в секції Private змінні FHI: Ticon і FNID: TNotifyIconData. Конструктор класу TForm1 перепишемо так:

constructor TForm1.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
FHI:=TIcon.Create;
FHI.Handle:=LoadIcon(HInstance,”MICON”);
FNID.cbSize:=sizeof(FNID);
FNID.Wnd:=Handle;
FNID.uID:=1;
FNID.uCallbackMessage:=WM_ICONNOTIFY;
FNID.HIcon:=FHI.Handle;
FNID.szTip:=”Hidden application”;
FNID.uFlags:=nif_Message or nif_Icon or nif_Tip;
Shell_NotifyIcon(NIM_ADD,@FNID);
end;

Спочатку створюється об'єкт TIcon і з ресурсів завантажується піктограма. Після цього заповнюється структура TNotifyIconData (вона визначена в модулі ShellAPI), при цьому відразу ж здійснюється створення коду для спливаючого меню. Вказується дескриптор вікна, якому слід надсилати повідомлення, – в даному випадку це головна форма програми. Поле FNID.uID містить порядковий номер піктограми в ресурсах. FNID.uCallbackMessage містить повідомлення, яке буде посилатися вікна FNID.Wnd при русі або натисканні кнопки миші над tray-піктограмою. Воно визначено як константа:

const
WM_ICONNOTIFY=WM_USER+1234;

Поле FNID.szTip містить підказку, яка буде спливати при проходженні курсора миші над піктограмою. Поле uFlags визначає тип tray-піктограми – в даному прикладі при подвійному клацанні на піктограмі повинні бути ініційовані посилка повідомлень вікна FNID.Wnd і показ піктограми плюс показ підказки. І нарешті, виклик методу Shell_NotifyIcon використовує заповнену структуру для показу піктограми.


Для роботи спливаючого меню помістимо компонент TPopupMenu на форму Form1. Визначимо два методи: Show (показ форми) і Close (припинення роботи програми). У обробнику події OnClick для рядка меню Show помістимо код зупинки сервісу:

procedure TForm1.Show1Click(Sender: TObject);
begin
Show;
BringWindowToTop(Handle);
end;

і в обробнику події OnClick рядка меню Properties здійснимо показ форми:

procedure TForm1.Close1Click(Sender: TObject);
begin
Application.Terminate;
end;

Але цього ще недостатньо – залишилося викликати спливаюче меню при натисненні правої кнопки миші над tray-піктограмою. Повідомлення WM_ICONNOTIFY буде посилатися головній формі. Створимо обробник подій WM_ICONNOTIFY наступним чином:

procedure TForm1.WMIconNotify(var Message:TMessage);
var
PT:TPoint;
begin
if Message.lParam=WM_LBUTTONDOWN then begin
Show;
BringWindowToTop(Handle);
end else if Message.lParam=WM_RBUTTONDOWN then begin
GetCursorPos(PT);
PopupMenu1.Popup(PT.X,PT.Y);
end;
end;

При натисненні лівої кнопки миші буде показуватися головна форма програми, а за допомогою правої кнопки миші над піктограмою буде викликатися спливаюче меню. Відповідно при виборі меню Show відбувається показ форми, а при виборі меню Close – закриття програми.


Після закінчення роботи програми з панелі завдань необхідно видалити піктограму. Зробити це необхідно обов'язково, інакше після закінчення роботи програми залишилася піктограма спробує звернутися до вже не існуючих вікон. Тому перепишемо деструктор головної форми:

destructor TForm1.Destroy;
begin
FNID.uFlags:=0;
Shell_NotifyIcon(NIM_DELETE,@FNID);
FHI.Free;
inherited Destroy;
end;

У цьому фрагменті коду, крім припинення показу TrayIcon, відбувається звільнення ресурсів.


І нарешті, остання проблема, яку необхідно вирішити при створенні даного додатку, – це його поведінка при натисканні кнопки Close на формі. За замовчуванням при натисканні цієї кнопки додаток закривається. В даному випадку це небажано – закриття програми розумно здійснювати в команді спливаючого меню, як це було описано вище. Форма має обробник події OnClose, де можна змінити змінну Action і тим самим змінити поведінку форми при натисканні кнопки Close. Однак це не відноситься до головної формою додатка – будь-яке значення змінної Action викличе деструктор головної форми і, як наслідок, закриття програми. Для того, щоб заховати головну форму, не викликаючи її деструктора, необхідно переписати подія WM_CLOSE:

procedure TForm1.WMClose(var Message:TMessage);
begin
Message.Result:=0;
Hide;
end;

Зверніть увагу на відсутність оператора Inherited в даному коді. Оброблювач WM_CLOSE викликає деструктор за умовчанням, тому Inherited не можна викликати. Це один з небагатьох випадків, коли не потрібно викликати оброблювач події за замовчуванням.


Інформація про версію


Інформація про версію перш за все використовується для коректної установки додатків з розширюваними ресурсами (наприклад, з DLL – динамічно завантажуваними бібліотеками). Сенс розширюваних ресурсів полягає в тому, що вони можуть використовувати кілька програм, в тому числі створеними і сторонніми виробниками. Оскільки розробники програм такого типу найчастіше нічого один про одного не знають, при створенні дистрибутивів їм необхідно враховувати можливості існування декількох версій даного розширюваного ресурсу. Навіть якщо в даний момент є тільки одна версія, цілком можливо, що виробник розширюваних ресурсів (найчастіше це компанія Microsoft) випустить їх у майбутньому.


Крім того, і в звичайних додатках необхідно використовувати інформацію про версію. На перший погляд, це може здатися дивним – при створенні дистрибутива програміст, природно, включає останню версію свого програмного забезпечення. Однак ніхто не гарантує, що у користувача не залишиться старий дистрибутив: помилково запустивши цей дистрибутив, він позбудеться нової версії програми.


Головний сенс контролю версії: будь-який файл, що містить код (виконуваний файл або бібліотеку спільного), містить в ресурсах інформацію про версію, яка зрозуміла Windows. Це чотири числа, наприклад 1.0.0.0 або 1.1.0.2. Чим більше ці числа, тим новіше версія, причому чим лівіше стоїть число, тим більший у нього пріоритет при визначенні версії. Перед копіюванням такого файлу на диск перевіряється, існує Чи файл з таким ім'ям. У разі позитивної відповіді порівнюються ці числа у вже існуючому і новому файлі. Якщо виявляється, що існуючий файл має ту ж саму або новішу версію, копіювання не виконується. Якщо ж існуючий файл має більш ранню версію, то старий файл переписується новим з дистрибутива. Деякі інсталяційні програми вимагають підтвердження копіювання у користувача і в кінці інсталяції дають йому список замінених файлів.


Є ряд функцій Windows API, які дозволяють виконувати маніпуляції з файлами з урахуванням їх версій. Наприклад, VerInstallFile копіює і розпаковує файл тільки в тому випадку, якщо у користувача відсутня файл з більш новою версією. Зазвичай програмісту не потрібно викликати ці методи, оскільки їх виклик здійснюється автоматично в інсталяційних програмах типу InstallShield. Але якщо програміст сам створює інсталяційне додаток, то без виклику цих методів йому не обійтися.


Для внесення інформації про версію у відкритому проекті необхідно викликати команду меню Project / Options і в постає діалозі вибрати закладку Version Info. Щоб додати інформацію про версію необхідно відзначити опцію Include Version Information in project.


Якщо на даному комп'ютері є підтримка російської мови, то за замовчуванням ставиться значення Language = $ 0419 (Російський). Це, в принципі неправильно, оскільки така інформація про версії може бути прочитана тільки російською версією Windows. Однак дуже багато користувачів мають англійську версію Windows c підтримкою російської мови. Якщо програма не представляє собою будь-яку тільки національну специфіку, в ньому обов'язково повинна бути включена інформація про версії англійською мовою (Language = $ 0409).


Значення всіх прапорів та елементів управління в цьому діалозі очевидні і не вимагають подальших пояснень. Деякі труднощі викликає заповнення текстових полів. Їх значення таке:



На закінчення корисно привести фрагмент коду, який дозволяє зчитувати інформацію про версію з ресурсів. Ця інформація може бути показана, наприклад, в діалозі About:

var
ThisVerComments,ThisVerCompanyName,
ThisVerFileDescription, ThisVerFileVersion, ThisVerInternalName,
ThisVerLegalCopyright, ThisVerLegalTrademarks, ThisVerOriginalFilename,
ThisVerProductVersion, ThisVerProductName, ThisVerSpecialBuild,
ThisVerPrivateBuild:string;
ThisVerFixedFileInfo:TVSFixedFileInfo;

procedure InitializeVersion;
var
VSize:integer;
VHandle:DWORD;
VData:pointer;
PC:pointer;
Len:UINT;
C:array[0..1000] of char;
begin
VData:=nil;
ThisVerComments:=””;
ThisVerCompanyName:=””;
ThisVerFileDescription:=””;
ThisVerFileVersion:=””;
ThisVerInternalName:=””;
ThisVerLegalCopyright:=””;
ThisVerLegalTrademarks:=””;
ThisVerOriginalFilename:=””;
ThisVerProductVersion:=””;
ThisVerProductName:=””;
ThisVerSpecialBuild:=””;
ThisVerPrivateBuild:=””;
{Reading This product version from resource}
GetModuleFileName(HInstance,C,1000);
VSize:=GetFileVersionInfoSize(C,VHandle);
if VSize>0 then begin
GetMem(VData,VSize);
if GetFileVersionInfo(C,VHandle,VSize,VData) then begin
if VerQueryValue (VData, "StringFileInfo 40904E4Comments", PC, Len)
then ThisVerComments:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4CompanyName", PC, Len)
then ThisVerCompanyName:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4FileDescription", PC, Len)
then ThisVerFileDescription:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4FileVersion", PC, Len)
then ThisVerFileVersion:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4InternalName", PC, Len)
then ThisVerInternalName:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4LegalCopyright", PC, Len)
then ThisVerLegalCopyright:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4LegalTrademarks", PC, Len)
then ThisVerLegalTrademarks:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4OriginalFilename", PC, Len)
then ThisVerOriginalFilename:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4ProductVersion", PC, Len)
then ThisVerProductVersion:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4ProductName", PC, Len)
then ThisVerProductName:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4SpecialBuild", PC, Len)
then ThisVerSpecialBuild:=StrPas(PC);
if VerQueryValue (VData, "StringFileInfo 40904E4PrivateBuild", PC, Len)
then ThisVerPrivateBuild:=StrPas(PC);
if VerQueryValue(VData,””,PC,Len)
then ThisVerFixedFileInfo:=PVSFixedFileInfo(PC)^;
end;
end;


Настроювані опції проекту


Деякі опції проекту в Delphi за умовчанням мають значення, призводять до зниження якості створюваних додатків. Перш за все, всі додані форми за замовчуванням стають автоматично створюваними (Auto Created). Це означає, що їхні конструктори викликаються автоматично в момент запуску програми. У серйозних проектах часто налічується більше ста форм, і виклик їх конструкторів у момент старту програми призводить до наступного:



Тому для всіх форм, крім головної і немодальному, які будуть показуватися в єдиному екземплярі, прапор Auto Created повинен бути прибраний. Для цього їх необхідно перенести в список Available. Відповідно їх конструктор і деструктор повинні бути викликані динамічно. Тому замість рядка коду:

Form2.ShowModal;

яка використовується для показу модального діалогу форми з прапором Auto Created, потрібно написати наступний код:

Form2:=nil;
try
Form2:=TForm2.Create(Self);
Form2.ShowModal;
finally
if Assigned(Form2) then Form2.Release;
end;

Така добавка не призводить до «роздування» коду програми, навіть якщо показ діалогів здійснюється із сотень місць. Однак з точки зору споживання ресурсів операційної системи це додаток буде набагато більш економним.


В опціях проекту необхідно також визначити ім'я та шлях до файлу довідки (Application / Help File). Ясно, що якщо вказати ім'я файлу з шляхом, то користувач повинен буде ставити додаток у фіксований каталог, чого допускати не можна. У разі ж відсутності шляху в імені файлу WinHelp здійснює пошук файлу допомоги в поточному каталозі. При цьому якщо поточний каталог не збігається з каталогом, в якому знаходиться *. Hlp-файл, то WinHelp оголошує про його відсутність. Тому не рекомендується вказувати ім'я файлу довідки при розробці проекту. Замість цього виконується наступна послідовність дій:


Application.HelpFile: = ExtractFilePath (ParamStr (0)) + 'myhelp.hlp';

При такого запису користувач може вибирати для інсталяції будь-який каталог і файл довідки буде знайдений завжди, незалежно від поточного каталогу.


Висновок


Для створення повноцінного інтерфейсу необхідно також коректно використовувати меню і панелі інструментів, створити систему допомоги і підказок. Перший пункт не викликає труднощів у досвідчених програмістів. Створення ж сучасної системи підказок – досить складний розділ, і, як правило, програмісти цим не займаються.


Наступний етап у створенні проектів (особливо великих) – це розбиття проекту на окремі модулі. Таке розбиття дозволяє організувати групову роботу над проектом, спростити налагодження, зробити опціонну інсталяцію проекту – коли частина функцій може бути недоступна користувачеві, заощадити ресурси. Про створення окремих модулів – динамічно завантажуваних бібліотек (DLL) – і піде мова в наступній публікації даного циклу.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*