在 Delphi 中创建可访问的 UI 组件

2024-02-06

我正在尝试从标准 VCL TEdit 控件检索可访问的信息。 get_accName() 和 Get_accDescription() 方法返回空字符串,但 get_accValue() 返回输入到 TEdit 中的文本值。

我刚刚开始尝试理解 MSAA,但此时我有点迷失。

我的 TEdit 是否需要具有向 MSA 公开的其他已发布属性?如果是这样,是否需要创建一个源自 TEdit 的新组件并添加其他已发布的属性,例如“AccessibleName”、“AccessibleDescription”等...?

另外,请注意,我查看了 VTVirtualTrees 组件,它是supposed可以访问,但即使在该控件上,MS Active Accessibility 对象检查器仍然看不到 AccessibleName 已发布属性。

在这一点上,我很茫然,非常感谢您在这件事上的任何建议或帮助。

...
interface
uses
   Winapi.Windows,
   Winapi.Messages,
   System.SysUtils,
   System.Variants,
   System.Classes,
   Vcl.Graphics,
   Vcl.Controls,
   Vcl.Forms,
   Vcl.Dialogs,
   Vcl.StdCtrls,
   Vcl.ComCtrls,
   Vcl.ExtCtrls,
   oleacc;

const
  WM_GETOBJECT = $003D; // Windows MSAA message identifier
  OBJID_NATIVEOM = $FFFFFFF0;

type
  TForm1 = class(TForm)
    lblFirstName: TLabel;
    edFirstName: TEdit;
    panel1: TPanel;
    btnGetAccInfo: TButton;
    accInfoOutput: TEdit;
    procedure btnGetAccInfoClick(Sender: TObject);
    procedure edFirstNameChange(Sender: TObject);
  private
    { Private declarations }
    FFocusedAccessibleObj: IAccessible;
    FvtChild: Variant;
    FAccProperties: TStringList;
    FAccName: string;
    FAccDesc: string;
    FAccValue: string;
    procedure DoGetAccessibleObjectFromPoint(aPoint: TPoint);
  public
   { Public declarations }
   procedure BeforeDestruction; override;
   property AccName: string read FAccName;
   property AccDescription: string read FAccName;
   property AccValue: string read FAccName;
  end;

var
  Form1: TForm1;

const
  cCRLF = #13#10;

implementation

{$R *.dfm}

function AccessibleObjectFromPoint(ptScreen: TPoint;
                                   out ppacc: IAccessible;
                                   out pvarChildt: Variant): HRESULT; stdcall; external   'oleacc.dll' ;

{------------------------------------------------------------------------------}
procedure TForm1.BeforeDestruction;
begin
  VarClear(FvtChild);
  FFocusedAccessibleObj := nil;
end;

{------------------------------------------------------------------------------}
procedure TForm1.DoGetAccessibleObjectFromPoint(aPoint: TPoint);
var
  pt: TPoint;
  bsName: WideString;
  bsDesc: WideString;
  bsValue: WideString;
begin
  if (SUCCEEDED(AccessibleObjectFromPoint(aPoint, FFocusedAccessibleObj, FvtChild))) then
    try
      // get_accName  returns an empty string
      bsName := '';
      FFocusedAccessibleObj.get_accName(FvtChild, bsName);
      FAccName := bsName;
      FAccProperties.Add('Acc Name: ' + FAccName + '  |  ' + cCRLF);

      // Get_accDescription  returns an empty string
      bsDesc := '';
      FFocusedAccessibleObj.Get_accDescription(FvtChild, bsDesc);
      FAccDesc := bsDesc;
      FAccProperties.Add('Acc Description: ' + FAccDesc + '  |  ' + cCRLF);

      // this works
      bsValue := '';
      FFocusedAccessibleObj.get_accValue(FvtChild, bsValue);
      FAccValue := bsValue;
      FAccProperties.Add('Acc Value: ' + FAccValue  + cCRLF);

   finally
     VarClear(FvtChild);
     FFocusedAccessibleObj := nil ;
   end;
  end;

  {------------------------------------------------------------------------------}
  procedure TForm1.btnGetAccInfoClick(Sender: TObject);
  begin
    FAccProperties := TStringList.Create;
    DoGetAccessibleObjectFromPoint(edFirstName.ClientOrigin);
    accInfoOutput.Text := FAccProperties.Text;
  end;   
