"Виртуальная библиотека Delphi" - читать интересную книгу автора

Общие вопросы по Delphi и данному FAQ (часть 3)

Object Pascal и Windows API

1. Как работает информация времени выполнения (RTTI)?

Имеются два новых оператора: as и is. as  — оператор защищенного преобразования типов (typecasting). Вы можете использовать его, чтобы заставить компилятор преобразовать объект из одного типа в другой, но, если в во время выполнения эти типы окажутся несовместимыми, то вы получите ошибку. Hапример, если вы имеете класс TSport, с потомоками TBasketball и TFootball, вам может потребоваться переменная типа TSport; далее может так случиться, что в программе эта переменная будет фактически содержать экземпляр типа TFootball. Тогда вы можете обратиться к этой переменной

(MySport as TFootball)

чтобы получить доступ к специфическим свойствам из типа TFootball. Однако, если вы ошиблись и на самом деле это экземпляр типа TBasketball, то при обращении к несуществующим свойствам будет возникать ошибка. Оператор is определяет, принадлежит ли экземпляр объекта к данному классу, либо к классу одного из его предков, и используется для проверки, сработает ли преобразование типов с данным объектом. Если вы имеете переменную MySport типа TSport, и в настоящее время она содержит экземпляр TBasketball, тогда следующие выражения истинны:

(MySport is TSport)

(MySport is TBasketball)

not (MySport is TFootball)

Следует иметь ввиду, что компилятор разрешает использовать данные конструкции только для выполнения преобразования типов, связанных родственными отношениями. Так, конструкция (Button1 as TEdit) (переменная Button1 имеет тип TButton) вызовет ошибку компиляции, так как ни при каких условиях не может быть выполнено преобразование типов от TButton к TEdit или наоборот. Комбинация двух операторов может привести к выражению типа следующего :

function PlayerGoodness(var MySport: TSport): Integer; 

begin 

  if (MySport is TBasketball) then 

    Result := (MySport as TBasketball).ReboundShots 

  else if (MySport is TFootball) then 

    Result := (MySport as TFootball).TotalYardage; 

end;

Также, базовый класс TObject имеет набор методов, которые возвращают информацию, созданную компилятором в момент компиляции текста для поддержки RTTI. Hапример, метод TObject.ClassName возвращает имя класса любого объекта, наследованного от TObject. Hапример, TButton.ClassName вернет значение 'TButton'


2. Как работает обработка исключительных ситуаций в Delphi?

Основная структура выглядит примерно так:

P := New(BigThing); 

try 

  try 

    Proc1(P); 

    Proc2(P); 

  except 

    Handle(P); 

    raise; 

  end; 

finally 

  Dispose(P); 

end;

Первая строка распределяет большой блок памяти. Затем, в блоке try, выполняется несколько операторов, каждый из которых может вызвать ошибку, или, другими словами, "вызвать исключительную ситуацию". Если возникает ошибка, оставшаяся часть блока try пропускается, и выполняются блоки except и finally. Если ошибок нет, то после выполнения всех операторов в блоке try выполнится блок finally. В любом случае, блок памяти будет освобожден. Блок try … finally ловит все, включая Windows GPF или Access Violation. Обратите внимание на вызов raise в блоке try … except. Он снова вызывает исключительную ситуацию, которая вызовет сообщение об ошибке после того, когда закончится блок finally. Если не вызвать raise, то считается, что вы обработали исключительную ситуацию самостоятельно в пределах блока except.


3. Есть ли простой способ перехватить exception?

Создайте метод для формы, перехватывающий исключения. Этот метод будет вызываться обработчиком OnException объекта Application. В вашем методе проверьте, тот ли это исключение, что вы ожидаете, например EDatabaseError. Почитайте on-line help для события OnException. Там есть информация, как вызвать собственный метод для события.  

procedure TForm1.MyExcept(Sender: TObject; E: Exception); 

begin 

  if E is EDatabaseError then MessageDlg('Поймали exception', mtInformation, [mbOk], 0) 

{ это не то, сделать raise } 

  else raise E; 

end; 

procedure TForm1.FormCreate(Sender: TObject); 

begin 

  Application.OnException := MyExcept; 

{ здесь вы указываете, что событие OnException выполнит ваш метод } 

end; 


4. Delphi используют строки в стиле Pascal или C?

И те и другие. Delphi имеет два различных набора функций манипулирования строками, один - для PChar; но в Delphi также есть функция MessageDlg, которая принимает строки типа Pascal.

