Showmessage error delphi

Как осуществляется вывод в окно сообщения Delphi? Окно сообщений применяется для того, чтобы привлечь внимание пользователей. Используя окно сообщений, программа может оповестить программиста об ошибке первичных данных или сделать запрос о подтверждении выполнения некоторой необратимой операции (удалить файл, очистить содержимое и др.). В Delphi для вывода на экран окна сообщения зарезервирована процедура ShowMessage (либо выполняющая

Как осуществляется вывод в окно сообщения Delphi? Окно сообщений применяется для того, чтобы привлечь внимание пользователей. Используя окно сообщений, программа может оповестить программиста об ошибке первичных данных или сделать запрос о подтверждении выполнения некоторой необратимой операции (удалить файл, очистить содержимое и др.).

В Delphi для вывода на экран окна сообщения зарезервирована процедура ShowMessage (либо выполняющая те же операции функция MessageDlg Delphi). Остановимся подробнее на них.

Процедура ShowMessage Delphi:

Применение процедуры ShowMessage позволяет вывести на экран окно с необходимым текстом и кнопкой подтверждения OK. Процедура ShowMessage Delphi организована следующим образом:

Процедура ShowMessage Delphi

откуда Сообщение представляет собой текст, впоследствии выведенный в диалоговом окне.

Пример 1. Представим иллюстрацию окна сообщения, которое получено вследствие выполнения следующей инструкции:

Процедура ShowMessage Delphi

Вывод в окно сообщения Delphi

Заметка. Заголовок окна сообщения, которое выводится при помощи процедуры ShowMessage, содержит название приложения, задающееся на специальной вкладке Application в окне Project Options. В случае, когда названия приложения не указано, заголовок будет включать в себя имя исполняемого файла.

Функция MessageDlg Delphi:

При помощи более универсальной процедуры MessageDlg возможно размещение в окне сообщения одного из стандартных значков, например, «Внимание», а также возможно задание количества и типа командных кнопок и обозначить, какую именно кнопку щелкнул программист. На рисунке ниже представлена иллюстрация окна, выведенного вследствие выполнения следующей инструкции:

Функция MessageDlg Delphi

Функция MessageDlg Delphi

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

откуда:

  • Сообщение представляет собой некоторый текст;
  • Тип определяет тип сообщения, которое включает в себя информационное, предупреждающее или сообщение о критической ошибке. Определенному типу ставится в соответствие некоторый значок. Именованная константа задает тип сообщения (табл. ниже).

окно сообщения

  • Кнопки выступают в качестве списка кнопок, которые отображаются в окне сообщения. В состав данного списка входят именованные константы, разделенные запятыми (табл. ниже), при этом весь список заключен в квадратных скобках. К примеру, для появления в окне сообщения кнопок OK и Cancel необходимо представить список Кнопки как [mbOk,mbCansel]. Помимо указанных в таблиц выше констант возможно применение и таких констант, как mbAbortRetryIgnore, а также mbYesNoCansel и др. Как правило, данные константы чаще используются в комбинациях командных кнопок диалоговых окон.

  • КонтекстСправки означает параметр, обозначающий раздел справочной системы, который впоследствии выведется на экран при нажатии пользователем на клавишу F1. В случае отсутствия справочной системы, параметр КонтекстСправки должен быть приравнен нулю.

Значение функции MessageDlg определяет, на какую из командных кнопок щелкнул пользователь при работе с программой (табл. ниже).


Похожие записи:

  • Процедуры Delphi
  • Вывод в поле диалогового окна Delphi
  • Ошибки открытия файлов Delphi
  • Операции со строками Delphi
  • Оператор goto Delphi

kravam

быдлокодер

1722 / 909 / 106

Регистрация: 04.06.2008

Сообщений: 5,644

1

10.01.2012, 18:39. Показов 11277. Ответов 8

Метки нет (Все метки)


Друзья! Ковыряясь с исходником чужой программы, я разведал, что выполняется вот этот код:

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
procedure TStringList.Delete(Index: Integer);
begin
 //А вот это вставил я
 ShowMessage ('Я здесь');
  if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index);
  Changing;
  Finalize(FList^[Index]);
  Dec(FCount);
  if Index < FCount then
    System.Move(FList^[Index + 1], FList^[Index],
      (FCount - Index) * SizeOf(TStringItem));
  Changed;
end;

ага, а для пущего контроля я вставил в него дианостическую надпись, компильнул без ошибок и без предупреждений и запустил, в надежде увидеть это сообщение, почему? Уж не решает же за меня компилятор, что мне надо, а что не надо!

Так, ладно, я трассировал даже и вот я дотрассировался до строки

Delphi
1
begin

Чё должно прозойти по F7 (в моём случае 2 раза на F7, ибо там коммент)? А должно вывестись диагностическое сообщение. Не выводится, ЧЯДНТ?



0



6044 / 2159 / 753

Регистрация: 10.12.2010

Сообщений: 6,007

Записей в блоге: 3

10.01.2012, 23:11

2

Возможно, что модуль Classes.pas не был откомпилирован, а был использован Classes.dcu



1



kravam

быдлокодер

1722 / 909 / 106

Регистрация: 04.06.2008

Сообщений: 5,644

13.01.2012, 20:24

 [ТС]

3

А как мне сделать так, чтобы этот модуль компилился вместе со всем проектом? Отдельно компилить не предлагать, я пробовал из командной строки, получил:

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
E:BorlandDelphi7Bin>dcc32.exe classes.pas
Borland Delphi Version 15.0
Copyright (c) 1983,2002 Borland Software Corporation
classes.pas(3737) Warning: Symbol 'Added' is deprecated
classes.pas(3922) Warning: Symbol 'Added' is deprecated
classes.pas(3926) Warning: Symbol 'Deleting' is deprecated
classes.pas(3934) Warning: Symbol 'Added' is deprecated
classes.pas(3935) Warning: Symbol 'Deleting' is deprecated
classes.pas(4620) Error: Undeclared identifier: 'ShowMessage'
classes.pas(5092) Warning: Symbol 'Win32Check' is specific to a platform
classes.pas(5259) Warning: Symbol 'HeapAllocFlags' is specific to a platform
classes.pas(5261) Warning: Symbol 'HeapAllocFlags' is specific to a platform
classes.pas(11106)

(я вставлял в classes.pas вызов ShowMessage)
Да вообще каждый раз муторно отдельно компилить этот модуль, лучше жать на F9. Моего ума хватило чтобы исключить слово Classes из списка uses в модуле main.pas и потом проект-Ю добавать в проект-> Classes.pas, после чего получил кучу предупрежждений и одну ошибку:
[Ошибка] classes.pas(4620): Undeclared identifier: ‘ShowMessage’

Всё, хорош экспериментов, а то взорвётся у меня компьютер. Надеюсь на указания.



0



Andyc

206 / 183 / 104

Регистрация: 10.01.2011

Сообщений: 302

13.01.2012, 20:41

4

Цитата
Сообщение от kravam
Посмотреть сообщение

[Ошибка] classes.pas(4620): Undeclared identifier: ‘ShowMessage’

В classes.pas добавить

Delphi
1
uses Dialogs;



0



быдлокодер

