"Виртуальная библиотека Delphi" - читать интересную книгу автора
Компоненты и VCL
1. Почему возникает ошибка компиляции при обращении к объекту Sender в обработчике события?
Я в обработчике события OnChange для компонента TEdit пытаюсь получить содержимое его текстового буфера. Однако, следующая конструкция вызывает ошибку компиляции 'неизвестный идентификатор':
Caption := Sender.Text;
Если вы рассматривали декларацию, объект Sender имеет тип TObject, который является классом, который наследуется почти всеми остальными объектами. Вы, вероятно, пробуете обращаться к свойству, которое не определено в TObject, вроде Text или Caption. По этой причине, выражение Sender.Text вызовет ошибку, но если (для примера) вы знаете, что Sender имеет тип TEdit, тогда вы можете использовать выражение:
Caption := (Sender as TEdit).Text;
Если вы не уверены, что объект Sender будет всегда иметь данный тип, то рекомендуется предварительно проверить это:
if Sender is TEdit then …
2. Проблемы с полями класса типа TObject, TTable и т.д.
Я объявляю поле класса как TTable, но при обращении к нему происходит ошибка.
Дело в том, что в Delphi все экземпляры объектов, объявленых как class, являются динамическими. Соответственно поле MyTable, объявленное как
type
TMyClass = class(TObject)
public
MyTable: TTable;
constructor Create;
destructor Destroy; override;
end;
является указателем на класс TTable, и должно быть инициализировано в конструкторе вашего объекта и соответственно разрушено в деструкторе следующим образом:
constructor TMyClass.Create;
begin
MyTable := TTable.Create(nil);
MyTable.DatabaseName := 'DBDEMOS';
end;
destructor TMyClass.Destroy;
begin
MyTable.Free;
end;
Подробнее см. Changes in Object Pascal Language в документации или on-line help.
3. Как закрыть модальную форму (ShowModal)? И вообще, каков лучший способ закрыть любую форму?
Вообще говоря, нужно вызывать метод Close для формы. Close вызывает событие OnClose (обработчик которого может решить, что форму нельзя закрывать, например, если имеются несохраненные данные). Close не освобождает память, связанную с формой, если вы, конечно, не поместите в обработчик события вызов метода Release.
Если вы хотите уничтожить форму без вызова события OnClose, используйте метод Release. Этот метод работает подобно Free, но позволяет всем обработчикам событий данной формы закончить работу перед тем, как память будет освобождена.
Модальные формы "прекращают свой модальный статус", когда вы устанавливаете свойство ModalResult формы в любое значение, отличное от нуля. Если вы поместите кнопку на модальную форму и установите свойство ModalResult для кнопки в некоторое значение, то, когда пользователь нажмет на эту кнопку, форма закроется с результатом, который вы определили. Этот результат можно узнать вызывая ShowModal как функцию. То есть:
Result := Form.ShowModal;
4. Перемещение существующих компонентов на TPanel, TGroup и т.п.
Я поместил кнопку (или что-то другое) на форму, затем поместил панель, и решил переместить кнопку на панель, но ничего не получилось.
Действительно, чтобы поместить кнопку на панель, необходимо на форму сначала поместить панель, выбрать ее, а затем уже помещать кнопку.
Но и в вашей ситуации есть решение. Скопируйте (Copy) или вырежьте (Cut) нужный компонент, выберите панель, и сделайте вставку (Paste). Рекомендуется предварительно "подогнать" копируемый компонент в левый верхний угол формы, иначе компонент на панели может выпасть из "пределов видимости" панели (или любого другого группового компонента).
Если компонент все-таки "выпал" из пределов видимости - найдите этот компонент в Инспекторе Объектов, и установите нужные значения его свойств Left и Top.
Используя группы компонент можно огранизовать форму-шаблон, на которой можно складывать (например в Notebook) компоненты с предварительно заданными свойствами, отличными от стандартных. Это решение проще чем добавлять такие компоненты в палитру компонент — не увеличивается размер библиотеки компонентов DCL (Delphi 3.0 не считается), не загромождается палитра компонент.
Учтите, что при таком копировании компонент их имена меняются на новые (Button1, Button2 и т.д.).
5. Как можно добавить новый компонент на страницу TTabbedNoteBook во время выполнения программы? Как нужно определить свойство Parent для этого компонента?
Для того, чтобы добавить компонент на страницу TabbedNotebook, свойству Parent нового компонента нужно присвоить указатель на требуемую страницу. Способ для доступа к любой странице TTabbedNotebook во время выполнения — массив свойств Objects у свойства Pages компонента TTabbedNotebook. Другими словами, страницы сохранены в виде объектов в свойстве Pages (тип TStringList). Пример демонстрирует создание кнопки TButton на второй странице TabbedNotebook1:
Можно поступить другим способом — выставить у формы свойство BorderStyle = bsNone, и написать следующий обработчик OnPaint:
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Brush.Style := bsClear;
Canvas.Rectangle(0, 0, Width, Height);
end;
8. Почему некоторые компоненты типа TPanel и TEdit не имеют свойства Canvas?
Все наследники TCustomControl имеют Canvas, однако, в большинстве случаев это свойство объявлено protected для предотвращения рисования 'чужаками' на компоненте. Наследники компонента всегда могут получить доступ к унаследованным protected свойствам (типа Canvas), но пользователь компонента — никогда.
type
TCanvasPanel = class(TPanel)
public
property Canvas;
end;
Если вы хотите рисовать на компоненте, у которого нет public свойства Canvas, то используйте, например, компонент TPaintBox: положите его на панель TPanel, сделайте Align = Client и рисуйте на TPaintBox.Canvas.
9. Почему при уничтожении компонента в методе OnClick происходит ошибка?
Допустим, вы поместили на форму кнопку, и создали метод OnClick в котором вызываете Button1.Free. Вы видите, что это метод формы — казалось бы, какие препятствия для правильного уничтожения кнопки?
На самом деле Button1.OnClick является свойством и после запуска вашего приложения содержит адрес метода Form1.Button1Click. Именно кнопка вызывает этот метод как свой собственный. А это означает, что кнопка не может удалить себя в своем-же методе. Даже если вы попытаетесь удалить ссылку в OnClick:
Button1.OnClick := nil;
Button1.Free;
то это не поможет — стек настроен на возврат в обработчик TButton, который и вызвал OnClick. Поскольку к моменту возврата объект разрушен — возникает GPF или Access Violation.
10. Есть ли у TDBGrid события OnMouseDown, OnMouseUp и OnMouseMove?
Они есть, но не объявлены published. Вы можете создать наследника TDBGrid и сделать их published.
11. Поиск компонента в форме по имени.
Я хочу делать текущими в форме произвольные компоненты. Как выставить фокус у конкретного компонента ясно - ListBox1.SetFocus. А если я хочу обращаться к некоему компоненту по имени (свойство Name)?
Свойство TForm.Components — массив компонентов формы, который и нужен вам. Вы можете перемещаться по этому массиву пока не найдете компонент с нужным Name. Например:
procedure TForm1.DooDah;
var
Count: Integer;
begin
Count := 0;
while (Count lt; ComponentCount) and (Components[Count] lt;gt; 'Button1') do Inc(Count);
TButton(Components[Count]).SetFocus;
end;
или еще проще:
procedure TForm1.DooDah;
var
Target: TComponent;
begin
Target := FindComponent('Button1');
TButton(Target).SetFocus;
end;
Оба этих примера показывают как найти компонент TButton с именем Button1, и вызвать его метод SetFocus.
12. Как получить горизонтальный ScrollBar на ListBox?
Пошлите сообщение LB_SETHORIZONTALEXTENT в ListBox. Например, сообщение может быть отослано в момент создания формы:
14. Постранична прокрутка TMemo, реализация Undo и определение строки курсора.
Как прокрутить содержимое компонента TMemo?
Приведенная ниже процедура предполагает, что фокус находится на Edit1 и осуществляет прокрутку в соответствии с нажатыми клавишами.
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key = VK_F8 then
SendMessage(Memo1.Handle, { HWND для Memo }
WM_VSCROLL, { сообщение Windows }
SB_PAGEDOWN, { на страницу вниз }
0) { не используется }
else if Key = VK_F7 then SendMessage(Memo1.Handle, WM_VSCROLL, SB_PAGEUP, 0);
end;
Если определено всплывающее (popup) меню для TMemo,и заданы клавиши для операций Cut, Copy, Paste, то я могу обрабатывать эти события, вызывая методы CutToClipboard, CopyToClipboard, и т.д. Однако, если я поместили пункт Undo в меню (обычно Ctrl+Z), то как дать знать TMemo, что нужно выполнить Undo?
Если встроенного Undo достаточно, то это очень просто:
Memo1.Perform(EM_UNDO, 0, 0);
Для переключения свойства Enabled пункта меню Undo1:
1. Table1BBBMemo — имя поля BLOB Memo (TMemoField).
2. Memo1 — имя компонента TMemo. Естественно, что этим же способом можно обмениваться информацией с BLOB-полями произвольного типа.
16. Как показать содержимое Memo поля в TDBGrid?
Используйте следующий код для обработки события OnDrawDataCell у TDBGrid. (Перед запуском программы создайте объект TMemoField для memo поля в Fields Editor).
Я создаю событие на SpeedButton1.OnDblClick, но оно, похоже, вообще никогда не возникает. OnClick работает. Что делать?
На самом деле работает, только в определенных ситуациях. Если вы помещаете на панель несколько кнопок, то по умолчанию они независимы и соответственно не фиксируются в нажатом состоянии. Поскольку одиночное нажатие мыши на кнопку отрабатывается немедленно, двойной щелчок мыши воспринимается как два нажатия и отпускания. Поэтому OnDblClick и не срабатывает.
Если же кнопки связаны в группу (GroupIndex lt;gt; 0), то они могут фиксироваться, и соответственно могут воспринимать двойной щелчок мыши.
18. Как разделить обработку OnClick и OnDblClick? Ведь OnClick будет вызываться всегда, и перед DblClick.
Именно так и происходит в Windows — посылаются оба сообщения. Для того чтобы обработать только какое-то одно событие необходимо чуть "задержать" выполнение OnClick. Сделать это можно следующим способом:
procedure TForm1.ListBox1Click(Sender: TObject);
var
Msg: TMsg;
TargetTime: Longint;
begin
{ get the maximum time to wait for a double-click message }
TargetTime := GetTickCount + GetDoubleClickTime;
{ cycle until DblClick received or wait time run out }
while GetTickCount lt; TargetTime do
if PeekMessage(Msg, ListBox1.Handle, WM_LBUTTONDBLCLK, WM_LBUTTONDBLCLK, WM_NOREMOVE)
Свойство ActiveControl для формы тоже можно использовать, однако, ActiveControl не обязательно является тем элементом, для которого произошло событие.
20. Как использовать case, чтобы определить, какой объект вызвал процедуру?
Используйте свойство Tag. Установите значение Tag свое у каждого объекта для опознания. (Использование констант, которые описывают объект — идеально подходит).
case (Sender as TComponent).Tag of
Button1ID: SomeProcedure;
Button2ID: AnotherProcedure;
end;
Таким образом вы можете обрабатывать события как от однотипных компонент, так и от компонент разного типа.
21. Как обрабатывать события от множества однотипных компонентов.
На моей форме находится примерно 10 кнопок. Я хочу обрабатывать нажатие на любую из них одним событием, но как их отличить внутри обработчика события?
Для этого базовый класс VCL TComponent имеет поле Tag типа Longint. В момент разработки вы можете присвоить этому полю любое значение, а в момент исполнения использовать его (или переопределять). В вашей ситуации достаточно присвоить полю ButtonX.Tag значение от 1 до 10 (или от 0 до 9, как удобнее), а в обработчике написать примерно следующее:
procedure MyForm.Button1Click(Sender: TObject);
begin
case (Sender as TComponent).Tag of
1: {...};
2: {...};
3: {...};
end;
end;
22. Использование TPanel в качестве "индикатора".
Я пытаюсь использовать TPanel как индикатор процесса обновления БД. Однако надпись на панели не обновляется пока не закончится цикл обработки БД. В цикле вызывается Panel.Caption := ...
После присвоения Panel.Caption вызывайте Panel.Refresh или Application.ProcessMessages (второй вариант предпочтительней, так как позволяет перерисовать себя всем клмплнентам, которые в этом нуждаются).
23. Включение и выключение подсказок (Hints) для всех элементов на форме.
Если ваша форма содержит панель подсказки в нижней части формы, то вы можете определить подменю для этой панели, и выставлять Form.ShowHint в True или False в зависимости от состояния Checked элемента меню.
Например, в TMenuItem.OnClick напишите:
ShowHint := not (Sender as TMenuItem).Checked;
В результате на локальном меню панели будет видно, включены подсказки для всех элементов или нет.
Image1.Refresh; { для того, чтобы изменения отобразились }
Однако, если вы используете свою палитру, то ее нужно создавать специально.
26. Как из программы 'открыть' TComboBox?
У TComboBox есть run-time свойство, не упомянутое в on-line help — DroppedDown.
Для открытия ComboBox напишите:
ComboBox1.DroppedDown := True;
Естественно, False закроет его.
27. Как заменить надпись 'Read only' в компонентах TSaveDialog и TOpenDialog?
Попробуйте посмотреть в Windows API Help разделы, связанные с lpTemplateName. Вообще говоря, вы можете заменить стандартный Open Dialog Box своим собственным шаблоном.
28. Проблема в использовании компонента TCustomGrid.
Делаю так:
1. Создаю новый компонент при помощи Эксперта Компонент
2. Имя класса TSampleCalendar
3. Имя родителя TCustomGrid
4. Использую страницу 'Samples'
5. Сохраняю модуль с именем CALSAMP.PAS
6. Подключаю к Палитре компонент
7. Создаю форму, помещаю новый компонент на форму и получаю Runtime Error 210 В чем дело?
Проблема в том, что TCustomGrid имеет метод DrawCell, который является абстрактным. То, что его безусловно надо переписывать у любого наследника TCustomGrid, к сожалению, не отражено в документации. Создайте этот метод (пусть даже пустой) и ваша проблема исчезнет.
29. Как установить формат для поля таблицы?
В Fields Editor выберите поле для форматирования. Используя свойства DisplayFormat и EditFormat сделайте то, что нужно. DisplayFormat работает для поля, на которое не установлен фокус. EditFormat работает для поля, на которое фокус установлен. Форматирование аналогично первому параметру в функции FormatFloat, но без скобок.
30. Можно ли использовать клавишу ENTER при вводе данных для перехода от поля к полю?
Используйте данный код для события OnKeyPress компонента TEdit.
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
SelectNext(Sender as TWinControl, True, True);
Key := #0;
end;
end;
Теперь Enter ведет себя как Tab. Затем, выберите все объекты, которые должны вести себя как Edit1 (за исключением кнопок) и в Object Inspector установите обработчик OnKeyPress в Edit1KeyPress. Каждый выбранный вами объект воспринимает Enter как Tab. Если вы хотите обрабатывать событие на уровне формы (а не в каждом отдельном компоненте), уберите обработчики события у всех компонент и создайте FormKeyPress — обработчик OnKeyPress для формы:
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
SelectNext(Sender as TWinControl, True, True);
Key := #0;
end;
end;
Все объекты на форме будут воспринимать Enter как Tab.