Delphi 2.0 добавляет так называемые длинные строки (AnsiString), которыми можно манипулировать как обычными строками в Pascal, но они имеют динамически изменяющийся размер и могут быть размером до 4Гбайт. Можно выполнять преобразования от PChar к AnsiString и наоборот. Старый строковый тип теперь называется ShortString. По умолчанию кличевое слово string соответствует типу AnsiString.


5. Есть ли в Delphi битовые множества?

В явном виде битовых множеств в языке Object Pascal нет. Но вместо этого можно использовать обычные множества, которые на самом деле и хранятся как битовые. Если множество вам нужно для проверки, установлен ли какой то бит в слове (байте и т.д.) можно попробовать такую конструкцию:

type 

  PByteSet = ^TByteSet; 

  TByteSet = set of Byte; 

var 

  W: Word; 

... 

{ если бит 3 в слове W установлен, тогда ... } 

  if 3 in PByteSet(@W)^ then ... 

... 

В Delphi 2.0 есть специальный класс TBitSet, который ведет себя как битовое множество.Для Delphi 1.0 вы можете написать такой класс самостоятельно.


6. Проблема с числом типа Single в DLL.

Я написал на C++ DLL, в которой у меня функция использует число типа float, передал из Delphi число типа Single и получил GPF 'Invalid Opcode'. Что неправильно?

Если вы используете числа с плавающей точкой, лучше передавать их не по значению, а по ссылке (указатель в C++). Вероятно DLL написана на MS Visual C++, так как Microsoft и Borland используют разные соглашения о передаче параметров при работе с сопроцессором. В случае Borland C++ и Delphi должны использовать одинаковый способ передачи параметров и значений (через стек сопроцессора). В любом случае вместо Single лучше использовать Double (double или long float в C++), так как вообще говоря, реальный тип, который соответствует типу Single точно не определен и может измениться в будущем.

7. Как заставить приложение Delphi отвечать на сообщения Windows?

Используем сообщение WM_WININICHANGED в качестве примера. Объявление метода в TForm позволит вам обрабатывать сообщение WM_WININICHANGED:

procedure WMWinIniChange(var Message: TMessage); message WM_WININICHANGE;

Код в implementation может выглядеть так:

procedure TForm1.WMWinIniChange(var Message: TMessage); 

begin 

  inherited; 

{ ... ваша реакция на событие ... } 

end;

Вызов inherited метода очень важен. Обратите внимание также на то, что для функций, объявленных с директивой message (обработчиков событий Windows) после inherited нет имени наследуемой процедуры, потому что она может быть неизвестна или вообще отсутствовать (в этом случае вы в действительности вызываете процедуру DefaultHandler).


8. Как обработать события от других приложений?

Попробуйте сделать это следующим образом:

type 

  TForm1 = class(TForm) 

  ... 

  private 

    procedure WMNCActivate(var Msg: TMessage); message WM_NCACTIVATE; 

  end; 

procedure TForm1.WMNCActivate(var Msg: TMessage); 

begin 

{ здесь обработка принятых событий } 

end; 


9. Как перехватить сообщения Windows и обработать их перед тем, как выполнится строка Application.Run?

Пример проекта показывает, как получить сообщения Windows в данном случае. Это редкий случай, в большинстве случаев переопределение процедуры Application.OnMessage будет делать то же самое.

program Project1; 

uses 

  Forms, 

  Unit1 in 'UNIT1.PAS' { Form1 }

  Messages, WinTypes, WinProcs, 

{$R *.RES} 

var 

  OldWndProc: TFarProc; 

function NewWndProc(hWndAppl: HWnd; Msg, wParam: Word; lParam: Longint): Longint; export; 

begin 

{ default WndProc return value } 

  Result := 0; 

{ handle messages here; the message number is in Msg } 

  Result := CallWindowProc(OldWndProc, hWndAppl, Msg, wParam, lParam); 

end; 

begin 

  Application.CreateForm(TForm1, Form1); 

  OldWndProc := TFarProc(GetWindowLong(Application.Handle, GWL_WNDPROC)); 

  SetWindowLong(Application.Handle, GWL_WNDPROC, Longint(@NewWndProc)); 

  Application.Run; 

end. 


10. Проблема с DragDrop для внешних программ.

Я пишу небольшую программку — "мусорную корзину". В FormCreate вызывается DragAcceptFiles(HANDLE, True). Проблема в том, что когда размер окна восстанавливается и затем минимизируется Drag and Drop перестает работать. Я безуспешно пробовал помещать DragAcceptFiles в разные методы формы. Однако если сделать вызов DragAcceptFiles(Application.Handle, True) в MainForm.Create, то все работает. Как перехватить событие WM_DROPFILES?