1722 / 909 / 106

Регистрация: 04.06.2008

Сообщений: 5,644

13.01.2012, 21:18

 [ТС]

5

НЕ получается, ошибка,
Circular unit reference to ‘Classes’

И то верно, вот файл Dialog.pas
uses WinUtils, Windows, Messages, SysUtils, CommDlg, Printers, Classes, Graphics, Controls, Forms, StdCtrls;



0



206 / 183 / 104

Регистрация: 10.01.2011

Сообщений: 302

13.01.2012, 21:31

6

Можно подключить DLL и вызвать диалог непосредственно из WinAPI.

Добавлено через 4 минуты
И да, можно попробовать подключить модуль в блоке Implementation, а не в interface



0



быдлокодер

1722 / 909 / 106

Регистрация: 04.06.2008

Сообщений: 5,644

13.01.2012, 22:06

 [ТС]

7

Мне точно надо как вызвать простой MessageBox из Classes.pas, а то я щас напробовался, вообще ничё не работает, ни с Classes.pas, ни с Classes.dcu, щас вообще всё придётся сносить и переустанавливать, блин, если IDE не даёт править исходники, зачем они вообще идут в комплекте?



0



6044 / 2159 / 753

Регистрация: 10.12.2010

Сообщений: 6,007

Записей в блоге: 3

14.01.2012, 00:28

8

Попробуйте следующее: создайте папку и туда поместите вашу модифицированную версию файла. В вашем случае Classes.pas. Затем Tools->Environment options->Library path и вот путь к созданной вами папке пометите в самое начало списка. Может сработает.



0



13091 / 5872 / 1706

Регистрация: 19.09.2009

Сообщений: 8,808

14.01.2012, 00:49

9

kravam, то что подвергаются правкам библиотечные модули из дистрибутива — с этим надо быть осторожным. Вообще-то этого желательно избегать. Если уж надо остановить процесс выполнения на каких-то строках в таких модулях — лучше там поставить точку останова и нужные значения проверять с помощью возможностей отладчика. Что это работало, надо в опциях компилятора поставить галочку: «Use Debug DCUs».



0



Новый «раздел» статей в блоге: показываем как можно применять возможности отладчика на практике.

На форуме человек задал вопрос: «почему не работает ShowMessage». Код в вопросе был такой:

library Project2;

uses
  Vcl.Dialogs;

begin
  ShowMessage('Test');
end.

При загрузке этой библиотеки через LoadLibrary мы должны увидеть сообщение:

Но этого не происходит, ShowMessage просто тихо ничего не делает.

(На самом деле, автор вопроса спрашивал «почему не выполняется код между begin/end». Он также сообщил некоторые интересные детали. В частности, загрузка DLL из другого приложения показывает сообщение. Также, если показ сообщения вставить в любую экспортируемую функцию, то оно будет показано в обоих случаях)

Итак, как же нам решить это загадочное дело?

Для начала нужно создать т.н. тестовый проект. В нашем случае мы создадим новую DLL, впишем в неё код выше. Также мы создадим новое приложение VCL Forms, добавим одну кнопку и напишем такой обработчик:

procedure TForm1.Button1Click(Sender: TObject);
begin
  LoadLibrary('Project2.dll');
end;

Запустим приложение вне отладчика через Ctrl + Shift + F9 (Run / Run Without Debugging) и нажмём на кнопку. Нам нужно убедится, что, действительно, ничего не происходит. Т.е. нам нужно воспроизвести поведение.

Если мы не видим проблемного поведения (т.е. сообщение будет всё же показано), значит, мы не смогли воспроизвести проблему. Возможно, поведение программы меняется под отладчиком? Пробуем запустить под отладчиком. Возможно, дело в версии Delphi? Пробуем другую версию Delphi. Возможно, дело в версии Windows? Пробуем другую версию ОС. И так далее. Мы подбираем условия окружения при которых мы надёжно воспроизводим ошибку.

(Забегая вперёд, скажу, что для воспроизведения проблемы нужна Delphi 2007 и выше, Windows Vista и выше)

Как только мы воспроизвели «плохое» поведение под отладчиком — нужно настроить проекты для отладки. Для этого откроем Project / Options каждого проекта (exe и DLL) и включим (если они не включены) следующие опции:

  • Compiling Stack Frames
  • Compiling Range Checking
  • Compiling все опции в категории Debugging
  • Compiling Use Debug DCUs
  • Linking Debug Information (старое название: TD32 Debug Info)
  • Linking Include remote debug symbols
  • Linking Map file = Detailed

и выключим опцию Compiling Optimization.

Конечно, не все эти опции нужно включать для нашего примера, но тут, что называется, «лишним не будет». Главные опции для нашего случая это Compiling Use Debug DCUs — т.к. мы собираемся отлаживать код RTL/VCL (ShowMessage) и Linking Debug Information — т.к. мы будем отлаживать DLL и EXE.

Кроме того, на вкладке Delphi Compiler мы сбросим Output Directory и Unit Output Directory в «.» (без кавычек) — что приведёт к выводу всех файлов в ту же папку где лежат исходники (вместо обычной подпапки Win32Debug).

Сделаем Project / Build каждому проекту (напомню, что простого Project / Compile недостаточно если вы меняете опции проекта, но не его исходный код).

Теперь, откроем проект DLL и установим точку останова:

Теперь используем Run / Parameters и укажем для какого исполняемого файла нужно запускать DLL (это делать не нужно, если вместо проекта DLL вы открываете проект EXE):

Запускаем проект. Вы увидите, что точка останова становится недействительной:

Это — нормально. Ведь DLL не загружена в процесс EXE. Если бы это была точка останова на код в EXE — тогда, да, это было бы не нормально, что-то пошло не так.

Когда вы нажмёте на кнопку в приложении, DLL будет загружена вызовом LoadLibrary, после чего точка останова снова станет действительной. Затем LoadLibrary вызовет DllMain нашей библиотеки с «причиной» = DLL_PROCESS_ATTACH. Для события DLL_PROCESS_ATTACH RTL Delphi автоматически вызывает секции initialization всех модулей из DLL, а также секцию begin/end .dpr файла (и, наоборот, для DLL_PROCESS_DETACH вызываются секции finalization всех модулей). В результате мы встаём на нашей точке останова:

В окне стека вызовов (View / Debug Windows / Call Stack) вы можете видеть, что нас вызвала LoadLibrary, которую, в свою очередь, вызвали мы из Button1Click. К сожалению, отладчик IDE не может полностью реконструировать стек, пропуская некоторые вызовы. Он также не знает где взять отладочную информацию для системных DLL.

В любом случае, мы нажимаем F7 (Run / Step Into), чтобы войти в ShowMessage:

Если вы не видите эту картину, а вместо этого отладчик просто перешёл на следующую после ShowMessage строку (с end) — т.е. F7 (Step Into) сработала как F8 (Step Over) — то это означает, что у отладчика нет отладочной информации о модулях RTL/VCL. Это происходит потому, что опция Use Debug DCUs не была включена. А если она была включена, значит, она не возымела действия. Последнее может происходить в двух случаях:

  1. Вы не сделали проекту DLL Project / Build (а, например, просто запустили его);
  2. Ваш проект использует пакеты времени выполнения (run-time packages, BPL).

