"Виртуальная библиотека Delphi" - читать интересную книгу автора
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. Там есть информация, как вызвать собственный метод для события.
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:
Вызов inherited метода очень важен. Обратите внимание также на то, что для функций, объявленных с директивой message (обработчиков событий Windows) после inherited нет имени наследуемой процедуры, потому что она может быть неизвестна или вообще отсутствовать (в этом случае вы в действительности вызываете процедуру DefaultHandler).
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);
Я пишу небольшую программку — "мусорную корзину". В FormCreate вызывается DragAcceptFiles(HANDLE, True). Проблема в том, что когда размер окна восстанавливается и затем минимизируется Drag and Drop перестает работать. Я безуспешно пробовал помещать DragAcceptFiles в разные методы формы. Однако если сделать вызов DragAcceptFiles(Application.Handle, True) в MainForm.Create, то все работает. Как перехватить событие WM_DROPFILES?
Подробнее о перехвате событий Windows см. Главу 7 руководства Component Writers Guide.
11. Как обрабатывать WM_DROPFILES (Drag/Drop)?
Следующий код показывает как обрабатывать это событие. Обрабатываются имена всех "брошенных" файлов. Для загрузки каждого файла вызывается CreateChild(FName). В обработчике OnCreate данной формы вы должны вызвать DragAcceptFiles.
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 не убирает заголовок. Это можно сделать так:
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), а за все поле ?
Мне нужно сделать приложение модальным, для того чтобы обезопасить систему и в то же время позволить работать с программой.
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) узнать, открыто меню или нет?