home   |   А-Я   |   A-Z   |   меню


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

И те и другие. 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

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


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

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

Если вы используете числа с плавающей точкой, лучше передавать их не по значению, а по ссылке (указатель в 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 для внешних программ.

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

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

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 < 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?

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

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


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

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

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

procedure TMDIChildForm.CreateParams(var Params: TCreateParams); 

begin 

  inherited CreateParams(Params); 

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

end;


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

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

Не только возможно, именно так поступают функции 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 <> 0 then 

  begin 

    BufPtr := GlobalLock(hBuf); 

    if BufPtr <> 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. Что означает Key<>#0 ? 

Виртуальная библиотека Delphi

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

if Key <> #0 then inherited KeyPress(#0);

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

Виртуальная библиотека Delphi

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


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

procedure TForm1.Delay(MSecs: Longint); 

var 

  FirstTick: Longint; 

begin 

  FirstTick := GetTickCount; 

  repeat 

    Application.ProcessMessages; 

  until GetTickCount - FirstTick >= MSecs; 

end

Виртуальная библиотека Delphi

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


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

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

Нужно обрабатывать сообщение 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. Как программно спрятать или показать заголовок у формы?

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

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

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) <> 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. Как сделать приложение модальным?

Виртуальная библиотека Delphi

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

Виртуальная библиотека Delphi

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.


Общие вопросы по Delphi и данному FAQ (часть 3) | Виртуальная библиотека Delphi | Разное