Окей, как только вы разобрались с проблемами, продолжаем нажимать F7…

Вот первая интересная функция. Мы видим, что при выполнении некоторых условий: Vista+ (TOSVersion.Check(6)), UseLatestCommonDialogs, ComCtl32.dll V6+ (IsNewCommonCtrl) и использовании стиля по умолчанию (StyleServices.IsSystemStyle), вызывается DoTaskMessageDlgPosHelp, в противном случае — DoMessageDlgPosHelp. Быстро глянем в DoTaskMessageDlgPosHelp:

function DoTaskMessageDlgPosHelp(const Instruction, Msg: string; DlgType: TMsgDlgType;
  Buttons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer;
  const HelpFileName: string): Integer;
var
  DefaultButton: TMsgDlgBtn;
begin
  if mbOk in Buttons then DefaultButton := mbOk else
    if mbYes in Buttons then DefaultButton := mbYes else
      DefaultButton := mbRetry;
  Result := DoTaskMessageDlgPosHelp(Instruction, Msg, DlgType, Buttons, HelpCtx,
    X, Y, HelpFileName, DefaultButton);
end;

// ...

function DoTaskMessageDlgPosHelp(const Instruction, Msg: string; DlgType: TMsgDlgType;
  Buttons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer;
  const HelpFileName: string; DefaultButton: TMsgDlgBtn): Integer;
// ...
begin
  Application.ModalStarted;
  LTaskDialog := TTaskMessageDialog.Create(nil);
  try
    // ...

    // Show dialog and return result
    Result := mrNone;
    if LTaskDialog.Execute then
      Result := LTaskDialog.ModalResult;
  finally
    LTaskDialog.Free;
    Application.ModalFinished;
  end;
end;

и DoMessageDlgPosHelp:

function DoMessageDlgPosHelp(MessageDialog: TForm; HelpCtx: Longint; X, Y: Integer;
  const HelpFileName: string): Integer;
begin
  with MessageDialog do
    try
      HelpContext := HelpCtx;
      HelpFile := HelpFileName;
      if X >= 0 then Left := X;
      if Y >= 0 then Top := Y;
      if (Y < 0) and (X < 0) then Position := poScreenCenter;
      Result := ShowModal;
    finally
      Free;
    end;
end;

Отсюда видно, что в зависимости от набора условий ShowMessage реализуется либо через вызовы TaskDialog API, либо через обычную VCL-форму (создаётся в CreateMessageDialog). Что-то похожее мы уже делали.

Итак, нажмём один раз F8 (Run / Step Over), чтобы выполнить строчку с if и посмотреть, куда мы встанем:

Окей, т.е. в нашем случае ShowMessage будет реализован через Task Dialog API. Заходим внутрь по F7 (Step Into), затем ещё раз (входим в DoTaskMessageDlgPosHelp). Функция DoTaskMessageDlgPosHelp настраивает диалог, а затем его вызывает. Нам интересно, что происходит в момент вызова диалога, поэтому весь код настройки мы проходим по F8 (Step Over) — вплоть до вызова if LTaskDialog.Execute then. Поскольку в теле DoTaskMessageDlgPosHelp есть цикл (да и в целом это не самая короткая функция) — можно пролистать код вниз и установить новую точку останова на строчку с LTaskDialog.Execute, после чего запустить программу через F9 (Run / Run). Отладчик выполнит код настройки и встанет на точке останова:

Заходим в LTaskDialog.Execute по F7 (Step Into):

Метод Execute — динамический (dynamic), поэтому он вызывается не напрямую. Этот код ищет адрес метода в таблице DMT и сохраняет его в регистр ESI, после чего делает на него переход (JMP). Мы могли бы (как и выше с LTaskDialog.Execute) установить точку останова на строчку JMP ESI, но вызов динамического метода — частая операция. Мы бы не хотели вставать на этой точке останова каждый раз, когда мы проходим по F8 (Step Over) вызовы других динамических методов. Поэтому мы установим курсор на строчку JMP ESI и нажмём F4 (Run / Run to Cursor), после чего нажмём F7 (Step Into) и, наконец, попадём внутрь метода Execute:

Проходим метод по F8 (Step Over) или используем F4 (Run to Cursor) и заходим по F7 (Step Into) в Result := Execute(LParentWnd). Как и ранее, Execute — метод динамический, поэтому используем F4 (Run to Cursor) на JMP ESI и F7 (Step Into) для входа в унаследованную реализацию:

Несколько раз повторим эти операции, путешествуя по реализациям Execute, пока не окажемся в самой нижней TCustomTaskDialog.DoExecute:

Как и выше с DoTaskMessageDlgPosHelp, TCustomTaskDialog.DoExecute сначала производит настройку, а затем вызывает интересующий нас кусок:

function TCustomTaskDialog.DoExecute(ParentWnd: HWND): Boolean;
// ...
begin
  // ...
  try
    Result := TaskDialogIndirect(LTaskDialog, {$IFNDEF CLR}@{$ENDIF}LModalResult,
      {$IFNDEF CLR}@{$ENDIF}LRadioButton, {$IFNDEF CLR}@{$ENDIF}LVerificationChecked) = S_OK;
    FModalResult := LModalResult;
    if Result then
  // ...
end;

Здесь нас интересует вызов TaskDialogIndirect, поэтому ставим курсор на строчку Result := TaskDialogIndirect(...) = S_OK; и жмём F4 (Run to Cursor). Далее нажимаем F7 (Step Into).

Мы ожидали, что TaskDialogIndirect — функция Windows, для неё у нас нет исходного кода (даже с включенной опцией Use Debug DCUs), поэтому F7 (Step Into) сработает как F8 (Step Over). Но, как мы видим, в Delphi TaskDialogIndirect — это переходник-обманка, которая динамически («по запросу») загружает «настоящую» TaskDialogIndirect (и сохраняет её в глобальной переменной _TaskDialogIndirect). Это (скрытие реализации под «известным» именем) — подводный камень при отладке, т.к. мы можем не предположить, что за вызовом TaskDialogIndirect скрывается какой-то «наш» код и пропустить его пройдя вызов TaskDialogIndirect по F8 (Step Over).

Если вы попались на эту удочку и выполнили TaskDialogIndirect по F8 (Step Over), то вы увидели, что Result стал равен False, а сообщения на экране не появилось. Т.е. TaskDialogIndirect вернула какой-то код ошибки, который код RTL/VCL успешно проигнорировал. Вы хотите узнать этот код. Для этого вы устанавливаете точку останова на строчку Result := TaskDialogIndirect(...) = S_OK — это интересующий нас участок кода. Ничего больше нас уже не интересует, поэтому все прочие точки останова (View / Debug Windows / Breakpoints) можно удалить.

(Подсказка: сначала удалите все старые точки останова, а лишь затем устанавливайте новую точку останова на строчку Result := TaskDialogIndirect(...) = S_OK, а не наоборот.)

Снимите приложение по F2 (Run / Program Reset) и запустите снова. Щёлкните по кнопке — и вы должны встать сразу на строчке Result := TaskDialogIndirect(...) = S_OK, минуя все предыдущие шаги:

