CCF: Listening to External Application Events

It is often necessary to listen to external application UI events when automating applications in CCF. For example, we may need to save a value in the Context if the user presses a button, or do something when an alert dialog appears. There are many ways we can handle events. In CCF 2008, the Hosted Application Toolkit (HAT) provides an easy way to listen to them. If not using HAT we can hook to events using the UIAutomation or MSAA (Microsoft Active Accessibility) APIs within our own application adapters.

Events in HAT

When we configure a hosted application for using an Automation Adapter, this adapter listens to the application events. We can hook and unhook to these events using the CCF's WF activities RegisterActionForEvent (see figure bellow)  and UnregisterActionForEvent respectively.

 

It is the Data Driven Adapter (DDA) the one who throws the ControlChanged event (to which the activity subscribes). The arguments of the event consists of eventTypeName, controlName and controlValue.

For example, the WinDataDrivenAdapter that CCF provides supports the following event types:

  1. SetControlValue: When the DDA set's a control's value.
  2. ExecuteControlAction: When the DDA executes a control.
  3. ContextChanged: When the context changes.
  4. MenuShown: When a menu is shown
  5. WindowShown: When a window is shown
  6. WindowDisappeared: When a window is closed
  7. LostFocus
  8. SetFocus
  9. CheckBoxSet
  10. CheckBoxCleared
  11. RadioButtonSet
  12. RadioButtonCleared
  13. ButtonPressed
  14. ButtonReleased

There are some tricks about how to use this events, for example:

  • 4-6: For menu and window events we must not register with the control's friendly name that we set in the databindings section of the AppInitString. We must specify the window or menu's caption instead. 
  • 7-14: For these events the related control must be previously found on the UI. You can use the FindControl activity to ensure that the control is successfully found. When the DDA finds the control it registers it in a KnownControls collection. Only for those registered controls the events are thrown.

Custom DDAs or Legacy Adapters

If we need to handle events from a custom DDA or from a legacy adapter we can use win32 or accessibility APIs. If we use a custom DDA we can throw a ControlChanged event in order to use the WF/Automation activities mentioned before.

WinEvents

A low level approach is to use the SetWinEventHook and UnhookWinEvent functions from user32.dll. When we hook for events we specify the callback function that will handle the events. You can see the list of supported events and its constants in the MSAA SDK documentation on msdn.

In order to show some sample code, here are the user32 functions imports:

[Flags]

internal enum SetWinEventHookFlags

{

    WINEVENT_INCONTEXT = 4,

    WINEVENT_OUTOFCONTEXT = 0,

    WINEVENT_SKIPOWNPROCESS = 2,

    WINEVENT_SKIPOWNTHREAD = 1

}

 

internal delegate void WinEventProc(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);

[DllImport("user32.dll", SetLastError = true)]

internal static extern IntPtr SetWinEventHook(int eventMin, int eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, int idProcess, int idThread, SetWinEventHookFlags dwflags);

[DllImport("user32.dll", SetLastError = true)]

internal static extern int UnhookWinEvent(IntPtr hWinEventHook);

 

Here's how we can subscribe to win events from our custom adapter:

WinEventProc wep = new WinEventProc(this.EventCallback);

SetWinEventHook(1, 0x7fffffff, IntPtr.Zero, wep, 0, threadId, SetWinEventHookFlags.WINEVENT_OUTOFCONTEXT);

 

And finally how to react to events in the event handler method:

private void EventCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)

{

    //get accObj from handler

    //get accObj's name, role, etc...

    ...

    switch (iEvent)

    {

        case 0x10: //EVENT_SYSTEM_DIALOGSTART

            if (role.Equals(localize.ACC_ROLE_TEXT_WINDOW))

            {

                  if (name.Length > 0)

                  {

                      //throw ControlChangedEvent

                      this.ControlChanged(accObj, new ControlChangedEventArgs("DialogStart", controlName, string.Empty));

                  }

            }

            break;

        ...

    }

}

 

UIAutomation

Another option is to use the UIAutomation accessibility API, included on .Net framework 3.0 to hook to events from custom adapters. The Automation element provides the following static methods to hook to different event on different controls:

//Registers a method that handles UI Automation events.

public static void AddAutomationEventHandler(AutomationEvent eventId, AutomationElement element, TreeScope scope, AutomationEventHandler eventHandler);

//Registers a method that will handle focus-changed events.

public static void AddAutomationFocusChangedEventHandler(AutomationFocusChangedEventHandler eventHandler);

//Registers a method that will handle property-changed events.

public static void AddAutomationPropertyChangedEventHandler(AutomationElement element, TreeScope scope, AutomationPropertyChangedEventHandler eventHandler, params AutomationProperty[] properties);

//Registers the method that will handle structure-changed events.

public static void AddStructureChangedEventHandler(AutomationElement element, TreeScope scope, StructureChangedEventHandler eventHandler);

 

The UIAutomation API is much more friendly to use, but some times it takes too long for the event to reach the handler. You should be very precise when setting the element and scope to subscribe the event to. You can find more information about this API and sample code on my previous post about WPF Accessibility.

5 Comments

  • how can i catch an event that happens in a textbox(eg:- Text Chaged)that is outside the CCF. it runs as a windows form when clicks the .exe. i don't have source code of that windows form. Help me plz?

  • Hi,

    I'm not too expierenced with CCF, but i'm trying to make a window monitor in an External Application. I want to fire an event when a form of the external application shown.

    Can you help me, please?

    Thanks.

  • Hi,

    I'm trying to launch an automation action when a window form shown (using your example above). But, when the form shown, there is nothing happened.


    Can you please help me?

    Thanks.

  • Hi,

    how can i capture left mouse click event within
    EventCallback event?

  • Hi,

    How can i find controls on a external application? I need to get it to register at their events?

Comments have been disabled for this content.