Советы по Delphi

         

FAQ 7, Объектный паскаль


Список вопросов
------------------------------
  1. Я использую форму из репозитория и при компиляции получаю ошибку 'unknown identifier' (неизвестный идентификатор). В чем дело?
  2. Могу ли я пользоваться стандартным выводом writeln?
  3. Как мне преобразовать число с плавающей точкой (например, имеющее тип real) в целое?
  4. Как мне сделать так, чтобы при нажатии пользователем клавиши <enter> управление передавалось бы следующему элементу управления, как это происходит при нажатиях клавиши Tab? (Примечание: Это не будет работать в DBGrid, поскольку очередное поле не является отдельным объектом.)
  5. Как в компоненте DBGrid использовать клавишу Enter вместо клавиши Tab?
  6. Как мне обработать параметры командной строки в Delphi-приложении?
  7. Как мне выбрать определенное поле TDBGrid для получения фокуса?
  8. У меня есть таблица paradox с паролем. Что мне необходимо сделать, чтобы форма, использующая эту таблицу, не запрашивала у пользователя пароль?
  9. Как мне сделать форму, не зависящую от разрешения экрана?
  10. Как можно производить вычисления с датой в калькулируемых полях?
  11. Как с помощью Delphi вычислить экспоненту?
  12. Как при запуске приложения сворачивать форму в иконку?
  13. У меня есть форма, которая, в своем роде, шаблон. Я хочу использовать форму несколько раз, но выводя каждый раз разные данные. Каким образом мне можно находить и изменять свойства расположенных на ней компонентов?
  14. Как определить класс компонента, при щелчке на котором появляется контекстное меню?
  15. Как мне захватить и обработать системное сообщение перед тем как выполнится моя строка application.run?
  16. Как мне использовать оператор case для определения того, какой объект вызвал данную процедуру?
  17. Существует ли функция прокрутки формы с помощью клавиш? Например, прокрутка вверх и вниз при нажатии PgUp и PgDown соответственно. Существует простое решение или необходимо перехватывать клавиши и реагировать на них вручную?
  18. Как мне передать тект компонента TMemo в поле TMemofield таблицы Paradox?
  19. Как мне из таблицы сделать таблицу с ASCII текстом фиксированной длины?
  20. Как мне установить формат для поля данных?
  21. Как мне отформатировать число так, чтобы запятая отделяла тысячи?
  22. У меня есть функция, сильно загружающая процессор. Я хочу организовать проверку того, не нажал ли пользователь кнопку отмены? Как мне сделать это, если в это время окно "зависает"?
  23. Как мне получить наибольший и наименьший байт слова? А как осуществить вставку?
  24. У меня есть OBJ-файл, где скомпилированы несколько ассемблерных программ. Я не хотел бы их снова перезаписывать. Есть какой-либо способ использовать этот файл в приложении Delphi?
  25. Какую модель памяти использует Delphi?
  26. Как мне быть, если компонент не обрабатывает системное сообщение?
  27. Borland решил, что доступ к переменным среды из программ Windows это плохо. Я хотя бы могу воспользоваться "устаревшим" модулем WinDos?
  28. Как мне создать простейший хранитель экрана (например, черный экран) с помощью Delphi?
  29. Как мне определить длину строки в пикселах с определенным шрифтом?
  30. Какое наилучшее место в коде программы, откуда можно вызвать окно с логотипом программы при ее запуске?
  31. Как вызвать функцию из DLL?
  32. Что такое функция Callback и как мне создать ее?