Вызовите CPU отладчик через Ctrl + Alt + C (View / Debug Windows / CPU Windows / Entire CPU):

Вы увидите машинный код, в который была скомпилирована строка исходного кода, на которой вы стоите. Вызов любой функции будет происходить в три шага:

  1. Подготовка аргументов
  2. Непосредственный вызов (передача управления)
  3. Чтение/сохранение/анализ результата

Обратите внимание, что п1 может включать в себя и другие вызовы функций (например, код DoExecute(GetParentWindow) сначала вызовет GetParentWindow, а лишь затем — DoExecute). Нас же интересует только п2. Несложно сообразить, что п2 будет последним вызовом функции среди всего кода, сгенерированного для этой строки.

Вызов другой функции на ассемблере — это инструкция call, поэтому нас интересует последняя инструкция call в строчках машинного кода между двумя жирными строками (Vcl.Dialogs.pas.5703: Result := ... и Vcl.Dialogs.pas.5705: FModalResult := ...). В данном случае это 04BF2AE0 E88751E7FF call $04a67c6c.

Вы можете нажимать F8 (Step Over), чтобы пройтись по строкам машинного кода вплоть до этой строки, или же вы можете установить курсор на эту строчку и нажать F4 (Run to Cursor). В любом случае вы встанете на этой строке:

Нажмите F8 (Step Over) ещё раз, чтобы выполнить эту функцию.

Результат функции будет помещён в регистр EAX — любая функция всегда возвращает результат через EAX. Но даже, если вы это не знаете, про это можно догадаться, т.к. п3 из списка выше («чтение/сохранение/анализ результата») первым делом проверяет регистр EAX (test eax,eax).

Поскольку TaskDialogIndirect возвращает HRESULT, то в EAX будет лежать искомый код ошибки (в виде HRESULT).

В любом случае, возвращаясь к коду TaskDialogIndirect — здесь нас интересует вызов _TaskDialogIndirect, но мы не знаем по какой ветке пойдёт выполнение, поэтому мы нажимаем F8 (Step Over), пока это не станет ясно (как оказывается, мы идём по ветке else). Дойдя до Result := _TaskDialogIndirect мы (на всякий случай) нажимаем F7 (Step Into):

В этот раз F7 (Step Into) сработала как F8 (Step Over) — т.к. мы вызвали функцию Windows (для которой у нас нет исходного кода). В данном случае мы можем увидеть, что результат вызова (значение Result типа HRESULT) равно -2147024809. Для этого вы можете просто навести мышью на слово Result в редакторе кода — и IDE покажет значение Result во всплывающей подсказке. Или вы можете использовать окно локальных переменных (View / Debug Windows / Local Variables). Или вы можете щёлкнуть правой кнопкой по Result и выбрать Debug / Evaluate/Modify из всплывающего меню.

Иногда отладчик может показывать сообщение о том, что значение переменной/выражения не доступно. Обычно это происходит при включенной оптимизации, когда код сохраняет/использует значение ранее и не хранит его вплоть до выхода из функции. Это проще всего лечить отключением оптимизации (что мы и сделали в самом начале при настройке проекта). Альтернативно, вы можете вычислять/проверять интересующие вас значения выше по тексту (пока они ещё не отбрасываются), либо позднее — уже после выхода из функции. В крайнем случае вы всегда можете открыть CPU-отладчик и проверить значения внутри строк — как мы сделали это выше в лирическом отступлении.

Итак, мы вроде как нашли причину, почему ShowMessage ничего не показывает. Потому что она вызывает TaskDialogIndirect, а она, в свою очередь, завершается с ошибкой номер -2147024809. Но что это за ошибка номер -2147024809?

Для этого мы запустим утилиту Error Lookup из состава EurekaLog. Если вы используете EurekaLog, то эта утилита у вас уже есть — её можно запустить через меню Пуск (Пуск / Программы / EurekaLog / Tools / Error Lookup) или через меню IDE Delphi (Tools / EurekaLog / Error Lookup). Если EurekaLog у вас нет, то Error Lookup можно установить бесплатно автономно — скачав её с сайта — вас интересует «Freeware EurekaLog Tools Pack».

Итак, скормим -2147024809 в Error Lookup:

Как вы можете видеть, -2147024809 — это ошибка HRESULT с кодом $80070057 = E_INVALIDARG (причём это спроецированная на HRESULT Win32 ошибка с кодом 87 = ERROR_INVALID_PARAMETER — что можно проверить запустив поиск ошибки 87). Итак, TaskDialogIndirect ругается на неверные аргументы. Уже в этот момент мы должны понять, что что-то идёт сильно не так. Предположительно отлаженный код RTL/VCL должен вызывать предположительно отлаженный код Windows, так что ошибки вида «неверный аргумент» возникать в принципе не должно, если только в функцию не просочатся «неверные» данные от нас лично.

Но какой именно аргумент не нравится TaskDialogIndirect? Жаль, что системные функции Windows не используют исключения — с кодами ошибок у нас нет указания на аргумент, который не понравился функции. У нас есть два вектора атаки:

  1. Окно-родитель (ParentWnd) устанавливается динамически самим RTL/VCL и не приходит от нашего кода. Возможно, TaskDialogIndirect не понравилось окно?
  2. Известно, что вызов ShowMessage в экспортируемой функции работает успешно. Мы можем сравнить, чем отличаются аргументы между успешным и не успешным вызовами.

Чтобы проверить первую гипотезу, мы установим точку останова на (вторую) строчку Result := _TaskDialogIndirect(...) в TaskDialogIndirect и удалим все прочие точки останова (как и ранее, это удобнее делать наоборот: сначала удалить все точки останова, потом добавить новую). Перезапустим программу, щёлкнем по кнопке и остановимся на точке. Проанализируем аргументы функции _TaskDialogIndirect (наводите на них мышь или используйте Evaluate/Modify). Вы увидите, что окно-родитель передаётся в pTaskConfig.hwndParent. Нам нужно сбросить это значение в ноль (и для начала стоит выяснить, что ноль (NULL) является допустимым аргументом — это так, мы проверили это по документации). Чтобы изменить это значение, удобнее всего вызвать Evaluate/Modify из окна локальных переменных:

Или щёлкните правой по pTaskConfig в редакторе кода и вызовите Evaluate/Modify из контекстного меню, затем допишите «.hwndParent» (без кавычек) в поле Expression и нажмите Evaluate.

Чтобы сбросить это значение, введите 0 в поле New value и нажмите Modify. Теперь значение обнулено:

Выполните функцию (по F8). Результат оказывается тем же самым (функция завершается с ошибкой E_INVALIDARG). Т.е. дело не в окне-родителе.

Для второй гипотезы нам нужно выписать все аргументы функции. Для этого удобно развернуть окно локальных переменных на всю высоту и развернуть в нём все под узлы. Альтернативно можно также открыть несколько окон Evaluate/Modify:

Просто сделайте скриншот экрана.

Теперь нам нужно вызвать ShowMessage из экспортируемой функции. Для этого снимите выполняющуюся программу и измените текст DLL так:

library Project2;

uses
  Vcl.Dialogs;

{$R *.res}

