The following call should match to first "Untitled - Notepad" window.
hnd := findWindowWildcat('', 'Untitled - Note');
It seems I don't know how to use Delphi/win32 callback functions
properly. EnumWindowProc returns only boolean, but I need to return a
matched handle back to the originating function.
---
function findwindowWildcat(const className, windowName: String): HWND;
var
handle: HWND;
{ Nested EnumWindows callback function }
function EnumWindowProc(wnd: HWND; lp: LParam): BOOL; stdcall;
var
buf: array[0..512] of char;
s: string;
begin
if not(className = '') then begin
GetClassName(wnd, buf, 512);
s := buf;
if not(className = s) then begin
// does not match, continue callback loop
result := true;
exit;
end;
end;
GetWindowText(wnd, buf, 512);
s := buf;
if not(uStrings.StartsWith(windowName, s)) then begin
result := true;
exit;
end;
// found matching window, store handle and
// break callback loop
handle := wnd;
result := false;
end;
begin
handle := 0;
EnumWindows(@EnumWindowProc, 0);
result := handle;
end;
The callback function cannot be a nested function or it shouldn't refer
to any local variables of the function it's nested in. This is for the
same reason you can't assign a nested function to a procedural type
variable: the stack at the time when it is called will be different
than expected. You should use the parameter for this purpose: make it a
pointer to the handle.
Uuhhh. "use parameter for this purpose...". What do you mean exactly?
enumWindows callback function has two parameters and it return BOOL.
function EnumWindowProc(wnd: HWND; lp: LParam): BOOL; stdcall;
I still don't see a solution to return windowhandle from the this
procedure. Should I declare a unit-level variable and then assign it in
callback function?
But then it's not a threadsafe variable in .dll function if I understand
it correctly.
With the "parameter" i mean the parameter passed to EnumWindows called
"lParam" (the second parameter). This parameter is passed to the
calback procedure:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/w
inui/windowsuserinterface/windowing/windows/windowreference/windowfuncti
ons/enumwindows.asp
> I still don't see a solution to return windowhandle from the this
> procedure. Should I declare a unit-level variable and then assign it
> in callback function?
> But then it's not a threadsafe variable in .dll function if I
> understand it correctly.
If you supply a pointer to the variable keeping the Handle (the local
variable in the function calling EnumWindows) you can modify the handle
by dereferencing the pointer and setting the value. There are not
global variables involved. This is what the extra parameter was
intended for.
If you don't understand what i mean there's another solution though:
use a treadvar. In this case you have a global var that will be
different for each thread.
Here's a similar one I just wrote - uses EnumThreadWindows instead of
EnumWindows, but same principle.
function EnumThreadWindowProc(hWindow: HWND; phPassedWindow: LPARAM):
BOOL; stdcall;
var
aCaption: array[0..18] of char;
begin
Result := true;
SendMessage(hWindow, WM_GETTEXT, 19, LPARAM(@aCaption));
if String(acaption) <> 'Untitled - Notepad' then exit;
LPARAM(Pointer(phPassedWindow)^) := hWindow;
Result := false;
end;
var
hWindow: HWND;
begin
EnumThreadWindows(ThreadID, @EnumThreadWindowProc,
LPARAM(@hWindow));
if hWindow > 0 then ...
end;
For what its worth, here is my solution. The resultant window
is returned by TMyForm1.FindWindowStartingWith . I hope I have
understood your purpose.
type
TTester = class( TObject) // Abstact tester object
private
FisFound: boolean;
FFoundWindow: HWND;
FPattern: string;
protected
function Test( const Subject: string): boolean; virtual; abstract;
constructor Create( const Pattern: string); virtual;
end;
TTesterClass = class of TTester;
TStartsWithTester = class( TTester)
protected
function Test( const Subject: string): boolean; override;
end;
constructor TTester.Create( const Pattern: string);
begin
FisFound := False;
FPattern := Pattern
end;
function TStartsWithTester.Test( const Subject: string): boolean;
begin
result := uStrings.StartsWith( Subject, FPattern)
end;
function EnumWindowProc(wnd: HWND; lp: LParam): BOOL; stdcall; far;
// The second parameter of EnumWindows is the Tester. This becomes
// the "lp" formal parameter here.
var
Tester: TTester;
WindowCaption: string;
begin
result := False;
Tester := TTester( lp);
if Tester.FisFound then exit; // Stop when a solution has been found.
SetLength( WindowCaption, 1000);
SetLength( WindowCaption, windows.GetWindowText( wnd,
PChar( WindowCaption), Length( WindowCaption)));
Tester.FisFound := Tester.Test( WindowCaption, Tester.FPattern)
end;
FindMatchingWindow( const Pattern: string;
TestClass: TTesterClass): HWND;
var
Tester: TTester;
begin
Tester := TestClass.Create( Pattern);
try
windows.EnumWindows( TFNWndEnumProc( @EnumWindowProc),
LPARAM( Tester);
if Tester.FFisFound then
result := Tester.FFoundWindow
else
result := 0 // or you could put another default solution.
finally
Tester.Free
end end;
TMyForm1.FindWindowStartingWith( const Prefix: string): HWND;
begin
result := FindMatchingWindow( Prefix, TStartsWithTester)
end;
Faithfully,
Sean B. Durkin
sean@{RemoveThis}getdata.com
I really appreciate your comments. About using LPARAM as a pointer to
the _class instance_ is exactly what I need.
I have done some horrible code previously here and there, but your tip
will simplify things a lot.
thx
> Sean B. Durkin wrote:
> For what its worth, here is my solution. The resultant window
> is returned by TMyForm1.FindWindowStartingWith . I hope I have
> understood your purpose.
> function EnumWindowProc(wnd: HWND; lp: LParam): BOOL; stdcall; far;
> // The second parameter of EnumWindows is the Tester. This becomes
> // the "lp" formal parameter here.
> var
> Tester: TTester;
> WindowCaption: string;
> begin
> result := False;
> Tester := TTester( lp);
>Threetwosevensixseven wrote:
> LPARAM(Pointer(phPassedWindow)^) := hWindow;