Difference between revisions of "Documentation/DevGuide/OfficeDev/Document Events"

From Apache OpenOffice Wiki
Jump to: navigation, search
m (FINAL VERSION FOR L10N)
m
Line 78: Line 78:
  
 
In {{PRODUCTNAME}} Basic, the fully qualified name including the interface can be used from version {{OO1.1.0}}:
 
In {{PRODUCTNAME}} Basic, the fully qualified name including the interface can be used from version {{OO1.1.0}}:
 
+
<source lang="oobas">
 
   Sub RegisterListener
 
   Sub RegisterListener
 
    
 
    
Line 97: Line 97:
 
   Sub DocumentListener_disposing()
 
   Sub DocumentListener_disposing()
 
   End Sub
 
   End Sub
 +
</source>
  
 
But the OLE automation bridge, and possibly other scripting language bindings, are unable to distinguish between both <code>addEventListener()</code> and <code>removeEventListener()</code> methods based on the method signature and must be told which interface you want to use.
 
But the OLE automation bridge, and possibly other scripting language bindings, are unable to distinguish between both <code>addEventListener()</code> and <code>removeEventListener()</code> 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.  
 
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.  
 
+
<source lang="vb">
 
   set xContext = objServiceManager.getPropertyValue( "DefaultContext" )
 
   set xContext = objServiceManager.getPropertyValue( "DefaultContext" )
 
   set xCoreReflection = xContext.getValueByName( "/singletons/com.sun.star.reflection.theCoreReflection" )
 
   set xCoreReflection = xContext.getValueByName( "/singletons/com.sun.star.reflection.theCoreReflection" )
Line 113: Line 114:
 
   call value.InitInOutParam("[]any", invokeargs)
 
   call value.InitInOutParam("[]any", invokeargs)
 
   call xMethod.invoke( objDocument, value )
 
   call xMethod.invoke( objDocument, value )
 
+
</source>
 
The C++ code below uses OLE Automation. Two helper functions are provided that help to execute UNO operations.
 
The C++ code below uses OLE Automation. Two helper functions are provided that help to execute UNO operations.
 
  <source lang="cpp">
 
  <source lang="cpp">

Revision as of 09:22, 23 October 2009



Recurring actions, such as loading, printing or saving, that occur when working with documents, are document events, and all documents in OpenOffice.org 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 OpenOffice.org 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 OpenOffice.org 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 OpenOffice.org 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 OpenOffice.org 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 OpenOffice.org 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). OpenOffice.org 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>.


Template:Documentation/Note

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 OpenOffice.org. 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 OpenOffice.org UI in the Tools - Configure dialog on the Events page. Two radio buttons on the right side of the dialog toggle between OpenOffice.org and Document binding. In OpenOffice.org 1.1.0, you can still only bind to OpenOffice.org 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