What can we do with the empty window that Wedit generates?
Let's do a more difficult problem: We want to find out all the windows that are opened at a given time in the system. We will display those windows in a tree control, since the child windows give naturally a tree structure. When the user clicks in a window label, the program should display some information about the window in the status bar.
We generate a skeleton with Wedit, as described above. We 252j98c create a new project, and generate a simple, single window application.
OK. Now we come back to the task at hand. The first thing to do is to create the tree control window. A good place to do these kinds of window creations is to use the opportunity the system gives to us, when it sends the WM_CREATE message to the main window. We go to the procedure for the main window, called MainWndProc, and we add the WM_CREATE case to the switch of messages:
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
This function receives a handle to the parent window, and the numeric ID that the tree control should have. We call the window creation procedure with a series of parameters that are well described in the documentation. We use the value of the hInst global as the instance, since the code generated by Wedit conveniently leaves that variable as a program global for us to use.
Note that we give the initial dimensions of the control as zero width and zero height. This is not so catastrophic as it seems, since we are relying in the fact that after the creation message, the main window procedure will receive a WM_SIZE message, and we will handle the sizing of the tree control there. This has the advantage that it will work when the user resizes the main window too.
We add code to the WM_SIZE message that Wedit already had there to handle the resizing of the status bar at the bottom of the window.
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
Simple isn't it? We just call "BuildTree" and we are done.
We start with the desktop window, we add it to the tree, and then we call a procedure that will enumerate all child windows of a given window. We have two directions to follow: the child windows of a given window, and the sibling windows of a given window. This is true for the desktop window too.
Let's look at the code of "BuildTree":
int BuildTree(HWND parent)
We start at the start, and we ask windows to give us the window handle of the desktop window. We will need the tree window handle too, so we use "GetDlgItem" with the parent window of the tree control, and it's ID. This works, even if the parent window is a normal window, and not a dialog window.
We go on by filling our TV_INSERTSTRUCT with the right values. This is a common interface for many window functions. Instead of passing n parameters, we just fill a structure and pass a pointer to it to the system. Of course, it is always a good idea to clean the memory space with zeroes before using it, so we zero it with the "memset" function. Then we fill the fields we need. We say that this item is the root item, that the insertion should happen after the last item, that the item will contain the text "Desktop", and that we want to reserve place for a pointer in the item itself (TVIF_PARAM). Having done that, we use the macro for inserting an item into the tree.
The root item created, we should then scan the siblings and child windows of the desktop. Since the desktop is the root of all windows it has no siblings, so we start at its first child. The GetWindow function, gives us a handle to it.
We call our "Scan" function with the handle of the tree control, the handle to the just inserted item, and the window handle of the first child that we just obtained.
The "Scan" function looks like this:
void Scan(HWND hTree,HTREEITEM hTreeParent,HWND Start)
We loop through all sibling windows, calling ourselves recursively with the child windows.
In our loop we do:
We get the text of the window, to show it in our tree. We do this by sending the WM_GETTEXT message to the window.
We get the class name of the window.
We format the text (enclosed in quotes) and the class name in a buffer.
We start filling the TV_INSERTSTRUCT. These steps are very similar to what we did for the desktop window.
After inserting our node in the tree, we ask if this window has child windows. If it has, we call Scan recursively with the new node and the new child window.
Then we ask if this window has sibling windows. If it has, the main loop will go on since GetWindow will give us a non-null window handle. If it hasn't we are done and we exit.
Let's look at our "BuildTree" function again and ask us:
How could this fail?
We notice immediately several things.
We always add items to the tree at the end, but we never cleanup the tree control. This means that after a few times the user has clicked in the menu, we will have several times all the windows of the system in our tree. All nodes should be deleted when we start.
The tree control will redraw itself several times when we add items. This is unnecessary and produces a disturbing blinking in the display. We should hold the window without any redrawing until all changes are done and then redraw once at the end.
We modify the "BuildTree" procedure as follows:
int BuildTree(HWND parent)
We enclose all our drawing to the control within two calls to the SendMessage function, that tell essentially the tree control not to redraw anything. The third parameter (i.e. the wParam of the message) is a Boolean flag that indicates whether redrawing should be on or off. This solves the second problem.
After setting the redraw flag to off, we send a command to the control to erase all items it may have. This solves our first problem.
Here is the output of the program after we press the "Scan" menu item.
A lot of code is necessary to make this work, but thankfully it is not our code but window's. The window resizes, redraws, etc, without any code from us.
Our task consisted in drawing the tree, but also of displaying some useful information about a window in the status bar when the user clicks on a tree item.
First, we have to figure out how we can get notified when the user clicks in an item.
The tree control (as many other controls) sends notifications through its WM_NOTIFY message. We add a snippet of code to our MainWndProc procedure:
case WM_CREATE:
hwndTree = CreateTree(hwnd,IDTREEWINDOW);
break;
case WM_NOTIFY:
return HandleWmNotify(hwnd,wParam,lParam);
The function HandleWmNotify looks as follows:
LRESULT HandleWmNotify(HWND hwnd, WPARAM wParam, LPARAM lParam)
return DefWindowProc(hwnd,WM_NOTIFY,wParam,lParam);
We just handle the NM_CLICK special case of all the possible notifications that this very complex control can send. We use the NMHDR part of the message information that is passed to us with this message in the lParam message parameter.
Our purpose here is to first know if the user has clicked in an item, or somewhere in the background of the tree control. We should only answer when there is actually an item under the coordinates where the user has clicked. The algorithm then, is like this:
Get the mouse position. Since windows has just sent a click message, the speed of current machines is largely enough to be sure that the mouse hasn't moved at all between the time that windows sent the message and the time we process it. Besides, when the user is clicking it is surely not moving the mouse at super-sonic speeds.
Map the coordinates we received into the coordinates of the tree window.
Ask the tree control if there is an item under this coordinates.
If there is none we stop
Now, we have a tree item. We need to know which window is associated with this item, so that we can query the window for more information. Since we have left in each item the window handle it is displaying, we retrieve this information. We hide the details of how we do this in a subroutine "GetTreeItemInfo", that returns us the window handle.
Using that window handle we call another function that will display the info in the status bar.
We pass all messages to the default window procedure. this is a non-intrusive approach. The tree control could use our notifications for something. We just need to do an action when this event happens, but we want to disturb as little as possible the whole environment.
To retrieve our window handle from a tree item, we do the following:
static HWND GetTreeItemInfo(HWND hwndTree,HTREEITEM hti)
As you can see, it is just a matter of filling a structure and querying the control for the item. we are interested only in the PARAM part of the item.
More complicated is the procedure for querying the window for information. Here is a simple approach:
void SetTextInStatusBar(HWND hParent,HWND hwnd)
The algorithm here is as follows:
Query the window rectangle (in screen coordinates).
We get the process ID associated with this window
We call a subroutine for putting the name of the process executable file given its process ID.
We format everything into a buffer
We call UpdateStatusBar, generated by wedit, with this character string we have built.
The procedure for finding the executable name beginning with a process ID is quite advanced, and here we just give it like that.
static char * PrintProcessNameAndID( DWORD processID )
CloseHandle( hProcess );
}
return szProcessName;
Note that you should add the library PSAPI.LIB to the linker command line. You should do this in the linker tab in the configuration of wedit:
And now we are done. Each time you click in an item window, the program will display the associated information in the status bar:
Summary:
There are many things that could be improved in this small program. For instance, it could be useful to have a right mouse menu, or a dialog box with much more information etc. This is just a blueprint to get you started however.
The whole code for this program is in the appendix 4.
|