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

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

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

       

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

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

        procedure TMainForm.FormCreate(Sender: TObject); begin keyPreview := true; {"Включаем" обработку.} end;
    procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char); begin if Key = #13 then begin Key := #0; PostMessage(Handle, WM_NEXTDLGCTL, 0, 0); end; end;

  • Как в компоненте DBGrid использовать клавишу Enter вместо клавиши Tab?
  • Действие клавиши Tab происходит после возникновения события OnKeyDown, а клавиша Enter "действует" уже в OnKeyPress. Следовательно, клавиша Enter должна приниматься во внимание в обоих обработчиках для достижения желаемого эффекта. Попробуйте:

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

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

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

        DBGrid1.SelectedField := Table1SomeField; DBGrid1.SetFocus;

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

        session.AddPassword('Мой секретный пароль'); table1.active := true;

  • Как мне сделать форму, не зависящую от разрешения экрана?
  • Вот некоторый код, показывающий как это можно сделать:

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

    Примечание: вы можете расширить данный код с целью пропорционального изменения размеров шрифтов.

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

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

        procedure TForm1.Table1CalcFields(DataSet: TDataset); var t1, t2: tDateTime; begin table1d1.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;

  • Как с помощью Delphi вычислить экспоненту?
  • Для ExpXY(X, Y) (например, Y^X), попробуйте:

        ExpXY := Exp(Y * Ln(X))

    Для использования функции объявите формальные параметры и результат функции как Extended, преобразование фактических параметров и возвращаемого функцией результата происходит автоматически. (Примечание: Ln - натуральный логарифм числа).

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

        PROCEDURE WMQueryOpen(VAR Msg : TWMQueryOpen); message WM_QUERYOPEN;

    В секции implementation создайте реализацию метода:

        PROCEDURE TForm1.WMQueryOpen(VAR Msg : TWMQueryOpen); begin Msg.Result := 0; end;

    Это все! Форма всегда будет в виде иконки. BTW, естественно, вы при этом вы должны установить свойство формы WindowState в wsMinimized.

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

        with TMyForm.create(self) do show;

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

        procedure TForm1.Button1Click(Sender: TObject);
    begin with tBox.create(self) do show; {Необходимо ComponentCount-1 поскольку отсчет идет с нуля} (form1.Components[form1.ComponentCount-1] as tForm).caption := 'About Box # ' + intToStr(form1.ComponentCount-2); end;

  • Как определить класс компонента, при щелчке на котором появляется контекстное меню?
  • Используйте свойство PopupComponent компонента PopupMenu для определения компонента, на котором была нажата правая клавиша мыши.

        procedure TForm1.PopupItem1Click(Sender: TObject); begin Label1.Caption := PopupMenu1.PopupComponent.ClassName; end;

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

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

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

    {$R *.RES}
    var OldWndProc: TFarProc;
    function NewWndProc(hWndAppl: HWnd; Msg, wParam: Word; lParam: Longint): Longint; export; begin result := 0; { Значение, возвращаемое WndProc по умолчанию } {*** Здесь дескриптор сообщения; Номер сообщения в 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.

  • Как мне использовать оператор case для определения того, какой объект вызвал данную процедуру?
  • Используйте свойство объекта TAG. Для группирования объектов по определенным признакам вы можете установить значение свойства tag каждого объекта. (Идеальным было бы использование констант, описывающих объект.)

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

        case (sender as tButton).tag of 0:  blah; 1:  blah_blah; end;

  • Существует ли функция прокрутки формы с помощью клавиш? Например, прокрутка вверх и вниз при нажатии PgUp и PgDown соответственно. Существует простое решение или необходимо перехватывать клавиши и реагировать на них вручную?
  • Прокрутка формы осуществляется с помощью изменения свойства Position свойства формы VertScrollbar или HorzScrollbar. Следующий код показывает как это можно сделать:

        procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); const PageDelta = 10; begin With VertScrollbar do if Key = vk_Next then  Position := Position+PageDelta else if Key = vk_Prior then Position := Position-PageDelta; end;

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

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

  • Как мне из таблицы сделать таблицу с ASCII текстом фиксированной длины?
  • Можно так:

        procedure TForm1.Button1Click(Sender: TObject); var t1, t2: tTable;  {t1 = PW таблица; t2 = ASCII версия} begin t1 := tTable.create(self); with t1 do begin DataBaseName := 'pw'; { Персональный псевдоним для каталога Paradox } tableName := 'customer.db'; { Исходная таблица } open; end; t2 := tTable.create(self); with t2 do begin DataBaseName := 'pw'; { Персональный псевдоним для каталога Paradox } tableName := 'asdf.txt'; TableType := ttASCII;
    createTable; open; edit; BatchMove(t1, batCopy); close; end; t1.close; end;

  • Как мне установить формат для поля данных?
  • Дважды щелкните на компоненте Table. Выберите поле, которое вы хотите форматировать. Далее воспользуйтесь свойствами DisplayFormat и EditFormat. DisplayFormat работает когда поле не имеет фокуса. EditFormat работает когда поле имеет фокус. Пользуйтесь теми же командами, что и для первого параметра функции FormatFloat, только без кавычек.

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

        function FormatNumber(l: longint): string; var len, count: integer; s: string; begin str(l, s); len := length(s); for count := ((len - 1) div 3) downto 1 do begin insert(',', s, len - (count * 3) + 1); len := len + 1; end; FormatNumber := s; end;

    Если вы пользуетесь Delphi, то, естественно, существует более простое решение:

        function FormatNumber(l: longint): string;
    begin FormatNumber := FormatFloat('#,##0', StrToFloat(IntToStr(l))); end;

  • У меня есть функция, сильно загружающая процессор. Я хочу организовать проверку того, не нажал ли пользователь кнопку отмены? Как мне сделать это, если в это время окно "зависает"?
  • Время от времени производите в вашем коде вызов процедуры Application.ProcessMessages.

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

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

        function GetHiByte(w: word): byte; assembler; asm mov ax, w shr ax, 8 end;
    function GetLoByte(w: word): byte; assembler; asm mov ax, w end;
    function SetHiByte(b: byte; w: word): word; assembler; asm xor ax, ax mov ax, w mov ah, b end;
    function SetLoByte(b: byte; w: word): word; assembler; asm xor ax, ax mov ax, w mov al, b end;

    Другой путь сделать это (с примером использования):

        Type TWord2Byte = record Lo,Hi: Byte; end;
    var W: Word; B: Byte; begin W := $1234; B := TWord2Byte(W).Hi; writeln(TWord2Byte(W).Hi); { возвращаем } TWord2Byte(W).Lo := $67; TWord2Byte(W).Hi := $98; { shl не нужен! } end.

  • У меня есть 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".

  • Какую модель памяти использует 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?
  • Вот простейшая реализация хранителя экрана:

        var f: tForm;
    begin f := 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; var OldFont : TFont; begin OldFont := TFont.Create; try OldFont.Assign( CanvasOWner.Font ); CanvasOWner.Font.Assign( TextFont ); Result := CanvasOWner.Canvas.TextWidth(Text); CanvasOWner.Font.Assign( OldFont ); finally OldFont.Free; end; end;

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

        program Project1;
    uses Forms,  Unit1 in 'UNIT1.PAS' {Form1}, Splash;
    {$R *.RES} var SplashScreen : TSplashScreen; {в модуле Splash}
    begin Application.CreateForm(TForm1, Form1); SplashScreen := TSplashScreen.Create(Application); try SplashScreen.Show; { осуществите остальные CreatForms или другие действия прежде чем приложение запустится } SplashScreen.Close; finally  {Убедитесь что окно с логотипом освобождается} SplashScreen.Free; end; Application.Run; end.

  • Как вызвать функцию из DLL?
  • Для вызова функции вы должны знать ее точный синтакс и установленный тип. Например, для вызова при нажатии на кнопку функции CallMe, принимающей в качестве параметров два целых числа, возвращающей строку и находящейся в MyTest.Dll, вы могли бы использовать следующий код:

        procedure TForm1.Button1Click(Sender: TObject);
    type TCallMeDll = function(a,b: Integer): string;
    var CallMeDll: TCallMeDll; FuncPtr: TFarProc; hDll: THandle; result: string;
    begin hDll:=LoadLibrary('Mytestdll.dll'); FuncPtr:=GetProcAddress(hDLL,'CallMe'); @CallMeDll:=FuncPtr; if @CallMeDll <> nil then result:=CallMeDll(4,5); FuncPtr:=nil; FreeLibrary(hDll); end;

    Обратите внимание на порядок действий: сначала мы нагружаем dll в память, затем получаем указатель на функцию, и затем назначаем его CallMeDll. В этой точке необходимо проверить, если ProcAdderss равен Nil, то вызов GetProcAddress был неудачным.

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

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

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

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

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

    Имейте в виду, что в 'Other' вы должны также объявить и использовать функциональный тип, например так:

        { в интерфейсе библиотеки Other }
    type TMainFunction = function(s: string): integer; TestCallBack(MainFunc: TMainFunction);
    { в реализации библиотеки Other }
    TestCallBack(MainFunc: TMainFunction); var result: integer; begin result:=MainFunc('тест'); end;

    [000583]



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