procedure Test;
begin
  ShowMessage('Test');
end;

exports
  Test;

begin
  ShowMessage('Test');
end.

Затем сохраните изменения, скомпилируйте, закройте проект DLL, откройте проект EXE и измените его код:

procedure TForm1.Button1Click(Sender: TObject);
var
  Lib: HMODULE;
  Test: procedure;
begin
  Lib := LoadLibrary('Project2.dll');
  Win32Check(Lib <> 0);
  Test := GetProcAddress(Lib, 'Test');
  Win32Check(Assigned(Test));
  Test;
end;

Мы пишем тестовый пример, поэтому мы можем наплевать на правильное освобождение ресурсов, но нам важна правильная обработка ошибок, т.к. она облегчает диагностику.

Сохраните и перекомпилируйте проект EXE. Запустите проект и нажмите на кнопку. Убедитесь, что теперь сообщение показывается (это происходит из экспортируемой функции). Закройте проект EXE и откройте проект DLL.

Вернитесь к нашей TaskDialogIndirect. Чтобы это быстро сделать — откройте список точек останова (View / Debug Windows / Breakpoints) и дважды щёлкните по (единственной) установленной точке останова — среда должна перенести вас на (вторую) строчку Result := _TaskDialogIndirect(...) внутри WinAPI.CommCtrl.TaskDialogIndirect (да, надеюсь, в вашей среде были включены autosave-опции). Напомню, нас интересует второй вызов ShowMessage и, следовательно, второй вызов TaskDialogIndirect. Несложно догадаться, что во второй раз переменная _TaskDialogIndirect будет уже присвоена, поэтому выполнение пойдёт по первой ветке:

function TaskDialogIndirect(const pTaskConfig: TTaskDialogConfig;
  pnButton: PInteger; pnRadioButton: PInteger; pfVerificationFlagChecked: PBOOL): HRESULT;
begin
  if Assigned(_TaskDialogIndirect) then
    // Выполняется из Test:
    Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton,
      pfVerificationFlagChecked)
  else
  begin
    InitComCtl;
    Result := E_NOTIMPL;
    if ComCtl32DLL <> 0 then
    begin
      @_TaskDialogIndirect := GetProcAddress(ComCtl32DLL, 'TaskDialogIndirect');
      if Assigned(_TaskDialogIndirect) then
        // Выполняется из DllMain:
        Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton, 
          pfVerificationFlagChecked)
    end;
  end;
end;

Что ж, установим точку останова на (первую) строчку Result := _TaskDialogIndirect(...) (и уберём точку останова со (второй) строчки Result := _TaskDialogIndirect(...)) и запустим программу. Мы встанем на точке, после чего мы можем «заскриншотить» аргументы вызова _TaskDialogIndirect и сравнить их с нашим скриншотом первого вызова (из DllMain).

Внезапно оказалось, что аргументы обоих вызовов полностью идентичны (но выполнение успешно лишь в одном случае, а в другом оно проваливается с ошибкой E_INVALIDARG). Следовательно, дело не в аргументах самой TaskDialogIndirect, а в аргументах какой-то другой функции, которую вызывает TaskDialogIndirect — опять же, поскольку системные функции Windows используют коды ошибок, а не исключения, то мы никак не можем идентифицировать точную функцию, вернувшую ошибку.

На этом часто исследование можно считать законченным — у нас нет исходного кода Windows и мы не можем отлаживать её код. В таких случаях нам остаётся только перебор входных значений «угадыванием». Но в данном случае мы получаем ошибку E_INVALIDARG. Проверка аргументов — предположительно, что-то такое, что должно выполняться в начале функции, не слишком глубоко внутрь. Поэтому есть шанс, что мы сможем понять в чём дело, не потратив на это много времени.

(Вздох)

Открываем CPU-отладчик:

Проходим по строчкам до вызова call и заходим в него по F7 (Step Into):

Как вы можете видеть, системная функция TaskDialogIndirect, на самом деле, очень короткая и является лишь переходником к чему-то другому (видимо, аналогично тому, как функция TaskDialog является переходником к TaskDialogIndirect). В машинном коде не видно каких либо проверок (условных JMP), поэтому можно смело дойти до call и снова войти в него:

В этот раз ситуация интереснее. Код виден большой, длинный, есть какие-то проверки (cmp, test), переходы (jz, jnz).

Любая функция заканчивается инструкцией ret (в крайне редких случаях функция может безусловно передать управление на другую функцию вместо возврата управления). $CC (int 3) является просто заполнителем свободного места между функциями (в системных DLL, Delphi же использует в качестве заполнителя случайный мусор). Иными словами, сейчас мы вошли в начало некоторой функции.

Однако не всегда ret обозначает конец функции. Это также может быть частью блока try/except/finally.

Что ещё отличается — перешли мы на какую-то внутреннюю функцию в ComCtl32.dll. Эта функция не экспортируется и не имеет имени. Поэтому отладчик IDE не может показать её имя в стеке вызовов (и показывает только адрес и DLL), а также не может обозначить её начало и имя в CPU-отладчике. Тем не менее, существует способ понять, где же мы оказались. Дело в том, что Microsoft даёт вам доступ к отладочной информации (но не исходному коду) системных DLL. К сожалению, Delphi не умеет использовать эту информацию. Зато её умеет использовать утилита Address Lookup из EurekaLog (или EurekaLog Tools Pack). Ей на вход нужны имя DLL и смещение адреса в ней от начала, а на выход она даст вам human-readable информацию об этом адресе. Заметьте, что абсолютный адрес (в нашем случае — $7244550A) не имеет никакого смысла для Address Lookup. Абсолютные адреса имеют смысл только в рамках того процесса, в котором они получены. В другом процессе эти адреса могут быть иными. Поэтому, чтобы идентифицировать место в коде, Address Lookup вместо одного абсолютного адреса использует пару: базовый адрес + смещение = абсолютный адрес.

Итак, абсолютный адрес у нас есть — это $7244550A. Как получить смещение? Для этого откройте View / Debug Windows / Modules и отсортируйте список загруженных DLL по имени (Name):

Найдите в списке DLL, соответствующую нашему адресу. В данном случае это будет ComCtl32.dll, что указывается в стеке вызовов. Альтернативно, вы можете отсортировать список по Base Address и взять максимальный адрес, который будет меньше нашего адреса. В обоих случаях вы найдёте строчку с ComCtl32.dll, откуда узнаете, что её базовый адрес — $72370000. Запускаем калькулятор Windows, переключаем его в режим «Программист», а также меняем режим на HEX, и: $7244550A — $72370000 = $000D550A. Именно это значение ($D550A) и будет смещением кода внутри (от начала) ComCtl32.dll. И именно это значение нужно скормить Address Lookup:

(Примечание: по умолчанию никакой настройки Address Lookup для использования сервера отладочной информации Microsoft выполнять не нужно, но если у вас что-то не работает — вот инструкция)

Окей, оказывается TaskDialogIndirect вызывает CTaskDialog.Show — что является методом Show класса CTaskDialog.

