C++/Win32: More GUI
March 2, 2008
Ok, now let’s work through the problem of making our output more meaningful. In particular, we would like to be able to show a list of results, rather than having every result overwrite the last one.
One solution that might spring to mind would be to use our GrabTextFromEdit function to get the text in the edit control, use strcat to add the new text to the end, then use SetDlgItemText to push all the text back to the control. That doesn’t work as easily as it sounds, and you’ll have to know a bit about how C++ stores memory or you could end up losing control of your buffers pretty quick. Plus, it’s pretty slow. There is a better way.
Remember I said earlier that windows (and window elements, since they are themselves windows) don’t just receive messages from the system. You can also send them messages when you need the control to change. Every “window” has its own set of messages that it receives and sends, so you’ll have to consult the VC++ docs for the particular element you’re dealing with. In our case, we’re dealing with an edit control, and edit controls can receive EM_SETSEL, EM_REPLACESEL and EM_SCROLLCARET (among many others). I mention these, because these are the messages we’re going to use to update the text in our edit box.
Sending messages to a window is about as simple as you’d like it to be:
SendMessage(hWnd, EM_SETSEL, -1, -1);
That particular message tells the control to move its cursor to the end of its text. We can send it new text by sending it three messages in a row:
int nTxtLen = GetWindowTextLength(hWnd); // Get length of existing text SendMessage(hWnd, EM_SETSEL, nTxtLen, nTxtLen); // move caret to end SendMessage(hWnd, EM_REPLACESEL, 0, NewText); // append text SendMessage(hWnd, EM_SCROLLCARET, 0, 0); // scroll to caret
The last two parameters of the SendMessage calls are the WPARAM and LPARAM values that accompany messages.
NewText, obviously, is a pointer to the string we want to give it, casted to the LPARAM type. So if we build up a string in a char * buffer, we can tell the control to move its cursor to the end, put this new text there, and then move back to the end again. Notice, though, that SendMessage expects the handle of our window. Of course we can send that handle, but since we’ve gotten comfortable dealing with resource IDs, we might as well keep using those. So we need to get the handle to the window, by calling GetDlgItem():
HWND myHWnd = GetDlgItem(m_hWnd,IDT_OUTPUT);
Yes, we do have to pass in a HWND still, but this time we have to pass in the HWND of the parent window, along with a resource ID. Looks pretty familiar, doesn’t it? We can then pass myHWnd into SendMessage and we’re good.
There is another function that kills two birds with one stone: SendDlgItemMessage(), which figures out the HWND and sends the message all
in one function call. Looks like this:
SendDlgItemMessage(m_hWnd, IDT_OUTPUT, WM_SETTEXT, 0, (LPARAM)"HELLO");
In this case, we passed in the parent window’s HWND, the resource ID of the edit box, the WM_SETTEXT message, which replaces ALL the text in the window, and we gave it the constant char * “HELLO” converted to LPARAM.
Of course, this is for edit controls, not static controls like IDT_OUTPUT. So for this version, I’ve changed IDT_OUTPUTs window class to EDIT. And if we want to be able to format it, we better make it a multi-line edit control, instead of the default single line (because if it’s single line, our newline (\n) characters will be meaningless, and we’ll just get one ugly string of results). To do this, OR the dwStyles we pass to CreateWindow with the ES_MULTILINE style type. And, since Windows is just too cool for school, and couldn’t possibly be bothered to follow standards, you will need to replace the \n characters that you think will give you the line break you’re looking for with \r\n, which is what Windows expects.
Now look at the updated goScan function. Compile and run. You’ll see we’re closer to something useful. Well, so long as your scan range doesn’t exceed about 22 ports. Because after 22, the text just goes into nothingness — our edit box isn’t big enough to hold more results than that. Seems that we’ll need a scrollbar.
Adding a scrollbar to an edit control is so ridiculously easy that you’ll wonder why we didn’t just do it from the beginning. Go back to the call to CreateWindow that created the IDT_OUTPUT edit box (you should have just recently gone there to add the ES_MULTILINE flag). Add another flag: WS_VSCROLL. Voila, now you have a vertical scrollbar. It shouldn’t take too much brainpower to figure out that you can create a horizontal scrollbar with WS_HSCROLL. And in fact, you can have both. You can even turn them off and on, by sending a message to the control.
Let’s turn the scrollbar on only when we need to. To do that, you will add the WS_VSCROLL style only when it’s needed. So take it out of the call to CreateWindow for now. Once it has been removed, the styles set at window creation are now:
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE
What we’ll need to do is call the function SetWindowLong, and pass it the above, plus | WS_SCROLL. SetWindowLong takes three parameters: the HWND of the window, an identifier telling it which long we want to set, and the value we want to set it to. We know how to get the HWND we need already, and we know the long value we want to set. All we don’t know is the identifier. Easy enough, it’s GWL_STYLE. By now you should be getting comfortable with how VC++ names all these constants. So here’s the call when we want to turn the scrollbar on:
SetWindowLong(GetDlgItem(m_hWnd,IDT_OUTPUT),
GWL_STYLE,
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | WS_VSCROLL);
SetWindowPos(GetDlgItem(m_hWnd,IDT_OUTPUT),
NULL,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
That’s all. The only mysterious thing is the call to SetWindowPos, but all that does is alert Windows that the window has changed. Not really, but effectively that’s what it does. For now, I’ve stuck the style change at the beginning of goScan, but I’ll leave it for later to put it in a more appropriate place. The best thing to do is to add the scrollbar when you need it, so all that’s left is to figure out when it’s needed, and execute those two functions above at that time.
But I’m going to do things the easy way, which is to call a function called ShowScrollBar like so:
ShowScrollBar(GetDlgItem(m_hWnd,IDT_OUTPUT),SB_VERT,TRUE);
In order for this to work, you’ll have to go back to the call to CreateWindow that created the edit control, and re-add the WS_VSCROLL style. Then, immediately after the call to CreateWindow, call ShowScrollBar to turn the scrollbar off — just use the above, but with FALSE for the last parameter instead of TRUE. If you are dealing with horizontal scrollbars, use the questionably named SB_HORZ flag instead of SB_VERT.
To make things easier, in this example we’ll give our text area the ES_AUTOVSCROLL style (telling Windows to handle scrolling automatically) and the WS_VSCROLL style (telling Windows to give the text area a vertical scrollbar). We can always go back and tweak the code to hide the scrollbar when it’s not necessary to show it. Maybe we’ll get to that in a later lesson.
Now that the Port Scanner is working, and it displays all of its data in a scrolling edit box, let’s take a break from this a do something
fun: let’s skin it.
Next: Skinning.
Source Code:
win32tut_part6.zip [745kb zipped]
Additional Information:
· theForger’s Win32 API Tutorial - Standard Controls
Further Reading:
· Nitty Gritty Windows Programming with C++ by Henning Hansen.
· Thinking in C++ by Bruce Eckel.




Comments
Got something to say?