Есть код:
1. try
2. f := strtofloat('q');
3. finally
4. f := 0;
5. end;
Во второй строке выскакивает ошибка:
EConverError .. with message ‘q’ is not a valid floating point value.
То есть выходит конструкция try finally как бы не работает. В Delphi 7 захожу в Debugger Options и снимал флажок со Stop on Delphi exceptions, а в Embarcadero RAD Studio 2010 не знаю, что с этим делать. (И так не только с этим типом исключений)
Подскажите, как с этим справиться в обоих средах?
Kromster
13.6k12 золотых знаков43 серебряных знака73 бронзовых знака
задан 10 мар 2012 в 13:09
Все работает, не бойся. Я когда только Delphi изучал, тоже думал, что исключительные ситуации не срабатывают. Просто дело в том, что когда компилируешь программу и запускаешь её непосредственно из среды Delphi, то она запускается в режиме отладки, поэтому эксепшены не срабатывают, дебаггер выдает все ошибки программисту. А вот когда запустишь скомпилированный exe-Файл, то все работает!
ответ дан 10 мар 2012 в 13:20
AseNAseN
13.6k13 золотых знаков61 серебряный знак122 бронзовых знака
3
В диалоговом окне дебаггера, которое отображается, когда возникает это исключение, есть чекбокс Ignore this exception type
. Воспользуйтесь этим чекбоксом, если вы не желаете видеть исключения такого типа.
ответ дан 29 фев 2016 в 6:11
kot-da-vincikot-da-vinci
3,6102 золотых знака18 серебряных знаков32 бронзовых знака
2 / 2 / 0 Регистрация: 07.10.2012 Сообщений: 32 |
|
1 |
|
Игнорировать ошибки16.10.2012, 20:13. Показов 5375. Ответов 11
Здравствуйте, можно ли как то игнорировать ошибки которые всплывают в программе?
1 |
157 / 300 / 47 Регистрация: 14.08.2012 Сообщений: 2,578 |
|
16.10.2012, 21:11 |
2 |
Здравствуйте, можно ли как то игнорировать ошибки которые всплывают в программе? вопросы:
0 |
VADIMGz 2 / 2 / 0 Регистрация: 07.10.2012 Сообщений: 32 |
||||||||
16.10.2012, 21:56 [ТС] |
3 |
|||||||
Тут выделено красным
тут не помню где
Это ошибка выскакивает когда ей захочеться, после 10 авторизация, после 30 авторизаций и т.д. вот только что тут вылетела. if Pos (‘Пароль успешно изменен.’, response) <> 0 then
0 |
Nutserus 210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
||||
16.10.2012, 23:35 |
4 |
|||
0 |
2 / 2 / 0 Регистрация: 07.10.2012 Сообщений: 32 |
|
17.10.2012, 01:57 [ТС] |
5 |
[Error] Unit1.pas(74): Declaration expected but ‘TRY’ found
0 |
210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
|
17.10.2012, 12:09 |
6 |
Ну если ты про begin end даже не знаешь, может стоит книжку для начала почитать?
0 |
VADIMGz 2 / 2 / 0 Регистрация: 07.10.2012 Сообщений: 32 |
||||
17.10.2012, 12:54 [ТС] |
7 |
|||
Вот так ставил
0 |
Nutserus 210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
||||
17.10.2012, 13:31 |
8 |
|||
А надо так:
1 |
VADIMGz 2 / 2 / 0 Регистрация: 07.10.2012 Сообщений: 32 |
||||
17.10.2012, 16:58 [ТС] |
9 |
|||
Вот тут пишет ошибку
Socket Error #10054
0 |
210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
|
17.10.2012, 18:06 |
10 |
Так это тебя сервак блокирует за через чур частые обращения, а ошибку ты будешь видеть только в режиме отладки, если exe запустишь то ошибку ты не увидишь.
0 |
Избранный 83 / 83 / 42 Регистрация: 08.06.2012 Сообщений: 174 |
|
17.10.2012, 21:54 |
11 |
каждые 9-10 аккаунтов Сдается мне брут пишешь
0 |
2 / 2 / 0 Регистрация: 07.10.2012 Сообщений: 32 |
|
17.10.2012, 22:56 [ТС] |
12 |
checker Добавлено через 32 секунды
0 |
Is there a way to disable all the dialog boxes when an exception or error occurs(like access violations, indy socket errors, timeouts etc.)? They are thrown sometimes in my program, but these errors aren’t fatal in any way and can be ignored, just the dialog boxes are disturbing. I use Delphi 7.
asked Jun 13, 2012 at 17:53
user1262737user1262737
411 gold badge1 silver badge5 bronze badges
7
If you just don’t wont to show the exception window then go to:
Tools/Options/Debugger Options/Language Exceptions and disable CheckBox Notify on language exceptions. That is walid for Delphi 2010.
(I don’t remember if it is the same CheckBox in Delphi 7).
EDIT:
In some cases the exceptions are unavoidable, specially when we are working with unknown files from internet. So, I believe that your exceptions are handled properly under Indy, just switch-off Notify on language exceptions
answered Jun 13, 2012 at 18:43
GJ.GJ.
10.8k2 gold badges46 silver badges62 bronze badges
1
You can customize Application.OnException event. But IMVHO…
- Application.OnException suits best to log exceptions which you forgot to handle.
- Application.OnException should be used to catch exceptions only when you desperately need performance (in this case you should anticipate broken execution paths).
And:
- Access violations are fatal errors — you have to trace and get rid of all AV’s.
- You can’t hide exception dialogs just by Application.OnException override — you should use try finally/except in right way.
answered Jun 13, 2012 at 18:34
g2mkg2mk
5093 silver badges13 bronze badges
6
Bypass the Application.OnException event. However I would NOT recommend hiding every exception, IMHO that is poor design.
answered Jun 13, 2012 at 18:26
GDFGDF
8305 silver badges11 bronze badges
@ GJ — Delphi 2007 has that check box. But again as was mentioned above, access violation ain’t the kind of exceptions that one should ignore.
answered Jun 14, 2012 at 22:22
magarwalmagarwal
5644 silver badges17 bronze badges
Обработка исключительных ситуаций в DelphiСодержание
Структурная обработка исключительных ситуаций Модель исключительных ситуаций в Delphi Синтаксис обработки исключительных ситуаций Примеры обработки исключительных ситуаций Вызов исключительной ситуации Доступ к экземпляру объекта exception Предопределенные обработчики исключительных ситуаций Исключения, возникающие при работе с базами данных Заключение ОбзорС целью поддержки структурной обработки исключительных ситуаций (exception) в Delphi введены новые расширения языка Pascal. В данной статье будет дано описание того, что из себя представляет такая обработка, почему она полезна, будут приведены соответствующий синтаксис Object Pascal и примеры использования исключительных ситуаций в Delphi. Структурная обработка исключительных ситуацийСтруктурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы «охраняют» фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в «охраняемом» участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния «железа» для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от «железа», не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций. При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась. Такая «пожарная бригада» для обработки ошибок трудоемка, требует написания большого количества кода в котором можно легко ошибиться и который трудно отлаживать. Одна ошибка в коде программы или переприсвоение в цепочке возвращаемых значений может привести к тому, что нельзя будет связать ошибочное состояние с положением дел во внешнем мире. Результатом будет ненормальное поведение программы, потеря данных или ресурсов, или крах системы. Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы «охрану» со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске «защищенного» участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается. Если в A нет подходящего обработчика, то поиск продолжается в более верхнем уровне, и так может идти, пока поиск не достигнет подходящего обработчика ошибок среди используемых по умолчанию обработчиков в RTL. Обработчики ошибок из RTL только показывают сообщение об ошибке и форсированно прекращают выполнение программы. Любая исключительная ситуация, которая осталась необработанной, приведет к прекращению выполнения приложения. Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу. Данная система называется структурной, поскольку обработка ошибок определяется областью «защищенного» кода; такие области могут быть вложенными. Выполнение программы не может перейти на произвольный участок кода; выполнение программы может перейти только на обработчик исключительной ситуации активной программы. Модель исключительных ситуаций в DelphiМодель исключительных ситуаций в Object Pascal является невозобновляемой(non-resumable). При возникновении исключительной ситуации Вы уже не сможете вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая(resumable) модель). Невозобновляемые исключительные ситуации разрушают стек, поскольку они сканируют его в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую. Синтаксис обработки исключительных ситуацийТеперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try. Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:
Первый тип используется для обработки исключительных ситуаций. Его синтаксис: try Statement 1; Statement 2; ... except on Exception1 do Statement; on Exception2 do Statement; ... else Statements; {default exception-handler} end; Для уверенности в том, что ресурсы, занятые вашим приложением, освободятся в любом случае, Вы можете использовать конструкцию второго типа. Код, расположенный в части finally, выполняется в любом случае, даже если возникает исключительная ситуация. Соответствующий синтаксис: try Statement1; Statement2; ... finally Statements; { These statements always execute } end; Примеры обработки исключительных ситуацийНиже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal: type ESampleError = class(Exception); var ErrorCondition: Boolean; procedure C; begin writeln('Enter C'); if (ErrorCondition) then begin writeln('Raising exception in C'); raise ESampleError.Create('Error!'); end; writeln('Exit C'); end; procedure B; begin writeln('enter B'); C; writeln('exit B'); end; procedure A; begin writeln('Enter A'); try writeln('Enter A''s try block'); B; writeln('After B call'); except on ESampleError do writeln('Inside A''s ESampleError handler'); on ESomethingElse do writeln('Inside A''s ESomethingElse handler'); end; writeln('Exit A'); end; begin writeln('begin main'); ErrorCondition := True; A; writeln('end main'); end. При ErrorCondition = True программа выдаст: begin main Enter A Enter A's try block enter B Enter C Raising exception in C Inside A's ESampleError handler Exit A end main Возможно вас удивила декларация типа ‘ESampleError =class’ вместо ‘=object’; это еще одно новое расширение языка. Delphi вводит новую модель объектов, доступную через декларацию типа ‘=class’. Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели. Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError. Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого. Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C. После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln(‘Exit A’)). Конструкция try..except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try..finally. Рассмотрим модифицированную процедуру B: procedure NewB; var P: Pointer; begin writeln('enter B'); GetMem(P, 1000); C; FreeMem(P, 1000); writeln('exit B'); end; Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например: procedure NewB; var P: Pointer; begin writeln('enter NewB'); GetMem(P, 1000); try writeln('enter NewB''s try block'); C; writeln('end of NewB''s try block'); finally writeln('inside NewB''s finally block'); FreeMem(P, 1000); end; writeln('exit NewB'); end; Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом: begin main Enter A Enter A's try block enter NewB enter NewB's try block Enter C Raising exception in C inside NewB's finally block Inside A's ESampleError handler Exit A end main Код в блоке finally выполнится при любой ошибке, возникшей в соответствующем блоке try. Он же выполнится и в том случае, если ошибки не возникло. В любом случае память будет освобождена. Если возникла ошибка, то сначала выполняется блок finally, затем начинается поиск подходящего обработчика. В штатной ситуации, после блока finally программа переходит на следующее предложение после блока. Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory. А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так: procedure NewB; var p,q,r,s: Pointer; begin writeln('enter B'); P := nil; Q := nil; R := nil; S := nil; try writeln('enter B''s try block'); GetMem(P, 1000); GetMem(Q, 1000); GetMem(R, 1000); GetMem(S, 1000); C; writeln('end of B''s try block'); finally writeln('inside B''s finally block'); if P <> nil then FreeMem(P, 1000); if Q <> nil then FreeMem(Q, 1000); if R <> nil then FreeMem(R, 1000); if S <> nil then FreeMem(S, 1000); end; writeln('exit B'); end; Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem. Оба типа конструкции try можно использовать в любом месте, допускается вложенность любой глубины. Исключительную ситуацию можно вызывать внутри обработчика ошибки, конструкцию try можно использовать внутри обработчика исключительной ситуации. Вызов исключительной ситуацииВ процедуре C из примера мы уже могли видеть, как должна поступать программа при обнаружении состояния ошибки — она вызывает исключительную ситуацию: raise ESampleError.Create('Error!'); После ключевого слова raise следует код, аналогичный тому, что используется для создания нового экземпляра класса. Действительно, в момент вызова исключительной ситуации создается экземпляр указанного класса; данный экземпляр существует до момента окончания обработки исключительной ситуации и затем автоматически уничтожается. Вся информация, которую нужно сообщить в обработчик ошибки передается в объект через его конструктор в момент создания. Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help. Доступ к экземпляру объекта exceptionДо сих пор мы рассматривали механизмы защиты кода и ресурсов, логику работы программы в исключительной ситуации. Теперь нужно немного разобраться с тем, как же обрабатывать возникшую ошибку. А точнее, как получить дополнительную информацию о коде ошибки, текст сообщения и т.п. Как уже говорилось, при вызове исключительной ситуации (raise) автоматически создается экземпляр соответствующего класса, который и содержит информацию об ошибке. Весь вопрос в том, как в обработчике данной ситуации получить доступ к этому объекту. Рассмотрим модифицированную процедуру A в нашем примере: procedure NewA; begin writeln('Enter A'); try writeln('Enter A''s try block'); B; writeln('After B call'); except on E: ESampleError do writeln(E.Message); on ESomethingElse do writeln('Inside A''s ESomethingElse handler'); end; writeln('Exit A'); end; Здесь все изменения внесены в строку on ESE: ESampleError do writeln(ESE.Message); Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C. Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject: on ESampleError do writeln(ESampleError(ExceptionObject).Message); Предопределенные обработчики исключительных ситуацийНиже Вы найдете справочную информацию по предопределенным исключениям, необходимую для профессионального программирования в Delphi.
procedure Abort; begin raise EAbort.CreateRes(SOperationAborted) at ReturnAddr; end;
Исключения, возникающие при работе с базами данныхDelphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе IDAPI, реализованной в виде библиотеки Borland Database Engine (BDE), включает ряд обработчиков исключительных ситуаций для регистрации ошибок в компонентах VCL работающим с БД. Дадим краткую характеристику основным из них:
repeat {пока не откроем таблицу или не нажмем кнопку Cancel} try Table1.Active := True; {Пытаемся открыть таблицу} Break; { Если нет ошибки - прерваем цикл} except on EDatabaseError do {Если нажата OK - повторяем попытку открытия Table1} if MessageDlg('Не могу открыть Table1', mtError, [mbOK, mbCancel], 0) <> mrOK then raise; end; until False;
EDBEngineError = class(EDatabaseError) private FErrors: TList; function GetError(Index: Integer): TDBError; function GetErrorCount: Integer; public constructor Create(ErrorCode: DBIResult); destructor Destroy; property ErrorCount: Integer; property Errors[Index: Integer]: TDBError; end; Особенно важны два свойства класса EDBEngineError : Errors ЗаключениеДанный урок должен был дать вам достаточно информации для того, чтобы начать исследование того, как Вы можете использовать систему обработки исключительных ситуаций в вашей программе. Вы, конечно, можете обрабатывать ошибки и без привлечения этой системы; но с ней Вы получите лучшие результаты с меньшими усилиями. Назад | Содержание |
Исключения и взаимодействие с API
На текущий момент мы уже знаем достаточно многое из основ ООП. Однако созда-ние приложений под Windows в среде Delphi не ограничивается применением объ-ектно-ориентированного подхода. В частности, иногда возникают такие ситуации, что приходится обращаться к функциям Windows API напрямую. Кроме того, нам следует рассмотреть обработку ошибок в программах, а так же осветить вопрос некоторых глобальных объектов.
Исключения и их классы
Исключительные ситуации, или исключения (exception) могут возникать по ходу выполнения программы ввиду целого ряда причин. Они могут быть вызваны как ошибками в коде программы (например, при попытке обратиться к объекту, который не был предварительно создан), при вводе пользователем неожидаемых значений (например, строки, которая не может быть приведена к числу), при ошибках работы оборудования и т.д. Любая программа, претендующая на качественную разработку, должна уметь обрабатывать все подобные исключительные ситуации.
При возникновении подобных ошибок в программах, созданных при помощи Delphi, автоматически создается объект — Exception. Класс Exception является базовым для ряда других классов исключений — EMathError, EInvalidOp, EZeroDivide и т.д. (названия всех классов, относящиеся к исключениям, принято начинать не с буквы T, а с буквы E). Он происходит непосредственно от класса TObject и имеет 2 свойства — Message и HelpContext, а так же 8 методов.
Свойство Message имеет тип string и содержит описание исключения. При возникновении ошибки этот текст используется в окне сообщения. Ас войство HelpContext определяет индекс раздела справочного файла, содержащего информацию об ошибке. Если значение этого свойства равно нулю, то оно будет ссылаться на раздел справки родительского объекта.
Что касается методов, то все они представлены различными вариантами метода Create. Сам метод Create для исключений определен следующим образом:
constructor Create(const Msg: string);
Т.е., фактически, создавая исключение, следует сразу же назначить значение его свойству Message при помощи аргумента конструктора. Другой вариант конструктора, CreateHelp, позволяет параллельно назначить значение и для второго свойства:
constructor CreateHelp(const Msg: string; AHelpContext: Integer);
Если в тексте сообщения следует привести какие-либо динамически получаемые данные, то используют вариант конструктора с суффиксом Fmt:
constructor CreateFmt(const Msg: string; const Args: array of const);
constructor CreateFmtHelp (const Msg: string; const Args: array of const; AHelpContext: Integer);
При этом значения, указанные в массиве Args, будут подставлены в строку. Для этого используется функция Format, которой передаются строка и массив в качестве аргументов. Эта функция выполняет подстановку значений массива в места строки, выделенные при помощи символа %. Например, если строка выглядит как «Ошибка в функции %s», а массив определен как «[‘MyFunc’]», то результатом выполнения этой функции будет «Ошибка в функции MyFunc». Соответственно, создание подобного исключения будет выглядеть следующим образом:
constructor CreateFmt('Ошибка в функции %s', ['MyFunc']);
Как уже было отмечено, класс Exception имеет ряд потомков, каждый из которых предназначен для обработки того или иного типа ошибок. Например, для математических ошибок определен класс EMathError. Однако этот класс сам по себе не используется, зато его потомки, среди которых отметим классы EInvalidOp, EOverflow, EZeroDivide, используются для оповещения о таких ситуациях, как неверная операция, переполнение буфера и попытка деления на 0, соответственно.
При возникновении исключительной ситуации создается исключение того или иного вида, на основании чего можно определить, в чем именно кроется проблема.
Вызвать исключение в программе можно и искусственным методом — при помощи ключевого слова raise. Например программа может проверять какой-либо ввод пользователя, и в том случае, если он оказывается не тем, что ожидалось, генерировать исключительную ситуацию:
if password <> 'password' then raise Exception.Create('Неверный пароль!');
Выполнение оператора, указанного после raise, приводит к возникновению исключительной ситуации. После этого дальнейшее выполнение кода процедуры прерывается, равно как и кода, вызвавшего эту процедуру, если вызов был произведен из другой подпрограммы. Перемещение исключения можно рассматривать с точки зрения всплытия, т.е. с места своего возникновения ошибка последовательно «всплывает» сначала к вызвавшей данную процедуру или функцию подпрограмме, от нее — к следующей и т.д., пока не дойдет до уровня выполнения программы, т.е. до глобального объекта Application. На этом, конечном этапе и будет выдано сообщение об ошибке.
ПРИМЕЧАНИЕ
С некоторыми глобальными объектами, в том числе с Application, мы ознакомимся несколько позже в этой же главе.
Если при этом ошибка возникла в основном коде программы (т.е. вызвавший ошибку код был написан в самом файле проекта dpr), то на этом выполнение программы прекратится, о чем будет выдано сообщение (рис. 10.1).
Рис. 10.1. Ошибка приложения
В том же случае, если исключительная ситуация произошла в каком-либо модуле, то программа продолжит свою работу, ожидая дальнейших действий пользователя. Однако некоторые данные при этом могут оказаться утерянными (например, функция не вернет значения), или же может оказаться невыполненным какой-либо иной важный код, скажем, создающий глобальные объекты, сохраняющий информацию и т.д. Все это говорит о том, что исключительные ситуации следует обрабатывать.
Обработка исключений
Для обработки исключительных ситуаций в Delphi используются специальные операторы — try…except и try…finally. Эти операторы являются своего рода ловушками для исключительных ситуаций и позволяют разработчику приложения предусмотреть код, обрабатывающий возникшие исключения. Тем самым можно на любом этапе перехватить дальнейшее всплытие ошибки.
При помощи оператора try…except выполняет перехват ошибки, как правило, с целью ее подавления. Он имеет следующий синтаксис:
try
<потенциально вызывающий исключения код>
except
[ on <Класс исключения> do <оператор>; ]
end;
В том случае, если между except и end не писать никакого кода, то исключительная ситуация будет просто подавлена. Однако такое подавление чаще всего не является достаточным условием, поскольку оно не несет никакой информации ни пользователю, ни самой программе. Например, если так подавить ошибку с неверным паролем (а из-за подавления никакого сообщения выдано не будет), то пользователь такой программы может лишь догадываться, почему после того, как он сообщил пароль, ничего не происходит. В данном случае было бы правильным все-таки сообщить о том, что пароль введен не верно. Для этого используют вложенную секцию on…do:
try
if password <> 'password' then raise Exception.Create('Неверный пароль!');
except
on E: Exception do ShowMessage(E.Message);
end;
На сей раз в случае возникновения исключения пользователь получит уведомление о том, что же произошло. Для этого мы создали объект E, которому автоматически присваивается значение ошибки, и использовали его для вывода информации о ней. Дальнейшее выполнение программы в данном случае будет продолжено, поскольку после окончания блока try…end исключение более не существует.
На самом деле, использование такого объекта может быть необязательным, если детальная информация об ошибке не представляется необходимой. В таком случае можно использовать следующий блок обработки исключения:
try
if password <> 'password' then raise Exception.Create('Неверный пароль!');
except
on Exception do ShowMessage('ОШИБКА!');
end;
Что касается блоков обработки, то их может быть несколько, каждый — для своего класса исключения:
try
a:=b*c/d;
except
on EZeroDivide do ShowMessage('Делить на 0 нельзя');
on EOverflow do ShowMessage('Слишком большое число');
on EMathError do ShowMessage('Математическая ошибка');;
end;
Здесь мы определили 3 блока, и в случае возникновения той или иной исключительной ситуации, будет выдано то или иное сообщение. Этим данная часть оператора напоминает оператор case, для которого, как мы помним, существовал вариант «для остальных случаев» — else. Имеется такая возможность и здесь:
try
a:=b*c/d;
except
on EZeroDivide do ShowMessage('Делить на 0 нельзя');
on EOverflow do ShowMessage('Слишком большое число');
on EMathError do ShowMessage('Математическая ошибка');
else
ShowMessage('Общая ошибка');
end;
Наконец, если тип ошибки не имеет никакого значения, то можно оставить только общий обработчик, для чего не требуется даже ключевого слова else:
try
a:=b*c/d;
except
ShowMessage('Общая ошибка');
end;
Важно лишь отметить, что все эти блоки выполняются только тогда, когда возникает исключительная ситуация. При этом, если после ключевого слова try расположено несколько операторов, и исключение возникает в одном из них, то все последующие выполнены не будут. Вместе с тем, случаются ситуации, когда имеется код, который следует выполнить в любом случае, без оглядки на то, что случится перед этим. В таких случаях используют другой оператор - try…finally, и требующий обязательного выполнения код помещают в часть после finally. Типичным примером использования такой конструкции можно считать уничтожение объектов или иные операции освобождения памяти, а так же закрытия файлов и т.д. Например, при работе с файлами всегда следует использовать try…finally для закрытия файла:
try
Rewrite(F);
writeln(F,s);
finally
CloseFile(F);
end;
В данном случае, если даже произойдет ошибка, связанная с доступом к файлу — т.е. если его не удастся открыть (например, если диск защищен от записи), или же записать в него информацию (нет места на диске), закрыт он будет в любом случае, что предотвратит возможные дальнейшие ошибки. При этом само исключение подавлено не будет, т.е. сообщение об ошибке будет выведено и дальнейшее выполнение подпрограммы (но уже после блока finally…end) будет прервано.
Но оба подхода можно комбинировать. Например, в данном случае блок try…finally можно вложить в блок try…except:
try
AssignFile(F);
try
Rewrite(F);
writeln(F,s);
finally
CloseFile(F);
end;
except
on E: Exception do ShowMessage(E.Message);
end;
Кроме этого, в Delphi допускается вкладывать однотипные обработчики ошибок друг в друга, например, один блок try…except может быть вложен в другой.
Глобальные объекты
При создании Windows-приложений нередко возникает необходимость в управлении программой в целом как отдельным объектом. Для этих целей в Delphi предусмотрен специальный объект — Application класса TApplication, представляющий программу в целом. Его использование можно увидеть в любом файле проекта VCL-приложения. Чтобы увидеть это, достаточно создать новое приложение и открыть файл проекта, для откытия которого можно воспользоваться списком модулей, вызываемого кнопкой View Unit (можно так же через главное меню — View ‘ Units, или при помощи сочетания горячих клавиш Ctrl+F12). По умолчанию он имеет название Project1, и его стандартный код имеет вид, приведенный в листинге 10.1.
Листинг 10.1. Заготовка кода для VCL-приложения
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Уже по приведенному в листинге коду мы можем познакомиться с 3 основными методами этого объекта — Initialize, CreateForm и Run. Первый производит подготовительную работу, т.е. фактически, создает объект приложения. Метод CreateForm используется для создания окон приложения, а метод Run производит фактический запуск программы на выполнение. Среди других методов приложения моно отметить такие, как Minimize и Restore, служащие, соответственно, для сворачивания программы на панель задач и для ее восстановления, а так же метод BringToFront, который выводит окно на верхнюю поверхность рабочего стола. Метод Terminate используется для прекращения работы программы (он вызывается автоматически, когда закрывается главное окно приложения). Еще 4 метода — HelpCommand, HelpContext, HelpJump и HelpKeyword — предназначены для работы со справочными файлами.
При работе приложения, в случае обработки больших массивов данных, возникают случаи, когда программа не только не реагирует на действия пользователя, но даже не может выполнить обновление собственного окна. Для того, чтобы предотвратить подобные ситуации, используют специальный метод — ProcessMessages, который предписывает приложению обработать накопившуюся очередь сообщений.
Среди свойств приложения, прежде всего, следует отметить такие, как Title, Icon и HelpFile. Свойство Title определяет заголовок программы, т.е. то, что вы видите на панели задач. Свойство Icon определяет значок («иконку») программы. Ну а свойство HelpFile связывает приложение с файлом справочной информации. Все эти свойства можно определить как программно, написав соответствующий код, так и при помощи окна свойств проекта (Project > Options), на закладке Application (рис. 10.2).
Рис. 10.2. Установка параметров приложения в окне свойств проекта
Если установить в диалоге Project Options новые значения и нажать на кнопку OK, то внесенные изменения для свойств Title и HelpFile отобразятся в коде программы. Что касается значка программы, то он хранится в отдельном файле ресурсов (res), который присоединяется к приложению в процессе компиляции, для чего используется директива «{$R *.res}».
Поскольку любой визуальный компонент может отображать всплывающую текстовую подсказку, то для объекта Application предусмотрен ряд свойств, управляющих видом и выводом таких подсказок. В частности, цвет определяют при помощи свойства HintColor, задержку перед появлением после наведения на компонент мышки — при помощи HintPause, а время его отображения — свойством HintHidePause.
Некоторые свойства приложения доступны только во время выполнения. Среди них можно выделить свойство ExeName, содержащее информацию об имени самого исполняемого файла, включая полный путь к нему.
Помимо Application, при запуске приложения создается еще один глобальный объект, представляющий экранную среду — Screen. При помощи этого объекта можно получить информацию о разрешение экрана, установить вид курсора мыши для приложения, или узнать количество его окон. Основные свойства класса TScreen приведены в таблице 10.1.
Таблица 10.1. Основные свойства TScreen
Свойство | Тип | Описание |
---|---|---|
ActiveControl | TWinControl | Указывает, какой элемент управления в данный момент имеет фокус ввода |
ActiveForm | TForm | Указывает, какое окно активно в данный момент |
Cursor | TCursor | Определяет вид указателя курсора мышки для приложения |
Cursors | array of HCursor | Список всех курсоров, доступных для приложения |
Fonts | TStrings | Список названий всех шрифтов, доступных для вывода на экран |
FormCount | Integer | Указывает на число окон (форм), созданных приложением |
Forms | array of TForm | Список всех окон, созданных приложением |
Height | Integer | Указывает на вертикальное разрешение экрана |
HintFont | TFont | Определяет шрифт для всплывающих подсказок |
IconFont | TFont | Определяет шрифт для подписей к значкам в диалогах выбора файлов |
MenuFont | TFont | Определяет шрифт для меню |
Width | Integer | Указывает на горизонтальное разрешение экрана |
WorkAreaHeight | Integer | Указывает на высоту рабочего стола Windows |
WorkAreaLeft | Integer | Указывает на координаты левого угла рабочего стола |
WorkAreaRect | Integer | Указывает на координаты прямоугольника, образующего рабочий стол |
WorkAreaTop | Integer | Указывает на координаты верхнего угла рабочего стола |
WorkAreaWidth | Integer | Указывает на ширину рабочего стола |
Использовать объекты Screen и Application можно как в главном модуле программы (файле проекта), так и в модулях отдельных форм. При использовании в главном мо-дуле обычно устанавливают глобальные параметры, например, вид всплывающих подсказок. В частности, можно определить довольно-таки экзотический вид всплы-вающих подсказок, дополнив программу следующими строками:
Screen.HintFont.Color:=$00408080; // цвет шрифта
Screen.HintFont.Size:=14; // размер шрифта
Application.HintColor:=$0080FF80; // цвет фона
Application.HintPause:=1000; // задержка перед появлением 1 секунда
Application.HintHidePause:=2000; // время показа 2 секунды
Если вставить этот код в dpr-файл перед обращением к методу Application.Run, то можно будет убедиться, что через секунду после наведения курсора на окно запу-щенного приложения будет появляться всплывающая подсказка с крупным коричне-вым текстом на зеленом фоне. Разумеется, при этом для окна приложения следует установить значения свойства ShowHint в true, и написать какой-либо текст для свой-ства Hint. Впрочем, это можно сделать не только через инспектор объекта в процессе разработки приложения, но и программно, поместив соответствующий код после создания формы. В результате мы получим код, приведенный в листинге 10.2.
Листинг 10.2. Использование объектов Application и Screen
program app_scr;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Form1.Hint:='Ну и подсказочка!';
Form1.ShowHint:=true;
Screen.HintFont.Color:=$00408080;
Screen.HintFont.Size:=14;
Application.HintColor:=$0080FF80;
Application.HintPause:=1000;
Application.HintHidePause:=2000;
Application.Run;
end.
Здесь же можно установить и такие параметры, как заголовок программы, используя свойство Title объекта Application:
Application.Title:='Super Hint!';
Кроме того, можно поэкспериментировать с такими свойствами объекта Screen, как Height и WorkAreaHeight, причем для вывода информации можно использовать заго-ловок главного окна:
Form1.Caption:='Экран '+IntToStr(Screen.Height)+', рабочий стол '+ IntToStr(Screen.WorkAreaHeight);
В данном случае в строку Uses потребуется дописать модуль SysUtils, поскольку ис-пользованная здесь функция IntToStr расположена именно в этом модуле. Оконча-тельный вариант программы можно найти в каталоге DemoPart2Global.
Работа с INI-файлами
При разработке приложений часто встает вопрос о том, где и как хранить информа-цию, связанную с его настройками. Нередко для этих целей используются специаль-ные INI-файлы, которые хранят в себе информацию, разбитую по логическим груп-пам в виде «ключ-значение». В Delphi имеется класс, обеспечивающий простую ра-боту с такими файлами — TIniFile. Чтобы приложение могло получить доступ к этому классу, в секцию используемых модулей следует добавить inifiles.
Имя файла, ассоциированного с объектом типа TIniFile, задается непосредственно при создании экземпляра этого класса, в конструкторе Create:
var MyIni: TIniFile;
...
TIniFile.Create('myfile.ini');
Впоследствии можно узнать, какой файл ассоциирован с данным объектом при по-мощи его свойства FileName, однако изменить его уже не получится. Вместе с тем, у TIniFile имеется свыше 20 методов, при помощи которых можно считывать, прове-рять и изменять содержимое INI-файла. Все они приведены в таблице 10.2.
Таблица 10.2. Методы класса TIniFile
Метод | Принимаемые параметры | Описание |
---|---|---|
DeleteKey | const Section, Ident: String | Удаляет указанный ключ из INI файла |
EraseSection | const Section: String | Удаляет содержимое указанной секции в INI файле |
ReadSection | const Section: String; Strings: TStrings | Считывает имена всех ключей в указанной секции и заносит их в список строк |
ReadSections | Strings: TStrings | Считывает названия всех секций в файле и заносит их в список строк |
ReadSectionValues | const Section: String; Strings: TStrings | Считывает все значения в указанной секции и заносит их в список строк |
ReadString | const Section, Ident, Default: String | Считывает и возвращает значение-строку из указанного ключа |
WriteString | const Section, Ident, Value: String | Записывает значение-строку в указанный ключ |
ReadBool | const Section, Ident: String; Default: Boolean | Считывает и возвращает булево значение из указанного ключа |
ReadDate | const Section, Ident: String; Default: TDateTime | Считывает и возвращает значение-дату из указанного ключа |
ReadDateTime | const Section, Ident: String; Default: TDateTime | Считывает и возвращает значение-дату и время из указанного ключа |
ReadFloat | const Section, Ident: String; Default: Double | Считывает и возвращает значение-вещественное число из указанного ключа |
ReadInteger | const Section, Ident: String; Default: Longint | Считывает и возвращает значение-целое число из указанного ключа |
ReadTime | const Section, Ident: String; Default: TDateTime | Считывает и возвращает значение-время из указанного ключа |
SectionExists | const Section: String | Проверяет INI файл на наличие указанной секции |
WriteBool | const Section, Ident: String; Value: Boolean | Записывает булево значение в указанный ключ |
WriteDate | const Section, Ident: String; Value: TDateTime | Записывает значение-дату в указанный ключ |
WriteDateTime | const Section, Ident: String; Value: TDateTime | Записывает значение-дату и время в указанный ключ |
WriteFloat | const Section, Ident: String; Value: Double | Записывает значение-вещественное число в указанный ключ |
WriteInteger | const Section, Ident: String; Value: Longint | Записывает значение-целое в указанный ключ |
WriteTime | const Section, Ident: String; Value: TDateTime | Записывает значение-время в указанный ключ |
ValueExists | const Section, Ident: String | Проверяет INI файл на наличие указанного ключа в определенной секции |
Таким образом, можно без каких-либо дополнительных накладных расходов (с точки зрения написания собственного кода), создавать и считывать стандартные INI-файлы. Например, мы можем создать приложение, которое сможет «запоминать» введенную информацию и отображать ее при следующем запуске. В принципе, мы уже делали нечто подобное еще при создании программы «угадывания чисел», рассмотренной в первой части. Однако тогда мы лишь последовательно записывали в файл пару строк, а затем таким же образом их считывали. Но если бы нам требовалось сохранить большее количество значений, то мы столкнулись бы с трудностями такого рода, как невозможность идентифицировать то или иное значение при просмотре файла. Кроме того, пришлось бы постоянно держать в уме, какая по строка что должна хранить. Использование INI-файлов решает эту задачу.
Для примера возьмем консольное приложение, которое будет последовательно спрашивать различную информацию у пользователя, а затем сохранит ее в указанном файле. При следующем запуске она сможет считать этот файл и вывести информацию из него на экран. Основное тело программы при этом может получиться примерно таким, как показано в листинге 10.3.
Листинг 10.3. Название листинга
program myini;
{$APPTYPE CONSOLE}
uses
SysUtils, IniFiles;
var
ans: Char;
fn: string;
begin
write('Load data from an INI file? [Y/N]');
readln(ans);
if (ans='Y') or (ans='y') then begin
write('Please input file name: ');
readln(fn);
fn:='c:'+fn+'.ini';
if FileExists(fn) then begin
ShowData(fn);
end else begin
writeln('File not found and will be created.');
FillData(fn);
end;
end else begin
write('Please input file name to save data: ');
readln(fn);
fn:='c:'+fn+'.ini';
FillData(fn);
end;
readln(fn);
end.
Прежде всего, наша программа интересуется, хочет ли пользователь просмотреть информацию из уже существующего файла, или нет, и если хочет, то запрашивает имя файла. Здесь мы подразумеваем, что пользователь будет вводить только имя файла, без пути и расширения, которые добавляются автоматически. Затем стандартная функция FileExists проверяет получившийся файл на существование, после чего либо выводит его содержимое при помощи процедуры ShowDate (которую нам еще предстоит создать), либо выводит сообщение о том, что файл не найден, но будет создан. После этого программа обращается к процедуре FillData, которая так же будет нами написана для ввода информации и сохранения ее в INI-файле. Эта же функция будет вызвана и в том случае, если пользователь изначально откажется от вывода информации, в таком случае программа предварительно запросит имя файла для дальнейшего сохранения.
Теперь, когда основа программы готова, можно определиться, какие данные мы хотим хранить, и какой для этого понадобится формат файла. Допустим, мы хотим сохранить информацию 2-х категорий: персональную и рабочую. В таком случае наш INI файл будет состоять из 2 секций, скажем, Userdata и Jobdata. В первой секции сохраним имя (Name) и возраст (Age), а во второй — должность (Title) и оклад (Salary). Процедура, отвечающая за вывод информации, получится достаточно простой — в ней достаточно создать INI-файл с указанным именем и последовательно считывать информацию, попутно выводя ее на экран. Например, для строкового значения мы получим следующий код:
writeln('Name...... '+IniF.ReadString('Userdata','Name','Anonymous'));
Если же речь идет о числовом значении, то нам придется предварительно преобразовать его в строку:
writeln('Age....... '+IntToStr(IniF.ReadInteger('Userdata','Age',0)));
Несколько сложнее получится код процедуры для записи файла, что, впрочем, связано не с самой записью данных, а в том, что их предварительно следует получить от пользователя. Поэтому там, где мы при выводе обходились одной строкой кода, для ввода понадобится целых 3, а так же переменная для хранения вводимого значения:
write('Name: ');
readln(s);
IniF.WriteString('Userdata','Name',s);
Подобный код потребуется выполнить для каждого поля данных, при этом нам понадобятся 3 различных переменных для хранения данных 3 типов (дважды — строк, и по разу целое и вещественное числа). Предварительно следует не забыть создать переменную типа TIniFile, и вывести пояснительный текст, а к завершению работы процедуры освободить память, занимаемую более не нужной переменной. Последнее условие следует выполнить и в процедуре ShowData. В итоге мы получим код, приведенный в листинге 10.4.
Листинг 10.4. Процедуры сохранения и считывания INI-файлов
procedure FillData(fn: string);
var
IniF: TIniFile;
s: string;
i: integer;
f: double;
begin
IniF:=TIniFile.Create(fn);
writeln('Please fill a form...');
write('Name: ');
readln(s);
IniF.WriteString('Userdata','Name',s);
write('Age: ');
readln(i);
IniF.WriteInteger('Userdata','Age',i);
write('Position: ');
readln(s);
IniF.WriteString('Jobdata','Title',s);
write('Salary: ');
readln(f);
IniF.WriteFloat('Jobdata','Salary',f);
IniF.Free;
end;
procedure ShowData(fn: string);
var
IniF: TIniFile;
begin
IniF:=TIniFile.Create(fn);
writeln('Name...... '+IniF.ReadString('Userdata','Name','Anonymous'));
writeln('Age....... '+IntToStr(IniF.ReadInteger('Userdata','Age',0)));
writeln('Position.. '+IniF.ReadString('Jobdata','Title','Unemployed'));
writeln('Salary.... '+FloatToStrF(IniF.ReadFloat('Jobdata','Salary',0.00),ffFixed,6,2));
IniF.Free;
end;
С полным исходным кодом программы можно ознакомиться в примере, расположенном в каталоге DemoPart2IniFiles.
Работа с реестром Windows
Файлы INI и класс TIniFiles — достаточно удобный способ хранения различной настроечной информации. Тем не менее, начиная с Windows 95, появилось централизованное хранилище для настроек системы и всех установленных программ — реестр (Registry). При разработке приложений в Delphi удобнее всего работать с реестром, используя класс TRegistry. Чтобы включить объявление этого класса, следует указать модуль registry в списке uses.
Реестр Windows имеет несколько ключевых разделов, в чем можно убедиться, открыв имеющуюся в Windows программу редактирования реестра (regedit). В частности это разделы HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_USERS, HKEY_LOCAL_MACHINE и HKEY_CURRENT_CONFIG. Чтобы приступить к работе с реестром из программы, требуется указать один из разделов. Делается это при помощи свойства RootKey:
var Reg: TRegistry;
...
Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CURRENT_USER;
Далее в ход идут методы класса TRegistry. В частности, за выбор раздела реестра, из которого надо будет считывать данные, используется метод OpenKeyReadOnly. В качестве аргумента ему передается адрес раздела реестра, например:
Reg.OpenKeyReadOnly('SOFTWAREMySoftTestApp');
Если указанный раздел существует, и к нему может быть обеспечен доступ, то обращение к данному методу вернет истину. Если же раздела может не существовать, или если требуется открыть раздел на запись, то используют метод OpenKey:
Reg.OpenKey('SOFTWAREMySoftTestApp',true);
Для него в качестве 2-го параметра указывают булево значение, которое указывает на то, должен ли указанный раздел быть создан, если его не существует. В результате выполнения приведенного кода раздел, при необходимости, будет создан и открыть на чтение и запись. Если же требуется только создать новый раздел, то используют метод CreateKey:
Reg.CreateKey('SOFTWAREMySoftTestApp');
Для удаления раздела используют метод DeleteKey, а для проверки указанного раздела на существование — KeyExists. Подобно методу CreateKey, эти методы так же принимают адрес раздела и возвращают ложь или истину в зависимости от результата операции.
Если же требуется выполнить проверку на наличие значения в текущем открытом разделе, то используют метод ValueExists, которому в качестве аргумента передают имя значения.
Что касается записи и считывания значений, то, подобно классу TIniFile, для TRegistry определен ряд методов для взаимодействия с данными различных типов, причем для реестра к типам Boolean, String, Double, Integer и даты-времени, добавляется еще и Currency. Соответственно, мы имеем 8 пар методов для этих целей.
Для примера рассмотрим приложение, состоящее из единственного окна, которое будет «запоминать» свои размеры и расположение на экране. Для этого создадим новое VCL-приложение (File ‘ New ‘ Application), щелкнем сначала по его форме (Form1), а затем — по окну инспектора объекта (Object Inspector). В нем выберем закладку Events (события), найдем событие OnClose и дважды щелкнем по строке напротив. В результате мы получим заготовку для процедуры TForm1.FormClose, в которую нам надо будет добавить объявление переменной для реестра:
var
Reg: TRegistry;
Затем в теле функции напишем следующие строки:
Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CURRENT_USER;
Reg.OpenKey('SOFTWAREMySoftTestApp',true);
Reg.WriteInteger('left',Form1.Left);
Reg.WriteInteger('top',Form1.Top);
Reg.WriteInteger('height',Form1.Height);
Reg.WriteInteger('width',Form1.Width);
Reg.Free;
Вначале мы создаем экземпляр класса, затем выбираем корневой раздел, после чего открываем ключ на запись (он будет создан при необходимости), и последовательно заносим в него пространственные координаты окна. В завершение работы этой процедуры мы экземпляр класса удаляется из памяти за ненадобностью.
Теперь рассмотрим считывание из реестра, для чего создадим процедуру, обрабатывающую событие создания окна, для чего в инспекторе объекта найдем событие OnCreate и сделаем двойной щелчок напротив него. В получившейся процедуре нам так же понадобится сначала объявить переменную Reg, затем создать экземпляр класса и установить корневой раздел. Затем следует открыть раздел на чтение, причем если это окажется невозможным (а при первом запуске так и будет, поскольку раздел будет создан только после выхода из программы), то считывать ничего не потребуется. Поэтому задействуем условный оператор:
if Reg.OpenKeyReadOnly('SOFTWAREMySoftTestApp') then begin
...
end;
После этого остается считать все нужные данные из реестра, присваивая хранящиеся в них значения соответствующим свойствам Form1. Например, для высоты и ширины мы получим:
Form1.Height:=Reg.ReadInteger('height');
Form1.Width:=Reg.ReadInteger('width');
Вместе с тем, было бы полезным все-таки проверять наличие запрашиваемых значений в реестре, чтобы избежать возникновения исключительных ситуаций. Для этого всякий раз надо будет проверять ключ на существование:
if Reg.ValueExists('width') then Form1.Width:=Reg.ReadInteger('width');
В результате код этого модуля программы получит приблизительно такой вид, как показано в листинге 10.5.
Листинг 10.5. Сохранение координат и размеров окна в реестре
unit Unit1;
interface
uses
Windows, Forms, Registry;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Reg: TRegistry;
begin
Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CURRENT_USER;
if Reg.OpenKeyReadOnly('SOFTWAREMySoftTestApp') then begin
if Reg.ValueExists('left') then
Form1.Left:=Reg.ReadInteger('left');
if Reg.ValueExists('top') then
Form1.Top:=Reg.ReadInteger('top');
if Reg.ValueExists('height') then
Form1.Height:=Reg.ReadInteger('height');
if Reg.ValueExists('width') then
Form1.Width:=Reg.ReadInteger('width');
end;
Reg.Free;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
Reg: TRegistry;
begin
Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CURRENT_USER;
Reg.OpenKey('SOFTWAREMySoftTestApp',true);
Reg.WriteInteger('left',Form1.Left);
Reg.WriteInteger('top',Form1.Top);
Reg.WriteInteger('height',Form1.Height);
Reg.WriteInteger('width',Form1.Width);
Reg.Free;
end;
end.
С исходным кодом приложения так же можно ознакомится, посмотрев его в каталоге DemoPart2Registry.
Процедуры и функции стандартных диалогов
В Delphi предусмотрено несколько процедур и функций, предназначенных для вывода простых диалоговых окон. В частности, процедура ShowMessage и функция MessageDlg позволяют вывести сообщение, а функции InputBox и InputQuery отображают окно для ввода информации.
Простейшим вариантом вывода сообщения является использование процедуры ShowMessage. Она отображает переданную ей в качестве аргумента строку на простом диалоговом окне с единственной кнопкой OK. Типичный пример использования этой процедуры — информирование пользователя о выполнении той или иной части программы:
ShowMessage('Формат диска C: завершен');
Кроме самой процедуры ShowMessage, имеются 2 других варианта — ShowMessagePos и ShowMessageFmt. Первый позволяет вывести диалоговое окно в определенном месте, что достигается путем указания координат по горизонтали и вертикали:
ShowMessagePos('Формат диска C: завершен',100,200);
Второй позволяет вывести отформатированную строку, используя обращение к функции Format, как и в случае с конструктором исключений. Таким образом, для вывода сообщения с переменной частью предпочтительно использовать именно этот вариант процедуры:
ShowMessageFmt('Формат диска %s завершен',['C:']);
Все варианты процедуры ShowMessage выводят окно с единственной кнопкой OK, при этом, разумеется, никакого значения не возвращается. В том же случае, если сообщение выводится для того, чтобы запросить у пользователя подтверждения на то или иное действие, то нам, во-первых, потребуется функция — чтобы получить вариант ответа, а так же возможность указать возможные варианты. Все это мы имеем в лице функции MessageDlg, которая имеет следующее определение:
function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint): Word;
Здесь сразу же требуется прояснить 2 момента: тип диалога и тип кнопок. За тип диалога отвкечает 2-й параметр, который имеет тип TMsgDlgType и может принимать одно из следующих значений:
- mtWarning — диалог типа «предупреждение», имеет заголовок «Warning» и рисунок, изображающий восклицательный знак на фоне желтого треугольника;
- mtError — диалог типа «ошибка», имеет заголовок «Error» и изображение косого креста в красном круге;
- mtInformation — диалог типа «информация», имеет заголовок «Information» и значок со стилизованной буквой «i» в синих тонах;
- mtConfirmation — диалог типа «подтверждение», имеет заголовок «Confirm» и рисунок с зеленым вопросительным знаком;
- mtCustom — диалог произвольного типа, имеет заголовок, соответствующий имени выполняемого файла и не содержит изображения.
ПРИМЕЧАНИЕ
Внешний вид изображений, символизирующих диалог того или иного типа, периодически претерпевает некоторые изменения, в зависимости от версии Delphi.
Следующий параметр, имеющий перечисляемый тип TMsgDlgButtons, позволяет указать, какие кнопки должны быть расположены на диалоговом окне. Всего предусмотрено 11 вариантов кнопок, среди них предусмотрены такие, как OK, Cancel, Yes, No и т.д. При этом каждая такая кнопка (кроме Help), будучи нажатой пользователем, закрывает окно, а функция возвращает значение, соответствующее нажатой кнопке. Все варианты кнопок и возвращаемые ими значения, приведены в таблице 10.3.
Таблица 10.3. Варианты кнопок и значения, возвращаемые при их нажатии
Значение | Описание | Возвращаемый результат |
---|---|---|
mbYes | Кнопка с надписью «Yes» (да) | mrYes |
mbNo | Кнопка с надписью «No» (нет) | mrNo |
mbOK | Кнопка с надписью «OK» | mrOk |
mbCancel | Кнопка с надписью «Cancel» (отмена) | mrCancel |
mbAbort | Кнопка с надписью «Abort» (прервать) | mrAbort |
mbRetry | Кнопка с надписью «Retry» (повторить) | mrRetry |
mbIgnore | Кнопка с надписью «Ignore» (игнорировать) | meIgnore |
mbAll | Кнопка с надписью «All» (все) | mrAll |
mbNoToAll | Кнопка с надписью «No to All» (нет для всех) | mrNoToAll |
mbYesToAll | Кнопка с надписью «Yes to All» (да для всех) | mrYesToAll |
mbHelp | Кнопка с надписью «Help» (справка) | — |
Следует оговориться, что все возвращаемые значения, на самом деле, являются целыми числами, что видно по определению функции. Но поскольку запомнить, что, к примеру, возвращаемое значение для OK — это 1, а для Yes — 6, весьма проблематично, то на практике вместо них используются константы, которые как раз и были приведены в таблице 10.3.
Что касается вариантов использования этой функции, то оно сводится к тому, что пользователю выводится какое-либо сообщение, предусматривающее возможность того или иного ответного действия:
MessageDlg('Ошибка чтения с диска. Продолжить?', mtError, [mbRetry, mbAbort], 0);
Поскольку эта функция возвращает то или иное значение, то ее использование часто сопровождается условным оператором:
if MessageDlg('Форматировать диск C:?',mtConfirmation,[mbYes,mbNo],0) = mrYes then FormatDriveCProc();
Другой вариант, для случая с множественными вариантами ответа — использование совместно с оператором-переключателем:
case MessageDlg('Файл изменен. Сохранить перед выходом?', mtWarning, [mbYes, mbNo, mbCancel], 0) of
mrYes: begin SaveFileProc(); Close; end;
mrNo: Close;
mrCancel: exit;
end;
Подобно процедуре ShowMessage, для функции MessageDlg так же предусмотрен вариант с позиционированным выводом окна. Такой вариант этой функции называется MessageDlgPos. Ее отличие от MessageDlg состоит в том, что к списку аргументов добавлено еще 2 параметра, отвечающих за расположение окна. Такой вариант используется, например, при поиске с заменой в текстовых редакторах:
MessageDlgPos('Заменить это вхождение?', mtConfirmation, [mbYes, mbNo], 0, X, Y);
Все рассмотренные нами подпрограммы применяются для вывода сообщений. Что касается ввода, то для этих целей, как уже отмечалось, используют функции InputBox и InputQuery. Обе они выводят окно, позволяющее пользователю ввести какое-либо значение — число или строку. Различие между ними состоит лишь в том, что InputBox возвращает непосредственно результат (в виде строки), а InputQuery — истину или ложь, в зависимости от того, нажмет пользователь OK или Cancel. При этом само значение возвращается в качестве одного из параметров. В итоге мы имеем следующий синтаксис для этих функций:
function InputBox(const ACaption, APrompt, ADefault: string): string;
function InputQuery(const ACaption, APrompt: string; var Value: string): Boolean;
Таким образом, какую из функций лучше использовать в данный момент, зависит от контекста применения. Например, если надо просто получить какое-либо значение от пользователя, то можно использовать функцию InputBox:
UserName := InputBox('Запрос','Введите ваше имя','анонимно');
В данном случае последний параметр функции будет использован в качестве значения по умолчанию (рис. 10.3).
Рис. 10.3. Диалоговое окно функции InputBox
Если же в зависимости от того, введет или нет пользователь новое значение, должна быть выполнена та или иная ветвь алгоритма, то предпочтительнее использовать функцию InputQurey:
if InputQurey('Курс доллара ','Введите новый курс',NewCur) then UpdatePrc();
Помимо приведенных здесь процедур и функций, в VCL имеется ряд иных подпрограмм, использующих диалоговые окна, включая такие, как диалог выбора каталога или файла. Но поскольку их использование сопряжено с некоторыми неудобствами, в частности, им приходится передавать большое число параметров, то на практике для тех же целей чаще используют компоненты. Например, функция PromptForFileName используется для вывода диалога сохранения или открытия файла. Но более типичным (и удобным!) вариантом обращения к таким диалогам является использование таких стандартных компонент VCL, как TOpenDialog и TSaveDialog, с которыми мы познакомимся в следующей части этой книги.
Обработка сообщений и Windows API
Как ни широк охват VCL, иногда все-таки возникает потребность в обращении к функциям Windows напрямую. Например, для того же самого вывода окна с текстовым сообщением можно использовать собственную функцию Windows API — MessageBox:
MessageBox(0, 'Текст сообщения', 'Заголовок', MB_OK);
Необходимость использования функций Windows API может быть вызвана, например, соображениями компактности исполняемого файла: использование диалогов Delphi автоматически подразумевает использование целого рада модулей, необходимых для оконного интерфейса. Если же в самой программе такие модули (например, forms) не задействуются, то их включение в исполняемый код только ради диалога не является хорошей идеей.
В то же время, обращение к функциям Windows API может быть вызвано, например, необходимостью перехвата непредусмотренных в Delphi сообщений.
ПРИМЕЧАНИЕ
Еще одной темой, важной для дальнейшего изучения программирования в Windows вообще и в среде Delphi в частности, является концепция событийного программирования. Дело в том, что хотя ОС Windows, в отличие от Delphi, и не является объектной средой, подход к организации взаимодействия приложений (как с пользователем, так и с системой), основан на одном и том же, а именно — на событиях.
Как мы уже знаем, для событий в VCL используются обработчики событий. Но важно знать, что каждое событие порождает сообщение. Таким образом, отслеживая поступающие сообщения и отсылая собственные, мы можем действовать в обход ограничений VCL.
Для отправки сообщений чаще всего используют функции SendMessage и PostMessage. Обе они выполняют отправку сообщения конкретному окну, разница заключается лишь в том, что SendMessage ожидает ответа от получившего сообщение обработчика, а PostMessage возвращает ответ немедленно. Ценность этих функций состоит в том, что в отличие от средств, предоставляемых VCL, они могут взаимодействовать не только в рамках одного приложения, но и между совершенно разными программами и даже устройствами.
Хотя детальное ознакомления с работой Windows API явно не вписывается в рамки данной книги (не забываем, что Delphi была создана как раз для того, чтобы скрыть сложную и неуклюжую API Windows), отметим все-таки некоторые связанные с ней аспекты. Прежде всего, это касается типов данных. Хотя ранние версии Windows были написаны на Pascal, со временем Microsoft перешла на использование C и C++, поэтому типы данных в представлении Windows несколько отличаются от таковых в Delphi. Прежде всего, это касается строк: при работе с Windows напрямую следует использовать не обычные, а C-строки. В Object Pascal для этого предусмотрен специальный тип данных — PChar, а так же функции для преобразования строк одного вида в другой. Так, для преобразования Pascal-строк в C-строки используют функцию StrPCopy, а для обратного преобразования — функцию StrPas.
var
a: PChar;
s: string;
...
s:='Строка';
new(a); // для С-строк следует предварительно выделять память
StrPCopy(a,s); // содержимое Pascal-строки s скопировано в C-строку a
s:=StrPas(a);
Другие типы данных, часто используемые при работе с API — целые числа, булевы значения и указатели. В таких случаях можно использовать стандартные для Object Pascal типы данных, а к нужному виду они, при необходимости, будут приводиться автоматически.
СОВЕТ
В поставку Delphi включена документация по Windows API. Ссылки на файлы вы найдете в разделе MS SDK Help Files, вложенном в раздел Help программной группы Delphi в меню кнопки пуск. Наибольший интерес с точки зрения изучения функций API представляет собой файл Win32 Programmer’s reference.
Что касается VCL, то в Delphi все же имеется специальный компонент, который может отлавливать все сообщения, адресуемые приложению. Для этого существует компонент AppEvents, который принимает все сообщения, адресованные объекту Application. Среди событий, отслеживаемых компонентом AppEvents, выделим OnMessage — именно это событие происходит, когда приложение получает сообщение от Windows или иной программы. Кроме того, ряд компонент, на самом деле, являются оболочкой для вызова тех или иных функций Windows. Впрочем, о компонентах Delphi будет рассказано в следующей части этой книги.
« Черчение, рисование и печать
|
Работа с VCL в среде Delphi »
Техподдержка / Связаться с нами
Copyright © 1999-2020 SNK. Все права защищены.
При использовании материалов с сайта ссылка на источник обязательна.