Next we simply get a handle to our edit control using GetDlgItem() which works just as well on regular windows as it does on dialogs, and the call SetWindowPos() to move and size it to fill the entire client area. You can of course change the values you pass into SetWindowPos() to do something like only fill half of the window's height, leaving the bottom free to place other controls.
Creating other controls at runtime
I'm not going to give examples of dynamically creating the other controls like LISTBOX, BUTTON, etc… because it's basically the same and it gets kinda boring after a while :) If you follow the links into MSDN above, or look in your local Win32 API reference you will be able to find all of the information needed to create any of the other standard controls.
We'll be doing more of this with the common controls in the next couple of sections so you'll get more practice eventually.
App Part 2: Using files and the common dialogs
The Common File Dialogs
The first step to opening or saving files is finding out the filename to use… of course you could always hard code the name of the file into your program, but honestly that doesn't make for very useful programs most of the time.
Since this is such a common task, there are predefined system dialogs that you can use to allow the user to select a file name. The most common open and save file dialogs are accessed through GetOpenFileName() and GetSaveFileName() respectively, both of which take an OPENFILENAME struct.
OPENFILENAME ofn;
char szFileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn); // SEE NOTE BELOW
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = "txt";
if (GetOpenFileName(&ofn)) {
// Do something usefull with the filename stored in szFileName
}
Note that we call ZeroMemory() on the struct in order to initialise it to 0. This is generally a wise practice, as some APIs are very picky about members that you don't use being set to NULL. This way you don't need to explicitely set each member that you don't use.
You can easily find out the meanings of the various members by looking them up in your documentation. The lpstrFilter value points to a double-NULL terminated string, and you can see from the example that there are several "\0" throughout it, including one at the end… the compiler will add the second one at the end as it always does with string constants (that's what you generally don't need to put them in yourself). The NULL s in this string break it up into filters, each one is two parts. The first filter has the description "Text Files (*.txt)" , the wildcard isn't required here I just put it in because I felt like it. The next part is the actual wildcard for the first filter, "*.txt" . We do the same thing with the second filter except that this is a generic filter for all files. You can add as many different filters as you'd like.
The lpstrFile points to the buffer we have allocated to store the name of the file, since filenames can't be larger than MAX_PATH this is the value that I've chosen for the buffer size.
The flags indicate that the dialog should only allow the user to enter filenames that already exist (since we want to open them, not create them) and to hide the option to open the file in readonly mode, which we aren't going to support. Finally we provide a default extention, so if the user types in "foo" and the file is not found, it will try to open "foo.txt" before finally giving up.
To select a file for saving instead of opening, the code is nearly the same, except for calling GetSaveFileName() we need only change the flags member to options more suitable for saving.
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
In this case we no longer want to require the file exist, but we do want the directory to exist since we aren't going to try and create it first. We'll also prompt the user if they select an existing file to make sure they want to overwrite it.
NOTE: MSDN states the following for the lStructSize member:
lStructSize
Specifies the length, in bytes, of the structure.
Windows NT 4.0: In an application that is compiled with WINVER and _WIN32_WINNT>= 0x0500, use OPENFILENAME_SIZE_VERSION_400 for this member.
Windows 2000/XP: use sizeof(OPENFILENAME) for this parameter.
Basically what this means is that as of Windows 2000 they added some members to this struct, and so it's size changed. If the code above doesn't work for you it's possibly because the size that your compiler used and the size that your operating system (ie. Windows 98, Windows NT4) expected were different and so the call failed. If this happens, try using OPENFILENAME_SIZE_VERSION_400 instead of sizeof(ofn). Thanks to people that pointed this out to me.
Reading and Writing Files
In windows you have a few options as to how you want to access files. You can use the old io.h open()/read()/write(), you can use stdio.h fopen()/fread()/fwrite(), and if you are in C++ use can use iostreams.
However in windows all of these method ultimately call the Win32 API functions, which are what I will use here. If you are already comfortable using file IO with another method it should be fairly easy to pick up, or if you want simply use your method of choice to access files.
To open files, you can use OpenFile() or CreateFile(). MS recommends using only CreateFile() as OpenFile() is now "obsolete". CreateFile() is a much more versatile function and provides a great deal of control over the way you open files.
Reading
Say for example you have allowed the user to select a file using GetOpenFileName()…
BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName) {
HANDLE hFile;
BOOL bSuccess = FALSE;
hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD dwFileSize;
dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize != 0xFFFFFFFF) {
LPSTR pszFileText;