Delphi 组件和屏幕阅读器
Posted
技术标签:
【中文标题】Delphi 组件和屏幕阅读器【英文标题】:Delphi Components and Screen Readers 【发布时间】:2016-11-11 10:54:35 【问题描述】:我使用 Delphi 和 C++Builder,对屏幕阅读器有一些无障碍问题。
我在从 TWinControl 继承的表单上有一个按钮。如果我在按钮上添加标题,屏幕阅读器会在按钮处于焦点时向我朗读。但是,在某些情况下,我使用带有图像但没有标题的按钮。如果没有标题,屏幕阅读器不会说任何内容。我该怎么做才能让屏幕阅读器说出这个按钮是什么?
对于从 TGraphicControl 继承的表单上的图像也是如此。当鼠标悬停在对象上时,如何告诉屏幕阅读器该说什么?
我研究了 IAccessible 包装器,但如果可能的话,我不希望扩展我们使用的每个控件。
【问题讨论】:
ATGraphic
无法获得焦点,它甚至不是一个组件。您可能想说TGraphicControl
,但仍然无法集中注意力。这是TGraphicControl
的主要目的之一。
谢谢杰瑞。我确实是指 TGraphicControl 并相应地更新了问题。我的意思是将鼠标悬停在控件上,而不是获得焦点。我很想知道其他人提出的在其表单上为 TGraphicControls 处理某种替代文本的想法。
【参考方案1】:
但是,在某些情况下,我会使用带有图像但没有标题的按钮。如果没有标题,屏幕阅读器不会说任何内容。我该怎么做才能让屏幕阅读器说出这个按钮是什么?
按钮的IAccessible
实现必须向屏幕阅读器提供所需的文本。默认情况下,操作系统为许多 UI 控件(包括按钮)提供默认的IAccessible
实现。
因此,您可以做的一个简单技巧是手动绘制按钮的所有者,然后您可以将其标准 Caption
设置为默认的 IAccessible
实现以正常使用,然后您可以简单地不包括Caption
绘制按钮时。
否则,您可以直接处理 WM_GETOBJECT
消息以检索按钮的默认 IAccessible
实现,然后将其包装,以便您可以返回所需的文本并将其他所有内容委托给默认实现。例如:
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;
对于源自 TGraphic 的表单上的图像也是如此。当对象获得焦点时,如何告诉屏幕阅读器该说什么?
首先,TGraphic
不是组件类。它是TPicture
使用的图像数据的包装器,例如,它本身就是TImage
使用的帮助器。我假设您的意思是 TGraphicControl
代替(TImage
派生自)。
默认情况下,屏幕阅读器无法直接访问基于TGraphicControl
的组件,因为它没有自己的窗口,因此操作系统本身也不知道它。
如果您希望屏幕阅读器与图形控件交互,您必须提供来自 Parent
组件(确实有一个窗口)的 IAccessible
的完整实现,并让它公开有关其图形子项的附加辅助功能信息.
我研究了 IAccessible 包装器,但如果可能的话,我不希望扩展我们使用的每个控件。
抱歉,您必须这样做(除非您可以找到满足您需要的第三方实现)。 VCL 根本没有实现任何IAccessible
功能,因此如果您需要在操作系统为您提供的功能之外对其进行自定义,则必须在自己的代码中手动实现它。
【讨论】:
@KenWhite:操作系统本身为许多标准 UI 控件(包括按钮)提供了默认实现。 @KenWhite:作为附加书签,请参阅几年前的my answer 至Creating Accessible UI components in Delphi。 我完全忘记了看到那个。我只是看了看,我不仅对它投了赞成票,而且这个问题被收藏了,所以我会把它作为参考。我一定是老了。 :-) 感谢您的提醒。 感谢雷米提供的详细信息。您关于创建可访问的 UI 组件的另一篇文章也很有帮助。这给了我一些前进的选择。以上是关于Delphi 组件和屏幕阅读器的主要内容,如果未能解决你的问题,请参考以下文章