Что теперь? Теперь я предлагаю пройтись по коду с F8 (Step Over), внимательно следя за тем, что происходит в коде. В частности — какие переходы срабатывают. Тут можно придерживаться разных стратегий. Мы можем запустить две среды и две сессии отладки и отлаживать успешный и не успешный вызовы TaskDialogIndirect одновременно, сравнивая выполнение и находя отличия. Это гарантировано даст вам результат, но уж больно трудоёмко. Можно сообразить, что функция TaskDialogIndirect вернула нам код ошибки как результат (в EAX), поэтому мы можем проследить по машинному коду, откуда пришло это значение («отладкой задом-наперёд»).

Можно также сообразить, что функция состоит непосредственно из кода, а также из вызовов других функций. Мы уже знаем (выяснили выше), что проблема — не в проверке аргументов непосредственно в самой TaskDialogIndirect, проблема в какой-то другой функции, которую вызывает TaskDialogIndirect. Есть ненулевая вероятность, что TaskDialogIndirect вызывает другую функцию, которая также возвращает HRESULT. Следовательно, мы можем следить за появлением известного кода ошибки ($80070057) в регистре EAX после вызова функций (call). Поэтому мы просто выполняем код CTaskDialog.Show по F8 (Step Over), пока не увидим $80070057 в EAX:

Окей, а вот и наша под-функция. Нам не пришлось идти слишком далеко (кажется, это третья вызываемая функция). Теперь мы можем установить точку останова на call $7248d137 и перезапустить программу, после чего остановиться на точке и войти в под-функцию по F7. Это снова будет какая-то внутренняя безымянная функция ComCtl32.dll. Снова используем калькулятор: $7248D137 — $72370000 = $11D137 и снова используем Address Lookup:

Немного странное имя, но ОК, предположим. В принципе, это имеет смысл, если учесть, что мы вызываем код из DllMain, которая имеет известные ограничения.

Что ж, повторим наш алгоритм: будем проходить код по F8 (Step Over), следя за появлением «волшебного кода» ($80070057) в регистре EAX после вызовов функций (call). В этот раз нам придётся пройти довольно много кода, но в итоге мы находим ещё один вызов:

(Кстати, обратите внимание, это как раз одна из функций, которая заканчивается на jmp, а не ret.)

Устанавливаем точку останова и перезапускаем программу (предыдущую точку останова можно удалить, либо просто пропускать/игнорировать при выполнении — главное, не запутаться).

Ой. В отличие от прошлого раза, где под-функция возвращала HRESULT, в этот раз HRESULT приходит от GetLastError, а сама проблемная функция вызывается ранее и, вероятно, возвращает просто 0 (False).

Само собой, искать 0 в EAX — гиблое дело, ибо там он будет ну очень часто. Вместо этого можно поступить двумя способами:

  1. Логично предположить, что провалившаяся функция вызывается непосредственно перед вызовом GetLastError, т.е. надо проверить только предыдущий call;
  2. Можно вместо EAX следить за GetLastError.

И хотя в нашем случае вполне подходит вариант 1, давайте посмотрим на вариант 2. Для этого откройте окно Watch List (View / Debug Windows / Watches), щёлкните по нему правой кнопкой мыши и выберите Add Watch во всплывающем меню:

Впишите «GetLastError» (без кавычек) в Expression, включите Allow side effects and function calls и переключите отображение в Hexadecimal. Allow side effects and function calls необходима, чтобы отладчик вообще вычислял бы выражение с «GetLastError». По умолчанию отладчик не будет вычислять выражения для отладки, если это потенциально может изменить состояние программы. В нашем случае мы знаем, что вызов GetLastError — «безопасен», поэтому мы явно указываем это отладчику. Переключение же вида в Hexadecimal необходимо по той причине, что функция GetLastError возвращает код ошибки в виде числа (DWORD/Cardinal), даже хотя в нашем случае этим числом является HRESULT. Переключение режима позволит нам увидеть $80070057, а не -2147024809 (похоже, отладчик Delphi не различает знаковые и беззнаковые типы в Watch-ах).

В любом случае:

Перезапустите программу и снова пройдитесь по коду, как мы это делали выше, только теперь вместо EAX смотрите и за EAX, и за GetLastError в Watch List.

Так или иначе вы находите проблемный вызов:

Установите точку останова, перезапуститесь, войдите:

Можно предположить, что этот странный код является заглушкой-переходником. «Входим» в jmp (не важно — по F8 или по F7, jmp это безусловный переход, а не вызов функции):

…и снова переходник! Входим в call:

…и ещё один! Снова входим в call:

Ага, а вот вам и причина для всех этих переходников: InitThread вызывает какую-то внешнюю (импортируемую) функцию, связанную с ComCtl32.dll через отложенный импорт (Delay Loaded). Собственно, здесь мы можем лишь войти в настоящую реализацию отложенного импорта:

Что ж, функция это довольно большая и длинная. Есть куча переходов, вызовов, даже ret-ы, и даже GetLastError будет меняться. Но нам не нужно анализировать весь этот код. Несложно догадаться, что функция должна найти адрес целевой функции (загрузить DLL, сделать туда GetProcAddress), после чего сохранить результат в переменную (регистр/память) и сделать на него переход (не вызов! т.к. для вызова нужно формировать параметры, про которые функция не в курсе). Т.е. нам нужно дойти до jmp на опосредованное значение (т.е. не на фиксированный адрес типа $7244559С, а на, скажем, регистр).

Более того, если вы были действительно внимательны, то заметили, что этот jmp у нас уже есть — посмотрите выше на функции-переходники: по адресу $723F86EE как раз лежит jmp, который переходит на EAX (результат функции). Т.е. нам нужно только установить там точку останова и запустить программу по F9 (Run / Run). Ну или через F4 (Run to Cursor).

А вот и наша функция — некая InitGadgets из DUser.dll. Описание DUser.dll сообщает, что это — «Windows DirectUser Engine». Это недокументированная, внутренняя DLL Windows.

Окей, продолжаем выполнение дальше. Я, кстати, рекомендую поставить точку останова на начало InitGadgets — когда мы пропустили так много кода (особенно — внутреннего кода), лучше иметь надёжную точку для отката (точку останова в экспортируемой функции), на всякий пожарный. В любом случае, немного пройдясь по коду, следя за EAX и GetLastError, вы быстро найдёте следующее звено:

$6716С0EС — $67160000 (база для DUser.dll) = $C0EC =

ResourceManager.InitContextNL. И буквально чуть-чуть далее:

Опа! И кто же это такой? ;)

Мы заметили явно «наше» проблемное значение $80070057, но не заметили, а как же мы попали в эту строчку. Очевидно, произошла какая-то проверка, которая отправила «хороших» — дальше по коду, а «плохих» завернула на эту ветку. Нам нужно перезапустить программу и пройти InitContextNL заново, внимательно следя на условными переходами — не отправит ли кто нас на адрес $67187А91. И вот мы находим проверку (мы уже так близки к разгадке!):

А вот и переход. Как мы видим, InitContextNL вызывает какую-то функцию, та возвращает ей, видимо, TRUE (1 в EAX), что не нравится InitContextNL, и она переходит на установку жёстко зашитого кода ошибки. Немного странно: т.е. функция по ESI не завершается с ошибкой (иначе она вернула бы FALSE), вместо этого функция возвращает какую-то информацию. Возможно, мы не так близки к разгадке, как думали…