Вопросы и ответы
---------------------

  1. Я использую форму из репозитория и при компиляции получаю ошибку 'unknown identifier' (неизвестный идентификатор). В чем дело? Если вы изменяете имя какого-либо субъекта, код, связанный с ним, также необходимо изменить. Delphi не может скомпилировать код, так как не может догадаться о ваших намерениях. Поэтому, взяв форму из репозитория, проверьте соответствие имен, используемых на форме с именами в вашем коде и наличие необходимых функций и обработчиков. Просто внимательно проверьте ваш код и сделайте соответствующие изменения.

  2. Могу ли я пользоваться стандартным выводом writeln? Включите WinCRT в список используемых модулей и пользуйтесь на здоровье.

  3. Как мне преобразовать число с плавающей точкой (например, имеющее тип real) в целое? Вот пара методов:

    vari: integer;r: real;
    beginr := 34.56;i := trunc(r); {i = 34} {Первый метод}i := round(r); {i = 35} {Второй метод}end;


  4. Как мне сделать так, чтобы при нажатии пользователем клавиши <enter> управление передавалось бы следующему элементу управления, как это происходит при нажатиях клавиши Tab? (Примечание: Это не будет работать в DBGrid, поскольку очередное поле не является отдельным объектом.) Вам необходимо перехватывать нажатие клавиши и устанавливать ваш собственный ответ на это. Попробуйте:

    procedure TMainForm.FormCreate(Sender: TObject);beginkeyPreview := true; {"Включаем" обработку.}end;
    procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);beginif Key = #13 thenbeginKey := #0;PostMessage(Handle, WM_NEXTDLGCTL, 0, 0);end;end;
  5. Как в компоненте DBGrid использовать клавишу Enter вместо клавиши Tab? Действие клавиши Tab происходит после возникновения события OnKeyDown, а клавиша Enter "действует" уже в OnKeyPress. Следовательно, клавиша Enter должна приниматься во внимание в обоих обработчиках для достижения желаемого эффекта. Попробуйте:

    procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift:TShiftState);beginif Key = VK_RETURN then Key = VK_TAB;end;
    procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);beginif Key = #13 then Key := #9;end;
    <


    /p>
  6. Как мне обработать параметры командной строки в Delphi-приложении? Пользуйтесь методами ParamCount и ParamString.

  7. Как мне выбрать определенное поле TDBGrid для получения фокуса? Используйте следующий код:

    DBGrid1.SelectedField := Table1SomeField;DBGrid1.SetFocus;
  8. У меня есть таблица paradox с паролем. Что мне необходимо сделать, чтобы форма, использующая эту таблицу, не запрашивала у пользователя пароль? Установите в компоненте Table свойство ACTIVE в FALSE. Разместите следующий код в обработчике создания формы:

    session.AddPassword('Мой секретный пароль');table1.active := true;
  9. Как мне сделать форму, не зависящую от разрешения экрана? Вот некоторый код, показывающий как это можно сделать:

    constScreenHeight: Integer = 800; {Я разрабатывал мою форму в режиме 800x600.}ScreenWidth: Integer = 600;
    procedure TForm1.FormCreate(Sender: TObject);varx, y: LongInt; {Целое не будет достаточной для этого величиной.}
    beginform1.scaled := true;x := getSystemMetrics(SM_CXSCREEN);y := getSystemMetrics(SM_CYSCREEN);if (x <> ScreenHeight) or (y <> ScreenWidth) thenbeginform1.height := form1.height * x DIV ScreenHeight;form1.width := form1.width * y DIV ScreenWidth;scaleBy(x, ScreenHeight);end;end;
    Примечание: вы можете расширить данный код с целью пропорционального изменения размеров шрифтов.

  10. Как можно производить вычисления с датой в калькулируемых полях? При работе с датами в вычисляемых полях важна гарантия того, что все применяемые значения имеют нужный тип. Метод double (не документирован) приводит величину к необходимому типу.

    В приведенном ниже методе d1 и d2 (часть table1) могут быть или датой (date) или иметь тип dateTime, d3 - целочисленное поле.

    procedure TForm1.Table1CalcFields(DataSet: TDataset);vart1, t2: tDateTime;begintable1d1.asDateTime := Date + 2; {или table1d1.value := date + 2;}table1d2.asDateTime := Date - 2;t1 := table1d1.asDateTime;t2 := table1d2.asDateTime;table1d3.asInteger := trunc(double(t1) - double(t2));end;
    <


    /p>
  11. Как с помощью Delphi вычислить экспоненту? Для ExpXY(X, Y) (например, Y^X), попробуйте:
    ExpXY := Exp(Y * Ln(X))
    Для использования функции объявите формальные параметры и результат функции как Extended, преобразование фактических параметров и возвращаемого функцией результата происходит автоматически. (Примечание: Ln - натуральный логарифм числа).

  12. Как при запуске приложения сворачивать форму в иконку? В секции private класса формы поместите:

    PROCEDURE WMQueryOpen(VAR Msg : TWMQueryOpen); message WM_QUERYOPEN;
    В секции implementation создайте реализацию метода:

    PROCEDURE TForm1.WMQueryOpen(VAR Msg : TWMQueryOpen);beginMsg.Result := 0;end;
    Это все! Форма всегда будет в виде иконки. BTW, естественно, вы при этом вы должны установить свойство формы WindowState в wsMinimized.

  13. У меня есть форма, которая, в своем роде, шаблон. Я хочу использовать форму несколько раз, но выводя каждый раз разные данные. Каким образом мне можно находить и изменять свойства расположенных на ней компонентов? К примеру, создадим независимое окно:

    with TMyForm.create(self) do show;
    Теперь нам нужно управлять этим. Вот один из способов изменения заголовка каждой созданной формы. Осуществим это через массив компонентов формы. В качестве другой формы данный пример использует диалоговое окно "О программе" (с именем "box").

    procedure TForm1.Button1Click(Sender: TObject);
    beginwith tBox.create(self) do show;{Необходимо ComponentCount-1 поскольку отсчет идет с нуля}(form1.Components[form1.ComponentCount-1] as tForm).caption :='About Box # ' + intToStr(form1.ComponentCount-2);end;
  14. Как определить класс компонента, при щелчке на котором появляется контекстное меню? Используйте свойство PopupComponent компонента PopupMenu для определения компонента, на котором была нажата правая клавиша мыши.

    procedure TForm1.PopupItem1Click(Sender: TObject);beginLabel1.Caption := PopupMenu1.PopupComponent.ClassName;end;
    Можно использовать свойство формы ActiveControl, но компонент, вызвавший контекстное меню, не обязательно должен быть активным элементом управления.




  15. Как мне захватить и обработать системное сообщение перед тем как выполнится моя строка application.run? Нижеследующий исходный код проекта демонстрирует способ получения системных сообщений перед тем, как будут вызваны оконные процедуры приложения. Необходимость в этом неочевидна, но иногда все же может возникнуть. В большинстве случаев для этой цели достаточно обработать сообщение Application.OnMessage.

    program Project1;
    usesForms, messages, wintypes, winprocs,Unit1 in 'UNIT1.PAS' {Form1};

    {$R *.RES}
    varOldWndProc: TFarProc;
    function NewWndProc(hWndAppl: HWnd; Msg, wParam: Word;lParam: Longint): Longint; export;beginresult := 0; { Значение, возвращаемое WndProc по умолчанию }{*** Здесь дескриптор сообщения; Номер сообщения в Msg ***}result := CallWindowProc(OldWndProc, hWndAppl, Msg, wParam, lParam);end;
    beginApplication.CreateForm(TForm1, Form1);OldWndProc := TFarProc(GetWindowLong(Application.Handle,GWL_WNDPROC));SetWindowLong(Application.Handle, GWL_WNDPROC,longint(@NewWndProc));Application.Run;end.
  16. Как мне использовать оператор case для определения того, какой объект вызвал данную процедуру? Используйте свойство объекта TAG. Для группирования объектов по определенным признакам вы можете установить значение свойства tag каждого объекта. (Идеальным было бы использование констант, описывающих объект.)

    Примечание: код подразумевает, что только tButton может вызвать эту процедуру.

    case (sender as tButton).tag of0: blah;1: blah_blah;end;
  17. Существует ли функция прокрутки формы с помощью клавиш? Например, прокрутка вверх и вниз при нажатии PgUp и PgDown соответственно. Существует простое решение или необходимо перехватывать клавиши и реагировать на них вручную? Прокрутка формы осуществляется с помощью изменения свойства Position свойства формы VertScrollbar или HorzScrollbar. Следующий код показывает как это можно сделать:

    procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;Shift: TShiftState);constPageDelta = 10;beginWith VertScrollbar doif Key = vk_Next then Position := Position+PageDeltaelse if Key = vk_Prior then Position := Position-PageDelta;end;
    <


    /p>
  18. Как мне передать тект компонента TMemo в поле TMemofield таблицы Paradox? Вот пример:

    procedure TForm1.Button1Click(Sender: TObject);vart: TTable;begint := TTable.create(self);with t dobeginDatabaseName := 'MyAlias'; {персональный псевдоним}TableName := 'MyTbl.db';open;edit;insert;fieldByName('TheField').assign(memo1.lines); {Вот оно!}post; {требуется!!!}close;end; { Конец with-блока. }end;
  19. Как мне из таблицы сделать таблицу с ASCII текстом фиксированной длины? Можно так:

    procedure TForm1.Button1Click(Sender: TObject);vart1, t2: tTable; {t1 = PW таблица; t2 = ASCII версия}begint1 := tTable.create(self);with t1 dobeginDataBaseName := 'pw'; { Персональный псевдоним для каталога Paradox }tableName := 'customer.db'; { Исходная таблица }open;end;t2 := tTable.create(self);with t2 dobeginDataBaseName := 'pw'; { Персональный псевдоним для каталога Paradox }tableName := 'asdf.txt';TableType := ttASCII;
    createTable;open;edit;BatchMove(t1, batCopy);close;end;t1.close;end;
  20. Как мне установить формат для поля данных? Дважды щелкните на компоненте Table. Выберите поле, которое вы хотите форматировать. Далее воспользуйтесь свойствами DisplayFormat и EditFormat. DisplayFormat работает когда поле не имеет фокуса. EditFormat работает когда поле имеет фокус. Пользуйтесь теми же командами, что и для первого параметра функции FormatFloat, только без кавычек.

  21. Как мне отформатировать число так, чтобы запятая отделяла тысячи? Если вы не используете Delphi, попробуйте следующий код:

    function FormatNumber(l: longint): string;varlen, count: integer;s: string;beginstr(l, s);len := length(s);for count := ((len - 1) div 3) downto 1 dobegininsert(',', s, len - (count * 3) + 1);len := len + 1;end;FormatNumber := s;end;
    Если вы пользуетесь Delphi, то, естественно, существует более простое решение:

    function FormatNumber(l: longint): string;
    beginFormatNumber := FormatFloat('#,##0', StrToFloat(IntToStr(l)));end;
  22. У меня есть функция, сильно загружающая процессор. Я хочу организовать проверку того, не нажал ли пользователь кнопку отмены? Как мне сделать это, если в это время окно "зависает"? Время от времени производите в вашем коде вызов процедуры Application.ProcessMessages.




  23. Как мне получить наибольший и наименьший байт слова? А как осуществить вставку? Существуют встроенные методы hi() и lo(), но если вы хотите знать как это делается и попытаться сделать по-другому... (хотя вряд ли вы сможете увеличить быстродействие <G>). Функция для вставки в Delphi отсутствует.

    Примечание: Функции ассемблера возвращают содержимое регистра AX.

    function GetHiByte(w: word): byte; assembler;asmmov ax, wshr ax, 8end;
    function GetLoByte(w: word): byte; assembler;asmmov ax, wend;
    function SetHiByte(b: byte; w: word): word; assembler;asmxor ax, axmov ax, wmov ah, bend;
    function SetLoByte(b: byte; w: word): word; assembler;asmxor ax, axmov ax, wmov al, bend;
    Другой путь сделать это (с примером использования):

    TypeTWord2Byte = recordLo,Hi: Byte;end;
    var W: Word;B: Byte;beginW := $1234;B := TWord2Byte(W).Hi;writeln(TWord2Byte(W).Hi);{ возвращаем }TWord2Byte(W).Lo := $67;TWord2Byte(W).Hi := $98; { shl не нужен! }end.
  24. У меня есть OBJ-файл, где скомпилированы несколько ассемблерных программ. Я не хотел бы их снова перезаписывать. Есть какой-либо способ использовать этот файл в приложении Delphi? Вы не указали, должны ли возвращаться значения или нет (в Pascal это важно.)

    Если нет, сделайте такое объявление:

    Procedure TurnOn; External;Procedure TurnOff; External;
    Если возвращаемые значения присутствуют, объявление должно быть таким:

    Function TurnOn: Integer; External;Function TurnOff: Integer; External;
    Замените Integer возвращаемым типом данных. В любом случае вам необходимо прилинковать ваш OBJ-файл:

    {$L filename.obj}
    Смотрите также в электронной справке раздел "linking external assembler code".

  25. Какую модель памяти использует Delphi? Delphi использует смешанную модель памяти, но это очень близко к большой модели "C". Установки по умолчанию:


    • Модель дальнего вызова методов
    • Процедуры в секции интерфейса также используют модель дальнего вызова
    • Только используемые процедуры в секции реализации имеют модель ближнего вызова
    • Данные кучи и все указатели (включая экземпляры класса) в основном пользуются модель дальнего вызова
    • Глобальные переменные - ближний вызов (DS based)
    • Процедурные параметры и локальные переменные - ближний вызов (SS based)
    • Процедуры объявленные как FAR или EXPORT используют дальний вызов
    • Таблицы виртуальной памяти - дальний вызов для новой модели класса и ближний для старой


      Данная схема используется в Borland Pascal уже долгое время. Я нахожу это гибким и эффективным.

      Именно поэтому все public-процедуры, методы и указатели уже готовы к эксплуатации на 32-битных платформах, Delphi32 не должна в них ничего менять. Похоже, Delphi32 также переключится на 32-битную адресацию данных и сегментов стека, что также не должно повлиять на ваши программы. Все, что может повлиять - изменение разрядности целого с 16 на 32 бита.

    • Как мне быть, если компонент не обрабатывает системное сообщение? Например, для того, чтобы ваш компонент реагировал на сообщение wm_size, добавьте следующую строку:

      procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
      В секции реализации все происходит примерно так:

      procedure TWhateverComponent.WMPaint(var Message: TWMPaint);begin{Теперь заставьте ваш компонент реагировать на событие должным образом}end;
      Это работает для любого системного сообщения. Большинство компонентов реагируют на наиболее популярные сообщения, и вы можете изменить поведение компонента, перекрыв или расширив его обработчик.

    • Borland решил, что доступ к переменным среды из программ Windows это плохо. Я хотя бы могу воспользоваться "устаревшим" модулем WinDos? Используйте API-вызов GetDOSEnvironment().

    • Как мне создать простейший хранитель экрана (например, черный экран) с помощью Delphi? Вот простейшая реализация хранителя экрана:

      varf: tForm;
      beginf := tForm.create(self);f.WindowState := wsMaximized;f.color := black;f.borderStyle := bsNone;f.show;end;
      Естественно, нужно сделать намного больше. Например, организовать обратную связь. Реально форма должна закрываться при щелчке на ней мыши. Также вам необходимо скрыть мышиный курсор. И. т.д., и т.п.. Но приведенный выше пример уже ответил на ваш вопрос.

    • Как мне определить длину строки в пикселах с определенным шрифтом? Для определения высоты и ширины строки в пикселах могут применяться два метода - TextHeigh и TextWidth. Данные методы могут быть доступны в компонентах, имеющих свойство Canvas, например, TForm. Компонент TPanel не имеет доступа к свойству Canvas, т.к. по умолчанию оно защищено (protected).

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

      function GetTextWidth(CanvasOWner: TForm; Text : String;TextFont : TFont): Integer;varOldFont : TFont;beginOldFont := TFont.Create;tryOldFont.Assign( CanvasOWner.Font );CanvasOWner.Font.Assign( TextFont );Result := CanvasOWner.Canvas.TextWidth(Text);CanvasOWner.Font.Assign( OldFont );finallyOldFont.Free;end;end;
      <


      /p>
    • Какое наилучшее место в коде программы, откуда можно вызвать окно с логотипом программы при ее запуске? Наилучшее место для показа окна с логотипом - в исходном коде проекта после первого FormCreate и перед Run. Этим мы осуществляем создание формы на лету и показ ее до момента фактического запуска приложения.

      program Project1;
      uses Forms, Unit1 in 'UNIT1.PAS' {Form1}, Splash;
      {$R *.RES}varSplashScreen : TSplashScreen; {в модуле Splash}
      beginApplication.CreateForm(TForm1, Form1);SplashScreen := TSplashScreen.Create(Application);trySplashScreen.Show;{осуществите остальные CreatForms или другиедействия прежде чем приложение запустится}SplashScreen.Close;finally {Убедитесь что окно с логотипом освобождается}SplashScreen.Free;end;Application.Run;end.
    • Как вызвать функцию из DLL? Для вызова функции вы должны знать ее точный синтакс и установленный тип. Например, для вызова при нажатии на кнопку функции CallMe, принимающей в качестве параметров два целых числа, возвращающей строку и находящейся в MyTest.Dll, вы могли бы использовать следующий код:

      procedure TForm1.Button1Click(Sender: TObject);
      typeTCallMeDll = function(a,b: Integer): string;
      varCallMeDll: TCallMeDll;FuncPtr: TFarProc;hDll: THandle;result: string;
      beginhDll:=LoadLibrary('Mytestdll.dll');FuncPtr:=GetProcAddress(hDLL,'CallMe');@CallMeDll:=FuncPtr;if @CallMeDll <> nil thenresult:=CallMeDll(4,5);FuncPtr:=nil;FreeLibrary(hDll);end;
      Обратите внимание на порядок действий: сначала мы нагружаем dll в память, затем получаем указатель на функцию, и затем назначаем его CallMeDll. В этой точке необходимо проверить, если ProcAdderss равен Nil, то вызов GetProcAddress был неудачным.

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

    • Что такое функция Callback и как мне создать ее? Функция косвенного вызова (Callback function) - одна из наиболее полезных реализаций вызовов в Windows. Вы определяете функцию в приложении, выполняющую некоторые действия, и передаете ее адрес в другую функцию, которая и осуществляет вызов первой функции, передавая ей параметры, с которыми она должна работать. Сделайте объявление в секции интерфейса:

      { В интерфейсе программы }
      typeTCallBackFunction = function(s: string): integer;CallMe(s: string): integer;
      <


      /p> И в секции реализации:

      { Программная реализация }
      procedure TestCallBack(CallBackFunction: TCallBackFunction); far; external 'Other';{ Имейте в виду, что 'other' - размещенная в Dll процедура с именем TestCallBack }
      function CallMe(s: PChar): integer;begin{ сделайте что-нибудь }CallMe := 1; { сделайте что-нибудь }end;
      procedure TForm1.Button1Click(Sender: TObject);beginTestCallBack(CallMe);end;
      Имейте в виду, что в 'Other' вы должны также объявить и использовать функциональный тип, например так:

      { в интерфейсе библиотеки Other }
      typeTMainFunction = function(s: string): integer;TestCallBack(MainFunc: TMainFunction);
      { в реализации библиотеки Other }
      TestCallBack(MainFunc: TMainFunction);varresult: integer;beginresult:=MainFunc('тест');end;
      [000583]



    Содержание раздела