Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Круглый стол
  
Правила КС
>> Настройки

Фильтр вопросов
>> Новые вопросы
отслеживать по
>> Новые ответы

Избранное

Страница вопросов
Поиск по КС


Специальные проекты:
>> К л ю к в а
>> Г о л о в о л о м к и

Вопрос №

Задать вопрос
Off-topic вопросы

Помощь

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  09:08[Войти] | [Зарегистрироваться]
Ответ на вопрос № 57348

28-11-2007 15:16
Доброго времени суток жителям Королевства!
Мне нужно написать функцию для поиска абонента в базе по части его наименования. Суть работы такова. Человек вводит часть нименования абонента или наименование полностью. Эта строка передаётся в функцию. Функция выполняет запрос к ХП на сервере, и отображает модальное окно с результатами этого запроса (там будут все абоненты, наименование которых содержит введённую строку). Это сделано.
Далее не пойму как. Предположим окно открылось, в списке оказалось 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.
Подскажите, пожалуйста, как решить данную задачу

[+] Добавить в избранные вопросы

Отслеживать ответы на этот вопрос по RSS

Ответы:


Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице.
Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.

11-12-2007 11:17 | Комментарий к предыдущим ответам
>>> найти то, что изменилось на момент второго запуска
Да научится кто-нибудь делать резервные копии, или нет????
>>> я бы сделал AbonResultForm локальной в функции
Я бы тоже, так как никогда без ЯВНОЙ на то причины не делаю переменными видимыми вне блока, где они используются - особенно стараюсь избегать глобальных переменных и доступных всякому полей - делаю их protected, а лучше private и прописать геттеры и сеттеры.

29-11-2007 13:46 | Комментарий к предыдущим ответам
Python:
Я, например, не вижу причины использовать классовый метод, обычной процедуры вполне достаточно


Согласен, что принципиальной необходимости в классовом методе нет. Просто отдельнолежащая функция скорее затеряется на фоне класса, нежели метод в секции public в объявлении класса. Кроме того, исключаются возможные пересечения по именам.

Python:
мало того, форму можно вообще убрать в implementation, чтобы не маячила.


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

Павел Чумичёв:
но при повторном вызове функции с тем же или с новым входным параметром выходное значение уже не меняется


К сожалению, я не смогу воспроизвести у себя вашу конкретику. Но попробую дать совет. Если при первом запуске все работает как надо, а при последующем - нет, то надо найти то, что изменилось на момент второго запуска. И постараться привести всё в первоначальный вид. Ну, и хотел бы порекомендовать такой подход: не игнорировать вещи из разряда "для порядка"... Иногда, делая что-то не очень, вроде бы, нужное, просто "для порядка", обходятся достаточно серьёзные грабли. Например, в вашем случае я бы сделал AbonResultForm локальной в функции, а датасет закрывал бы перед уничтожением формы...

29-11-2007 01:53 | Сообщение от автора вопроса
Пока делал предыдущий свой пост, много сообщений добавилось :) Почитаю, разберусь...

29-11-2007 01:48
Спасибо всем за ответы, и в особенности Бел Амору. Разобрался в сути решения, добавил в код функции, работает, но есть одно НО... Тут тоже у меня никаких мыслей о причине пока нет.
Вызываю функцию:

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

unit GlobalProc;

interface

uses SysUtils, DBGridEh, DBCtrls, Classes, Forms, pFIBDataset, DB, Controls;

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 (здесь Бел Амор угодал в первом сообщении :), и ко всему в проекте и так много форм, так что те что пропроще решил не делать заранее а описать их в общем юните, тем более что всё что в нём описано должно быть доступно из любого места приложения

29-11-2007 01:13
Добавлю свои...соображения.
Коль скоро автору нужен только некий ID, то...
1. Наверняка в БД есть еще справочные таблицы, соответственно зачем под каждую создавать формы?
2. Если это так, тогда в создаваемой форме нет необходимости иметь TQuery. Лучше выполнять запрос до создания формы, а в функцию передавать набор данных для заполнения ListBox-а.
3. В этом случае, если на форме добавить еще TEdit, то при наборе в нем чего-нибудь можно добиться одновременного добавления в справочник новой записи. Т.е. при Result = True дополнительно проверять ItemIndex ListBox-а. Если он = -1 - значит нужной записи в таблице нет и ее надо добавить.

29-11-2007 00:46
Ну единственное с чем можно безоговорочно согласиться, так это с тем, что форму надо создавать в дизайнере, а создавать ее динамически. Все остальное довольно спорно. Я, например, не вижу причины использовать классовый метод, обычной процедуры вполне достаточно, мало того, форму можно вообще убрать в implementation, чтобы не маячила. То есть код, приведенный Бел Амор в моей интерпретации будет выглядеть так:

unit Unit2;

interface

function ChooseAbon(Mask: String; Field: TField): Boolean;

implementation

uses
  Windows, Messages, ...;

{$R *.dfm}     

type
  TForm2 = class(TForm)
    DBGrid1: TDBGrid;
    btnOk: TButton;
    btnCancel: TButton;
    Edit1: TEdit;
    DataSource1: TDataSource;
    Query1: TQuery;
  public
    constructor Create(AOwner: TComponent); override;
  end;

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 ассоциированного датасета мы легко получаем необходимую Нам информацию.

29-11-2007 00:23
Таким образом получаем индексы ячейки, внутри которой пользователь нажал кнопку мыши.

Зачем нам индекс ячейки, когда нужно совсем другое - ID абонента. Как правильно сказал Бел Амор:

Result := pFibDataSet.FieldByName('ID').AsInteger;


28-11-2007 23:46
Упустил, что нужно по двойному клику. Тогда можно так:

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;


28-11-2007 21:08
Не знаю, как в 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 -- задание на самостоятельную работу.

С уважением.

28-11-2007 20:11
Если отвечать на тот вопрос, который был задан, то можно написать примерно так:

  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++ они называются статическими).

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DB, DBTables, StdCtrls, Grids, DBGrids;

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;

end.

Вызов:

  TForm2.ChooseAbon('Сидоров', Table1.FieldByName('AbonId'));


Добавьте свое cообщение

Вашe имя:  [Войти]
Ваш адрес (e-mail):На Королевстве все адреса защищаются от спам-роботов
контрольный вопрос:
Вода мокрая или сухая?
в качестве ответа на вопрос или загадку следует давать только одно слово в именительном падеже и именно в такой форме, как оно используется в оригинале.
Надоело отвечать на странные вопросы? Зарегистрируйтесь на сайте.
Тип сообщения:
Текст:
Жирный шрифт  Наклонный шрифт  Подчеркнутый шрифт  Выравнивание по центру  Список  Заголовок  Разделительная линия  Код  Маленький шрифт  Крупный шрифт  Цитирование блока текста  Строчное цитирование
  • вопрос Круглого стола № XXX

  • вопрос № YYY в тесте № XXX Рыцарской Квинтаны

  • сообщение № YYY в теме № XXX Базарной площади
  • обсуждение темы № YYY Базарной площади
  •  
     Правила оформления сообщений на Королевстве

    Страница избранных вопросов Круглого стола.
      
    Время на сайте: GMT минус 5 часов

    Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
    Функция может не работать в некоторых версиях броузеров.

    Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
    Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

     
    © При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
    Все используемые на сайте торговые марки являются собственностью их производителей.

    Яндекс цитирования