Смотрим, $6716BC60 — это:

WinNT.IsInsideLoaderLock! Тайна раскрыта!

Давайте реконструируем стек вызовов:

  • Button1Click
  • LoadLibrary
  • DllMain
  • ShowMessage
  • TaskDialogIndirect
  • CTaskDialog.Show
  • InitThread
  • InitGadgets
  • ResourceManager.InitContextNL
  • WinNT.IsInsideLoaderLock


WinNT.IsInsideLoaderLock возвращает True — это действительно так, ведь мы находимся внутри DllMain, т.е. критическая секция загрузчика ОС занята нами. Это значение трактуется как ошибка методом InitContextNL класса ResourceManager, который и возвращает искомый код ошибки E_INVALIDARG ($80070057) — даже хотя ошибка не имеет отношения к аргументам. Ну и несложно догадаться, что далее этот код ошибки всплывает до вызова TaskDialogIndirect внутри ShowMessage (где и успешно игнорируется).

Иными словами, Task Dialog API явно проверяет, не вызывают ли его из DllMain, и если да — то отказывается работать.

Это не указано явно в документации к Task Dialog API, но указано опосредовано в описании DllMain:

Calling functions that require DLLs other than Kernel32.dll may result in problems that are difficult to diagnose.

Окей, тайна раскрыта, создан тикет.

P.S. Но что насчёт «сообщение показывается в других программах» и «всегда показывается в экспортируемой функции»? Ну, с экспортируемой функцией всё просто — её вызывают вне DllMain и блокировка загрузчика ОС не удерживается, так что WinNT.IsInsideLoaderLock возвращает False, и проверка в InitContextNL проходит. Т.е. TaskDialogIndirect выполняется успешно и показывает сообщение. А насчёт других программ: можно предположить (и потом проверить), что «другие программы» не используют ComCtl32.dll версии 6, а используют ComCtl32.dll версии 5 — т.е. они не используют т.н. «XP манифест» и темы. Следовательно, когда эти программы загружают нашу DLL, в MessageDlgPosHelp условие не выполняется, и выполнение идёт по второй ветке, вызывая DoMessageDlgPosHelp вместо DoTaskMessageDlgPosHelp. Т.е. сообщение показывается через VCL-форму, а Task Dialog API не участвует.

P.P.S. Кстати, если кому интересно: DUser.WinNT.IsInsideLoaderLock просто вызывает RtlIsThreadWithinLoaderCallout, которую вы можете импортировать из ntdll.dll. RtlIsThreadWithinLoaderCallout имеет следующий прототип:

function RtlIsThreadWithinLoaderCallout: BOOL; stdcall;

Исключения и взаимодействие с 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).

Ошибка приложения (исключение Delphi)

Рис. 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).

Установка параметров приложения в окне свойств проекта Delphi

Рис. 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).

Диалоговое окно функции InputBox

Рис. 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. Все права защищены.
При использовании материалов с сайта ссылка на источник обязательна.
Рейтинг@Mail.ru

Окна
диалогов
5

Процедура
ShowMessage
Модуль
Dialogs

procedure
ShowMessage(const Msg: string);

Отображает
окно сообщения с кнопкой OK.

Вызов
ShowMessage отображает простое
окно сообщения с кнопкой OK.
Текст сообщения задается параметром
Msg. Заголовок окна
совпадает с именем выполняемого файла
приложения.

При
необходимости анализировать ответ
пользователя на сообщение пользуйтесь
функцией Delphi MessageDlg.

Примеры:

1.)
ShowMessage(‘Работа приложения
успешно завершена.’);

2. ) В
приведенном ниже примере предполагается,
что целые переменные N1 и
N2 содержат соответствующие
числа, которые переводятся в строки
функцией IntToStr. Сообщение,
приведенное в этом примере проще
реализовать процедурой ShowMessageFmt.

ShowMessage(‘Задано
‘ + IntToStr(N1) + ‘ параметров из ‘

+
IntToStr(N2));

ShowMessageFmt
процедура

Модуль
Dialogs

procedure
ShowMessageFmt(const Msg: string; Params: array of const);

Отображает
окно форматированного сообщения с
кнопкой OK.

Вызов
ShowMessageFmt отображает окно
сообщения с кнопкой OK.
Параметр Msg задает
строку описания формата, а параметр
Params задает массив
параметров, форматируемых строкой Msg.
Заголовок окна совпадает с именем
выполняемого файла приложения.

Пример

ShowMessageFmt(‘Задано
%d параметров из %d
‘, [N1, N2]);

Функция
TApplication.MessageBox

function
MessageBox(Text, Caption: PChar; Flags: Longint): Integer;

Функция
MessageBox является методом переменной
Application типа TApplication, доступной в любом
проекте Delphi. Это метод является наиболее
удачным способом отображения диалоговых
окон. Он отображает диалоговое окно с
заданными кнопками, сообщением и
заголовком и позволяет проанализировать
ответ пользователя. Во многих отношениях
это окно подобно окнам, создаваемым
функциями MessageDlg и CreateMessageDialog. Но имеются
и существенные отличия, связанные с
возможностью русификации окна. Заголовок
окна может быть написан по-русски, что
отличает эту функцию от функции MessageDlg
(впрочем, в окне, созданном CreateMessageDialog,
это тоже можно сделать). Другим приятным
отличием являются русские надписи на
кнопках (в русифицированных версиях
Windows).

Функция
MessageBox инкапсулирует функцию MessageBox API
Windows.

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

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

Флаги
кнопок, отображаемых в диалоговом окне.

Флаг Значение
(в скобках даны надписи в русифицированных
версиях Windows)

MB_ABORTRETRYIGNORE Кнопки
Abort (Стоп), Retry (Повтор) и Ignore (Пропустить).

MB_OK Кнопка
OK. Этот флаг принят по умолчанию.

MB_OKCANCEL Кнопки
OK и Cancel (Отмена).

MB_RETRYCANCEL Кнопки
Retry (Повтор) и Cancel (Отмена).

MB_YESNO Кнопки
Yes (Да) и No (Нет).

MB_YESNOCANCEL Кнопки
Yes (Да), No (Нет) и Cancel (Отмена).

Флаги
пиктограмм в диалоговом окне

MB_ICONEXCLAMATION,
MB_ICONWARNING Восклицательный знак

(замечание, предупреждение).

MB_ICONINFORMATION,
MB_ICONASTERISK Буква i в круге (подтверждение).

MB_ICONQUESTION Знак
вопроса (ожидание ответа).

MB_ICONSTOP,
MB_ICONERROR, MB_ICONHAND Знак креста на красном
круге

(запрет, ошибка).

Флаги,
указывающие кнопку по умолчанию (которая
в первый момент находится в фокусе)

MB_DEFBUTTON1 Первая
кнопка. Это принято по умолчанию.

MB_DEFBUTTON2 Вторая
кнопка.

MB_DEFBUTTON3 Третья
кнопка.

MB_DEFBUTTON4 Четвертая
кнопка.

Флаги
модальности

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

