{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* AWinTitle: Window Title/Caption
* }
function ControlSetText(const AControl, ANewText, AWinTitle: string): boolean;
function EnumChildren(AWindowHandle: HWND; ALParam: lParam): bool; stdcall;
begin
ShowMessage(AControl); // if commented out - code works fine
TStrings(ALParam).Add(IntToStr(GetDlgCtrlID(AWindowHandle)));
Result := true;
end;
var
_MainWindowHandle: HWND;
_WindowControlList: TStringlist;
i: integer;
_ControlHandle: integer;
begin
Result := false;
_MainWindowHandle := FindWindow(nil, PWideChar(AWinTitle));
if _MainWindowHandle <> 0 then
begin
_WindowControlList := TStringlist.Create;
try
if TryStrToInt('$' + Trim(AControl), _ControlHandle) then
try
EnumChildWindows(_MainWindowHandle, @EnumChildren,
UINT_PTR(_WindowControlList));
for i := 0 to _WindowControlList.Count - 1 do
begin
if (StrToInt(_WindowControlList[i]) = _ControlHandle)
then
begin
SendMessage(StrToInt(_WindowControlList[i]), WM_SETTEXT, 0,
integer(PCHAR(ANewText)));
Result := true;
end;
end;
except
on E: Exception do
MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0)
end;
finally
FreeAndNil(_WindowControlList);
end;
end;
end;
The debugger raises an exception with the message
调试器会使用该消息引发异常
--------------------------- Debugger Exception Notification ---------------------------
---------------------------调试器异常通知-------------------- -------
Project Default_project.exe raised exception class $C0000005 with message 'access violation at 0x00406fae: write of address 0x00408dbb'.
项目Default_project.exe引发异常类$ C0000005,消息'访问冲突位于0x00406fae:写入地址0x00408dbb'。
It breaks at:
它打破了:
for i := 0 to _WindowControlList.Count - 1 do
I call it like this:
我称之为:
ControlSetText('00070828', 'New TEdit text', 'Delphi_test_app');
I am planning an update, so, not only control handle could be passed, but also control type+identifier e.g. 'Edit1'.
我正在计划更新,因此,不仅可以传递控制句柄,还可以控制类型+标识符,例如'EDIT1'。
EDIT:
What I am trying is to do is to implement http://www.autohotkey.com/docs/commands/ControlSetText.htm
我想要做的是实现http://www.autohotkey.com/docs/commands/ControlSetText.htm
2 个解决方案
#1
1
The root cause of your crash is that your are using an inner function as the EnumChildWindows()
callback and it is referencing a parameter from its outer function, which will not work (and why it does work when you comment out the access of that parameter). The call stack frame is not what EnumChildWindows()
is expecting. You need to make the inner function be a standalone function instead.
崩溃的根本原因是您正在使用内部函数作为EnumChildWindows()回调,并且它正在引用来自其外部函数的参数,这将无法工作(以及当您注释掉该参数的访问权限时它为什么会起作用)。调用堆栈帧不是EnumChildWindows()所期望的。您需要使内部函数成为独立函数。
With that said, there is another bug in your code. Even if the above worked, your code would still fail because you are storing child Control IDs in your TStringList
but then using them as if they were HWND
values instead. They are not!
话虽如此,您的代码中还有另一个错误。即使上述工作正常,您的代码仍然会失败,因为您将子控件ID存储在TStringList中,然后使用它们就好像它们是HWND值一样。他们不是!
Try something more like this:
尝试更像这样的东西:
uses
..., System.Generics.Collections;
{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* AWinTitle: Window Title/Caption
* }
type
THWndList = TList<HWND>;
function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
THWndList(AParam).Add(AWindowHandle);
Result := TRUE;
end;
function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
{$IFDEF WIN64}
Result := TryStrToInt64(AStr, Int64(Wnd));
{$ELSE}
Result := TryStrToInt(AStr, Integer(Wnd));
{$ENDIF}
end;
function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
_MainWindowHandle: HWND;
_WindowControlList: THWndList;
i: integer;
_ControlHandle: HWND;
EnumInfo: TEnumInfo;
begin
Result := False;
_MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
if _MainWindowHandle <> 0 then
begin
_WindowControlList := THWndList;
try
if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
try
EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(_WindowControlList));
for i := 0 to _WindowControlList.Count - 1 do
begin
if (_WindowControlList[i] = _ControlHandle) then
begin
Result := SendMessage(_WindowControlList[i], WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
Break;
end;
end;
except
on E: Exception do
MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
end;
finally
FreeAndNil(_WindowControlList);
end;
end;
end;
Alternatively:
{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* AWinTitle: Window Title/Caption
* }
type
PEnumInfo = ^TEnumInfo;
TEnumInfo = record
Control: HWND;
Found: Boolean;
end;
function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
PEnumInfo(AParam).Found := (AWindowHandle = PEnumInfo(AParam).Control);
Result := not PEnumInfo(AParam).Found;
end;
function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
{$IFDEF WIN64}
Result := TryStrToInt64(AStr, Int64(Wnd));
{$ELSE}
Result := TryStrToInt(AStr, Integer(Wnd));
{$ENDIF}
end;
function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
_MainWindowHandle: HWND;
_ControlHandle: HWND;
EnumInfo: TEnumInfo;
begin
Result := False;
_MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
if _MainWindowHandle <> 0 then
begin
if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
try
EnumInfo.Control := _ControlHandle;
EnumInfo.Found := False;
EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(@EnumInfo));
if EnumInfo.Found then
begin
Result := SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
end;
except
on E: Exception do
MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
end;
end;
end;
Or just get rid of EnumChilWindows()
and let Windows validate the HWND
you try to send to:
或者只是摆脱EnumChilWindows()并让Windows验证您尝试发送到的HWND:
{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* }
function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
{$IFDEF WIN64}
Result := TryStrToInt64(AStr, Int64(Wnd));
{$ELSE}
Result := TryStrToInt(AStr, Integer(Wnd));
{$ENDIF}
end;
function ControlSetText(const AControl, ANewText: String): Boolean;
var
_ControlHandle: HWND;
begin
Result := TryStrToHWnd('$' + Trim(AControl), _ControlHandle) and
(SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1);
end;
#2
4
The problem is that your callback is a local nested function. That is it is nested inside ControlSetText
. It must be declared at global scope.
问题是你的回调是一个本地嵌套函数。那就是它嵌套在ControlSetText中。它必须在全球范围内声明。
Any extra state information must be passed in through the lParam
parameter.
必须通过lParam参数传递任何额外的状态信息。
I also find it odd that you store integers and pointers in strings. Store them as integers or pointers.
我也觉得奇怪的是你在字符串中存储整数和指针。将它们存储为整数或指针。
In fact it is more than odd. You put control ids in the list, as strings, but then use them as window handles. So once you get past the crash the code won't work. I don't want to get into debugging that in this question.
事实上,这不仅仅是奇怪的。您将控件ID放在列表中,作为字符串,但然后将它们用作窗口句柄。因此,一旦您通过崩溃,代码将无法正常工作。我不想在这个问题中进行调试。
#1
1
The root cause of your crash is that your are using an inner function as the EnumChildWindows()
callback and it is referencing a parameter from its outer function, which will not work (and why it does work when you comment out the access of that parameter). The call stack frame is not what EnumChildWindows()
is expecting. You need to make the inner function be a standalone function instead.
崩溃的根本原因是您正在使用内部函数作为EnumChildWindows()回调,并且它正在引用来自其外部函数的参数,这将无法工作(以及当您注释掉该参数的访问权限时它为什么会起作用)。调用堆栈帧不是EnumChildWindows()所期望的。您需要使内部函数成为独立函数。
With that said, there is another bug in your code. Even if the above worked, your code would still fail because you are storing child Control IDs in your TStringList
but then using them as if they were HWND
values instead. They are not!
话虽如此,您的代码中还有另一个错误。即使上述工作正常,您的代码仍然会失败,因为您将子控件ID存储在TStringList中,然后使用它们就好像它们是HWND值一样。他们不是!
Try something more like this:
尝试更像这样的东西:
uses
..., System.Generics.Collections;
{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* AWinTitle: Window Title/Caption
* }
type
THWndList = TList<HWND>;
function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
THWndList(AParam).Add(AWindowHandle);
Result := TRUE;
end;
function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
{$IFDEF WIN64}
Result := TryStrToInt64(AStr, Int64(Wnd));
{$ELSE}
Result := TryStrToInt(AStr, Integer(Wnd));
{$ENDIF}
end;
function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
_MainWindowHandle: HWND;
_WindowControlList: THWndList;
i: integer;
_ControlHandle: HWND;
EnumInfo: TEnumInfo;
begin
Result := False;
_MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
if _MainWindowHandle <> 0 then
begin
_WindowControlList := THWndList;
try
if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
try
EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(_WindowControlList));
for i := 0 to _WindowControlList.Count - 1 do
begin
if (_WindowControlList[i] = _ControlHandle) then
begin
Result := SendMessage(_WindowControlList[i], WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
Break;
end;
end;
except
on E: Exception do
MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
end;
finally
FreeAndNil(_WindowControlList);
end;
end;
end;
Alternatively:
{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* AWinTitle: Window Title/Caption
* }
type
PEnumInfo = ^TEnumInfo;
TEnumInfo = record
Control: HWND;
Found: Boolean;
end;
function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
PEnumInfo(AParam).Found := (AWindowHandle = PEnumInfo(AParam).Control);
Result := not PEnumInfo(AParam).Found;
end;
function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
{$IFDEF WIN64}
Result := TryStrToInt64(AStr, Int64(Wnd));
{$ELSE}
Result := TryStrToInt(AStr, Integer(Wnd));
{$ENDIF}
end;
function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
_MainWindowHandle: HWND;
_ControlHandle: HWND;
EnumInfo: TEnumInfo;
begin
Result := False;
_MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
if _MainWindowHandle <> 0 then
begin
if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
try
EnumInfo.Control := _ControlHandle;
EnumInfo.Found := False;
EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(@EnumInfo));
if EnumInfo.Found then
begin
Result := SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
end;
except
on E: Exception do
MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
end;
end;
end;
Or just get rid of EnumChilWindows()
and let Windows validate the HWND
you try to send to:
或者只是摆脱EnumChilWindows()并让Windows验证您尝试发送到的HWND:
{ *
* AControl: Control handle determined by Spy++ (e.g. 0037064A)
* ANewText: Text to assign to control
* }
function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
{$IFDEF WIN64}
Result := TryStrToInt64(AStr, Int64(Wnd));
{$ELSE}
Result := TryStrToInt(AStr, Integer(Wnd));
{$ENDIF}
end;
function ControlSetText(const AControl, ANewText: String): Boolean;
var
_ControlHandle: HWND;
begin
Result := TryStrToHWnd('$' + Trim(AControl), _ControlHandle) and
(SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1);
end;
#2
4
The problem is that your callback is a local nested function. That is it is nested inside ControlSetText
. It must be declared at global scope.
问题是你的回调是一个本地嵌套函数。那就是它嵌套在ControlSetText中。它必须在全球范围内声明。
Any extra state information must be passed in through the lParam
parameter.
必须通过lParam参数传递任何额外的状态信息。
I also find it odd that you store integers and pointers in strings. Store them as integers or pointers.
我也觉得奇怪的是你在字符串中存储整数和指针。将它们存储为整数或指针。
In fact it is more than odd. You put control ids in the list, as strings, but then use them as window handles. So once you get past the crash the code won't work. I don't want to get into debugging that in this question.
事实上,这不仅仅是奇怪的。您将控件ID放在列表中,作为字符串,但然后将它们用作窗口句柄。因此,一旦您通过崩溃,代码将无法正常工作。我不想在这个问题中进行调试。