Delphi 组件和屏幕阅读器

标签 delphi components screen-readers

我使用 Delphi 和 C++Builder,并且对屏幕阅读器有一些辅助功能问题。

我的窗体上有一个源自 TWinControl 的按钮。如果我在按钮上添加标题,当按钮处于焦点状态时,屏幕阅读器就会将其读给我听。但是,在某些情况下,我使用带有图像但没有标题的按钮。如果没有标题,屏幕阅读器不会说什么。我该怎么做才能让屏幕阅读器说出这个按钮是什么?

对于源自 TGraphicControl 的窗体上的图像也类似。当鼠标悬停在对象上时,如何告诉屏幕阅读器要说什么?

我研究了 IAccessible 包装器,但如果可能的话,我不想扩展我们使用的每个控件。

最佳答案

However, there are cases where I use buttons with an image and no caption. The screen reader doesn’t say anything if there is no caption. What can I do to have the screen reader say what this button is?

按钮的 IAccessible 实现必须向屏幕阅读器提供所需的文本。默认情况下,操作系统为许多 UI 控件(包括按钮)提供默认的 IAccessible 实现。

因此,您可以做的一个简单技巧是所有者手动绘制按钮,然后您可以为默认的 IAccessible 实现设置其标准 Caption 以便正常使用,然后您可以在绘制按钮时不包含 Caption

否则,您可以直接处理 WM_GETOBJECT 消息以检索按钮的默认 IAccessible 实现,然后对其进行包装,以便您可以返回所需的文本并将其他所有内容委托(delegate)给默认实现。例如:

type
  TMyAccessibleText = class(TInterfacedObject, IAccessible)
  private
    fAcc: IAccessible;
    fAccessibleText: string;
  public:
    constructor Create(Acc: IAccessible; AccessibleText: string);

    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 TMyAccessibleText.Create(Acc: IAccessible; AccessibleText: string);
begin
  inherited Create;
  fAcc := Acc;
  fAccessibleText := AccessibleText;
end;

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

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

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

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

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

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

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

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

function TMyAccessibleText.Get_accName(varChild: OleVariant; out pszName: WideString): HResult; stdcall;
begin
  Result := fAcc.Get_accName(varChild, pszName);
end;

function TMyAccessibleText.Get_accValue(varChild: OleVariant; out pszValue: WideString): HResult; stdcall;
begin
  if varChild = CHILDID_SELF then
  begin
    pszValue := fAccessibleText;
    Result := S_OK;
  end else
    Result := fAcc.Get_accValue(varChild, pszValue);
end;

function TMyAccessibleText.Get_accDescription(varChild: OleVariant; out pszDescription: WideString): HResult; stdcall;
begin
  Result := fAcc.Get_accDescription(varChild, pszDescription);
end;

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

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

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

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

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

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

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

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

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

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

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

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

function TMyAccessibleText.accDoDefaultAction(varChild: OleVariant): HResult; stdcall;
begin
  Result := fAcc.accDoDefaultAction(varChild);
end;

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

function TMyAccessibleText.Set_accValue(varChild: OleVariant; const pszValue: WideString): HResult; stdcall;
begin
  if varChild = CHILDID_SELF then
  begin
    fAccessibleText := pszValue;
    Result := S_OK;
  end else
    Result := fAcc.Set_accValue(varChild, pszValue);
end;

type
  TBitBtn = class(Vcl.Buttons.TBitBtn)
  private
    procedure WMGetObject(var Message: TMessage): message WM_GETOBJECT;
  public
    MyAccessibleText: string;
  end;

  TMyForm = class(TForm)
    Button1: TBitBtn;
    ...
    procedure FormCreate(Sender: TObject);
    ...
  end;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  Button1.MyAccessibleText := 'There is an image here';
end;

procedure TBitBtn.WMGetObject(var Message: TMessage);
var
  Acc: IAccessible;
begin
  inherited;
  if (Message.LParam = OBJID_CLIENT) and (Message.Result > 0) and (Caption = '') and (MyAccessibleText <> '') then
  begin
    if ObjectFromLresult(Message.Result, IAccessible, Message.WParam, Acc) = S_OK then
    begin
      Acc := TMyAccessibleText.Create(Acc, MyAccessibleText) as IAccessible;
      Message.Result := LresultFromObject(IAccessible, Message.WParam, Acc);
    end;
  end;
end;

Similarly for an image on a form which descends from TGraphic. How can I tell the screen reader what to say when the object gets focus?

首先,TGraphic 不是一个组件类。它是 TPicture 使用的图像数据的包装器,例如,它本身是 TImage 使用的帮助器。我假设您的意思是 TGraphicControl (TImage 派生自)。

默认情况下,屏幕阅读器无法直接访问基于 TGraphicControl 的组件,因为它没有自己的窗口,因此操作系统本身甚至不知道它。

如果您希望屏幕阅读器与图形控件交互,则必须从 Parent 组件(它有一个窗口)提供 IAccessible 的完整实现,并且让它公开有关其图形子项的附加辅助功能信息。

I’ve looked into the IAccessible wrapper, but I would prefer not to extend every control we use if at all possible.

抱歉,但您必须这样做(除非您能找到满足您需要的第三方实现)。 VCL 根本不实现任何 IAccessible 功能,因此,如果您需要自定义超出操作系统提供的功能,则必须在自己的代码中手动实现它。

关于Delphi 组件和屏幕阅读器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38273974/

相关文章:

delphi - 有没有办法在按下 Ctrl +"some key"时停止使用控制字符生成烦人的 OnKeyPress 事件?

delphi - 寻找 Delphi 的径向弹出菜单组件

SVG 可访问性导致无效的 HTML(重复 ID)

delphi - 在 Delphi IDE 中排除 EStackoverflow 异常的提示

delphi - Delphi中如何获取动态数组的长度?

multithreading - 如何在OmniThreadLibrary 3中暂停/恢复线程?

php - 创建自定义 joomla 验证码

javascript - 在react中设置另一个组件的状态

html - 如何防止 JAWS 在必填字段上说 "invalid entry"?

javascript - 如果 aria-label 发生变化,让屏幕阅读器重读