Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding UIAutomation Providers to Delphi controls (specifically grids)

Our VCL Delphi application has a number of grids that we need to start to interact with via UIAutomation. There are a number of issues, not least that the TStringGrid doesn't implement any of the IUIAutomation patterns (IGridProvider or ITableProvider, or for that matter even IValueProvider).

I am trying to find out what I need to added to a TStringGrid to allow it to implement the providers (which in the System.Windows.Automation.Provider namespace in .NET).

like image 784
mmmm Avatar asked Apr 24 '15 07:04

mmmm


2 Answers

Here are my steps ...

(The actual files are too large to post all of them, so this is a distillation of the major points).

ALSO - This still has major issues, probably of my own making, but it is enough for me to make progress.

  1. Get the UIAutomationCore.idl (mine was as part of a Visual Studio installation).
  2. Run midl.exe to create the type library.
  3. Run tlibimp.exe from the command-line (as Delphi doesn't seem to like the .tlb created in step 3), and create the UIAutomationCore_TLB.pas file. This ends up being a rather large file, with all of the COM parts of UIAutomationCore defined in pascal.
  4. There are methods in the original DLL that are not COM, and these need to be defined as well. These I added to the generated file from Step 3 - although probably they should be defined elsewhere in case this file is regenerated.

    function UiaHostProviderFromHwnd(hwnd: HWND; provider: IRawElementProviderSimple): LRESULT; stdcall; external 'UIAutomationCore.dll' name 'UiaHostProviderFromHwnd';    
    function UiaReturnRawElementProvider(hwnd: HWND; wParam: WPARAM; lParam: LPARAM; element : IRawElementProviderSimple) : LRESULT; stdcall; external 'UIAutomationCore.dll' name 'UiaReturnRawElementProvider';
  1. The component needs to implement the IRawElementProviderSimple interface, as well as any other providers - in the example case I have used ISelectionProvide, in order to illustrate what I did.
    // IRawElementProviderSimple
    function Get_ProviderOptions(out pRetVal: ProviderOptions): HResult; stdcall;
    function GetPatternProvider(patternId: SYSINT; out pRetVal: IUnknown): HResult; stdcall;
    function GetPropertyValue(propertyId: SYSINT; out pRetVal: OleVariant): HResult; stdcall;
    function Get_HostRawElementProvider(out pRetVal: IRawElementProviderSimple): HResult; stdcall;

    // ISelectionProvider
    function GetSelection(out pRetVal: PSafeArray): HResult; stdcall;
    function Get_CanSelectMultiple(out pRetVal: Integer): HResult; stdcall;
    function Get_IsSelectionRequired(out pRetVal: Integer): HResult; stdcall;

These are implemented as follows ..

function TAutomationStringGrid.Get_ProviderOptions(
  out pRetVal: ProviderOptions): HResult;
begin
  pRetVal:= ProviderOptions_ClientSideProvider;
  Result := S_OK;
end;

function TAutomationStringGrid.GetPatternProvider(patternId: SYSINT;
  out pRetVal: IInterface): HResult;
begin
  pRetval := nil;
  if (patternID = UIA_SelectionPatternId) then
  begin
    result := QueryInterface(ISelectionProvider, pRetVal);
  end
  else
    result := S_OK;
end;

function TAutomationStringGrid.GetPropertyValue(propertyId: SYSINT;
  out pRetVal: OleVariant): HResult;
begin
  if(propertyId = UIA_ControlTypePropertyId) then
  begin
    TVarData(pRetVal).VType := varWord;
    TVarData(pRetVal).VWord := UIA_DataGridControlTypeId;
  end;
  result := S_OK;
end;

function TAutomationStringGrid.Get_HostRawElementProvider(
  out pRetVal: IRawElementProviderSimple): HResult;
begin
  result := UiaHostProviderFromHwnd (self.Handle, pRetVal);
end;

function TAutomationStringGrid.GetSelection(out pRetVal: PSafeArray): HResult;
begin
end;
function TAutomationStringGrid.Get_CanSelectMultiple(
  out pRetVal: Integer): HResult;
begin
end;

function TAutomationStringGrid.Get_IsSelectionRequired(
  out pRetVal: Integer): HResult;
begin
end;

In order to actually get the control, the WM_GETOBJECT message needs to be handled ...

    procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT;

This is implemented as follows ..

procedure TAutomationStringGrid.WMGetObject(var Message: TMessage);
begin
  if (Message.Msg = WM_GETOBJECT) then
  begin
    QueryInterface(IID_IRawElementProviderSimple, FRawElementProviderSimple);

    message.Result := UiaReturnRawElementProvider(self.Handle, Message.WParam, Message.LParam, FRawElementProviderSimple);
  end
  else
    Message.Result := DefWindowProc(self.Handle, Message.Msg, Message.WParam, Message.LParam);
end;
like image 180
mmmm Avatar answered Sep 27 '22 20:09

mmmm


Although I cannot provide the specific steps required to implement the automation capabilities you require on TStringGrid, I can say that based on the comments you have almost everything you need.

The article you found describing the basic implementation of UI Automation support for Win32 Unmanaged code is a good place to start.

The questions over what is and is not exposed through the IDL in UIAutomationCore.DLL are then addressed by the fact that the DLL in question is itself intended to be consumed by unmanaged code. It contains no managed code itself. At least not that is involved in an unmanaged use case.

What is does contain is a COM interface described by IDL, but also some functions simply exported by the DLL. As far as I know, IDL does not describe the exports table of a DLL. Even if it is capable of doing so, in the case of this DLL it does not (at least not in all cases).

For example, the UiaHostProviderFromHwnd() function that you have mentioned is a simple DLL export. Some of the additional functions exported in this way are described in this MSDN blog post describing creating a .net interop interface for this library. In that article they are called "flat API methods".

Using PE Explorer I can see 81 such functions exported by the UIAutomationCore.dll library.

Unfortunately a DLL exports table does not describe the parameters or return types of any exported function, only the names. So, in addition to the type library (produced from the IDL) you will also need to locate and convert the UIAutomationCore.h header file for use with Delphi (i.e. Pascal).

You should then have everything need to implement the UI Automation capabilities for any VCL control you desire.

like image 27
Deltics Avatar answered Sep 27 '22 19:09

Deltics