Это можно сделать так:

type 

  TMainForm = class(TForm) 

  ... 

    procedure FormCreate(Sender: TObject); 

  private 

    procedure DropFiles(var Msg : TWMDropFiles); message WM_DROPFILES; 

  end; 


procedure TMainForm.DropFiles(var Msg : TWMDropFiles); 

begin 

  DragQueryPoint(Msg.Drop, Point); 

  NrOfFiles := DragQueryFile(Msg.Drop, Word(-1), FileName, BufSize); 

  DragQueryFile(Msg.Drop, 0, FileName, BufSize); 

end; 

procedure TMainForm.FormCreate(Sender: TObject); 

begin 

  DragAcceptFiles(Handle, True); 

end; 

Подробнее о перехвате событий Windows см. Главу 7 руководства Component Writers Guide.


11. Как обрабатывать WM_DROPFILES (Drag/Drop)?

Следующий код показывает как обрабатывать это событие. Обрабатываются имена всех "брошенных" файлов. Для загрузки каждого файла вызывается CreateChild(FName). В обработчике OnCreate данной формы вы должны вызвать DragAcceptFiles.


type 

  TFrameForm = class(TForm) 

  ... 

  protected 

    procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES; 

  end; 

procedure TFrameForm.WMDropFiles(var Msg : TMessage); 

var 

  I, N, Size: Word; 

  FName: string; 

  HDrop: Word; 

begin 

  HDrop := Msg.WParam; 

  N := DragQueryFile(HDrop, $FFFF, nil, 0); 

  for I := 0 to (N-1) do 

  begin 

    Size := DragQueryFile(HDrop, I, nil, 0); 

    if Size lt; 255 then { 255 char. string limit - not really a problem } 

    begin 

      FName[0] := Chr(Size); 

      DragQueryFile(HDrop, I, @FName[1], Size+1); 

      CreateChild(FName); 

    end; 

  end; 

  Msg.Result := 0; 

  inherited; 

end; 


12. Как может выделить время CPU другим задачам , подобно "DoEvents" в VB?

Эквивалент в Delphi — Application.ProcessMessages.

Если вы выполняете долгие вычисления, то вызов данного метода позволит в Win 16 выполняться параллельно другим приложениям, а в Win 32 - корректно перерисовываться вашему приложению.


13. В каком порядке происходят события при создании и показе окна?

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

• OnCreate

• OnShow

• OnPaint

• OnActivate

• OnResize

• OnPaint (снова)


14. UpCase для русского языка.

Данная функция (UpCase) производит преобразование только латинских символов в верхний регистр. Для правильного преобразования необходимо использовать функции Windows API, поскольку именно Windows должна "знать" о кодировке национальных символов. Причем к конфигурации BDE кодровка Windows не имеет никакого отношения — имея английские Windows без русификатора и выставив в BDE кодировку Paradox ANSII Cyrillic нормальных русских букв получить не удастся.

А функции для преобразования следующие — OemToAnsi, AnsiToOem, OemToAnsiBuf, AnsiToOemBuf в Win16 (модуль WinProcs) и OemToChar, CharToOem, OemToCharBuf и CharToOemBuf в Win32 (модуль Windows)..


15. Приложение, написанное на Delphi, не запускается минимизированным.

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

procedure TForm1.FormCreate(Sender: TObject); 

begin 

  if CmdShow = SW_SHOWMINNOACTIVE then WindowState := wsMinimized; 

end;

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

procedure TForm1.FormCreate(Sender: TObject);

begin 

  if CmdShow = SW_SHOWMINNOACTIVE then WindowState := wsMinimized 

  else WindowState := wsMaximized; 

end;


16. Объясните разницу в помещении uses в секцию interface или implementation.

Секция interface — интерфейсная. Туда попадают объявления констант, типов (в т.ч. и объектов или классов) переменных, процедур и функций. Поэтому для этой части uses должен содержать ссылки на те модули, которые используются для объявлений в этой части.

Секция implementation — описание реализации интерфейсной части, здесь в uses должны быть упомянуты те модули, которыми вы пользуетесь для написания кода. Например, Вы хотите в модуле пользоваться функциями API Windows, для этого добавьте в объявлении implementation строку uses WinTypes, WinProcs; или uses Windows;. Таким образом, вы явно указываете что данными модулями будете пользоваться только в секции реализации.

Конечно, можно упоминать модули только в части interface, но правильная расстановка имен модулей в соответствующем uses гарантирует исключение циклических ссылок, а также улучшает читаемость программы.


17. Как спрятать окна MDI Child?

