Доброго времени суток жителям Королевства!
Мне нужно написать функцию для поиска абонента в базе по части его наименования. Суть работы такова. Человек вводит часть нименования абонента или наименование полностью. Эта строка передаётся в функцию. Функция выполняет запрос к ХП на сервере, и отображает модальное окно с результатами этого запроса (там будут все абоненты, наименование которых содержит введённую строку). Это сделано.
Далее не пойму как. Предположим окно открылось, в списке оказалось 10 абонентов. Человек должен двойным кликом выбрать того абонента который нужен и по этому двойному клику в Result функции должен передаться ID абонента после чего окно с результатом запроса закрывается.
Не могу разобраться как написать обработчик OnDblClick для грида и как потом результат вернуть в Result. Функция находится в юните который содержит процедуры и функции используемые по всему проекту. Вот что есть сейчас:
function GetAbonentId(NamePattern: String):Integer;
var AbonQuery: TpFIBDataSet;
AbonSrc: TDataSource;
ResultGrid: TDBGridEh;
NamePatt: String;
AbonID: Integer;
begin
NamePatt:='%'+NamePattern+'%';
AbonResultForm:=TForm.Create(nil);
with AbonResultForm do
begin
//тут задаём параметры формы
end;
//создаём pFibDataSet для выполнения запроса к ХП
AbonQuery:=TpFIBDataset.Create(AbonResultForm);
with AbonQuery do
begin
DataBase:=DM.BillBase;
Transaction:=DM.trRead;
AutoCommit:=True;
SelectSQL.Add('SELECT * FROM ABON_SEARCH(:name_pattern) ORDER BY ABON_NAME COLLATE PXW_CYRL');
ParamByName('name_pattern').AsString:=NamePatt;
end;
//создаём источник данных для FIBDataset
AbonSrc:=TDataSource.Create(AbonResultForm);
AbonSrc.DataSet:=AbonQuery;
//создаём грид для отображения результата
ResultGrid:=TDBGridEh.Create(AbonResultForm);
with ResultGrid do
begin
Parent:=AbonResultForm;
//далее задаём параметры грида
end;
AbonQuery.Open;
AbonQuery.FetchAll;
AbonResultForm.Caption:='Найдено '+IntToStr(AbonQuery.RecordCount)+' абонентов'
AbonResultForm.ShowModal;
//
//
// что-то нужно сюда добавить? не могу разобраться что...
//
//
Result:=AbonId;
AbonResultForm.Free;
end;
Почитал статью Елены Филипповой "Жизнь и смерть в режиме run-time", но немного не понял как быть как раз в таком случае, когда сама форма-родитель контролов создаётся в run-time.
Подскажите, пожалуйста, как решить данную задачу
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
11-12-2007 11:17 | Комментарий к предыдущим ответам
>>> найти то, что изменилось на момент второго запуска
Да научится кто-нибудь делать резервные копии, или нет????
>>> я бы сделал AbonResultForm локальной в функции
Я бы тоже, так как никогда без ЯВНОЙ на то причины не делаю переменными видимыми вне блока, где они используются - особенно стараюсь избегать глобальных переменных и доступных всякому полей - делаю их protected, а лучше private и прописать геттеры и сеттеры.
29-11-2007 13:46 | Комментарий к предыдущим ответам
Python:
Я, например, не вижу причины использовать классовый метод, обычной процедуры вполне достаточно
Согласен, что принципиальной необходимости в классовом методе нет. Просто отдельнолежащая функция скорее затеряется на фоне класса, нежели метод в секции public в объявлении класса. Кроме того, исключаются возможные пересечения по именам.
Python:
мало того, форму можно вообще убрать в implementation, чтобы не маячила.
Согласен, это выглядит уже покрасивее... Все мы имеем свои привычки и стереотипы и не всегда замечаем достаточно очевидные вещи...
Павел Чумичёв:
но при повторном вызове функции с тем же или с новым входным параметром выходное значение уже не меняется
К сожалению, я не смогу воспроизвести у себя вашу конкретику. Но попробую дать совет. Если при первом запуске все работает как надо, а при последующем - нет, то надо найти то, что изменилось на момент второго запуска. И постараться привести всё в первоначальный вид. Ну, и хотел бы порекомендовать такой подход: не игнорировать вещи из разряда "для порядка"... Иногда, делая что-то не очень, вроде бы, нужное, просто "для порядка", обходятся достаточно серьёзные грабли. Например, в вашем случае я бы сделал AbonResultForm локальной в функции, а датасет закрывал бы перед уничтожением формы...
Спасибо всем за ответы, и в особенности Бел Амору. Разобрался в сути решения, добавил в код функции, работает, но есть одно НО... Тут тоже у меня никаких мыслей о причине пока нет.
Вызываю функцию:
procedure TForm1.N3Click(Sender: TObject);
var id: integer;
begin
id :=GetAbonent(Edit1.Text);
Label1.Caption:=IntToStr(id);
end;
Всё как положено - открывается модальное окно с абонентами, по двойному клику Label показывает ID абонента, но при повторном вызове функции с тем же или с новым входным параметром выходное значение уже не меняется. Причём это происходит если запускать приложение не под IDE. Если запустить его под IDE в режиме пошагового выполнения, то всё работает - ясно видно, что переменной ID присваивается значение функции, а затем Label1.Caption после преобразования получает этот самый ID и он отображается на форме. Снимаю строки с отладки - опять та же картина, Label1.Caption показывает старое значение независимо от входных аргументов функции.
Теперь уже привожу полный код модуля (за исключением процедур и функций не имеющих отношения к задаче):
type
TUniversalHandler = class
public
procedure FormOkOnDblClick(Sender: TObject);
end;
function GetAbonent(NamePattern: String):Integer;
var
SQL_Text: String;
AbonResultForm: TForm;
implementation
uses U_DM; //это DataModule с FIBDatabase и FIBTransaction
procedure TUniversalHandler.FormOkOnDblClick(Sender: TObject);
begin
((Sender as TWinControl).Parent as TForm).ModalResult := mrOk;
end;
function GetAbonent(NamePattern: String):Integer;
var AbonQuery: TpFIBDataSet;
AbonSrc: TDataSource;
ResultGrid: TDBGridEh;
NamePatt: String;
begin
Result:=-1;
NamePatt:='%'+NamePattern+'%';
AbonResultForm:=TForm.Create(nil);
with AbonResultForm do
begin
Width:=750;
Height:=350;
BorderIcons:=[biSystemMenu];
Position:=poDesktopCenter;
end;
//создаём pFibDataSet для выполнения запроса к ХП
AbonQuery:=TpFIBDataset.Create(AbonResultForm);
with AbonQuery do
begin
DataBase:=DM.BillBase;
Transaction:=DM.trRead;
AutoCommit:=True;
SelectSQL.Add('SELECT * FROM ABON_SEARCH(:name_pattern) ORDER BY ABON_NAME COLLATE PXW_CYRL');
ParamByName('name_pattern').AsString:=NamePatt;
end;
//создаём источник данных для FIBDataset
AbonSrc:=TDataSource.Create(AbonResultForm);
AbonSrc.DataSet:=AbonQuery;
//создаём сетку для отображения результата
ResultGrid:=TDBGridEh.Create(AbonResultForm);
with ResultGrid do
begin
Parent:=AbonResultForm;
Left:=8;
Top:=8;
Width:=AbonResultForm.Width - 16;
Height:=AbonResultForm.Height - 16;
Anchors:=[akLeft, akTop, akRight, akBottom];
Flat:=True;
Options:=[dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit];
OptionsEh:=[dghFixed3D, dghClearSelection, dghFitRowHeightToText, dghRowHighlight, dghDialogFind];
AutoFitColWidths:=True;
VertScrollBar.Tracking:=True;
VertScrollBar.VisibleMode:=sbAlwaysShowEh;
DataSource:=AbonSrc;
ShowHint:=True;
OnDblClick := TUniversalHandler(nil).FormOkOnDblClick;
with Columns do
begin
Add;
Add;
Add;
with Items[0] do
begin
FieldName := 'ABON_NAME';
Width := 50;
Title.Caption := 'Наименование абонента';
Title.Font.Size := 9;
Title.Alignment := taCenter;
ToolTips:=True;
EndEllipsis:=True;
end;
with Items[1] do
begin
FieldName := 'ABON_ADDR';
Title.Caption := 'Юр.адрес/Адрес прописки';
Title.Font.Size := 9;
Title.Alignment := taCenter;
ToolTips:=True;
EndEllipsis:=True;
end;
//это временно для проверки правильности возвращаемого значения
with Items[2] do
begin
FieldName := 'ABON_ID';
Title.Caption := 'ID';
Title.Font.Size := 9;
Title.Alignment := taCenter;
ToolTips:=True;
EndEllipsis:=True;
end;
end;
end;
AbonQuery.Open;
AbonQuery.FetchAll;
AbonResultForm.Caption:='Найдено '+IntToStr(AbonQuery.RecordCount)+' абонентов';
if AbonResultForm.ShowModal = mrOk then
Result := AbonQuery.FieldByName('ABON_ID').AsInteger
else
Result := -1;
AbonResultForm.Free;
end;
end.
Ну а насчёт того чтобы форму в design-time создать - хотел так сделать, но подумал, что неплохо было бы разобраться с созданием в run-time (здесь Бел Амор угодал в первом сообщении :), и ко всему в проекте и так много форм, так что те что пропроще решил не делать заранее а описать их в общем юните, тем более что всё что в нём описано должно быть доступно из любого места приложения
Добавлю свои...соображения.
Коль скоро автору нужен только некий ID, то...
1. Наверняка в БД есть еще справочные таблицы, соответственно зачем под каждую создавать формы?
2. Если это так, тогда в создаваемой форме нет необходимости иметь TQuery. Лучше выполнять запрос до создания формы, а в функцию передавать набор данных для заполнения ListBox-а.
3. В этом случае, если на форме добавить еще TEdit, то при наборе в нем чего-нибудь можно добиться одновременного добавления в справочник новой записи. Т.е. при Result = True дополнительно проверять ItemIndex ListBox-а. Если он = -1 - значит нужной записи в таблице нет и ее надо добавить.
Ну единственное с чем можно безоговорочно согласиться, так это с тем, что форму надо создавать в дизайнере, а создавать ее динамически. Все остальное довольно спорно. Я, например, не вижу причины использовать классовый метод, обычной процедуры вполне достаточно, мало того, форму можно вообще убрать в implementation, чтобы не маячила. То есть код, приведенный Бел Амор в моей интерпретации будет выглядеть так:
unit Unit2;
interface
function ChooseAbon(Mask: String; Field: TField): Boolean;
constructor TForm2.Create(AOwner: TComponent);
begin
inherited;
// Запросы и т.д.
end;
function ChooseAbon(Mask: String; Field: TField): Boolean;
begin
with TForm2.Create(Application) do
begin
Result := (ShowModal = mrOk);
if Result then
Field.AsInteger := Query1.FieldByName('ID').AsInteger;
Free;
end;
end;
end.
Ну и секция uses будет много от чего зависеть, не надо на это делать упор. Второе - на кой использовать MouseToCell??? При КЛИКЕ (хоть двойном, хоть одинарном) текущая запись позиционируется на кликнутую, то есть используя FieldByName ассоциированного датасета мы легко получаем необходимую Нам информацию.
Упустил, что нужно по двойному клику. Тогда можно так:
type
TUniversalHandler = class // Экземпляр не создавать
public
procedure FormOkOnDblClick(Sender: TObject);
end;
...
procedure TUniversalHandler.FormOkOnDblClick(Sender: TObject);
begin
((Sender as TWinControl).Parent as TForm).ModalResult := mrOk;
end;
...
ResultGrid.OnDblClick := TUniversalHandler(nil).FormOkOnDblClick;
if AbonResultForm.ShowModal = mrOk then
Result := pFibDataSet.FieldByName('ID').AsInteger
else
Result := -1;
Не знаю, как в TDBGridEh, но вообще в VCL обработчик onDblClick не получает данных для решения Вашей проблемы.
Как вариант можно предложить создание обработчика onMouseDown со следующим кодом:
var ResCell: TPoint;
....
procedure MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
ResultGrid.MouseToCell(X, Y, ResCell.X, ResCell.Y);
end;
Таким образом получаем индексы ячейки, внутри которой пользователь нажал кнопку мыши.
Эмуляция DblClick -- задание на самостоятельную работу.
Если отвечать на тот вопрос, который был задан, то можно написать примерно так:
with TButton.Create(AbonResultForm) do
begin
Parent := AbonResultForm;
SetBounds(50, 200, 75, 25);
Caption := 'Выбрать';
Default := True;
ModalResult := mrOk;
Enabled := (pFibDataSet.RecordCount > 0)
end;
with TButton.Create(AbonResultForm) do
begin
Parent := AbonResultForm;
SetBounds(150, 200, 75, 25);
Caption := 'Отмена';
Cancel := True;
ModalResult := mrCancel;
end;
if AbonResultForm.ShowModal = mrOk then
Result := pFibDataSet.FieldByName('ID').AsInteger
else
Result := -1;
Однако я хотел бы сделать несколько дополнений...
Для тренировки по динамическому созданию компонентов ваш код вполне подходит. Однако в случае реального проекта это является искусственным усложнением, т.к. в данном конкретном случае нет никакой необходимости формировать форму вручную. Можно в дизайнере разработать форму и уже её создавать в ран-тайме. Приведу альтернативный вариант без особых пояснений. Я думаю, все будет достаточно понятно. Скажу только, что в проекте автосоздаваемой должна быть только главная форма (и, возможно, дата-модули). И посмотрите, что такое классовые методы (неудачное название... в C++ они называются статическими).
type
TForm2 = class(TForm)
DBGrid1: TDBGrid;
btnOk: TButton;
btnCancel: TButton;
Edit1: TEdit;
DataSource1: TDataSource;
Query1: TQuery;
public
constructor Create(AOwner: TComponent); override;
class function ChooseAbon(Mask: String; Field: TField): Boolean;
end;
//var
// Form2: TForm2;
implementation
{$R *.dfm}
constructor TForm2.Create(AOwner: TComponent);
begin
inherited;
// Запросы и т.д.
end;
class function TForm2.ChooseAbon(Mask: String; Field: TField): Boolean;
begin
with TForm2.Create(Application) do
begin
Result := (ShowModal = mrOk);
if Result then
Field.AsInteger := Query1.FieldByName('ID').AsInteger;
Free;
end;
end;
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.