MB_SYSTEMMODAL То же самое, что MB_APPLMODAL, но окно
диалога отображается в стиле WS_EX_TOPMOST,
то есть всегда остается поверх других
окон, даже если пользователь перешел к
другим приложениям. Используется для
предупреждения о серьезных ошибках,
требующих немедленного вмешательства.

Некоторые
дополнительные флаги (могут задаваться
оба флага)

Флаг Пояснение

MB_HELP Добавляет
в окно кнопку Help (Справка), щелчок на
которой или нажатие клавиши F1 генерирует
событие Help.

MB_TOPMOST Помещает
окно всегда сверху (в стиле WS_EX_TOPMOST).

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

Функция
возвращает нуль, если не хватает памяти
для создания диалогового окна. Если же
функция выполнена успешно, то возвращаемая
величина
свидетельствует о следующем:

Значение Численное
значение Пояснение

IDABORT 3 Выбрана
кнопка Abort (Стоп).

IDCANCEL 2 Выбрана
кнопка Cancel (Отмена) или нажата клавиша
Esc.

IDIGNORE 5 Выбрана
кнопка Ignore (Пропустить).

IDNO 7 Выбрана
кнопка No (Нет).

IDOK 1 Выбрана
кнопка OK.

IDRETRY 4 Выбрана
кнопка Retry (Повтор).

IDYES 6 Выбрана
кнопка Yes (Да).

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

if
(проверка введенных данных)

then
begin

if
(Application.MessageBox(

‘Хотите занести текущую запись в базу
данных?’,

‘Подтвердите занесение в базу данных’,

MB_YESNOCANCEL + MB_ICONQUESTION) <> IDYES)

then begin

DataSet.Cancel;

Abort;

end

end

else begin

Application.MessageBox(‘Ошибочные данные’,’Ошибка’,
MB_ICONSTOP);

Abort;

end;

MessageDlg
функция
Модуль Dialogs

function
MessageDlg(const Msg: string; AType: TMsgDlgType;

AButtons: TMsgDlgButtons;
HelpCtx: Longint):
Word;

Отображает
диалоговое окно сообщений в центре
экрана.

Вызов
MessageDlg отображает диалоговое окно и
ожидает ответа пользователя. Сообщение
в окне задается параметром функции Msg.

Вид
отображаемого окна задается параметром
AType. Возможные значения этого
параметра:

Значение Описание

mtWarning Окно
замечаний, содержащее желтый восклицательный
знак.

mtError Окно
ошибок, содержащее красный стоп-сигнал.

mtInformation Информационное
окно, содержащее голубой символ «i».

mtConfirmation Окно
подтверждения, содержащее зеленый
вопросительный знак.

mtCustom Заказное
окно без рисунка. Заголовок соответствует
имени выполняемого файла приложения.

Параметр
AButtons определяет, какие кнопки будут
присутствовать в окне. Тип TMsgDlgBtns
параметра AButtons является множеством,
которое включает различные кнопки.
Возможные значения видов кнопок:

Значение Описание

mbYes Кнопка
с надписью ‘Yes’

mbNo Кнопка
с надписью ‘No’

mbOK Кнопка
с надписью ‘OK’

mbCancel Кнопка
с надписью ‘Cancel’

mbHelp Кнопка
с надписью ‘Help’

mbAbort Кнопка
с надписью ‘Abort’

mbRetry Кнопка
с надписью ‘Retry’

mbIgnore Кнопка
с надписью ‘Ignore’

mbAll Кнопка
с надписью ‘All’

Список
необходимых кнопок заключается в
квадратные скобки [ ], поскольку параметр
AButtons является множеством. Если внутри
скобок список отсутствует, в окне не
будет ни одной кнопки и пользователю
придется закрывать окно системными
кнопками Windows.

Кроме
множества значений, соответствующих
отдельным кнопкам, в Delphi определены три
константы, соответствующие часто
используемым сочетаниям кнопок:

Значение Описание

mbYesNoCancel
Включает в окно кнопки Yes, No и Cancel

mbOkCancel Включает
в окно кнопки OK и Cancel

mbAbortRetryIgnore Включает
в окно кнопки Abort, Retry и Ignore

Эти
константы являются предопределенными
множествами. Поэтому при их использовании
их не надо заключать в квадратные скобки
[ ].

Параметр
HelpCtx определяет экран контекстной
справки, соответствующий данному
диалоговому окну. Этот экран справки
будет появляться при нажатии пользователем
клавиши F1. Если вы справку не планируете,
при вызове MessageDlg надо задать нулевое
значение параметра HelpCtx..

Функция
MessageDlg возвращает значение, соответствующее
выбранной пользователем кнопке. Возможные
возвращаемые значения:

mrNone
mrAbort mrYes

mrOk
mrRetry mrNo

mrCancel
mrIgnore mrAll

Функция
MessageDlg очень полезна для быстрого создания
прототипа приложения и проверки
диалогового взаимодействия с пользователем.
Но у нее есть заметный недостаток: в
заголовках и надписях на кнопках тексты
английские, так что при использовании
русских сообщений получается смесь
русского с английским.

Имеется
также функция MessageDlgPos, во всем аналогичная
функции MessageDlg, но отображающее окно в
заданном месте экрана.

При
выводе простых сообщений без необходимости
анализировать ответ пользователя удобно
использовать другие процедуры Delphi —
ShowMessage и ShowMessageFmt.

Примеры:

1.
Заключительный диалог при окончании
работы приложения.

if
MessageDlg(‘Действительно хотите закончить
приложение?’,

mtConfirmation, [mbYes, mbNo], 0) = mrYes then

begin

MessageDlg(‘Работа приложение закончена’,
mtInformation,

[mbOk], 0);

Close;

end;

Первый
вызов MessageDlg приводит к отображению окна
типа mtConfirmation с вопросом о завершении
приложения. Если пользователь нажимает
кнопку Yes, то выводится второе окно типа
mtInformation с сообщением о завершении.

1.
Сообщение об ошибке и замечание.

on Exception do

begin

MessageDlg(‘Произошла
ошибка.’, mtError,

[mbOk], 0);

MessageDlg(‘Будьте
внимательнее.

‘, mtWarning,

[mbOk], 0);

end;

3. В
каком-то диалоге, после редактирования
пользователем записи ему предлагается
вопрос о сохранении ее в базе данных.
Если пользователь выбирает кнопку Yes,
запись сохраняется методом Post; если
пользователь выбирает кнопку No, результаты
редактирования уничтожаются методом
Cancel; если же пользователь выбирает
кнопку Cancel, диалог закрывается.

case
MessageDlg(‘Занести запись
в БД?’,
mtCustom,

mbYesNoCancel, 0) of

mrYes: DataSet1.Post;

mrNo: DataSet1.Cancel;

mrCancel:
Close;

end;

В вызове
MessageDlg использован тип mtCustom, в результате
чего в заголовке окна указано имя
приложения. Для задания кнопок использована
константа mbYesNoCancel

Соседние файлы в папке _Delphi_1курс лекции

  • #

    23.03.20151.13 Mб23~WRL3549.tmp

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Понравилась статья? Поделить с друзьями:
  • Show port error disabled
  • Show mysql connection error
  • Show error ports fragment
  • Show error laravel
  • Shot atomizer как исправить ошибку на вейпе