Document Events

From Apache OpenOffice Wiki
Jump to: navigation, search



Recurring actions, such as loading, printing or saving, that occur when working with documents, are document events, and all documents in Apache OpenOffice offer an interface that sends notifications when these events take place.

There are general events common every document, such as loading, printing, or saving, and there are other events that are specific to a particular document type. Both can be accessed through the same interface.

In the document events API, these events are represented by an event name. The following table shows a list of all general document event names:

General Document Event Names
OnNew New Document was created
OnLoad Document has been loaded
OnSaveAs Document is going to be saved under a new name
OnSaveAsDone Document was saved under a new name
OnSave Document is going to be saved
OnSaveDone Document was saved
OnPrepareUnload Document is going to be removed, but still fully available
OnUnload Document has been removed, document ist still valid, but closing can no longer be prevented
OnFocus Document was activated
OnUnfocus Document was deactivated
OnPrint Document will be printed
OnModifyChange Modified state of the document has changed

These event names are documented in the com.sun.star.document.Events service. Note that this service description exceeds the scope of events that happen on the document as a whole - so it also contains events that can only be accessed by finding the part of the document where the event occurred, for example, a button in a form. This list of events can also be extended by new events, so that future versions of Apache OpenOffice can support new types of events through the same API. Therefore, every client that wants to deal with a particular document event must check if this event is supported, or whether it should be prepared to catch an exception.

Every client that is interested in document events can register for being notified. The necessary interface for notification is com.sun.star.document.XEventBroadcaster, which is an optional interface of the service com.sun.star.document.OfficeDocument. All document objects in Apache OpenOffice implement this interface. It has two methods to add and remove listeners for document events:

  [oneway] void addEventListener( [in] ::com::sun::star::document::XEventListener Listener ); 
  [oneway] void removeEventListener( [in] ::com::sun::star::document::XEventListener Listener );

The listeners must implement the interface com.sun.star.document.XEventListener and get a notification through a call of their method:

  [oneway] void notifyEvent( [in] ::com::sun::star::document::EventObject Event );

The argument of this call is a com.sun.star.document.EventObject struct, which is derived from the usual com.sun.star.lang.EventObject and contains two members: the member Source, which contains an interface pointer to the event source (here the com.sun.star.document.OfficeDocument service) and the member EventName which can be one of the names shown in the preceding table.

Both methods in the interface com.sun.star.document.XEventBroadcaster can cause problems in scripting languages if the object that implements this interface also implements com.sun.star.lang.XComponent, because it has two very similar methods:

  [oneway] void addEventListener( [in] ::com::sun::star::lang::XEventListener Listener ); 
  [oneway] void removeEventListener( [in] ::com::sun::star::lang::XEventListener Listener );

Unfortunately this applies to all Apache OpenOffice documents.

In C++ and Java this is no problem, because the complete signature of a method, including the arguments, is used to identify it.

In Apache OpenOffice Basic, the fully qualified name including the interface can be used from version 1.1.0:

  Sub RegisterListener
  
    oListener = CreateUnoListener( "DocumentListener_","com.sun.star.document.XEventListener" )
  
    ThisComponent.com_sun_star_document_XEventBroadcaster_addEventListener( oListener )
  
  End Sub
  
  Sub DocumentListener_notifyEvent( o as object )
  
    IF o.EventName = "OnPrepareUnload" THEN
          print o.Source.URL
    ENDIF
  
  end sub
  
  Sub DocumentListener_disposing()
  End Sub

But the OLE automation bridge, and possibly other scripting language bindings, are unable to distinguish between both addEventListener() and removeEventListener() methods based on the method signature and must be told which interface you want to use.

You must use the core reflection to get access to either method. The following code shows an example in VBScript, which registers a document event listener at the current document.

  set xContext = objServiceManager.getPropertyValue( "DefaultContext" )
  set xCoreReflection = xContext.getValueByName( "/singletons/com.sun.star.reflection.theCoreReflection" )
  set xClass = xCoreReflection.forName( "com.sun.star.document.XEventBroadcaster" )
  set xMethod = xClass.getMethod( "addEventListener" )
  
  dim invokeargs(0)
  invokeargs(0) = myListener
  
  set value = objServiceManager.Bridge_GetValueObject()
  call value.InitInOutParam("[]any", invokeargs)
  call xMethod.invoke( objDocument, value )

