Creating Accessible UI components in Delphi - delphi

I am trying to retrieve accessible information from a standard VCL TEdit control. The get_accName() and Get_accDescription() methods return empty strings, but get_accValue() returns the text value entered into the TEdit.
I am just starting to try to understand the MSAA and I'm a bit lost at this point.
Does my TEdit need to have additional published properties that would be exposed to the MSA? If so would that necessitate creating a new component that descends from TEdit and adds the additional published properties such as "AccessibleName", "AccessibleDescription", etc... ?
Also, note, I have looked at the VTVirtualTrees component which is supposed to be accessible, but the MS Active Accessibility Object Inspector still does not see the AccessibleName published property even on that control.
At this point I am at a loss and would be grateful for any advice or help in this matter.
...
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.

The VCL itself does not natively implement any support for MSAA. Windows provides default implementations for standard UI controls, which many standard VCL components wrap. If you need more MSAA support than Windows provides, you will have to implement the IAccessible interface yourself, and then have your control respond to the WM_GETOBJECT message so it can return a pointer to an instance of your implementation.
Update: For example, one way to add MSAA to an existing TEdit (if you do not want to derive your own component) might look something like this:
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;

I was able to get this working via
unit mainAcc;
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;
type
TForm1 = class(TForm)
lblFirstName: TLabel;
btnGetAccInfo: TButton;
accInfoOutput: TEdit;
procedure btnGetAccInfoClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
aEdit: TTWEdit;
FAccProperties: TStringList;
public
{ Public declarations }
end;
TAccessibleEdit = class(TEdit, IAccessible)
private
FOwner: TComponent;
FAccessibleItem: IAccessible;
FAccessibleName: string;
FAccessibleDescription: string;
procedure WMGetMSAAObject(var Message : TMessage); message WM_GETOBJECT;
// IAccessible
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;
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; override;
public
constructor Create(AOwner: TComponent); override;
published
property AccessibleItem: IAccessible read FAccessibleItem write FAccessibleItem;
property AccessibleName: string read FAccessibleName write FAccessibleName;
property AccessibleDescription: string read FAccessibleDescription write FAccessibleDescription;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{------------------------------------------------------------------------------}
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
inherited;
FreeAndNil(aEdit);
end;
{------------------------------------------------------------------------------}
procedure TForm1.FormCreate(Sender: TObject);
begin
aEdit := TAccessibleEdit.Create(self);
aEdit.Visible := true;
aEdit.Parent := Form1;
aEdit.Left := 91;
aEdit.Top := 17;
aEdit.Height := 21;
aEdit.Width := 204;
aEdit.Hint := 'This is a custom accessible edit control hint';
end;
{------------------------------------------------------------------------------}
procedure TForm1.btnGetAccInfoClick(Sender: TObject);
var
vWSTemp: WideString;
vAccObj: IAccessible;
begin
FAccProperties := TStringList.Create;
if (AccessibleObjectFromWindow(aEdit.Handle, OBJID_CLIENT, IID_IAccessible, vAccObj) = S_OK) then
begin
vAccObj.Get_accName(CHILDID_SELF, vWSTemp);
FAccProperties.Add('Name: ' + vWSTemp);
vWSTemp := '';
vAccObj.Get_accDescription(CHILDID_SELF, vWSTemp);
FAccProperties.Add('Description: ' + vWSTemp);
vWSTemp := '';
vAccObj.Get_accValue(CHILDID_SELF, vWSTemp);
FAccProperties.Add('Value: ' + vWSTemp);
end;
accInfoOutput.Text := FAccProperties.Text;
end;
{ TAccessibleEdit }
{------------------------------------------------------------------------------}
constructor TAccessibleEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOwner := AOwner;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accDoDefaultAction(varChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accHitTest(xLeft, yTop: Integer;
out pvarChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accLocation(out pxLeft, pyTop, pcxWidth, pcyHeight: Integer;
varChild: OleVariant): HResult;
var
P: TPoint;
begin
Result := S_FALSE;
pxLeft := 0;
pyTop := 0;
pcxWidth := 0;
pcyHeight := 0;
if varChild = CHILDID_SELF then
begin
P := self.ClientToScreen(self.ClientRect.TopLeft);
pxLeft := P.X;
pyTop := P.Y;
pcxWidth := self.Width;
pcyHeight := self.Height;
Result := S_OK;
end
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accNavigate(navDir: Integer; varStart: OleVariant;
out pvarEndUpAt: OleVariant): HResult;
begin
result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.accSelect(flagsSelect: Integer; varChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accChild(varChild: OleVariant;
out ppdispChild: IDispatch): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accChildCount(out pcountChildren: Integer): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accDefaultAction(varChild: OleVariant;
out pszDefaultAction: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accDescription(varChild: OleVariant;
out pszDescription: WideString): HResult;
begin
pszDescription := '';
result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszDescription := 'TAccessibleEdit_AccessibleDescription';
Result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accFocus(out pvarChild: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accHelp(varChild: OleVariant;
out pszHelp: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accHelpTopic(out pszHelpFile: WideString;
varChild: OleVariant; out pidTopic: Integer): HResult;
begin
pszHelpFile := '';
pidTopic := 0;
Result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszHelpFile := '';
pidTopic := self.HelpContext;
Result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accKeyboardShortcut(varChild: OleVariant;
out pszKeyboardShortcut: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accName(varChild: OleVariant; out pszName: WideString): HResult;
begin
pszName := '';
Result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszName := 'TAccessibleEdit_AccessibleName';
result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accParent(out ppdispParent: IDispatch): HResult;
begin
ppdispParent := nil;
result := AccessibleObjectFromWindow(self.ParentWindow, CHILDID_SELF, IID_IAccessible, Pointer(ppDispParent));
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accRole(varChild: OleVariant;
out pvarRole: OleVariant): HResult;
begin
Result := S_OK;
if varChild = CHILDID_SELF then
pvarRole := ROLE_SYSTEM_OUTLINE;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accSelection(out pvarChildren: OleVariant): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accState(varChild: OleVariant;
out pvarState: OleVariant): HResult;
begin
Result := S_OK;
if varChild = CHILDID_SELF then
pvarState := STATE_SYSTEM_FOCUSED;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Get_accValue(varChild: OleVariant;
out pszValue: WideString): HResult;
begin
pszValue := '';
Result := S_FALSE;
if varChild = CHILDID_SELF then
begin
pszValue := WideString(self.Text);
result := S_OK;
end;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Set_accName(varChild: OleVariant;
const pszName: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
function TAccessibleEdit.Set_accValue(varChild: OleVariant;
const pszValue: WideString): HResult;
begin
Result := DISP_E_MEMBERNOTFOUND;
end;
{------------------------------------------------------------------------------}
procedure TAccessibleEdit.WMGetMSAAObject(var Message : TMessage);
begin
if (Message.Msg = WM_GETOBJECT) then
begin
QueryInterface(IID_IAccessible, FAccessibleItem);
Message.Result := LresultFromObject(IID_IAccessible, Message.WParam, FAccessibleItem);
end
else
Message.Result := DefWindowProc(Handle, Message.Msg, Message.WParam, Message.LParam);
end;
end.
end.

Related

Delphi - Sound change notification (mute / unmute)

how can I get notified of system audio changes?
Or how to use the callback functions
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): Hresult; stdcall;
First a disclaimer: I am not an expert on the audio APIs. Still, I can get it to work using the documentation.
First, we need to get hold of an IMMDeviceEnumerator interface using CoCreateInstance. Then we use the IMMDeviceEnumerator.GetDefaultAudioEndpoint method to obtain the default audio output device. Using the device's Activate method, we request an IAudioEndpointVolume interface and call its RegisterControlChangeNotify method to subscribe to volume notifications, including mute and unmute.
We must provide a recipient for these notifications, and that recipient must implement the IAudioEndpointVolumeCallback interface, which specifies how the recipient object actually does receive the notifications.
In a single-form GUI application, like the demo application I wrote for this answer, it makes sense to use the main form. Hence, we must let the form implement the IAudioEndpointVolumeCallback.OnNotify method. This method is called by the audio system when the volume is changed (or (un)muted), and the notification data is passed in a AUDIO_VOLUME_NOTIFICATION_DATA structure.
I don't want to touch the GUI or risk raising exceptions in this method, so just to feel safe I only let this method post a message to the form with the required data.
Full code:
unit OSD;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, ActiveX,
ComObj, AudioEndpoint, Gauge;
// Gauge: https://specials.rejbrand.se/dev/controls/gauge/
const
WM_VOLNOTIFY = WM_USER + 1;
type
TSndVolFrm = class(TForm, IAudioEndpointVolumeCallback)
ArcGauge: TArcGauge;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FDeviceEnumerator: IMMDeviceEnumerator;
FMMDevice: IMMDevice;
FAudioEndpointVolume: IAudioEndpointVolume;
function OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
stdcall;
procedure WMVolNotify(var Msg: TMessage); message WM_VOLNOTIFY;
public
end;
var
SndVolFrm: TSndVolFrm;
implementation
uses
Math;
{$R *.dfm}
procedure TSndVolFrm.FormCreate(Sender: TObject);
begin
if not Succeeded(CoInitialize(nil)) then
ExitProcess(1);
OleCheck(CoCreateInstance(CLASS_IMMDeviceEnumerator, nil, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, FDeviceEnumerator));
OleCheck(FDeviceEnumerator.GetDefaultAudioEndpoint(0, 0, FMMDevice));
OleCheck(FMMDevice.Activate(IID_IAudioEndpointVolume, CLSCTX_INPROC_SERVER, nil, FAudioEndpointVolume));
OleCheck(FAudioEndpointVolume.RegisterControlChangeNotify(Self));
end;
procedure TSndVolFrm.FormDestroy(Sender: TObject);
begin
CoUninitialize;
end;
function TSndVolFrm.OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
begin
if pNotify = nil then
Exit(E_POINTER);
try
PostMessage(Handle, WM_VOLNOTIFY, WPARAM(pNotify.bMuted <> False), LPARAM(Round(100 * pNotify.fMasterVolume)));
Result := S_OK;
except
Result := E_UNEXPECTED;
end;
end;
procedure TSndVolFrm.WMVolNotify(var Msg: TMessage);
begin
var LMute := Msg.WParam <> 0;
var LVolume := Msg.LParam;
if LMute then
begin
ArcGauge.ShowCaption := False;
ArcGauge.FgBrush.Color := $777777;
end
else
begin
ArcGauge.ShowCaption := True;
ArcGauge.FgBrush.Color := clHighlight;
end;
ArcGauge.Position := LVolume;
end;
end.
Interface unit:
unit AudioEndpoint;
interface
uses
Windows,
Messages,
SysUtils,
ActiveX,
ComObj;
const
CLASS_IMMDeviceEnumerator : TGUID = '{BCDE0395-E52F-467C-8E3D-C4579291692E}';
IID_IMMDeviceEnumerator : TGUID = '{A95664D2-9614-4F35-A746-DE8DB63617E6}';
IID_IAudioEndpointVolume : TGUID = '{5CDF2C82-841E-4546-9722-0CF74078229A}';
type
PAUDIO_VOLUME_NOTIFICATION_DATA = ^AUDIO_VOLUME_NOTIFICATION_DATA;
AUDIO_VOLUME_NOTIFICATION_DATA = record
guidEventContext: TGUID;
bMuted: BOOL;
fMasterVolume: Single;
nChannels: UINT;
afChannelVolumes: Single;
end;
IAudioEndpointVolumeCallback = interface(IUnknown)
['{657804FA-D6AD-4496-8A60-352752AF4F89}']
function OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT; stdcall;
end;
IAudioEndpointVolume = interface(IUnknown)
['{5CDF2C82-841E-4546-9722-0CF74078229A}']
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function GetChannelCount(out PInteger): HRESULT; stdcall;
function SetMasterVolumeLevel(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function SetMasterVolumeLevelScalar(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMasterVolumeLevel(out fLevelDB: single): HRESULT; stdcall;
function GetMasterVolumeLevelScaler(out fLevelDB: single): HRESULT; stdcall;
function SetChannelVolumeLevel(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function SetChannelVolumeLevelScalar(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function GetChannelVolumeLevel(nChannel: Integer; out fLevelDB: double): HRESULT; stdcall;
function GetChannelVolumeLevelScalar(nChannel: Integer; out fLevel: double): HRESULT; stdcall;
function SetMute(bMute: Boolean; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMute(out bMute: Boolean): HRESULT; stdcall;
function GetVolumeStepInfo(pnStep: Integer; out pnStepCount: Integer): HRESULT; stdcall;
function VolumeStepUp(pguidEventContext: PGUID): HRESULT; stdcall;
function VolumeStepDown(pguidEventContext: PGUID): HRESULT; stdcall;
function QueryHardwareSupport(out pdwHardwareSupportMask): HRESULT; stdcall;
function GetVolumeRange(out pflVolumeMindB: double; out pflVolumeMaxdB: double; out pflVolumeIncrementdB: double): HRESULT; stdcall;
end;
IAudioMeterInformation = interface(IUnknown)
['{C02216F6-8C67-4B5B-9D00-D008E73E0064}']
end;
IPropertyStore = interface(IUnknown)
end;
IMMDevice = interface(IUnknown)
['{D666063F-1587-4E43-81F1-B948E807363F}']
function Activate(const refId: TGUID; dwClsCtx: DWORD; pActivationParams: PInteger; out pEndpointVolume: IAudioEndpointVolume): HRESULT; stdCall;
function OpenPropertyStore(stgmAccess: DWORD; out ppProperties: IPropertyStore): HRESULT; stdcall;
function GetId(out ppstrId: PLPWSTR): HRESULT; stdcall;
function GetState(out State: Integer): HRESULT; stdcall;
end;
IMMDeviceCollection = interface(IUnknown)
['{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}']
end;
IMMNotificationClient = interface(IUnknown)
['{7991EEC9-7E89-4D85-8390-6C703CEC60C0}']
end;
IMMDeviceEnumerator = interface(IUnknown)
['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
function EnumAudioEndpoints(dataFlow: TOleEnum; deviceState: SYSUINT; DevCollection: IMMDeviceCollection): HRESULT; stdcall;
function GetDefaultAudioEndpoint(EDF: SYSUINT; ER: SYSUINT; out Dev :IMMDevice ): HRESULT; stdcall;
function GetDevice(pwstrId: pointer; out Dev: IMMDevice): HRESULT; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
end;
implementation
end.
I have some code for you, 3 source code files: A unit with a class handling volume control notification, a unit to interface with Windows API and a simple demo program. The demo is actually all you have to look in details. The rest can be considered as obscure support routines :-)
Let's see the demo program. It is a simple VCL form having only a TMemo on it. It register for volume control notification and display a simple message in the memo (You probably want a nice UI instead).
The code is really very simple: create an interface pointing to TVolumeControl, assign an event handler to the OnVolumeChange and call the Initialize method. When the event fires, call GetLevelInfo to get the information and display it. When the form is destroyed, call Dispose method to stop getting notification.
unit SoundChangeNotificationDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages, Winapi.ActiveX,
System.SysUtils, System.Variants, System.Classes, System.SyncObjs,
System.Win.ComObj,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Ovb.VolumeMonitor,
Ovb.MMDevApi;
type
TSoundChangeDemoForm = class(TForm)
Memo1: TMemo;
protected
FVolumeMonitor : IVolumeMonitor;
procedure VolumeMonitorVolumeChange(Sender : TObject);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
SoundChangeDemoForm: TSoundChangeDemoForm;
implementation
{$R *.dfm}
constructor TSoundChangeDemoForm.Create(AOwner: TComponent);
var
HR : HRESULT;
begin
inherited;
FVolumeMonitor := TVolumeMonitor.Create;
FVolumeMonitor.OnVolumeChange := VolumeMonitorVolumeChange;
HR := FVolumeMonitor.Initialize();
if not SUCCEEDED(HR) then
ShowMessage('Volume control initialization failed');
end;
destructor TSoundChangeDemoForm.Destroy;
begin
FVolumeMonitor.Dispose;
inherited Destroy;
end;
procedure TSoundChangeDemoForm.VolumeMonitorVolumeChange(Sender: TObject);
var
Info: TVOLUME_INFO;
begin
FVolumeMonitor.GetLevelInfo(Info);
Memo1.Lines.Add(Format('Volume change: nStep=%d cSteps=%d Mute=%d',
[Info.nStep, Info.cSteps, Ord(Info.bMuted)]));
end;
The hard work is done is a unit I named Ovb.VolumeMonitor. This unit interact with Windows API to request notification when the volume is changed on the default audio device.
Note that this is not a component but a class and you use this class thru an interface. See the demo app above.
unit Ovb.VolumeMonitor;
interface
uses
Winapi.Windows, Winapi.Messages, Winapi.ActiveX,
System.SysUtils, System.Variants, System.Classes, System.SyncObjs,
System.Win.ComObj,
Ovb.MMDevApi;
const
WM_VOLUMECHANGE = (WM_USER + 12);
WM_ENDPOINTCHANGE = (WM_USER + 13); // Not implemented yet
type
IVolumeMonitor = interface
['{B06EE2E9-E707-4086-829A-D5664978069F}']
function Initialize() : HRESULT;
procedure Dispose;
function GetLevelInfo(var Info: TVOLUME_INFO) : HRESULT;
function GetOnVolumeChange: TNotifyEvent;
procedure SetOnVolumeChange(const Value: TNotifyEvent);
property OnVolumeChange : TNotifyEvent read GetOnVolumeChange
write SetOnVolumeChange;
end;
TVolumeMonitor = class(TInterfacedObject,
IVolumeMonitor,
IMMNotificationClient,
IAudioEndpointVolumeCallback)
private
FRegisteredForEndpointNotifications : BOOL;
FRegisteredForVolumeNotifications : BOOL;
FDeviceEnumerator : IMMDeviceEnumerator;
FAudioEndpoint : IMMDevice;
FAudioEndpointVolume : IAudioEndpointVolume;
FEndPointCritSect : TRTLCriticalSection;
FWindowHandle : HWND;
FOnVolumeChange : TNotifyEvent;
procedure WndProc(var Msg: TMessage);
procedure WMVolumeChange(var Msg: TMessage);
function GetOnVolumeChange: TNotifyEvent;
procedure SetOnVolumeChange(const Value: TNotifyEvent);
function AttachToDefaultEndpoint() : HRESULT;
function OnNotify(pNotify : PAUDIO_VOLUME_NOTIFICATION_DATA) : HRESULT; stdcall;
public
constructor Create; virtual;
destructor Destroy; override;
function Initialize() : HRESULT;
procedure DetachFromEndpoint();
procedure Dispose;
function GetLevelInfo(var Info: TVOLUME_INFO) : HRESULT;
property OnVolumeChange : TNotifyEvent read GetOnVolumeChange
write SetOnVolumeChange;
end;
implementation
{ TVolumeMonitor }
constructor TVolumeMonitor.Create;
begin
inherited Create;
FWindowHandle := AllocateHWnd(WndProc);
FRegisteredForEndpointNotifications := FALSE;
FRegisteredForVolumeNotifications := FALSE;
FEndPointCritSect.Initialize();
end;
destructor TVolumeMonitor.Destroy;
begin
if FWindowHandle <> INVALID_HANDLE_VALUE then begin
DeallocateHWnd(FWindowHandle);
FWindowHandle := INVALID_HANDLE_VALUE;
end;
FEndPointCritSect.Free;
inherited Destroy;
end;
// Initialize this object. Call after constructor.
function TVolumeMonitor.Initialize: HRESULT;
var
hr : HRESULT;
begin
hr := CoCreateInstance(CLASS_IMMDeviceEnumerator,
nil,
CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator,
FDeviceEnumerator);
if SUCCEEDED(hr) then begin
hr := FDeviceEnumerator.RegisterEndpointNotificationCallback(Self);
if SUCCEEDED(hr) then
hr := AttachToDefaultEndpoint();
end;
Result := hr;
end;
function TVolumeMonitor.AttachToDefaultEndpoint: HRESULT;
var
hr : HRESULT;
begin
FEndPointCritSect.Enter();
// Get the default music & movies playback device
hr := FDeviceEnumerator.GetDefaultAudioEndpoint(eRender, eMultimedia, FAudioEndpoint);
if SUCCEEDED(hr) then begin
// Get the volume control for it
hr := FAudioEndpoint.Activate(IAudioEndpointVolume, CLSCTX_INPROC_SERVER, nil, FAudioEndpointVolume);
if SUCCEEDED(hr) then begin
// Register for callbacks
hr := FAudioEndpointVolume.RegisterControlChangeNotify(self);
FRegisteredForVolumeNotifications := SUCCEEDED(hr);
end;
end;
FEndPointCritSect.Leave();
Result := hr;
end;
// Stop monitoring the device and release all associated references
procedure TVolumeMonitor.DetachFromEndpoint();
begin
FEndPointCritSect.Enter();
if FAudioEndpointVolume <> nil then begin
// be sure to unregister...
if FRegisteredForVolumeNotifications then begin
FAudioEndpointVolume.UnregisterControlChangeNotify(Self);
FRegisteredForVolumeNotifications := FALSE;
end;
FAudioEndpointVolume := nil
end;
if FAudioEndpoint <> nil then
FAudioEndpoint := nil;
FEndPointCritSect.Leave();
end;
// Call when the app is done with this object before calling release.
// This detaches from the endpoint and releases all audio service references.
procedure TVolumeMonitor.Dispose;
begin
DetachFromEndpoint();
if FRegisteredForEndpointNotifications then begin
FDeviceEnumerator.UnregisterEndpointNotificationCallback(Self);
FRegisteredForEndpointNotifications := FALSE;
end;
end;
function TVolumeMonitor.GetLevelInfo(var Info: TVOLUME_INFO): HRESULT;
var
hr : HRESULT;
begin
hr := E_FAIL;
FEndPointCritSect.Enter();
if FAudioEndpointVolume <> nil then begin
hr := FAudioEndpointVolume.GetMute(Info.bMuted);
if SUCCEEDED(hr) then
hr := FAudioEndpointVolume.GetVolumeStepInfo(Info.nStep, Info.cSteps);
end;
FEndPointCritSect.Leave();
Result := hr;
end;
function TVolumeMonitor.GetOnVolumeChange: TNotifyEvent;
begin
Result := FOnVolumeChange;
end;
// Callback for Windows API
function TVolumeMonitor.OnNotify(
pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
begin
if FWindowHandle <> INVALID_HANDLE_VALUE then
PostMessage(FWindowHandle, WM_VOLUMECHANGE, 0, 0);
Result := S_OK;
end;
procedure TVolumeMonitor.SetOnVolumeChange(const Value: TNotifyEvent);
begin
FOnVolumeChange := Value;
end;
procedure TVolumeMonitor.WMVolumeChange(var Msg: TMessage);
begin
if Assigned(FOnVolumeChange) then
FOnVolumeChange(Self);
end;
procedure TVolumeMonitor.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_VOLUMECHANGE : WMVolumeChange(Msg);
else
Winapi.Windows.DefWindowProc(FWindowHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end;
Finally, tho interact with Windows API, we need a few declarations for structure and interfaces that Windows make use.
unit Ovb.MMDevApi;
interface
uses
WinApi.Windows,
WinApi.ActiveX;
const
CLASS_IMMDeviceEnumerator : TGUID = '{BCDE0395-E52F-467C-8E3D-C4579291692E}';
IID_IMMDeviceEnumerator : TGUID = '{A95664D2-9614-4F35-A746-DE8DB63617E6}';
IID_IAudioEndpointVolume : TGUID = '{5CDF2C82-841E-4546-9722-0CF74078229A}';
// Data-flow direction
eRender = $00000000;
eCapture = $00000001;
eAll = $00000002;
// Role constant
eConsole = $00000000;
eMultimedia = $00000001;
eCommunications = $00000002;
type
TAUDIO_VOLUME_NOTIFICATION_DATA = record
guidEventContext : TGUID;
Muted : BOOL;
fMasterVolume : Single;
nChannels : UINT;
afChannelVolumes : array [1..1] of Single;
end;
PAUDIO_VOLUME_NOTIFICATION_DATA = ^TAUDIO_VOLUME_NOTIFICATION_DATA;
TVOLUME_INFO = record
nStep : UINT;
cSteps : UINT;
bMuted : BOOL;
end;
PVOLUME_INFO = ^TVOLUME_INFO;
IAudioEndpointVolumeCallback = interface(IUnknown)
['{657804FA-D6AD-4496-8A60-352752AF4F89}']
function OnNotify(pNotify : PAUDIO_VOLUME_NOTIFICATION_DATA) : HRESULT; stdcall;
end;
IAudioEndpointVolume = interface(IUnknown)
['{5CDF2C82-841E-4546-9722-0CF74078229A}']
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function GetChannelCount(out PInteger): HRESULT; stdcall;
function SetMasterVolumeLevel(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function SetMasterVolumeLevelScalar(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMasterVolumeLevel(out fLevelDB: single): HRESULT; stdcall;
function GetMasterVolumeLevelScaler(out fLevelDB: single): HRESULT; stdcall;
function SetChannelVolumeLevel(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function SetChannelVolumeLevelScalar(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function GetChannelVolumeLevel(nChannel: Integer; out fLevelDB: double): HRESULT; stdcall;
function GetChannelVolumeLevelScalar(nChannel: Integer; out fLevel: double): HRESULT; stdcall;
function SetMute(bMute: Boolean; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMute(out bMute: BOOL): HRESULT; stdcall;
function GetVolumeStepInfo(out pnStep: UINT; out pnStepCount: UINT): HRESULT; stdcall;
function VolumeStepUp(pguidEventContext: PGUID): HRESULT; stdcall;
function VolumeStepDown(pguidEventContext: PGUID): HRESULT; stdcall;
function QueryHardwareSupport(out pdwHardwareSupportMask): HRESULT; stdcall;
function GetVolumeRange(out pflVolumeMindB: double; out pflVolumeMaxdB: double; out pflVolumeIncrementdB: double): HRESULT; stdcall;
end;
IAudioMeterInformation = interface(IUnknown)
['{C02216F6-8C67-4B5B-9D00-D008E73E0064}']
end;
IPropertyStore = interface(IUnknown)
end;
IMMDevice = interface(IUnknown)
['{D666063F-1587-4E43-81F1-B948E807363F}']
function Activate(const refId: TGUID; dwClsCtx: DWORD; pActivationParams: PInteger; out pEndpointVolume: IAudioEndpointVolume): HRESULT; stdCall;
function OpenPropertyStore(stgmAccess: DWORD; out ppProperties: IPropertyStore): HRESULT; stdcall;
function GetId(out ppstrId: PLPWSTR): HRESULT; stdcall;
function GetState(out State: Integer): HRESULT; stdcall;
end;
IMMDeviceCollection = interface(IUnknown)
['{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}']
end;
IMMNotificationClient = interface(IUnknown)
['{7991EEC9-7E89-4D85-8390-6C703CEC60C0}']
end;
IMMDeviceEnumerator = interface(IUnknown)
['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
function EnumAudioEndpoints(dataFlow: TOleEnum; deviceState: SYSUINT; DevCollection: IMMDeviceCollection): HRESULT; stdcall;
function GetDefaultAudioEndpoint(EDF: SYSUINT; ER: SYSUINT; out Dev :IMMDevice ): HRESULT; stdcall;
function GetDevice(pwstrId: pointer; out Dev: IMMDevice): HRESULT; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
function UnregisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
end;
implementation
end.

Delphi Components and Screen Readers

I use Delphi and C++Builder and have some accessibility questions in regards to screen readers.
I have a button on a form which descends from TWinControl. If I put a caption on the button the screen reader will read it to me when the button is in focus. 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?
Similarly for an image on a form which descends from TGraphicControl. How can I tell the screen reader what to say when the object is moused over?
I’ve looked into the IAccessible wrapper, but I would prefer not to extend every control we use if at all possible.
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?
An IAccessible implementation for the button must provide the desired text to screen readers. By default, the OS provides a default IAccessible implementation for many UI controls, including buttons.
So, one simple trick you could do would be to owner-draw the button manually, then you can set its standard Caption for the default IAccessible implementation to use normally, and then you could simply do not include the Caption when you draw the button.
Otherwise, you can handle the WM_GETOBJECT message directly to retrieve the button's default IAccessible implementation and then wrap it so you can return your desired text and delegate everything else to the default implementation. For example:
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?
First off, TGraphic is not a component class. It is a wrapper for image data used by TPicture, which itself is a helper used by TImage, for instance. I assume you mean TGraphicControl instead (which TImage derives from).
A TGraphicControl-based component is not directly accessible to screen readers by default, as it has no window of its own, and as such it is not even known to the OS itself.
If you want a screen reader to interact with a graphical control, you must provide a full implementation of IAccessible from the Parent component (which does have a window) and have it expose additional Accessibility information about its graphical children.
I’ve looked into the IAccessible wrapper, but I would prefer not to extend every control we use if at all possible.
Sorry, but you will have to (unless you can find a 3rd party implementation that does what you need). The VCL simply does not implement any IAccessible functionality, so you have to implement it manually in your own code if you need to customize it beyond what the OS provides for you.

How to check if the system master volume is mute or unmute?

I'm using this code to mute/unmute system master volume:
const
APPCOMMAND_VOLUME_MUTE = $80000;
WM_APPCOMMAND = $319;
procedure TForm1.Button1Click(Sender: TObject);
begin
// toggle mute/unmute
SendMessageW(Handle, WM_APPCOMMAND, Handle, APPCOMMAND_VOLUME_MUTE);
end;
(Got the code from https://stackoverflow.com/a/154128/1140885)
It works fine on XP (Didn't test it on Win7 yet).
I need a method to check (get) what is the current "mute" state? Is it mute or not.
Any ideas?
Update: For XP I ended up using the code from here: How to get the master volume in windows xp? (Thanks to #Sertac Akyuz)
I had to change only a single line:
mxlc.dwControlType := MIXERCONTROL_CONTROLTYPE_VOLUME;
to:
mxlc.dwControlType := MIXERCONTROL_CONTROLTYPE_MUTE;
return value is either 0 (not mute) or 1 (mute).
Starting with Windows Vista you must use the Core Audio SDK to control the Windows audio. To check if the master volume is muted you must use the IAudioEndpointVolume.GetMute method.
Try this sample code
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows,
ActiveX,
ComObj;
const
CLASS_IMMDeviceEnumerator : TGUID = '{BCDE0395-E52F-467C-8E3D-C4579291692E}';
IID_IMMDeviceEnumerator : TGUID = '{A95664D2-9614-4F35-A746-DE8DB63617E6}';
IID_IAudioEndpointVolume : TGUID = '{5CDF2C82-841E-4546-9722-0CF74078229A}';
type
IAudioEndpointVolumeCallback = interface(IUnknown)
['{657804FA-D6AD-4496-8A60-352752AF4F89}']
end;
IAudioEndpointVolume = interface(IUnknown)
['{5CDF2C82-841E-4546-9722-0CF74078229A}']
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function GetChannelCount(out PInteger): HRESULT; stdcall;
function SetMasterVolumeLevel(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function SetMasterVolumeLevelScalar(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMasterVolumeLevel(out fLevelDB: single): HRESULT; stdcall;
function GetMasterVolumeLevelScaler(out fLevelDB: single): HRESULT; stdcall;
function SetChannelVolumeLevel(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function SetChannelVolumeLevelScalar(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function GetChannelVolumeLevel(nChannel: Integer; out fLevelDB: double): HRESULT; stdcall;
function GetChannelVolumeLevelScalar(nChannel: Integer; out fLevel: double): HRESULT; stdcall;
function SetMute(bMute: Boolean; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMute(out bMute: Boolean): HRESULT; stdcall;
function GetVolumeStepInfo(pnStep: Integer; out pnStepCount: Integer): HRESULT; stdcall;
function VolumeStepUp(pguidEventContext: PGUID): HRESULT; stdcall;
function VolumeStepDown(pguidEventContext: PGUID): HRESULT; stdcall;
function QueryHardwareSupport(out pdwHardwareSupportMask): HRESULT; stdcall;
function GetVolumeRange(out pflVolumeMindB: double; out pflVolumeMaxdB: double; out pflVolumeIncrementdB: double): HRESULT; stdcall;
end;
IAudioMeterInformation = interface(IUnknown)
['{C02216F6-8C67-4B5B-9D00-D008E73E0064}']
end;
IPropertyStore = interface(IUnknown)
end;
IMMDevice = interface(IUnknown)
['{D666063F-1587-4E43-81F1-B948E807363F}']
function Activate(const refId: TGUID; dwClsCtx: DWORD; pActivationParams: PInteger; out pEndpointVolume: IAudioEndpointVolume): HRESULT; stdCall;
function OpenPropertyStore(stgmAccess: DWORD; out ppProperties: IPropertyStore): HRESULT; stdcall;
function GetId(out ppstrId: PLPWSTR): HRESULT; stdcall;
function GetState(out State: Integer): HRESULT; stdcall;
end;
IMMDeviceCollection = interface(IUnknown)
['{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}']
end;
IMMNotificationClient = interface(IUnknown)
['{7991EEC9-7E89-4D85-8390-6C703CEC60C0}']
end;
IMMDeviceEnumerator = interface(IUnknown)
['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
function EnumAudioEndpoints(dataFlow: TOleEnum; deviceState: SYSUINT; DevCollection: IMMDeviceCollection): HRESULT; stdcall;
function GetDefaultAudioEndpoint(EDF: SYSUINT; ER: SYSUINT; out Dev :IMMDevice ): HRESULT; stdcall;
function GetDevice(pwstrId: pointer; out Dev: IMMDevice): HRESULT; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
end;
function IsMasterVolumeMute : Boolean;
var
pEndpointVolume: IAudioEndpointVolume;
LDeviceEnumerator: IMMDeviceEnumerator;
Dev: IMMDevice;
bMute: Boolean;
begin
if not Succeeded(CoCreateInstance(CLASS_IMMDeviceEnumerator, nil, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, LDeviceEnumerator)) then
RaiseLastOSError;
if not Succeeded(LDeviceEnumerator.GetDefaultAudioEndpoint($00000000, $00000000, Dev)) then
RaiseLastOSError;
if not Succeeded( Dev.Activate(IID_IAudioEndpointVolume, CLSCTX_INPROC_SERVER, nil, pEndpointVolume)) then
RaiseLastOSError;
if not Succeeded(pEndpointVolume.GetMute(bMute)) then
RaiseLastOSError
else
Result:=bMute;
end;
begin
try
CoInitialize(nil);
try
Writeln(Format('Master Volume is Mute ? : %s',[BoolToStr(IsMasterVolumeMute, True)]));
finally
CoUninitialize;
end;
except
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
Use this snipped, I've tested it and works for me.
This will check and set master volume.
(Copied from http://www.swissdelphicenter.ch/torry/showcode.php?id=1630)
I hope this helps:
uses
MMSystem;
function GetMasterMute(
Mixer: hMixerObj;
var Control: TMixerControl): MMResult;
// Returns True on success
var
Line: TMixerLine;
Controls: TMixerLineControls;
begin
ZeroMemory(#Line, SizeOf(Line));
Line.cbStruct := SizeOf(Line);
Line.dwComponentType := MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
Result := mixerGetLineInfo(Mixer, #Line,
MIXER_GETLINEINFOF_COMPONENTTYPE);
if Result = MMSYSERR_NOERROR then
begin
ZeroMemory(#Controls, SizeOf(Controls));
Controls.cbStruct := SizeOf(Controls);
Controls.dwLineID := Line.dwLineID;
Controls.cControls := 1;
Controls.dwControlType := MIXERCONTROL_CONTROLTYPE_MUTE;
Controls.cbmxctrl := SizeOf(Control);
Controls.pamxctrl := #Control;
Result := mixerGetLineControls(Mixer, #Controls,
MIXER_GETLINECONTROLSF_ONEBYTYPE);
end;
end;
procedure SetMasterMuteValue(
Mixer: hMixerObj;
Value: Boolean);
var
MasterMute: TMixerControl;
Details: TMixerControlDetails;
BoolDetails: TMixerControlDetailsBoolean;
Code: MMResult;
begin
Code := GetMasterMute(0, MasterMute);
if Code = MMSYSERR_NOERROR then
begin
with Details do
begin
cbStruct := SizeOf(Details);
dwControlID := MasterMute.dwControlID;
cChannels := 1;
cMultipleItems := 0;
cbDetails := SizeOf(BoolDetails);
paDetails := #BoolDetails;
end;
LongBool(BoolDetails.fValue) := Value;
Code := mixerSetControlDetails(0, #Details,
MIXER_SETCONTROLDETAILSF_VALUE);
end;
if Code <> MMSYSERR_NOERROR then
raise Exception.CreateFmt('SetMasterMuteValue failure, '+
'multimedia system error #%d', [Code]);
end;
// Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
SetMasterMuteValue(0, CheckBox1.Checked); // Mixer device #0 mute on/off
end;
The GetMute method's parameter should be BOOL rather than Boolean. Likewise for SetMute. –
Well, that is to say.. Yes and no.. Yes A Delphi BOOL (actually a LongBool) can store a C-BOOL.
No, because it can't be used to write to a C-BOOL property. You will get a 0x80070057 = "Wrong Parameter" result.
The simple reason is that In Delphi True means "everything but 0" and represents -1.
A C-BOOL True, however represents 1 and only 1.
So, using a LongBool doesn't work and you should use a workaround using an INT, LongInt, Integer or your own proper defined "BOOL" to avoid "Wrong Parameter" results.
Here an example (that works in Delphi XE7 and tested with SDK version 10.0.10586.15:
// Workaround for BOOL
type TcBOOL = (cFalse = Integer(0),
cTrue = Integer(1));
// IAudioEndpointVolume
function SetMute(bMute: BOOL; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMute(out pbMute: BOOL): HRESULT; stdcall;
// Client functions
function TMfpMixer.GetMute(): TcBOOL;
var
hr: HResult;
Res: BOOL;
begin
hr:= FAudioEndpoint.GetMute(Res);
if FAILED(hr) then
OleCheck(hr);
Result:= TcBOOL(Res);
end;
//
procedure TMfpMixer.SetMute(Value: TcBOOL);
var
hr: HResult;
begin
// This is a workaround on the Delphi BOOL issue.
hr:= FAudioEndpoint.SetMute(BOOL(Value),
Nil);
OleCheck(hr);
end;

How to draw something over WebBrowser component in Delphi

Is it possible to draw or put something over the WebBrowser component to draw on it ?
When I add an image on WebBrowser this image is always under the WebBrowser.
I need this to draw area over different map types always in the same way.
For example I need to draw the same area on Google Maps and open street maps...
You should use IHTMLPainter.Draw event method for doing this. The following code needs a TWebBrowser where you have to write the OnDocumentComplete event handler.
Note that this example has one big weakness, the user input events like mouse clicking are active because the only thing what this example do is the painting over the element. I've been playing with this a little bit, but without success. This might be a good topic for another question.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
StdCtrls, SHDocVw, MSHTML, OleCtrls;
type
TElementBehavior = class(TInterfacedObject, IElementBehavior, IHTMLPainter)
private
FPaintSite: IHTMLPaintSite;
public
{ IElementBehavior }
function Init(const pBehaviorSite: IElementBehaviorSite): HRESULT; stdcall;
function Notify(lEvent: Integer; var pVar: OleVariant): HRESULT; stdcall;
function Detach: HRESULT; stdcall;
{ IHTMLPainter }
function Draw(rcBounds: tagRECT; rcUpdate: tagRECT; lDrawFlags: Integer;
hdc: hdc; pvDrawObject: Pointer): HRESULT; stdcall;
function OnResize(size: tagSIZE): HRESULT; stdcall;
function GetPainterInfo(out pInfo: _HTML_PAINTER_INFO): HRESULT; stdcall;
function HitTestPoint(pt: tagPOINT; out pbHit: Integer; out plPartID: Integer): HRESULT; stdcall;
end;
TElementBehaviorFactory = class(TInterfacedObject, IElementBehaviorFactory)
public
function FindBehavior(const bstrBehavior: WideString;
const bstrBehaviorUrl: WideString; const pSite: IElementBehaviorSite;
out ppBehavior: IElementBehavior): HRESULT; stdcall;
end;
TForm1 = class(TForm)
WebBrowser1: TWebBrowser;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Image: TBitmap;
Behavior: TElementBehavior;
Factory: TElementBehaviorFactory;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Image := TBitmap.Create;
Image.LoadFromFile('c:\yourpicture.bmp');
WebBrowser1.Navigate('maps.google.com');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Behavior := nil;
Factory := nil;
Image.Free;
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
var
HTMLElement: IHTMLElement2;
FactoryVariant: OleVariant;
begin
HTMLElement := (WebBrowser1.Document as IHTMLDocument3).getElementById('map') as IHTMLElement2;
if Assigned(HTMLElement) then
begin
Behavior := TElementBehavior.Create;
Factory := TElementBehaviorFactory.Create;
FactoryVariant := IElementBehaviorFactory(Factory);
HTMLElement.addBehavior('', FactoryVariant);
end;
end;
function TElementBehaviorFactory.FindBehavior(const bstrBehavior,
bstrBehaviorUrl: WideString; const pSite: IElementBehaviorSite;
out ppBehavior: IElementBehavior): HRESULT;
begin
ppBehavior := Behavior;
Result := S_OK;
end;
function TElementBehavior.Draw(rcBounds: tagRECT; rcUpdate: tagRECT; lDrawFlags: Integer;
hdc: hdc; pvDrawObject: Pointer): HRESULT;
begin
StretchBlt(
hdc,
rcBounds.Left,
rcBounds.Top,
rcBounds.Right - rcBounds.Left,
rcBounds.Bottom - rcBounds.Top,
Image.Canvas.Handle,
0,
0,
Image.Canvas.ClipRect.Right - Image.Canvas.ClipRect.Left,
Image.Canvas.ClipRect.Bottom - Image.Canvas.ClipRect.Top,
SRCCOPY);
Result := S_OK;
end;
function TElementBehavior.GetPainterInfo(out pInfo: _HTML_PAINTER_INFO): HRESULT;
begin
pInfo.lFlags := HTMLPAINTER_OPAQUE;
pInfo.lZOrder := HTMLPAINT_ZORDER_WINDOW_TOP;
FillChar(pInfo.rcExpand, SizeOf(TRect), 0);
Result := S_OK;
end;
function TElementBehavior.HitTestPoint(pt: tagPOINT; out pbHit,
plPartID: Integer): HRESULT;
begin
Result := E_NOTIMPL;
end;
function TElementBehavior.OnResize(size: tagSIZE): HRESULT;
begin
Result := S_OK;
end;
function TElementBehavior.Detach: HRESULT;
begin
if Assigned(FPaintSite) then
FPaintSite.InvalidateRect(nil);
Result := S_OK;
end;
function TElementBehavior.Init(
const pBehaviorSite: IElementBehaviorSite): HRESULT;
begin
Result := pBehaviorSite.QueryInterface(IHTMLPaintSite, FPaintSite);
if Assigned(FPaintSite) then
FPaintSite.InvalidateRect(nil);
end;
function TElementBehavior.Notify(lEvent: Integer;
var pVar: OleVariant): HRESULT;
begin
Result := E_NOTIMPL;
end;
end.

How do I show progress in status/task bar button using Delphi 7?

If you copy files under Windows 7, you will see the progress of the copy in a sort of progress bar begin displayed in the status or task bar button of the application.
Can this be achieved using Delphi 7 ?
I have some lengthy operation which would be ideally suited to show it's progress this way.
sample of such a button using copy with Total Commander
.
Use the ITaskbarList3 interface for that, specifically its SetProgressState and SetProgressValue methods. Use CoCreateInstance() specifying CLSID_TaskbarList and IID_ITaskbarList3 to access the interface.
For example:
type
ITaskbarList = interface(IUnknown)
['{56FDF342-FD6D-11D0-958A-006097C9A090}']
function HrInit: HRESULT; stdcall;
function AddTab(hwnd: HWND): HRESULT; stdcall;
function DeleteTab(hwnd: HWND): HRESULT; stdcall;
function ActivateTab(hwnd: HWND): HRESULT; stdcall;
function SetActiveAlt(hwnd: HWND): HRESULT; stdcall;
end;
ITaskbarList2 = interface(ITaskbarList)
['{602D4995-B13A-429B-A66E-1935E44F4317}']
function MarkFullscreenWindow(hwnd: HWND;
fFullscreen: BOOL): HRESULT; stdcall;
end;
THUMBBUTTON = record
dwMask: DWORD;
iId: UINT;
iBitmap: UINT;
hIcon: HICON;
szTip: packed array[0..259] of WCHAR;
dwFlags: DWORD;
end;
TThumbButton = THUMBBUTTON;
PThumbButton = ^TThumbButton;
ITaskbarList3 = interface(ITaskbarList2)
['{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}']
function SetProgressValue(hwnd: HWND; ullCompleted: ULONGLONG;
ullTotal: ULONGLONG): HRESULT; stdcall;
function SetProgressState(hwnd: HWND;
tbpFlags: Integer): HRESULT; stdcall;
function RegisterTab(hwndTab: HWND; hwndMDI: HWND): HRESULT; stdcall;
function UnregisterTab(hwndTab: HWND): HRESULT; stdcall;
function SetTabOrder(hwndTab: HWND;
hwndInsertBefore: HWND): HRESULT; stdcall;
function SetTabActive(hwndTab: HWND; hwndMDI: HWND;
tbatFlags: Integer): HRESULT; stdcall;
function ThumbBarAddButtons(hwnd: HWND; cButtons: UINT;
pButton: PThumbButton): HRESULT; stdcall;
function ThumbBarUpdateButtons(hwnd: HWND; cButtons: UINT;
pButton: PThumbButton): HRESULT; stdcall;
function ThumbBarSetImageList(hwnd: HWND;
himl: HIMAGELIST): HRESULT; stdcall;
function SetOverlayIcon(hwnd: HWND; hIcon: HICON;
pszDescription: LPCWSTR): HRESULT; stdcall;
function SetThumbnailTooltip(hwnd: HWND;
pszTip: LPCWSTR): HRESULT; stdcall;
function SetThumbnailClip(hwnd: HWND;
var prcClip: TRect): HRESULT; stdcall;
end;
const
CLSID_TaskbarList: TGUID = '{56FDF344-FD6D-11d0-958A-006097C9A090}';
TBPF_NOPROGRESS = 0;
TBPF_INDETERMINATE = $1;
TBPF_NORMAL = $2;
TBPF_ERROR = $4;
TBPF_PAUSED = $8;
var
TBL: ITaskbarList3;
I: Integer;
begin
CoCreateInstance(CLSID_TaskbarList, nil, CLSCTX_INPROC, ITaskbarList3, TBL);
if (TBL <> nil) then
TBL.SetProgressState(Application.Handle, TBPF_INDETERMINATE);
try
for I := 0 to 100 do
begin
if (TBL <> nil) then
TBL.SetProgressValue(Application.Handle, I, 100);
Sleep(1000);
end;
finally
if (TBL <> nil) then
TBL.SetProgressState(Application.Handle, TBPF_NOPROGRESS);
end;
end;
Also you can try my unit (Tested in Delphi 7):
{*******************************************************************************
Windows 7 TaskBar Progress Unit File
File Version: 0.0.0.3
https://github.com/tarampampam
*******************************************************************************}
unit Win7TaskBarProgressUnit;
interface
uses
Windows, ActiveX;
type
ITaskbarList = interface(IUnknown)
['{56FDF342-FD6D-11D0-958A-006097C9A090}']
function HrInit: HRESULT; stdcall;
function AddTab(hwnd: LongWord): HRESULT; stdcall;
function DeleteTab(hwnd: LongWord): HRESULT; stdcall;
function ActivateTab(hwnd: LongWord): HRESULT; stdcall;
function SetActiveAlt(hwnd: LongWord): HRESULT; stdcall;
end;
ITaskbarList2 = interface(ITaskbarList)
['{602D4995-B13A-429B-A66E-1935E44F4317}']
function MarkFullscreenWindow(hwnd: LongWord;
fFullscreen: LongBool): HRESULT; stdcall;
end;
ITaskbarList3 = interface(ITaskbarList2)
['{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}']
procedure SetProgressValue(hwnd: LongWord; ullCompleted: UInt64; ullTotal: UInt64); stdcall;
procedure SetProgressState(hwnd: LongWord; tbpFlags: Integer); stdcall;
end;
type
TTaskBarProgressStyle = (tbpsNone, tbpsIndeterminate, tbpsNormal, tbpsError, tbpsPaused);
TWin7TaskProgressBar = class
glHandle: LongWord;
glMin: Byte;
glMax,
glValue: Integer;
glStyle: TTaskBarProgressStyle;
glVisible,
glMarquee: Boolean;
glTaskBarInterface: ITaskbarList3;
private
procedure SetProgress(const Value: Integer);
procedure SetMax(const Value: Integer);
procedure SetStyle(const Style: TTaskBarProgressStyle);
procedure SetVisible(const IsVisible: Boolean);
procedure SetMarquee(const IsMarquee: Boolean);
published
constructor Create(const Handle: LongWord);
property Max: Integer read glMax write SetMax default 100;
property Min: Byte read glMin default 0;
property Progress: Integer read glValue write SetProgress default 0;
property Marquee: Boolean read glMarquee write SetMarquee default False;
property Style: TTaskBarProgressStyle read glStyle write SetStyle default tbpsNone;
property Visible: Boolean read glVisible write SetVisible default False;
destructor Destroy; override;
end;
implementation
procedure TWin7TaskProgressBar.SetMax(const Value: Integer);
begin
glMax := Value;
SetProgress(glValue);
end;
procedure TWin7TaskProgressBar.SetProgress(const Value: Integer);
begin
if (glTaskBarInterface <> nil) and (glHandle <> 0) then begin
glValue := Value;
if not glMarquee then
glTaskBarInterface.SetProgressValue(glHandle, UInt64(glValue), UInt64(glMax));
end;
end;
procedure TWin7TaskProgressBar.SetStyle(const Style: TTaskBarProgressStyle);
const
Flags: array[TTaskBarProgressStyle] of Cardinal = (0, 1, 2, 4, 8);
begin
if (glTaskBarInterface <> nil) and (glHandle <> 0) then
glTaskBarInterface.SetProgressState(glHandle, Flags[Style]);
glStyle := Style;
end;
procedure TWin7TaskProgressBar.SetVisible(const IsVisible: Boolean);
begin
if IsVisible then begin
if (glStyle <> tbpsNormal) then
SetStyle(tbpsNormal)
end else
SetStyle(tbpsNone);
glVisible := IsVisible;
end;
procedure TWin7TaskProgressBar.SetMarquee(const IsMarquee: Boolean);
begin
if IsMarquee then
SetStyle(tbpsIndeterminate)
else begin
SetStyle(tbpsNone);
if glVisible then begin
SetProgress(glValue);
SetStyle(tbpsNormal);
end;
end;
glMarquee := IsMarquee;
end;
constructor TWin7TaskProgressBar.Create(const Handle: LongWord);
const
CLSID_TaskbarList: TGUID = '{56FDF344-FD6D-11d0-958A-006097C9A090}';
var
OSVersionInfo : TOSVersionInfo;
begin
OSVersionInfo.dwOSVersionInfoSize := sizeof(TOSVersionInfo);
if (Handle <> 0) and GetVersionEx(OSVersionInfo) then
if OSVersionInfo.dwMajorVersion >= 6 then try
glHandle := Handle;
CoCreateInstance(CLSID_TaskbarList, nil, CLSCTX_INPROC, ITaskbarList3, glTaskBarInterface);
if (glTaskBarInterface <> nil) then
glTaskBarInterface.SetProgressState(glHandle, 0);
glMin := 0;
glMax := 100;
glValue := 10;
glStyle := tbpsNormal;
SetStyle(glStyle);
SetVisible(glVisible);
except
glTaskBarInterface := nil;
end;
end;
destructor TWin7TaskProgressBar.Destroy;
begin
if (glTaskBarInterface <> nil) then begin
glTaskBarInterface.SetProgressState(glHandle, 0);
glTaskBarInterface := nil;
end;
end;
end.
There are some complete solutions like TaskBarList component.
TTaskbarListProgress is a Pascal wrapper class for displaying progress in the taskbar.

Resources