我正在尝试在 Lazarus (Windows) 中编写一个应用程序,它使用 LibreOffice (Calc) 自动化来编辑电子表格并响应修改和保存事件。 我在 Delphi 中有一个工作版本,但我无法直接移植代码,因为 FreePascal 似乎没有 IConnectionPoint 和 IConnectionPoints 接口的任何实现。
我可以使用标准 CreateOleObject 函数成功打开和操作文档以获取顶级对象:
var libreMgr, libreDsk: OleVariant;
libreMgr := CreateOleObject('com.sun.star.ServiceManager');
libreDsk := libreMgr.createInstance('com.sun.star.frame.Desktop');
etc...
但我不知道如何创建和连接事件接口。
我的 Delphi 实现(来自很多年前)涉及重写 Delphi TComServer 来加载 LibreOffice 类型库而不将其添加到注册表中,我已经丢失了对此的原始归属,但我当然没有自己从头开始弄清楚它!
这是我原来的 Delphi 单元 - 由于 TConnectionPoints 类型,它无法在 Lazarus 中编译 - 我尝试重新实现此类,但它在运行时什么也不做 - 应用程序运行,但没有收到任何事件。
interface
uses
ComObj, Windows, ActiveX, AxCtrls, Classes, LibreOfficeEventListener_TLB;
type
TLibreEvent = procedure(const Source: OleVariant) of object;
ILibreOfficeEvents = interface(IDispatch)
['{06E57844-1345-406B-A18B-C7D88C44F11E}']
// additional events can be added here if required, this interface
// does not need to be immutable
function Get_OnUnload: TLibreEvent;
procedure Set_OnUnload(Value: TLibreEvent);
function Get_OnModifyChanged: TLibreEvent;
procedure Set_OnModifyChanged(Value: TLibreEvent);
function Get_OnPrepareUnload: TLibreEvent;
procedure Set_OnPrepareUnload(Value: TLibreEvent);
property OnUnload: TLibreEvent read Get_OnUnload write Set_OnUnload;
property OnModifyChanged: TLibreEvent read Get_OnModifyChanged write Set_OnModifyChanged;
property OnPrepareUnload: TLibreEvent read Get_OnPrepareUnload write Set_OnPrepareUnload;
end;
// To connect to LibreOffice document events, we have to implement an
// IDispatch listener with a type library, but it does not need to be registered.
// This is an acceptable compromise - it is not a full-blown COM object,
// but the type-library must be included as a resource in the EXE.
TLibreOfficeEventListener = class(TAutoObject,
IConnectionPointContainer,
ILibreOfficeEventListener,
ILibreOfficeEvents)
private
{ Private declarations }
FConnectionPoints: TConnectionPoints;
FConnectionPoint: TConnectionPoint;
FEvents: ILibreOfficeEventListenerEvents;
{ 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. }
FOnUnload: TLibreEvent;
FOnModifyChanged: TLibreEvent;
FOnPrepareUnload: TLibreEvent;
protected
{ Protected declarations }
property ConnectionPoints: TConnectionPoints read FConnectionPoints implements IConnectionPointContainer;
procedure EventSinkChanged(const EventSink: IUnknown); override;
{ ILibreOfficeEventListener methods }
procedure disposing; safecall;
procedure notifyEvent(const event: IDispatch); safecall;
{ ILibreOfficeEvents methods }
function Get_OnUnload: TLibreEvent;
procedure Set_OnUnload(Value: TLibreEvent);
function Get_OnModifyChanged: TLibreEvent;
procedure Set_OnModifyChanged(Value: TLibreEvent);
function Get_OnPrepareUnload: TLibreEvent;
procedure Set_OnPrepareUnload(Value: TLibreEvent);
public
procedure Initialize; override;
end;
{$R LibreOfficeEventListener.TLB} // type library is REQUIRED for hooking LibreOffice events
implementation
uses ComServNoRegister;
type
TAutoObjectFactoryNoRegister = class(TAutoObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
procedure TLibreOfficeEventListener.EventSinkChanged(const EventSink: IUnknown);
begin
FEvents := EventSink as ILibreOfficeEventListenerEvents;
end;
procedure TLibreOfficeEventListener.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 TLibreOfficeEventListener.disposing;
begin
// do nothing here
end;
procedure TLibreOfficeEventListener.notifyEvent(const event: IDispatch);
var
e: OleVariant;
begin
e := event;
{ document events which can be intercepted:
OnTitleChanged
OnModifyChanged
OnStorageChanged
OnModeChanged
OnSave
OnSaveDone
OnSaveAs
OnSaveAsDone
OnPrepareUnload
OnUnload
}
if e.EventName='OnModifyChanged' then begin
if Assigned(FOnModifyChanged) then FOnModifyChanged(e.Source);
end
else if e.EventName='OnPrepareUnload' then begin // CM 12/09/2024 added
if Assigned(FOnPrepareUnload) then FOnPrepareUnload(e.Source);
end
else if e.EventName='OnUnload' then begin
if Assigned(FOnUnload) then FOnUnload(e.Source);
end;
end;
function TLibreOfficeEventListener.Get_OnUnload: TLibreEvent;
begin
Result := FOnUnload;
end;
procedure TLibreOfficeEventListener.Set_OnUnload(Value: TLibreEvent);
begin
FOnUnload := Value;
end;
{ TAutoObjectFactoryNoRegister }
procedure TAutoObjectFactoryNoRegister.UpdateRegistry(Register: Boolean);
begin
// do nothing - do not save COM class or typelib info in the registry.
// See also ComServNoRegister.pas
end;
function TLibreOfficeEventListener.Get_OnModifyChanged: TLibreEvent;
begin
Result := FOnModifyChanged;
end;
procedure TLibreOfficeEventListener.Set_OnModifyChanged(Value: TLibreEvent);
begin
FOnModifyChanged := Value;
end;
function TLibreOfficeEventListener.Get_OnPrepareUnload: TLibreEvent;
begin
Result := FOnPrepareUnload;
end;
procedure TLibreOfficeEventListener.Set_OnPrepareUnload(Value: TLibreEvent);
begin
FOnPrepareUnload := Value;
end;
initialization
TAutoObjectFactoryNoRegister.Create(ComServer, TLibreOfficeEventListener,
Class_LibreOfficeEventListener, ciMultiInstance, tmApartment);
end.
这应该通过调用
与打开的 LO 文档挂钩libreDoc.addEventListener(libreEvents);
但是,正如我所说,这没有任何作用。
任何帮助,或指向其他实现的指针都会有很大的帮助,谢谢!
以前有人这样做过吗?
您对 TLibreEvent 的定义是一个双指针(一个用于方法地址,一个用于 SELF/THIS),并且它在 COM 接口中使用。 libreoffice 不太可能支持这种 Delphi 内部样式事件。
我还错过了接口定义中的任何调用约定修饰符。
您可以尝试在 lazarus 论坛上发布示例项目,一般来说,C/C++ 示例比 Python 更适合(因为您永远不知道解释器和/或包在幕后屏蔽了什么)