end.

VCL 本身并不原生实现对 MSAA 的任何支持。 Windows 提供了标准 UI 控件的默认实现,许多标准 VCL 组件都封装了这些控件。如果您需要比 Windows 提供的更多 MSAA 支持,则必须实施IAccessible http://msdn.microsoft.com/en-us/library/windows/desktop/dd318466.aspx自己连接,然后让你的控件响应WM_GETOBJECT http://msdn.microsoft.com/en-us/library/windows/desktop/dd373894.aspx消息,以便它可以返回指向您的实现实例的指针。

Update:例如,将 MSAA 添加到现有的TEdit(如果您不想派生自己的组件)可能看起来像这样:

uses
  ..., oleacc;

type
  TMyAccessibleEdit = class(TInterfacedObject, IAccessible)
  private
    fEdit: TEdit;
    fDefAcc: IAccessible;
  public
    constructor Create(aEdit: TEdit; aDefAcc: IAccessible);

    function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;

    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;

    function Get_accParent(out ppdispParent: IDispatch): HResult; stdcall;
    function Get_accChildCount(out pcountChildren: Integer): HResult; stdcall;
    function Get_accChild(varChild: OleVariant; out ppdispChild: IDispatch): HResult; stdcall;
    function Get_accName(varChild: OleVariant; out pszName: WideString): HResult; stdcall;
    function Get_accValue(varChild: OleVariant; out pszValue: WideString): HResult; stdcall;
    function Get_accDescription(varChild: OleVariant; out pszDescription: WideString): HResult; stdcall;
    function Get_accRole(varChild: OleVariant; out pvarRole: OleVariant): HResult; stdcall;
    function Get_accState(varChild: OleVariant; out pvarState: OleVariant): HResult; stdcall;
    function Get_accHelp(varChild: OleVariant; out pszHelp: WideString): HResult; stdcall;
    function Get_accHelpTopic(out pszHelpFile: WideString; varChild: OleVariant; out pidTopic: Integer): HResult; stdcall;
    function Get_accKeyboardShortcut(varChild: OleVariant; out pszKeyboardShortcut: WideString): HResult; stdcall;
    function Get_accFocus(out pvarChild: OleVariant): HResult; stdcall;
    function Get_accSelection(out pvarChildren: OleVariant): HResult; stdcall;
    function Get_accDefaultAction(varChild: OleVariant; out pszDefaultAction: WideString): HResult; stdcall;
    function accSelect(flagsSelect: Integer; varChild: OleVariant): HResult; stdcall;
    function accLocation(out pxLeft: Integer; out pyTop: Integer; out pcxWidth: Integer; out pcyHeight: Integer; varChild: OleVariant): HResult; stdcall;
    function accNavigate(navDir: Integer; varStart: OleVariant; out pvarEndUpAt: OleVariant): HResult; stdcall;
    function accHitTest(xLeft: Integer; yTop: Integer; out pvarChild: OleVariant): HResult; stdcall;
    function accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
    function Set_accName(varChild: OleVariant; const pszName: WideString): HResult; stdcall;
    function Set_accValue(varChild: OleVariant; const pszValue: WideString): HResult; stdcall;
  end;
constructor TMyAccessibleEdit.Create(aEdit: TEdit; aDefAcc: IAccessible);
begin
  inherited Create;
  fEdit := aEdit;
  fDefAcc := aDefAcc;
end;

