将 UIAutomation 提供程序添加到 Delphi 控件(特别是网格)
Adding UIAutomation Providers to Delphi controls (specifically grids)
我们的 VCL Delphi 应用程序有许多网格,我们需要开始通过 UIAutomation 与之交互。存在许多问题,尤其是 TStringGrid 未实现任何 IUIAutomation 模式(IGridProvider 或 ITableProvider,或者就此而言甚至是 IValueProvider)。
我正在尝试找出我需要添加到 TStringGrid 以允许它实现提供程序(在 .NET 的 System.Windows.Automation.Provider 命名空间中)的内容。
尽管我无法提供在 TStringGrid 上实现您所需的自动化功能所需的具体步骤,但我可以说,根据评论,您几乎拥有所需的一切。
您找到的文章 describing the basic implementation of UI Automation support for Win32 Unmanaged code 是一个很好的起点。
UIAutomationCore.DLL 中关于通过 IDL 公开什么和不公开什么的问题随后通过所讨论的 DLL 本身旨在被使用的事实得到解决通过非托管代码。它本身不包含托管代码。至少不涉及非托管用例。
里面包含的是一个IDL描述的COM接口,也包含一些简单的DLL导出的函数。据我所知,IDL 不描述 DLL 的导出 table。即使它能够这样做,在这个 DLL 的情况下它也不能(至少不是在所有情况下)。
例如,您提到的UiaHostProviderFromHwnd()函数就是一个简单的DLL导出。一些 additional functions exported in this way are described in this MSDN blog post describing creating a .net interop interface for this library。在那篇文章中,它们被称为 "flat API methods".
使用 PE Explorer 我可以看到 UIAutomationCore.dll 库导出了 81 个这样的函数。
不幸的是,DLL 导出 table 不描述任何导出函数的参数或 return 类型,仅描述名称。因此,除了类型库(由 IDL 生成)之外,您还需要找到并转换 UIAutomationCore.h header 文件用于 Delphi(即 Pascal)。
然后您应该拥有为您想要的任何 VCL 控件实现 UI 自动化功能所需的一切。
这是我的步骤...
(实际文件太大,无法post全部,所以这是对要点的提炼)。
另外 - 这仍然存在重大问题,可能是我自己造成的,但足以让我取得进步。
- 获取 UIAutomationCore.idl(我的是 Visual Studio 安装的一部分)。
- 运行 midl.exe 创建类型库。
- 运行 tlibimp.exe 从命令行(因为 Delphi 似乎不喜欢在步骤 3 中创建的 .tlb),然后创建 UIAutomationCore_TLB.pas文件。这最终成为一个相当大的文件,UIAutomationCore 的所有 COM 部分都以 pascal 定义。
- 原DLL中有方法不是COM的,也需要定义。我将这些添加到步骤 3 生成的文件中 - 尽管可能应该在其他地方定义它们以防重新生成此文件。
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';
</pre>
<ol start="5">
<li>该组件需要实现 IRawElementProviderSimple 接口以及任何其他提供程序 - 在示例中我使用了 ISelectionProvide,以说明我所做的事情。</li>
</ol>
<pre>
// 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;
</pre>
<p>这些实现如下..</p>
<pre>
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;
</pre>
<p>为了真正获得控制权,需要处理WM_GETOBJECT消息...</p>
<pre>
procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT;
</pre>
<p>实现如下..</p>
<pre>
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;
我们的 VCL Delphi 应用程序有许多网格,我们需要开始通过 UIAutomation 与之交互。存在许多问题,尤其是 TStringGrid 未实现任何 IUIAutomation 模式(IGridProvider 或 ITableProvider,或者就此而言甚至是 IValueProvider)。
我正在尝试找出我需要添加到 TStringGrid 以允许它实现提供程序(在 .NET 的 System.Windows.Automation.Provider 命名空间中)的内容。
尽管我无法提供在 TStringGrid 上实现您所需的自动化功能所需的具体步骤,但我可以说,根据评论,您几乎拥有所需的一切。
您找到的文章 describing the basic implementation of UI Automation support for Win32 Unmanaged code 是一个很好的起点。
UIAutomationCore.DLL 中关于通过 IDL 公开什么和不公开什么的问题随后通过所讨论的 DLL 本身旨在被使用的事实得到解决通过非托管代码。它本身不包含托管代码。至少不涉及非托管用例。
里面包含的是一个IDL描述的COM接口,也包含一些简单的DLL导出的函数。据我所知,IDL 不描述 DLL 的导出 table。即使它能够这样做,在这个 DLL 的情况下它也不能(至少不是在所有情况下)。
例如,您提到的UiaHostProviderFromHwnd()函数就是一个简单的DLL导出。一些 additional functions exported in this way are described in this MSDN blog post describing creating a .net interop interface for this library。在那篇文章中,它们被称为 "flat API methods".
使用 PE Explorer 我可以看到 UIAutomationCore.dll 库导出了 81 个这样的函数。
不幸的是,DLL 导出 table 不描述任何导出函数的参数或 return 类型,仅描述名称。因此,除了类型库(由 IDL 生成)之外,您还需要找到并转换 UIAutomationCore.h header 文件用于 Delphi(即 Pascal)。
然后您应该拥有为您想要的任何 VCL 控件实现 UI 自动化功能所需的一切。
这是我的步骤...
(实际文件太大,无法post全部,所以这是对要点的提炼)。
另外 - 这仍然存在重大问题,可能是我自己造成的,但足以让我取得进步。
- 获取 UIAutomationCore.idl(我的是 Visual Studio 安装的一部分)。
- 运行 midl.exe 创建类型库。
- 运行 tlibimp.exe 从命令行(因为 Delphi 似乎不喜欢在步骤 3 中创建的 .tlb),然后创建 UIAutomationCore_TLB.pas文件。这最终成为一个相当大的文件,UIAutomationCore 的所有 COM 部分都以 pascal 定义。
- 原DLL中有方法不是COM的,也需要定义。我将这些添加到步骤 3 生成的文件中 - 尽管可能应该在其他地方定义它们以防重新生成此文件。
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';
</pre>
<ol start="5">
<li>该组件需要实现 IRawElementProviderSimple 接口以及任何其他提供程序 - 在示例中我使用了 ISelectionProvide,以说明我所做的事情。</li>
</ol>
<pre>
// 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;
</pre>
<p>这些实现如下..</p>
<pre>
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;
</pre>
<p>为了真正获得控制权,需要处理WM_GETOBJECT消息...</p>
<pre>
procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT;
</pre>
<p>实现如下..</p>
<pre>
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;