Delphi 组件和屏幕阅读器
Delphi Components and Screen Readers
我使用 Delphi 和 C++Builder,并且有一些关于屏幕 readers 的辅助功能问题。
我在窗体上有一个从 TWinControl 派生出来的按钮。如果我在按钮上放置标题,屏幕 reader 会在按钮处于焦点时读给我听。但是,在某些情况下,我会使用带有图像但没有标题的按钮。如果没有标题,屏幕 reader 什么也不会说。我该怎么做才能让屏幕 reader 说出这个按钮是什么?
类似地,对于从 TGraphicControl 派生的窗体上的图像。当鼠标悬停在 object 上时,我如何告诉屏幕 reader 要说什么?
我研究了 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
实现必须向屏幕 reader 提供所需的文本。默认情况下,OS 为许多 UI 控件(包括按钮)提供默认 IAccessible
实现。
因此,您可以做的一个简单技巧是手动绘制按钮所有者,然后您可以将其标准 Caption
设置为默认 IAccessible
实现正常使用,然后您可以在绘制按钮时不包含 Caption
。
否则,您可以直接处理 WM_GETOBJECT
消息以检索按钮的默认 IAccessible
实现,然后包装它,这样您就可以 return 您想要的文本并将其他所有内容委托给默认实现。例如:
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
不是组件 class。例如,它是 TPicture
使用的图像数据的包装器,它本身是 TImage
使用的助手。我假设你的意思是 TGraphicControl
而不是(TImage
派生自)。
基于 TGraphicControl
的组件在默认情况下不能直接被屏幕 reader 访问,因为它没有自己的 window,因此甚至不知道OS 本身。
如果您希望屏幕 reader 与图形控件交互,您必须从 Parent
组件(确实有 window) 并让它公开有关其图形子项的其他辅助功能信息。
I’ve looked into the IAccessible wrapper, but I would prefer not to extend every control we use if at all possible.
抱歉,您必须这样做(除非您可以找到满足您需要的第 3 方实施)。 VCL 根本不实现任何 IAccessible
功能,因此如果您需要自定义它超出 OS 为您提供的功能,您必须在自己的代码中手动实现它。
我使用 Delphi 和 C++Builder,并且有一些关于屏幕 readers 的辅助功能问题。
我在窗体上有一个从 TWinControl 派生出来的按钮。如果我在按钮上放置标题,屏幕 reader 会在按钮处于焦点时读给我听。但是,在某些情况下,我会使用带有图像但没有标题的按钮。如果没有标题,屏幕 reader 什么也不会说。我该怎么做才能让屏幕 reader 说出这个按钮是什么?
类似地,对于从 TGraphicControl 派生的窗体上的图像。当鼠标悬停在 object 上时,我如何告诉屏幕 reader 要说什么?
我研究了 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
实现必须向屏幕 reader 提供所需的文本。默认情况下,OS 为许多 UI 控件(包括按钮)提供默认 IAccessible
实现。
因此,您可以做的一个简单技巧是手动绘制按钮所有者,然后您可以将其标准 Caption
设置为默认 IAccessible
实现正常使用,然后您可以在绘制按钮时不包含 Caption
。
否则,您可以直接处理 WM_GETOBJECT
消息以检索按钮的默认 IAccessible
实现,然后包装它,这样您就可以 return 您想要的文本并将其他所有内容委托给默认实现。例如:
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
不是组件 class。例如,它是 TPicture
使用的图像数据的包装器,它本身是 TImage
使用的助手。我假设你的意思是 TGraphicControl
而不是(TImage
派生自)。
基于 TGraphicControl
的组件在默认情况下不能直接被屏幕 reader 访问,因为它没有自己的 window,因此甚至不知道OS 本身。
如果您希望屏幕 reader 与图形控件交互,您必须从 Parent
组件(确实有 window) 并让它公开有关其图形子项的其他辅助功能信息。
I’ve looked into the IAccessible wrapper, but I would prefer not to extend every control we use if at all possible.
抱歉,您必须这样做(除非您可以找到满足您需要的第 3 方实施)。 VCL 根本不实现任何 IAccessible
功能,因此如果您需要自定义它超出 OS 为您提供的功能,您必须在自己的代码中手动实现它。