Я пытаюсь это сделать, выставляя Form1.Visible := False, но это не помогает.

Windows не позволяет прятать окна MDI Child.


18. Как убрать заголовок у формы MDIChild?

Как убрать заголовок (Caption) из MDIChild?

Для MDIChild установка свойства BorderStyle := bsNone не убирает заголовок. Это можно сделать так:

procedure TMDIChildForm.CreateParams(var Params: TCreateParams); 

begin 

  inherited CreateParams(Params); 

  Params.Style := Params.Style and (not WS_CAPTION); 

end;


19. Сохранение данных в Clipboard.

Мне нужно использовать clipboard для сохранения данных в собственном формате и я хочу для этого написать набор процедур ввода/вывода с использованием потоков (streams). Возможно ли создать объект TMemoryStream, эаполнить его и поместить в Clipboard?

Не только возможно, именно так поступают функции Clipboard.GetComponent и Clipboard.SetComponent. Сначала вы должны зарегистрировать свой собственный формат данных для Clipboard с помощью функции RegisterClipboardFormat:

CF_MYFORMAT := RegisterClipboardFormat('My Format Description'); Далее вы должны выполнить шаги:

1. Создать поток (memory stream) и записать туда данные.

2. Создать глобальный буфер в памяти и скопировать поток туда.

3. Вызвать Clipboard.SetAsHandle(), чтобы поместить буфер в Clipboard.

Пример:

var 

  hBuf: THandle; 

  Bufptr: Pointer; 

  MStream: TMemoryStream; 

begin 

  MStream := TMemoryStream.Create; 

  try 

  { write your data to the stream } 

    hBuf := GlobalAlloc(GMEM_MOVEABLE, MStream.Size); 

    try 

      BufPtr := GlobalLock(hBuf); 

      try 

        Move(MStream.Memory^, BufPtr^, MStream.Size); 

        Clipboard.SetAsHandle(CF_MYFORMAT, hBuf); 

      finally 

        GlobalUnlock(hBuf); 

      end; 

    except 

      GlobalFree(hBuf); 

      raise; 

    end; 

  finally 

    MStream.Free; 

  end; 

end; 

Внимание: не уничтожайте буфер, созданный с GlobalAlloc. Поскольку вы поместили его в Clipboard, это уже дело clipboard'а его уничтожить. Опять же, получая буфер из Clipboard, не уничтожайте этот буфер - просто сделайте копию содержимого.

Для обратного получения потока и данных, сделайте что-нибудь вроде этого:

var 

  hBuf: THandle; 

  BufPtr: Pointer; 

  MStream: TMemoryStream; 

begin 

  hBuf := Clipboard.GetAsHandle(CF_MYFORMAT); 

  if hBuf lt;gt; 0 then 

  begin 

    BufPtr := GlobalLock(hBuf); 

    if BufPtr lt;gt; nil then 

    try 

      MStream := TMemoryStream.Create; 

      try 

        MStream.WriteBuffer(BufPtr^, GlobalSize(hBuf)); 

        MStream.Position := 0; 

      { read your data from the stream } 

      finally 

        MStream.Free; 

      end; 

    finally 

      GlobalUnlock(hBuf); 

    end; 

  end; 

end; 


20. Что означает Keylt;gt;#0 ? 

В исходном тексте одного из компонентов третьих фирм я увидел строку:

if Key lt;gt; #0 then inherited KeyPress(#0);

В Windows виртуальные коды находятся в диапазоне 1-145 (Dec). Зачем нужна такая проверка?

В соответствии с соглашением Windows код клавиши #0 означает отсутствие реального нажатия. Управление в данную точку программы могло попасть, например вследствие прямого вызова, а не нажатия клавиши или же нажатие уже было обработано предком, вследствие чего код нажатой клавиши был сброшен в 0.


21. Аналог процедуры TP/BP Delay.

procedure TForm1.Delay(MSecs: Longint); 

var 

  FirstTick: Longint; 

begin 

  FirstTick := GetTickCount; 

  repeat 

    Application.ProcessMessages; 

  until GetTickCount - FirstTick gt;= MSecs; 

end; 

В Win32 API существуют также функции Sleep и SleepEx.


22. Каким образом создать форму, которую можно таскать за поле?

Как сделать форму (окно), которое перетаскивается не за заголовок (Сaption), а за все поле ?

Нужно обрабатывать сообщение WM_NCHITTEST:

type 

  TForm1 = class(TForm) 

  ... 

  private 

    procedure WMNCHitTest(var M: TWMNCHitTest); message WM_NCHITTEST; 

  end; 

