2

I am trying to make a program similar to Winspector Spy. My problem is that I would like my Virtual Treeview to be updated at all times - that is, update it when a window is created, when a window is destroyed, etc. Of course, all external HWND's.

For that, I was thinking of writing a data container that contained all the Handles + information, and do the EnumWindows and EnumChildWindows in a seperate Thread, where I would fill my data container with said information.

Would you recommend I do it that way, or do you have another solution? If I do it this way, then should I make my Thread run during the whole Program Lifetime, and then just have an infinite loop within the Execute that would clear my datacontainer, and fill it again, every second, or something?

Here is my Data Container:

unit WindowList;

interface

Uses
  Windows, SysUtils, Classes, VirtualTrees, WinHandles, Messages,
  Generics.Collections;


type
  TWindow = class;
  TWindowList = class(TObjectList<TWindow>)
  public
    constructor Create;
    function AddWindow(Wnd : HWND):TWindow;
  end;

  ///////////////////////////////////////

  TWindow = class
  public
    Node         : PVirtualNode;
    Children     : TObjectList<TWindow>;
    Handle       : HWND;
    Icon         : HICON;
    ClassName    : string;
    Text         : string;
    constructor Create(Wnd : HWND);
    destructor Destroy;
    function AddWindow(Wnd : HWND):TWindow;
  end;


implementation

{ TWindowList }

function TWindowList.AddWindow(Wnd: HWND): TWindow;
var
  Window : TWindow;
begin
  Window := TWindow.Create(Wnd);
  Add(Window);
  Result := Window;
end;

constructor TWindowList.Create;
begin
  inherited Create(True);
end;

{ TWindow }

function TWindow.AddWindow(Wnd: HWND): TWindow;
var
  Window : TWindow;
begin
  Window := TWindow.Create(Wnd);
  Children.Add(Window);
  Result := Window;
end;

constructor TWindow.Create(Wnd: HWND);
begin
  Handle := Wnd;
  if Handle = 0 then Exit;
  ClassName := GetClassName(Handle);
  Text := GetHandleText(Handle);
  Node := Nil;
  Children := TObjectList<TWindow>.Create(True);
end;

destructor TWindow.Destroy;
begin
  ClassName := '';
  Text := '';
  Children.Free;
end;

end.
Johan
  • 74,508
  • 24
  • 191
  • 319
Jeff
  • 12,085
  • 12
  • 82
  • 152

2 Answers2

3

You could hook for WH_CBT and examine HCBT_CREATEWND/HCBT_DESTROYWND

The system calls a WH_CBT hook procedure before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window ...

Also take a look at What do I have to do to make my WH_SHELL or WH_CBT hook procedure receive events from other processes?

Community
  • 1
  • 1
Alex K.
  • 171,639
  • 30
  • 264
  • 288
  • @Alex - So, will I have to create a DLL for this? Is there a way to avoid that? – Jeff May 14 '11 at 14:15
  • 1
    I dont beleive so, SetWindowsHookEx requires the code be hosted in a dll, iirc you will also need 32/64 bit hook dlls and so will need to invoke them from a process of the corresonding bitness – Alex K. May 14 '11 at 14:35
  • @Alex wouldnt my method be easier then? – Jeff May 14 '11 at 14:45
  • @Alex - But would it be too "heavy"? :P – Jeff May 14 '11 at 16:19
  • I wouldnt think so, you could limit it by only examining windows that were visible in the treeviews viewport, that were expanded, or that the user was actually inspecting – Alex K. May 14 '11 at 16:22
  • Edit; if you want spy++ like behaviour of intercepting messages for an external window, you have to use setwindowshookex anyway – Alex K. May 14 '11 at 16:25
  • @Alex - I just want to be able to view properties of the Windows :) – Jeff May 14 '11 at 17:17
  • @Alex - I just realized that i order to access the Main Thread (to add my Tree Nodes), I have to use Synchronize in the EnumWindowsProc, however that proc cant belong to a class - in my case, TThread, so I can't perform Synchronize. What do you suggest? – Jeff May 14 '11 at 18:39
  • 1
    Pass your instance to as the `lParam` parameter to `EnumWindows` and then you have access to it inside your callback proc. But I really don't think you want to be calling Synchronize from an enumerator! Build the list of window handles first and then process them later. – David Heffernan May 14 '11 at 19:31
  • @David - I am not sure what you mean. You could post it as an answer, as this might well be what I need! :) – Jeff May 14 '11 at 20:10
  • 1
    @Jeff Why do you even need to do all this in a separate thread anyway? – David Heffernan May 15 '11 at 09:27
  • @David - Because I need my Treeview to be updated whenever a Handle is destroyed and created, and using a Timer to do EnumWindows(); would make my app freeze more than it would be thawed. – Jeff May 15 '11 at 12:52
  • @David - As you see, Windspector automatically updates it's Treeview whenever you start a program, and whenever you close it again. Now, in order for me to do that, and to avoid using hooks, I have to clear my VT, and then fill it again, using EnumWindows. However, doing that will make my app freeze, so I thought if I used Threads, it would not freeze it. – Jeff May 15 '11 at 14:39
