СТАТЬЯ |
26.09.01 |
Профессиональная разработка приложений с помощью Delphi 5
Часть 3. Оформление приложений для Windows95/98/NT/2000 в Delphi
Сергей Трепалин,
УКЦ Interface Ltd.
КомпьютерПресс #3 2001
Статья была опубликована в КомпьютерПресс (www.cpress.ru)
В настоящей публикации речь пойдет о правилах, которые желательно соблюдать при написании 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('\MainFormData\Shell\Open\Command',True); Reg.WriteString('',ParamStr(0)+' %1'); Reg.CloseKey; Reg.OpenKey('\MainFormData\DefaultIcon',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:\MyDir\MyProject.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-приложениях необходимо выполнить следующую последовательность действий:
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: библиотека программиста». — Прим. ред.).
Дополнительную информацию Вы можете получить в компании Interface Ltd.
Отправить ссылку на страницу по e-mail
Обсудить на форуме Inprise/Borland
Interface Ltd. Отправить E-Mail http://www.interface.ru |
|
Ваши замечания и предложения отправляйте автору По техническим вопросам обращайтесь к вебмастеру Документ опубликован: 26.09.01 |