procedure TForm1.WMNCHitTest(var M: TWMNCHitTest); 

begin 

  inherited;                  { вызов унаследованного обработчика      } 

  if M.Result = htClient then { Мышь сидит на окне?                    } 

     M.Result := htCaption;   { Если да - то пусть Windows думает, что } 

                              { мышь на caption bar                    } 

end; 

Примечание: окно можно сделать вообще без Сaption.


23. Как программно спрятать или показать заголовок у формы?

Как программно спрятать или показать заголовок (Caption) у формы?

Вы можете попробовать следующее:

procedure TForm1.HideTitlebar; 

var 

  Save: Longint; 

begin 

  if BorderStyle=bsNone then Exit; 

  Save := GetWindowLong(Handle, GWL_STYLE); 

  if (Save and WS_CAPTION) = WS_CAPTION then 

  begin 

    case BorderStyle of 

      bsSingle, bsSizeable: 

        SetWindowLong(Handle, GWL_STYLE, Save and (not WS_CAPTION) or WS_BORDER); 

     bsDialog: 

        SetWindowLong(Handle, GWL_STYLE, Save and (not WS_CAPTION) or DS_MODALFRAME or WS_DLGFRAME); 

    end; 

    Height := Height-GetSystemMetrics(SM_CYCAPTION); 

    Refresh; 

  end; 

end; 

procedure TForm1.ShowTitlebar; 

var 

  Save: Longint; 

begin 

  if BorderStyle = bsNone then Exit; 

  Save := GetWindowLong(Handle, GWL_STYLE); 

  if (Save and WS_CAPTION) lt;gt; WS_CAPTION then 

  begin 

    case BorderStyle of 

      bsSingle, bsSizeable: 

        SetWindowLong(Handle, GWL_STYLE, Save or WS_CAPTION or WS_BORDER); 

      bsDialog: 

        SetWindowLong(Handle, GWL_STYLE, Save or WS_CAPTION or DS_MODALFRAME or WS_DLGFRAME); 

    end; 

    Height := Height + GetSystemMetrics(SM_CYCAPTION); 

   Refresh; 

  end; 

end; 

24. Как сделать приложение модальным?

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

Ok, пара предложений на эту тему:

1. Создайте форму, занимающую весь экран (maximized) без системных кнопок (Maximize, Minimize, System)

2. В обработчике FormDeactivate для формы вызовите метод SetFocus — это предотвратит Ctrl+Esc:

Form1.SetFocus;

3. В обработчике события FormActivate, нужно присвоить метод Deactivate для приложения:

Application.OnDeactivate := FormDeactivate;

4. Создайте всплывающее меню TPopupMenu с единственным пунктом. В свойствах данного компонента нужно установить Visible=False. Создайте процедуру для этого пункта меню, и в теле поставьте две фигурные скобки {} (для того, чтобы Delphi не удалил эту процедуру)

5. Присвойте созданное Popup-меню форме (св-во PopupMenu)

6. Задайте горячую клавишу (shortcut) для Popup-меню в методе FormActivate как показано ниже:

NullItem1.ShortCut := ShortCut(VK_Tab, [ssAlt]);

(NullItem1 нужно заменить на название созданного вами объекта — пункта меню)

Шаги 4-6 предотвращают переход на приложение по Alt-Tab.


25. Как изменить шрифт у Application.Title (заголовка приложения)?

Никак. Это ограничение Windows — вы не можете изменить шрифт ни у одного заголовка ни у приложения, ни у окна. Для окна можно предложить следующее — создать свое окно без заголовка (Caption) и рамки, которое будет само выводить нужную надпись нужным шрифтом и одновременно будет способно изменять свои размеры.


26. Каким образом (желательно не специфичным для Delphi) узнать, открыто меню или нет?

Вот так:

type 

  TForm1 = class(TForm) 

    MainMenu1: TMainMenu; 

    Item01: TMenuItem; 

    Item11: TMenuItem; 

    Item21: TMenuItem; 

  private 

    { Private declarations } 

  public 

    procedure WMMenuSelect(var M: TWMMenuSelect); message WM_MENUSELECT; 

  end; 

implementation 

{$R *.RES} 

procedure TForm1.WMMenuSelect(var M: TWMMenuSelect); 

begin 

  inherited; 

{ Этот Beep сигнализирует вообще об открытии меню } 

  MessageBeep(MB_ICONASTERISK); 

{ А зтот Beep - только о выборе в меню нового Item } 

  if M.Menu = MainMenu1.Handle then MessageBeep(MB_ICONASTERISK); 

end; 

end.