Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When iterating through controls on a form, how can I identify particular buttons?

Tags:

winapi

delphi

I need to make some changes to a TaskDialog before it is shown to the user. It's fairly simple to use Windows API calls to work with each of the controls on the dialog box. I need to be more sure which button I have found. I would have expected to find a place where I could read the result the button would give if pressed.

in other words, if I pressed a button that would cause a return value (in Delphi, it's called a modal result) of 100, I would have expected there to be an API call I could call to find out what the button's "return value" would be. I haven't yet found any such call.

I don't want to rely on the button text..

Here's what I have so far.

function EnumWindowsProcToFindDlgControls(hWindow: HWND; _param:LPARAM): BOOL; stdcall;
var
  sClassName:string;
  hBMP:THandle;
  i:integer;
begin
  SetLength(sClassName, MAX_PATH);
  GetClassName(hWindow, PChar(sClassName), MAX_PATH);
  SetLength(sClassName, StrLen(PChar(sClassName)));

  if sClassName='Button' then
    begin    
      // always 0...
      i:=GetDlgCtrlID(hWindow);

      if (i=100) or (i=102) then
        begin
          hBmp := LoadImage(HInstance, 'DISA', IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE or LR_LOADTRANSPARENT );

          SendMessage(hWindow, BM_SETIMAGE, WPARAM(IMAGE_BITMAP), LPARAM(hBmp));
        end;
    end;

  // keep looking
  Result:=true;
end;

procedure TForm2.TaskDialog1DialogConstructed(Sender: TObject);
begin
  EnumChildWindows(TaskDialog1.Handle, @EnumWindowsProcToFindDlgControls, 0);
end;

I suspect it's not entirely "respectable" to do things like this with a dialog.

This is a Delphi 10 Win32 application using Delphi's VCL TTaskDialog component which is a wrapper around Windows task dialog feature. before it's shown, the OnConstructed event fires, executing this code.

Thank you for your help!

like image 443
X-Ray Avatar asked Sep 01 '16 16:09

X-Ray


1 Answers

Win32 buttons do not have "return values", which is why there is no API to retrieve such a value from them. What you are thinking of is strictly a VCL feature.

In Win32 API terms, a button can have a control ID, and in the case of MessageBox(), for example, standard ID values like IDOK, IDCANCEL, etc are assigned to the dialog buttons. When a button is clicked and the dialog is closed, the button's control ID is used as the function return value.

But task dialogs do not use control IDs, which is why you do not see any assigned to the dialog buttons.

To identify a particular task dialog button, I can think of two ways:

  1. during child enumeration, retrieve each button's caption text (GetWindowText()), and compare that to captions you are interested in. Just know that the standard buttons (from the TTaskDialog.CommonButtons property) use localized text, which does not make this a well-suited option for locating standard buttons unless you have control over the app's locale settings.

  2. send the dialog a TDM_ENABLE_BUTTON message to temporarily disable the desired button that has a given ID, then enumerate the dialog's controls until you find a disabled child window (using IsWindowEnabled()), and then re-enable the control. You can then manipulate the found window as needed.

    For Task Dialog messages and Task Dialog Notifications that operate on buttons (like TDN_BUTTON_CLICKED, which triggers the TTaskDialog.OnButtonClicked event), the standard buttons use IDs like IDOK, IDCANCEL, etc while custom buttons (from the TTaskDialog.Buttons property) use their ModalResult property as their ID.

    You can send TDM_ENABLE_BUTTON directly via SendMessage() for standard buttons, or via the TTaskDialogBaseButtonItem.Enabled property for custom buttons.

For #2, this works when I try it:

uses
  Winapi.CommCtrl;

function FindDisabledDlgControl(hWindow: HWND; _param: LPARAM): BOOL; stdcall;
type
  PHWND = ^HWND;
begin
  if not IsWindowEnabled(hWindow) then
  begin
    PHWND(_param)^ := hWindow;
    Result := False;
  end else
    Result := True;
end;

procedure TForm2.TaskDialog1DialogConstructed(Sender: TObject);
var
  hButton: HWND;
begin
  // common tcbOk button
  SendMessage(TaskDialog1.Handle, TDM_ENABLE_BUTTON, IDOK, 0);
  hButton := 0;
  EnumChildWindows(TaskDialog1.Handle, @FindDisabledDlgControl, LPARAM(@hButton));
  SendMessage(TaskDialog1.Handle, TDM_ENABLE_BUTTON, IDOK, 1);
  if hButton <> 0 then
  begin
    // use hButton as needed...
  end;

  // custom button
  TaskDialog1.Buttons[0].Enabled := False;
  hButton := 0;
  EnumChildWindows(TaskDialog1.Handle, @FindDisabledDlgControl, LPARAM(@hButton));
  TaskDialog1.Buttons[0].Enabled := True;
  if hButton <> 0 then
  begin
    // use hButton as needed...
  end;
end;
like image 200
Remy Lebeau Avatar answered Sep 30 '22 18:09

Remy Lebeau