function TMyAccessibleEdit.QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
begin
  if IID = IID_IAccessible then
    Result := inherited QueryInterface(IID, Obj)
  else
    Result := fDefAcc.QueryInterface(IID, Obj);
end;

function TMyAccessibleEdit.GetTypeInfoCount(out Count: Integer): HResult; stdcall;
begin
  Result := fDefAcc.GetTypeInfoCount(Count);
end;

function TMyAccessibleEdit.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
begin
  Result := fDefAcc.GetTypeInfo(Index, LocaleID, TypeInfo);
end;

function TMyAccessibleEdit.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
begin
  Result := fDefAcc.GetIDsOfNames(IID, Names, NameCount, LocaleID, DispIDs);
end;

function TMyAccessibleEdit.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
begin
  Result := fDefAcc.Invoke(DispID, IID, LocaleID, Flags, Params, VarResult, ExcepInfo, ArgErr);
end;

function TMyAccessibleEdit.Get_accParent(out ppdispParent: IDispatch): HResult; stdcall;
begin
  Result := fDefAcc.Get_accParent(ppdispParent);
end;

function TMyAccessibleEdit.Get_accChildCount(out pcountChildren: Integer): HResult; stdcall;
begin
  Result := fDefAcc.Get_accChildCount(pcountChildren);
end;

function TMyAccessibleEdit.Get_accChild(varChild: OleVariant; out ppdispChild: IDispatch): HResult; stdcall;
begin
  Result := fDefAcc.Get_accChild(varChild, ppdispChild);
end;