The C++ code below uses OLE Automation. Two helper functions are provided that help to execute UNO operations.

  // helper function to execute UNO operations via IDispatch
  HRESULT ExecuteFunc( IDispatch* idispUnoObject,
                                          OLECHAR* sFuncName,
                                          CComVariant* params,
                                          unsigned int count,
                                          CComVariant* pResult )
  {
   if( !idispUnoObject ) 
     return E_FAIL;
  
  DISPID id;
  HRESULT hr = idispUnoObject->GetIDsOfNames( IID_NULL, &sFuncName, 1, LOCALE_USER_DEFAULT, &id); 
  if( !SUCCEEDED( hr ) ) return hr;
  
  DISPPARAMS dispparams= { params, 0, count, 0};
  
  // DEBUG
  EXCEPINFO myInfo;
  return idispUnoObject->Invoke( id, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_METHOD,
                    &dispparams, pResult, &myInfo, 0);
  }
  // helper function to execute UNO methods that return interfaces
  HRESULT GetIDispByFunc( IDispatch* idispUnoObject,
                                                   OLECHAR* sFuncName,
                                                   CComVariant* params,
                                                   unsigned int count,
                                                   CComPtr<IDispatch>& pdispResult )
  {
   if( !idispUnoObject )
     return E_FAIL; 
  
   CComVariant result;
   HRESULT hr = ExecuteFunc( idispUnoObject, sFuncName, params, count, &result );
   if( !SUCCEEDED( hr ) ) return hr; 
   
   if( result.vt != VT_DISPATCH || result.pdispVal == NULL )
     return E_FAIL;
   
   pdispResult = CComPtr<IDispatch>( result.pdispVal );
   
   return S_OK;
  }
  
  // it's assumed that pServiceManager (by creating it as a COM object), pDocument (f.e. by loading it) 
  // and pListener (the listener we want to add) are passed as parameters
  
  HRESULT AddDocumentEventListener(
   CComPtr<IDispatch> pServiceManager, CComPtr<IDispatch> pDocument, CComPtr<IDispatch> pListener)
  
  {
    CComPtr<IDispatch> pdispContext;
   hr = GetIDispByFunc( pServiceManager, L"getPropertyValue", &CComVariant( L"DefaultContext" ), 1,
                pdispContext );
   if( !SUCCEEDED( hr ) ) return hr; 
  
   CComPtr<IDispatch> pdispCoreReflection; 
   hr = GetIDispByFunc( pdispContext,
                          L"getValueByName",
                          &CcomVariant( L"/singletons/com.sun.star.reflection.theCoreReflection" ),
                          1,
                          pdispCoreReflection );
   if( !SUCCEEDED( hr ) ) return hr;
  
   CComPtr<IDispatch> pdispClass;
   hr = GetIDispByFunc( pdispCoreReflection,
                          L"forName",
                          &CComVariant( L"com.sun.star.document.XEventBroadcaster" ),
                          1,
                          pdispClass );
   if( !SUCCEEDED( hr ) ) return hr;
  
   CComPtr<IDispatch> pdispMethod;
   hr = GetIDispByFunc( pdispClass, L"getMethod", &CComVariant( L"addEventListener" ), 1, pdispMethod );
   if( !SUCCEEDED( hr ) ) return hr;
  
   CComPtr<IDispatch> pdispListener;
   CComPtr<IDispatch> pdispValueObj;
    hr = GetIDispByFunc( mpDispFactory, L"Bridge_GetValueObject", NULL, 0, pdispValueObj );
    if( !SUCCEEDED( hr ) ) return hr;
  
    CComVariant pValParams[2];
    pValParams[1] = CComVariant( L"com.sun.star.document.XEventListener" );
    pValParams[0] = CComVariant( pdispListener );
    CComVariant dummyResult;
    hr = ExecuteFunc( pdispValueObj, L"Set", pValParams, 2, &dummyResult );
    if( !SUCCEEDED( hr ) ) return hr;
    
    SAFERRAY FAR* pPropVal = SafeArrayCreateVector( VT_VARIANT, 0, 1 );
    long ix1 = 0;
  
    CComVariant aArgs( pdispValueObj );
    SafeArrayPutElement( pPropVal, &ix, &aArgs );
    
    CComVariant aDoc( pdispDocument );
    CComVariant pParams[2];
    pParams[1] = aDoc;
    pParams[0].vt = VT_ARRAY | VT_VARIANT; pParams[0].parray = pPropVal;
    
    CComVariant result;
   
    //invoking the method addeventlistner
    hr = ExecuteFunc( pdispMethod, L"invoke", pParams, 2, &result );
    if( !SUCCEEDED( hr ) ) return hr;
   
    return S_OK;
  }

Another way to react to document events is to bind a macro to it - a process called event binding. From OpenOffice.org 1.1.0 you can also use scripts in other languages, provided that a corresponding scripting framework implementation is present.