1

This should really be a comment, but the code doesn't look OK in comments.

There are a few oddities in your code:

destructor TWindow.Destroy;
begin
  ClassName := '';
  Text := '';
  Children.Free;
end;

There's no need to empty strings in a destructor, but you need to call inherited Destroy.
Change it to:

destructor TWindow.Destroy;
begin
  Children.Free;
  inherited Destroy;
end;

TWindow inherits from TObject, so it doesn't matter in this code, but if you change the inheritance your code will break, so never omit inherited in a destructor.

In a constructor you need to call the inherited constructor:

Change this:

constructor TWindow.Create(Wnd: HWND);
begin
  Handle := Wnd;
  if Handle = 0 then Exit;
  ClassName := GetClassName(Handle);
  Text := GetHandleText(Handle);
  Node := Nil;
  Children := TObjectList<TWindow>.Create(True);
end;

To this:

constructor TWindow.Create(Wnd: HWND);
begin
  inherited Create;
  Handle := Wnd;
  if Handle = 0 then Exit;
  ClassName := GetClassName(Handle);
  Text := GetHandleText(Handle);
  Children := TObjectList<TWindow>.Create(True);
end;

Because TWindow inherits from TObject, it does not matter that you ommited inherited Create here, but if you decide to change the code and inherit from something else, than your constructor will break.

There's no need to set anything to 0, nil or '' in a constructor, all class member are automatically set to 0 before create is called.

Finally a note on style

YoUr Capitalization style Is Hard too Read and distracts from the issue.
Also your indentation is inconstant and unusual.
Indentation is important to follow the logic of a program. People who use it to scan program structure a lot get very distracted by unusual indentation.

Same with the keywords. In my code I know that reserved words always start with a lower case and names I gave to vars and routines always start with a capital.
This enables me to scan the structure of a program quickly.
Use of Capitals In Reserved words Breaks the FloW of scanning (like it does when reading the previous sentence) and is not recommended for that reason.

Especially for people who are allergic to sloppy indentation and Use Of Capitals In reserved words.

See: http://en.wikipedia.org/wiki/Indent_style

I'd recommend using the same style that is used in the VCL source. It's not my personal favourite, but it is a clean style used by a lot of people.

Steve McConnel his excellent book code complete denotes pages 399 to 452 to layout and style, making it the largest chapter of the book.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • I dont see what is wrong with my code - very readable for me. The indentation makes it easier for me to seperate variables from values. – Jeff May 14 '11 at 15:08
  • @Jeff: like you said: the keyword is `for me`, you are posting code for other people to look at. – Johan May 14 '11 at 15:19
  • @Johan - Agree, however I havent had these kinds of.. Lets call them "tips" about my code formatting before, and I always do this. :) – Jeff May 14 '11 at 15:41
  • @Bruce - Well, what exactly is wrong with the way I am doing it? :) – Jeff May 14 '11 at 16:05
  • @Jeff, this is so called `religious issue` and if it's just you looking at the code then that's fine, but there's a reason people join a religion on don't just start their own. It provides a standard way of doing things and adds structure to your world so you can communicate with people using a shared frame of reference. These things are very important and not trivial at all. – Johan May 14 '11 at 16:32
  • @Jeff, your indentations looks fine to me, been using more or less the same principle since programming was punching holecards. When publishing code however, use the built in code formatter in Delphi. This to avoid this kind of nitpicking, which is an religious issue as Johan says. – LU RD May 14 '11 at 17:11
  • @LU - how do I do that with code I already wrote, and wish to preserve? – Jeff May 14 '11 at 17:18
  • @Jeff, I'm afraid it has to be some manual handling to do this. Make a copy and do it for the copy is one way. – LU RD May 14 '11 at 19:47
  • @LU - I did not realize that Formatting was done with Ctrl+D :P – Jeff May 14 '11 at 20:08
  • @Jeff, The biggest single thing I noticed was the lack of indentation to identify scope in the class definition. This makes it difficult to read the intent of the code at a glance. One could argue that the other issues (case of "var" and "end", single space indentation, odd alignment of assignments, etc) are personal preference, but they make the code read oddly to me. Unless they serve a real purpose, that's a drawback. – Bruce McGee May 15 '11 at 02:34
  • @Bruce - Well, as I said, the reason I formatted it as I did, was because I think its easier to seperate Var's from Values. Hmm.. It's like in Word, when you align the text to fill all the way out to the margin, so there are like no short lines, long lines, etc. Dont know if I make sense here. :P – Jeff May 15 '11 at 12:56
  • @Jeff, Do you really find your original class declaration easier to read with the scope qualifiers indented the same as the class members? How about with a large class? – Bruce McGee May 15 '11 at 13:26
  • @Bruce - Honestly that part I didnt even notice. – Jeff May 15 '11 at 14:40