Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Немного об отзывах - хочу сообщить и повторить снова в данных циклах статей не будет информации об ActiveX компоненте MapX (о работе с ней, отзывы о ней и т.п.) по причине отсутствия у меня оной (может кто поделится J).
Использование уведомляющих вызовов (Callbacks) для получения информации из Maplnfo - краткий учебный курс.
Вы можете построить Ваше приложение так, чтобы Maplnfo автоматически посылало информацию Вашей клиентской программе. Например, можно сделать так, чтобы всякий раз при открытии и смене диалоговых окон сообщать ID-номер текущего окна.
Такой тип уведомления известен как обратный вызов или уведомление (callback).
Уведомления используються в следующих случаях:
Требования к функциям уведомления
Программа должна быть способна функционировать, как DDE-сервер или как сервер Автоматизации OLE.
Предопределенные процедуры SetStatusText, WindowContentsChanged.
Если Вы хотите имитировать строку состояния MapInfo, создайте метод, называемый SetStatusText. Определите этот метод так, чтобы у него был один аргумент: строка.
метод WindowContentsChanged, MapInfo посылает четырехбайтовое целое число (ID окна MapInfo), чтобы указать, какое из окон Карты изменилось. Напишите код, делающий необходимую обработку.
Возможно так-же и регистрация пользовательских событий. но это отложим пока на третью часть.
Переинсталяция компонента TKDMapInfoServer
Вот в принципе и все.
Cервер автоматизации OLE для обработки CallBack
Данный сервер я разместил в ActiveX DLL.(данная DLL называется MICallBack.dll) в виде Automation Object.-а.
Что-бы вам просмотреть методы и свойства данногоAutomation Object.-а. откройте MICallBack.dpr и в меню Run Delphi выбирите TypeLibrary
Откроется окно - Где я реализовал CallBack методы MapInfo и создал сервер автоматизации MICallBack. Обратите внимание, что у данного сервера помимо присутствия интерфейса IMapInfoCallBack присутствует и еще интерфейс ImapInfoCallBackEvents (он нам нужен будет для перенаправления событий в компонент и далее в обработчик).
Листинг интерфейсного модуля
unit Call; {$WARN SYMBOL_PLATFORM OFF} interface uses ComObj, ActiveX, Dialogs, AxCtrls, Classes, MICallBack_TLB, StdVcl; type TMapInfoCallBack = class(TAutoObject, IConnectionPointContainer, IMapInfoCallBack) private { Private declarations } FConnectionPoints: TConnectionPoints; FConnectionPoint: TConnectionPoint; FEvents: IMapInfoCallBackEvents; { note: FEvents maintains a *single* event sink. For access to more than one event sink, use FConnectionPoint.SinkList, and iterate through the list of sinks. } public procedure Initialize; override; protected { Protected declarations } property ConnectionPoints: TConnectionPoints read FConnectionPoints implements IConnectionPointContainer; procedure EventSinkChanged(const EventSink: IUnknown); override; procedure SetStatusText(const Status: WideString); safecall; procedure WindowContentsChanged(ID: Integer); safecall; procedure MyEvent(const Info: WideString); safecall; end; var FDLLCall: THandle; implementation uses ComServ; procedure TMapInfoCallBack.EventSinkChanged(const EventSink: IUnknown); begin FEvents := EventSink as IMapInfoCallBackEvents; end; procedure TMapInfoCallBack.Initialize; begin inherited Initialize; FConnectionPoints := TConnectionPoints.Create(Self); if AutoFactory.EventTypeInfo <> nil then FConnectionPoint := FConnectionPoints.CreateConnectionPoint( AutoFactory.EventIID, ckSingle, EventConnect) else FConnectionPoint := nil; end; procedure TMapInfoCallBack.SetStatusText(const Status: WideString); begin if FEvents <> nil then FEvents.OnChangeStatusText(Status); end; procedure TMapInfoCallBack.WindowContentsChanged(ID: Integer); begin if FEvents <> nil then FEvents.OnChangeWindowContentsChanged(ID); end; procedure TMapInfoCallBack.MyEvent(const Info: WideString); begin if FEvents <> nil then FEvents.OnChangeMyEvent(Info); end; initialization TAutoObjectFactory.Create(ComServer, TMapInfoCallBack, Class_MapInfoCallBack, ciMultiInstance, tmApartment); end.
Обратите внимание на присутствие двух предопределенных методов MapInfo SetStatusText и WindowContentsChanged.
Метод MyEvent я пока зарезервировал для реализации своих сообщений (более подробно будет изложено в 3 части цикла)
И так что мы видим.
// если есть обработчик if FEvents <> nil then begin // Отправка сообщения далее - в данном случае в компонент FEvents.OnChangeStatusText(Status);
Как заставить MapInfo пересылать CallBack данному OLE серверу и как нам обрабатывать сообщения в компоненте от OLE сервера.
Итак представляю переработанный компонент -
unit KDMapInfoServer; interface uses Stdctrls, Dialogs, ComObj, Controls, Variants, ExtCtrls, Windows, ActiveX, Messages, SysUtils, Classes, MICallBack_TLB; // - сгенерировано из DLL type // запись "типа" Variant TEvalResult = record AsVariant: OLEVariant; AsString: string; AsInteger: Integer; AsFloat: Extended; AsBoolean: Boolean; end; type // Событие на изменение SetStatusText // генерируется при обратном вызове TSetStatusTextEvent = procedure(Sender : TObject; StatusText: WideString) of object; // WindowContentsChanged TWindowContentsChanged = procedure(Sender : TObject; ID : Integer) of object; // Для собственных событий TMyEvent = procedure(Sender : TObject; Info : WideString) of object; TEvent = class(TInterfacedObject,IUnknown,IDispatch) private FAppConnection : Integer; FAppDispatch : IDispatch; FAppDispIntfIID : TGUID; protected function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; function GetTypeInfoCount(out Count: Integer): HResult; stdcall; function GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall; function Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall; public constructor Create( AnAppDispatch : IDispatch; const AnAppDispIntfIID : TGUID); destructor Destroy ; override; end; TKDMapInfoServer = class(TComponent) private { Private declarations } FOwner : TWinControl; // Владелец Responder : Variant; // Для OLE Disp FServer : Variant; FHandle : THandle; // Зарезервировано FActive : Boolean; // Запущен/незапущен FPanel : TPanel; // Панель вывода srv_OLE : OLEVariant; srv_disp : IMapInfoCallBackDisp; srv_vTable : IMapInfoCallBack; FEvent : TEvent; FSetStatusTextEvent : TSetStatusTextEvent; // события компонента FWindowContentsChanged : TWindowContentsChanged; FMyEvent : TMyEvent; Connected : Boolean; // Установлено ли соединение MapperID : Cardinal; // ИД окна procedure SetActive(const Value: Boolean); procedure SetPanel(const Value: TPanel); procedure CreateMapInfoServer; procedure DestroyMapInfoServer; protected { Protected declarations } public { Public declarations } constructor Create(AOwner: TComponent); override; destructor Destroy; override; // Данная процедура выполеняет метод сервера MapInfo - Do procedure ExecuteCommandMapBasic(Command: string; const Args: array of const); function Eval(Command: string; const Args: array of const): TEvalResult; virtual; procedure WindowMapDef; procedure OpenMap(Path : string); procedure RepaintWindowMap; // Дополнил для генерации события SetStatus при изменении строки состояния // в MapInfo procedure DoSetStatus(StatusText: WideString); // Дополнил.для генерации события WindowContentsChanged при изменении окна // в MapInfo procedure DoWindowContentsChanged(ID : Integer); // Дополнил для генерации собственно события в MapInfo procedure DoMyEvent(Info: WideString); published { Published declarations } // Создает соединение с сервером MapInfo property Active: Boolean read FActive write SetActive; property PanelMap : TPanel read FPanel write SetPanel; // Событие возникающее при изменении строки состояния MapInfo property StatusTextChange : TSetStatusTextEvent read FSetStatusTextEvent write FSetStatusTextEvent; property WindowContentsChanged : TWindowContentsChanged read FWindowContentsChanged write FWindowContentsChanged; property MyEventChange : TMyEvent read FMyEvent write FMyEvent; end; var // О это вообще хитрость - используеться для определения созданного компонента // TKDMapInfoServer (см. SetStatusText и Create KDMapInfoServ : TKDMapInfoServer; procedure register; implementation // Вот тут то и хитрость если сервер создан то тогда и вызываем SetStatus //// IF KDMapInfoServ <> nil Then /// KDMapInfoServ.SetStatus(StatusText); procedure register; begin RegisterComponents('Kuzan', [TKDMapInfoServer]); end; { TKDMapInfoServer } constructor TKDMapInfoServer.Create(AOwner: TComponent); begin inherited Create(AOwner); FOwner := AOwner as TWinControl; KDMapInfoServ := Self; // **** Вот тут и указываеться созданный компонент // TKDMapInfoServer FHandle := 0; FActive := False; Connected := False; end; destructor TKDMapInfoServer.Destroy; begin DestroyMapInfoServer; inherited Destroy; end; procedure TKDMapInfoServer.CreateMapInfoServer; begin try FServer := CreateOleObject('MapInfo.Application'); except FServer := Unassigned; end; // Скрываем панели управления MapInfo ExecuteCommandMapBasic('Alter ButtonPad ID 4 ToolbarPosition (0, 0) Show Fixed', []); ExecuteCommandMapBasic('Alter ButtonPad ID 3 ToolbarPosition (0, 2) Show Fixed', []); ExecuteCommandMapBasic('Alter ButtonPad ID 1 ToolbarPosition (1, 0) Show Fixed', []); ExecuteCommandMapBasic('Alter ButtonPad ID 2 ToolbarPosition (1, 1) Show Fixed', []); ExecuteCommandMapBasic('Close All', []); ExecuteCommandMapBasic('Set ProgressBars Off', []); ExecuteCommandMapBasic('Set Application Window %D', [FOwner.Handle]); ExecuteCommandMapBasic('Set Window Info Parent %D', [FOwner.Handle]); FServer.Application.Visible := True; if IsIconic(FOwner.Handle)then ShowWindow(FOwner.Handle, SW_Restore); BringWindowToTop(FOwner.Handle); srv_ole := CreateOleObject('MICallBack.MapInfoCallBack') as IDispatch; srv_vtable := CoMapInfoCallBack.Create; srv_disp := CreateComObject(CLASS_MapInfoCallBack) as IMapInfoCallBackDisp; FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents); // Указываем MapInfo что нужно передовать обратные вызовы нашему OLE // а тм далее по цепочке (см.начало) FServer.SetCallBack(srv_disp); end; procedure TKDMapInfoServer.DestroyMapInfoServer; begin ExecuteCommandMapBasic('End MapInfo', []); FServer := Unassigned; end; procedure TKDMapInfoServer.ExecuteCommandMapBasic(Command: string; const Args: array of const); begin if Connected then try FServer.do(Format(Command, Args)); except on E: Exception do MessageBox(FOwner.Handle, PChar(Format('Ошибка выполнения () - %S', [E.message])), 'Warning', MB_ICONINFORMATION or MB_OK); end; end; function TKDMapInfoServer.Eval(Command: string; const Args: array of const): TEvalResult; function IsInt(Str : string): Boolean; var Pos : Integer; begin Result := True; for Pos := 1 to Length(Trim(Str)) do begin if (Str[Pos] <> '0') and (Str[Pos] <> '1') and (Str[Pos] <> '2') and (Str[Pos] <> '3') and (Str[Pos] <> '4') and (Str[Pos] <> '5') and (Str[Pos] <> '6') and (Str[Pos] <> '7') and (Str[Pos] <> '8') and (Str[Pos] <> '9') and (Str[Pos] <> '.') then begin Result := False; Exit; end; end; end; var ds_save: Char; begin if Connected then begin Result.AsVariant := FServer.Eval(Format(Command, Args)); Result.AsString := Result.AsVariant; Result.AsBoolean := (Result.AsString = 'T') or (Result.AsString = 't'); if IsInt(Result.AsVariant) then begin try ds_save := DecimalSeparator; try DecimalSeparator := '.'; Result.AsFloat := StrToFloat(Result.AsString); finally DecimalSeparator := ds_save; end; except Result.AsFloat := 0.00; end; try Result.AsInteger := Trunc(Result.AsFloat); except Result.AsInteger := 0; end; end else begin Result.AsInteger := 0; Result.AsFloat := 0.00; end; end; end; procedure TKDMapInfoServer.SetActive(const Value: Boolean); begin FActive := Value; if FActive then begin CreateMapInfoServer; WindowMapDef; Connected := True; end else begin if Connected then begin DestroyMapInfoServer; Connected := False; end; end; end; procedure TKDMapInfoServer.SetPanel(const Value: TPanel); begin FPanel := Value; end; procedure TKDMapInfoServer.WindowMapDef; begin ExecuteCommandMapBasic('Set Next Document Parent %D Style 1', [FPanel.Handle]); RepaintWindowMap; end; procedure TKDMapInfoServer.OpenMap(Path: string); begin ExecuteCommandMapBasic('Run Application "%S"', [Path]); MapperID := Eval('WindowInfo(FrontWindow(),%D)',[12]).AsInteger; RepaintWindowMap; end; procedure TKDMapInfoServer.DoSetStatus(StatusText: WideString); begin if Assigned(FSetStatusTextEvent) then FSetStatusTextEvent(Self,StatusText); end; procedure TKDMapInfoServer.DoWindowContentsChanged(ID: Integer); begin if Assigned(FWindowContentsChanged) then FWindowContentsChanged(Self,ID); end; procedure TKDMapInfoServer.DoMyEvent(Info: WideString); begin if Assigned(FWindowContentsChanged) then FMyEvent(Self,Info); end; procedure TKDMapInfoServer.RepaintWindowMap; begin with PanelMap do MoveWindow(MapperID, 0, 0, FPanel.ClientWidth, FPanel.ClientHeight, True); end; { TEvent } function TEvent._AddRef: Integer; begin Result := 2; // Заглушка end; function TEvent._Release: Integer; begin Result := 1; // Заглушка end; constructor TEvent.Create(AnAppDispatch: IDispatch; const AnAppDispIntfIID: TGUID); begin inherited Create; FAppDispatch := AnAppDispatch; FAppDispIntfIID := AnAppDispIntfIID; // Передадим серверу InterfaceConnect(FAppDispatch,FAppDispIntfIID,self,FAppConnection); end; destructor TEvent.Destroy; begin InterfaceDisConnect(FAppDispatch,FAppDispIntfIID,FAppConnection); inherited; end; function TEvent.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; begin // Заглушка не реализовано Result := E_NOTIMPL; end; function TEvent.GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; begin // Заглушка не реализовано Result := E_NOTIMPL; end; function TEvent.GetTypeInfoCount(out Count: Integer): HResult; begin // Заглушка не реализовано Count := 0; Result := S_OK; end; function TEvent.Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; var Info,Status : string; IDWin : Integer; begin case dispid of 1 : begin Status := TDispParams(Params).rgvarg^[0].bstrval; if KDMapInfoServ <> nil then KDMapInfoServ.DoSetStatus(Status); end; 2 : begin IDWin := TDispParams(Params).rgvarg^[0].bval; if KDMapInfoServ <> nil then KDMapInfoServ.DoWindowContentsChanged(IDWin); end; 3 : begin Info := TDispParams(Params).rgvarg^[0].bstrval; if KDMapInfoServ <> nil then KDMapInfoServ.DoMyEvent(Info); end; end; Result := S_OK; end; function TEvent.QueryInterface(const IID: TGUID; out Obj): HResult; begin Result := E_NOINTERFACE; if GetInterface(IID,Obj) then Result := S_OK; if IsEqualGUID(IID,FAppDispIntfIID) and GetInterface(IDispatch,Obj) then Result := S_OK; end; end.
И так что добавилось - Метод CreateMapInfoServer;
// Создаем наш сервер OLE srv_ole := CreateOleObject('MICallBack.MapInfoCallBack') as IDispatch; srv_vtable := CoMapInfoCallBack.Create; // Получаем Idispatch созданного сервера srv_disp := CreateComObject(CLASS_MapInfoCallBack) as IMapInfoCallBackDisp; FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents); // Указываем MapInfo что нужно передовать обратные вызовы нашему OLE серверу // а там далее по цепочке (см.начало) FServer.SetCallBack(srv_disp); end;
Здесь мы столкнулись с еще одним методом MapInfo помимо рассмотренных ранее методов Do и Eval - Метод SetCallBack(IDispatch). Описание - Регистрирует объект механизма-управления объектами OLE (OLE Automation) как получатель уведомлений, генерируемых программой MapInfo. Только одна функция уведомления может быть зарегистрирована в каждый данный момент. Параметр интерфейс Idispatch объекта OLE (COM)
Реализация FServer.SetCallBack(srv_disp); - данным кодом мы заставили MapInfo уведомлять наш OLE сервер.
Хорошо, скажете вы, ну заставили но он то уведомляет сервер OLE а не нашу программу, для этого я ввел следующий код (прим. Реализацию использования интерфейса событий OLE сервера я подробно расписывать не стану - для этого читайте в книгах главы по COM)
Я сделал так: ввел класс отвечающий за принятие событий от COM(OLE) объекта
TEvent = class(TInterfacedObject,IUnknown,IDispatch) private FAppConnection : Integer; FAppDispatch : IDispatch; FAppDispIntfIID : TGUID; protected function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; function GetTypeInfoCount(out Count: Integer): HResult; stdcall; function GetTypeInfo(index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall; function Invoke(dispid: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall; public constructor Create( AnAppDispatch : IDispatch; const AnAppDispIntfIID : TGUID); destructor Destroy ; override; end;
создание этого класса в компоненте реализовано так
FEvent := TEvent.Create(srv_disp,IMapInfoCallBackEvents);
В методе Invoke и происходит прием и получение сообщений и пересылка их в обработчик моего компонента.
Еще раз на последующие вопросы касательно COM (OLE) серверов отвечу: данная тема выходит за рамки данной статьи - советую почитать книгу Александроского А.Д - Delphi 5 разработка корпоративных приложений.
Напоследок — модуль MICallBack_TLB.pas импортирован из DLL командой меню DELPHI Import Type Libray.
Примечание:
при импорте данный сервер инсталировать не нужно, нет смысла он нам нужен только для приема сообщений из MapInfo.
Вот в принципе все во второй части; создание пользовательских событий и обработка их в следующей главе.
До встречи