function TMyAccessibleEdit.Get_accName(varChild: OleVariant; out pszName: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Get_accName(varChild, pszName);
  if (Result = S_OK) and (pszName <> '') then Exit;
  if Integer(varChild) = CHILDID_SELF then begin
    pszName := fEdit.Name;
    Result := S_OK;
  end else
    Result := S_FALSE;
end;

function TMyAccessibleEdit.Get_accValue(varChild: OleVariant; out pszValue: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Get_accValue(varChild, pszValue);
end;

function TMyAccessibleEdit.Get_accDescription(varChild: OleVariant; out pszDescription: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Get_accDescription(varChild, pszDescription);
  if (Result = S_OK) and (pszDescription <> '') then Exit;
  if Integer(varChild) = CHILDID_SELF then begin
    pszDescription := fEdit.Hint;
    Result := S_OK;
  end else
    Result := S_FALSE;
end;

function TMyAccessibleEdit.Get_accRole(varChild: OleVariant; out pvarRole: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.Get_accRole(varChild, pvarRole);
end;

function TMyAccessibleEdit.Get_accState(varChild: OleVariant; out pvarState: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.Get_accState(varChild, pvarState);
end;

function TMyAccessibleEdit.Get_accHelp(varChild: OleVariant; out pszHelp: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Get_accHelp(varChild, pszHelp);
end;

function TMyAccessibleEdit.Get_accHelpTopic(out pszHelpFile: WideString; varChild: OleVariant; out pidTopic: Integer): HResult; stdcall;
begin
  Result := fDefAcc.Get_accHelpTopic(pszHelpFile, varChild, pidTopic);
end;

function TMyAccessibleEdit.Get_accKeyboardShortcut(varChild: OleVariant; out pszKeyboardShortcut: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Get_accKeyboardShortcut(varChild, pszKeyboardShortcut);
end;

function TMyAccessibleEdit.Get_accFocus(out pvarChild: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.Get_accFocus(pvarChild);
end;

function TMyAccessibleEdit.Get_accSelection(out pvarChildren: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.Get_accSelection(pvarChildren);
end;

function TMyAccessibleEdit.Get_accDefaultAction(varChild: OleVariant; out pszDefaultAction: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Get_accDefaultAction(varChild, pszDefaultAction);
end;

function TMyAccessibleEdit.accSelect(flagsSelect: Integer; varChild: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.accSelect(flagsSelect, varChild);
end;

function TMyAccessibleEdit.accLocation(out pxLeft: Integer; out pyTop: Integer; out pcxWidth: Integer; out pcyHeight: Integer; varChild: OleVariant): HResult; stdcall;
 begin
  Result := fDefAcc.accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
end;

function TMyAccessibleEdit.accNavigate(navDir: Integer; varStart: OleVariant; out pvarEndUpAt: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.accNavigate(navDir, varStart, pvarEndUpAt);
end;

function TMyAccessibleEdit.accHitTest(xLeft: Integer; yTop: Integer; out pvarChild: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.accHitTest(xLeft, yTop, pvarChild);
end;

function TMyAccessibleEdit.accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
begin
  Result := fDefAcc.accDoDefaultAction(varChild);
end;

function TMyAccessibleEdit.Set_accName(varChild: OleVariant; const pszName: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Set_accName(varChild, pszName);
end;

function TMyAccessibleEdit.Set_accValue(varChild: OleVariant; const pszValue: WideString): HResult; stdcall;
begin
  Result := fDefAcc.Set_accValue(varChild, pszValue);
end;
type
  TMyForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    ...
  private
    DefEditWndProc: TWndMethod;
    procedure EditWndProc(var Message: TMessage);
    ...
  end;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  DefEditWndProc := Edit1.WindowProc;
  Edit1.WindowProc := EditWndProc;
end;

procedure TMyForm.EditWndProc(var Message: TMessage);
var
  DefAcc, MyAcc: IAccessible;
  Ret: LRESULT;
begin
  DefEditWndProc(Message);
  if (Message.Msg = WM_GETOBJECT) and (DWORD(Message.LParam) = OBJID_CLIENT) and (Message.Result > 0) then
  begin
    if ObjectFromLresult(Message.Result, IAccessible, Message.WParam, DefAcc) = S_OK then
    begin
      MyAcc := TMyAccessibleEdit.Create(Edit1, DefAcc) as IAccessible;
      Message.Result := LresultFromObject(IAccessible, Message.WParam, MyAcc);
    end;
  end;
end;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Delphi 中创建可访问的 UI 组件 的相关文章

随机推荐

  • 在 pandas to_csv 方法中保留列顺序

    pandas 的 to csv 方法不保留列的顺序 它选择按字母顺序排列 CSV 中的列 这是一个错误 已被报告并应该在版本 0 11 0 中得到纠正 我有0 18 0 import pandas as pd df pd DataFrame
  • 屏幕更新效果

    我一直在测量代码执行时间 以衡量本地执行脚本和在服务器上执行脚本之间的差异 有一次我忘记禁用screen updating庆幸的是 在更详细地考虑之前 我对闪光灯不敏感 当我第一次开始使用时VBA我一直认为它只是被使用 这样就不会吓到最终用
  • bash:jstat:找不到命令

    我想使用 gc 实用程序来分析我的 Cassandra 数据库的垃圾收集 但是当我运行 jstat 命令时 输出显示 bash jstat command not found 我搜索并发现 jstat 位于 JAVA HOME bin 但我
  • 为什么我无法向 Hotmail 发送电子邮件?

    这是我的代码 boundary sha1 whatever headers MIME Version 1 0 r n headers From Domainname email protected cdn cgi l email prote
  • 如何在通过回调添加和删除节点时固定 Dash Cytoscape 中节点的位置?

    我想使用以下示例数据源制作一个带有交互式网络图的 Web 应用程序 data Source Node a a b b c Destination Node b c c d d Link likes likes likes likes dis
  • 谷歌地图API获取国家和城市

    我可以使用 ajax 自动完成功能来从 google 地图 api 获取国家 地区吗 当有人输入 United 在 google 地图 api 上搜索该国家 地区并显示类似的结果时 你能给我举个例子 或者链接到谷歌地图API的解决方案吗 看
  • Android - Retrofit2 - java.security.cert.CertPathValidatorException:未找到证书路径的信任锚

    我想从服务器获取数据 https data egov kz api v2 zheke zhane zandy tulgalardy k1 v6 pretty https data egov kz api v2 zheke zhane zan
  • 如何在谷歌可视化图表上加载JSON数据?

    我是谷歌可视化的新手 我正在开发一个完整的仪表板 例如谷歌完整仪表板示例 https code google com apis ajax playground type visualization full dashboard 按照示例 我
  • 回车换行 Windows 和 Linux java 应用程序

    我正在开发一个集成测试应用程序 这就是我在测试用例中所做的事情 我读取了一个测试输入文件 该文件存储在 cvs 中 将其写入文件系统中的文件 应用程序轮询该文件的目录 处理它并创建输出文件 然后我轮询输出文件的目录 如果两个文件内容相等 则
  • 非 WSDL 模式下的 SOAP 问题

    我正在制作一个简单的网络服务 用于我拥有的两个网站之间的通信 由于它只是一个基本应用程序 我一直在没有 WSDL 文件的情况下工作 所以在non WSDL mode正如 PHP 手册所称 这基本上就是客户端的样子 client new So
  • ASP Response.Flush() 刷新部分数据

    我正在开发一个带有 ASP 服务器端的 Web 应用程序 并使用 iframe 进行数据推送 ASP 处理程序每 隔一段时间就会将一些 javascript 刷新到 iframe context Response Write context
  • 字符串是如何排序的?

    有人可以解释一下以下函数的输出 public static void main String args String str1 new String 20 String str2 new String 100 List
  • 派生重载运算符,但仅对相同类型进行操作

    假设我有一个基类和两个从它派生的类 class Base protected double value public virtual Base Base double value value value Base const Base B
  • 无法设置未定义的属性“clientMutationId”

    我收到以下错误 当尝试通过 graphiql 运行突变时 请帮助我解决此问题或指向一个链接 我可以在其中找到反应中继突变示例 mutation createUser input username Hamza Khan clientMutat
  • pylint 为可调用的对象属性提供不可调用错误

    不确定我是否做错了什么或者这是否是一个问题pylint 在下面的代码中我得到一个 linting 错误self type不可调用E1102 虽然我可以忽略它并继续工作 但似乎这种事情应该很容易解决 只是不知道如何解决它 from typin
  • 使用正则表达式进行全字匹配

    我想要一个匹配 bananas 或 睡衣 但不匹配 bananas2 或 bananaspajamas 或 banana 或基本上除了这两个单词之外的任何内容的C 正则表达式 所以我这样做了 include
  • 在golang中传输一个大文件

    客户端发送文件 大小可能大于5G 发送到从服务器 然后从服务器发送到主服务器 从属设备会将临时文件保存到自身吗 我不希望发生这种情况 因为它会减慢上传速度并浪费从站的内存 有什么办法可以避免这种情况吗 在 golang 中传输大文件的最佳方
  • 将 DOM 操作应用于 HTML 并保存结果?

    我有大约 100 个静态 HTML 页面 我想对其应用一些 DOM 操作 它们都遵循相同的 HTML 结构 我想对每个文件应用一些 DOM 操作 然后保存生成的 HTML 这些是我想要应用的操作 start h1 title h2 desc
  • [Flags] Enum 属性在 C# 中意味着什么?

    我有时会看到如下的枚举 Flags public enum Options None 0 Option1 1 Option2 2 Option3 4 Option4 8 我不明白到底是什么 Flags 属性确实如此 有人可以发布一个很好的解
  • 在 Delphi 中创建可访问的 UI 组件

    我正在尝试从标准 VCL TEdit 控件检索可访问的信息 get accName 和 Get accDescription 方法返回空字符串 但 get accValue 返回输入到 TEdit 中的文本值 我刚刚开始尝试理解 MSAA