All document objects in Apache OpenOffice support event binding through an interface com.sun.star.document.XEventsSupplier. This interface has only one method:

  ::com::sun::star::container::XNameReplace getEvents();

This method gives access to a container of event bindings. The container is represented by a com.sun.star.container.XNameReplace interface that, together with the methods of its base interfaces, offers the following methods:

  void replaceByName( [in] string aName, [in] any aElement );
  any getByName( [in] string aName );
  sequence< string > getElementNames(); 
  boolean hasByName( [in] string aName );
  type getElementType(); 
  boolean hasElements();

Each container element represents an event binding. By default, all bindings are empty. The element names are the event names shown in the preceding table. In addition, there are document type-specific events. The method getElementNames() yields all possible events that are supported by the object and hasByName() checks for the existence of a particular event.

For every supported event name you can use getByName() to query for the current event binding or replaceByName() to set a new one. Both methods may throw a com.sun.star.container.NoSuchElementException exception if an unsupported event name is used.

The type of an event binding, which is wrapped in the any returned by getByName(), is a sequence of com.sun.star.beans.PropertyValue that describes the event binding.

PropertyValue structs in the event binding description
EventType string. Can assume the values "StarBasic" or "Script". The event type "Script" describes the location as URL.The event type "StarBasic" is provided for compatibility reasons and describes the location of the macro through the properties Library and MacroName, in addition to URL.
Script string. Available for the event types Script and StarBasic. Describes the location of the macro/script routine which is bound. For the URL property, a command URL is expected (see Using the Dispatch Framework). Apache OpenOffice will execute this command when the event occurs.

For the event type StarBasic, the URL uses the macro: protocol. For the event type Script, other protocols are possible, especially the script: protocol.

The macro protocol has two forms:

 macro:///<Library>.<Module>.<Method(args)>
 macro://./<Library>.<Module>.<Method(args)>

The first form points to a method in the global basic storage, while the second one points to a method embedded in the current document. <Library>.<Module>.<Method(args)> represent the names of the library, the module and the method. Currently, for args only string arguments (separated by comma) are possible. If no args exist, empty brackets must be used, because the brackets are part of the scheme. An example URL could look like:

 macro:///MyLib.MyModule.MyMethod(foo,bar)

The exact form of the script: command URL protocol depends on the installed scripting module. They will be available later as additional components for OpenOffice.org 1.1.0.

Library string. Deprecated. Available for EventType "StarBasic". Can assume the values "application" or empty string for the global basic storage, and "document" for the document where the code is embedded.
MacroName string. Deprecated. Available for EventType "StarBasic". Describes the macro location as <Library>.<MyModule>.<MyMethod>.


Documentation note.png In OpenOffice.org 1.1.0 all properties (URL, Library, MacroName) will be returned for event bindings of type StarBasic, regardless if the binding was created with a URL property only or with the Library and MacroName property. The internal implementation does the necessary conversion. Older versions of Apache OpenOffice always returned only Library and MacroName, even if the binding was created with the URL property.

In OpenOffice.org 1.1.0 there is another important extension in the area of document events and event bindings. This version has a new service com.sun.star.frame.GlobalEventBroadcaster that offers the same document-event-related functionality as described previously (interfaces com.sun.star.document.XEventBroadcaster, com.sun.star.document.XEventsSupplier), but it allows you to register for events that happen in any document and also allows you to set bindings for all documents that are stored in the global UI configuration of Apache OpenOffice. Using this services frees you from registering at every single document that has been created or loaded.

Though a potential listener registers for event notifications at this global service and not at any document itself, the received event source in the event notification is the document, not the GlobalEventBroadcaster. The reason for this is that usually a listener contains code that works on the document, so it needs a reference to it.

The service com.sun.star.frame.GlobalEventBroadcaster also supports two more events that do not occur in any document but are useful for working with document events:

Global Event Names
OnStartApp Application has been started
OnCloseApp Application is going to be closed. This event is fired after all documents have been closed and nobody objected to the shutdown.

The event source in the notifications is NULL (empty).

All event bindings can be seen or set in the Apache OpenOffice UI in the Tools → Configure dialog on the Events page. Two radio buttons on the right side of the dialog toggle between Apache OpenOffice and Document binding. In OpenOffice.org 1.1.0, you can still only bind to Apache OpenOffice Basic macros in the dialog. Bindings to script: URLs can only be set using the API, but the dialog is at least able to display them. If, in OpenOffice.org 1.1.0, a global and a document binding are set for the same event, first the global and then the document binding is executed. With older versions, only the document binding was executed, and the global binding was only executed if no document binding was set.

Content on this page is licensed under the Public Documentation License (PDL).
Personal tools
In other languages