I have been playing around with the windows API recently with the intention of using it as the GUI for some tools that I am building for use with DarkGDK.
The thought struck me, that wouldnt it be cool, if we could put the DarkGDK window, in a child window of it's main window, kind of like a preview window or the "3d view" in application like 3ds max.
So I started looking around at tutorials and code examples of using the Multiple Document Interface and modal/modeless dialogs.
While I was able to find a heap of information about MDI's on their own, and creating the client and children etc, I couldnt find anything about how to use GDK in such a way.. so I had to experiment with various commands and functions until I figured it out.
Anyway, I went through both Sephnroth's and theForger's tutorials for the WinAPI, and most of this code is built ontop of theirs, and its also not optimized at all but thats not important for the purpose of an example.
The code contains 2 modal dialogs, 1 modeless dialog, an MDI client window and MDI child windows that are generated during run-time, and also windows forms controls that are generated at runtime.
(The difference between modal and modeless dialogs is that a modal dialog takes control away from the parent window while its open, much like an "About Box" window in something like Visual Studio, and all messages are handled by the dialog, which has its own built in handler, whereas a "Modeless" dialog box will not take away control from the main window, and also has no built in message handler.)
Usage of the EXE :
Double-click to open..
pressing <space> will open the "Test Dialog" - you can enter data and check the box in that dialog, it will remember what you have entered when it is opened and closed(although it wont remeber more than 10 digits in the lower box) and if its checkbox was ticked or not.
The menu across the top of the app is self-explanatory, File, Edit, Window, Dialog, Misc.
File : open, close, save and exit the app, all working
Edit : Copy, Paste, Cut - all working inside the edit windows
Window : Tile, Cascade - all working
Dialog - Hide, Show - all working
Misc - Ticked, About, Test Dialog - all working
Escape will exit the app if the main window has focus.
The attachment has everything you need to compile the code example, all resources are included as are the .RC and resource.h files.
To compile it, start a new "DarkGDK - Game" project inside Visual Studio. Copy the contents of the ZIP file to the project's folder, overwriting main.cpp.
add "main.h" to the project with by right clicking on the project name and selecting "Add Existing Item".
The resources should be found automatically as they are included in the headers.
Here is a copy/paste of the Main.cpp code - be warned, its long and procedural
#include <windows.h> // included so we can use the windows API
#include <commctrl.h> // include to access windows common controls
#include "DarkGDK.h" // gdk main header
#include "resource.h" // vs produced resource header file
#include "globstruct.h" // gdk globals to get windows handle
#include "stdio.h" // required for some of the string/char operations we are doing in the app with txt file open/close
#include "main.h" // just a bunch of defines for things like display size, depth, and keyboard scancodes
// constants for the names of our window classes.....
// NOTE : A "window class" has ABSOLUTELY NOTHING AT ALL to do with C++ Classes or OOP
const char g_szClassName[] = "myWindowClass"; // unused i think in this iteration of the app
const char g_szGDKClassName[] = "myMDIGDKWindowClass"; // MDI child that will contain the GDK window - only ever 1 made
const char g_szChildClassName[] = "myMDIChildWindowClass"; // MDI child windows that will be text editors
// defines for our run-time generated windows/controlls
// these are placed here since normally, we wouldnt have any resources to load from file, but they cuold be placed inside
// the resource.h file if you are using resource files.
#define IDC_MAIN_MDI 101
#define IDC_MAIN_TOOL 102
#define IDC_MAIN_STATUS 103
#define IDC_CHILD_EDIT 101
#define ID_MDI_FIRSTCHILD 50000
// global variable to hold a handle to our MDI client, which will be the "surface" in the original GDK window when
// the GDK "surface" is moved to the MDI child
HWND g_hMDIClient = NULL;
HWND g_hMainWindow = NULL;
// Function prototypes - Callbacks, Wndprocs and normal functions
// main window message handler
LRESULT APIENTRY MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// callback/proc/class reg for editor MDI's
HWND CreateNewMDIChild(HWND hMDIClient);
BOOL SetUpMDIChildWindowClass(HINSTANCE hInstance);
LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
// callback/proc/class reg for GDK MDI window
HWND CreateNewGDKMDIChild(HWND hMDIClient);
BOOL SetUpGDKMDIChildWindowClass(HINSTANCE hInstance);
LRESULT CALLBACK GDKMDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
// callbacks for 2 modal dialogs - AboutProc and TestProc open "modal" dialogs
// a modal dialog will take control away from the main window while it is open, and handles all
// of its own messages, control returns to the parent window when the modal dialog is closed
BOOL CALLBACK AboutProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK TestProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
// callback for modeless dialog - this is a dialog that has its messages handles thru the main window's proc
// it does not take away control from the parent when it is created....
BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
// paintscreen is called from various places inside the app, usually at WM_PAINT or WM_SIZE commands, all it does
// is handle all of the programs DarkGDK 2D drawing and text, as they are not updated in the same way as the
// 3D surface, they need to be told to re-draw themselves.
void PaintScreen();
// 4 function prototypes whos job is to open and read and save text files... pretty simple stuff here,
// straight from theForger's Win32API tutorial
void DoFileSave(HWND hwnd);
void DoFileOpen(HWND hwnd);
BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName);
BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName);
// this is OS dependant how we declare the main window's proc
#ifdef STRICT
WNDPROC lpPrevWndProc;
#else
FARPROC lpPrevWndProc;
#endif
// General Global Variables - Most of these are here from me experimenting with the code
// in various ways to make it work. Most of them dont need to be in the global scope, and the code
// could be optimized in many many ways.
MSG Msg;
HMENU MyMenu;
HMENU MyTools;
HBITMAP MyBitmap = NULL;
HWND g_hToolbar = NULL;
bool b_Exit = false;
bool MenuTicked = true;
bool DialogChecked = false;
char DialogText[256];
int DialogNumber = 0;
char* Tempa;
HWND hTool;
TBBUTTON tbb[3];
TBADDBITMAP tbab;
HWND hStatus;
int statwidths[] = {100, -1};
CLIENTCREATESTRUCT ccs;
// main program entry for DarkGDK
void DarkGDK ( void )
{
dbSetWindowTitle("Using DarkGDK with the Win32API MDI, Modal/Modeless Dialogs & Win32API Controls -- by Mista Wilson 2009 --");
// this MUST be called before anything else that may affect our common controls, if we do not call
// this function, we will get all sorts of errors trying to use our run-time controls.
InitCommonControls();
// these 4 lines are responsible for registering our 2 MDI Child classes..
// the first is for the normal editor windows, the second is the GDK Child
// if the registration fails, the app will exit..
if(!SetUpMDIChildWindowClass(g_pGlob->hInstance))
{
MessageBox(NULL, "Fatal Error! Could not Register MDI Class, Exiting Application!","*",MB_OK);
return;
}
if(!SetUpGDKMDIChildWindowClass(g_pGlob->hInstance)) // register GDK MDI window class
{
MessageBox(NULL, "Fatal Error! Could not Register MDI Class, Exiting Application!","*",MB_OK);
return;
}
// Initialize main window's menu, messagebox if it fails
MyMenu = LoadMenu(NULL, MAKEINTRESOURCE(IDR_MENU1));
if(!MyMenu) {MessageBox(NULL, "Fatal Error! Could not load the menu resource!","*",MB_OK); return;}
///////////////////////////////////////////////////////////
// Create MDI Client
// Find window menu where children will be listed
ccs.hWindowMenu = GetSubMenu(MyMenu, 2);
ccs.idFirstChild = ID_MDI_FIRSTCHILD;
// create the MDI client area inside our main window and set its properties
g_hMDIClient = CreateWindowEx(WS_EX_CLIENTEDGE, "mdiclient", NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
g_pGlob->hWnd, (HMENU)IDC_MAIN_MDI, GetModuleHandle(NULL), (LPVOID)&ccs);
// msg if it fails
if(g_hMDIClient == NULL)
MessageBox(g_pGlob->hWnd, "Could not create MDI client.", "Error", MB_OK | MB_ICONERROR);
/////////////////////////////////////////////////////////////
// Create Toolbar
// create our toolbar item and put it in its correct place by sending the appropriate messages
hTool = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
g_pGlob->hWnd, (HMENU)IDC_MAIN_TOOL, GetModuleHandle(NULL), NULL);
// msg if it fails
if(hTool == NULL)
MessageBox(g_pGlob->hWnd, "Could not create tool bar.", "Error", MB_OK | MB_ICONERROR);
// Send the TB_BUTTONSTRUCTSIZE message, which is required for
// backward compatibility.
SendMessage(hTool, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
//send toolbar messages to put the buttons in
tbab.hInst = HINST_COMMCTRL;
tbab.nID = IDB_STD_SMALL_COLOR;
SendMessage(hTool, TB_ADDBITMAP, 0, (LPARAM)&tbab);
ZeroMemory(tbb, sizeof(tbb));
// add the NEW button to the toolbar
tbb[0].iBitmap = STD_FILENEW;
tbb[0].fsState = TBSTATE_ENABLED;
tbb[0].fsStyle = TBSTYLE_BUTTON;
tbb[0].idCommand = ID_FILE_NEWGAME;
// add the OPEN button to the toolbar
tbb[1].iBitmap = STD_FILEOPEN;
tbb[1].fsState = TBSTATE_ENABLED;
tbb[1].fsStyle = TBSTYLE_BUTTON;
tbb[1].idCommand = ID_FILE_OPENGAME;
// add the SAVE button to the toolbar
tbb[2].iBitmap = STD_FILESAVE;
tbb[2].fsState = TBSTATE_ENABLED;
tbb[2].fsStyle = TBSTYLE_BUTTON;
tbb[2].idCommand = ID_FILE_SAVEAS;
SendMessage(hTool, TB_ADDBUTTONS, sizeof(tbb)/sizeof(TBBUTTON), (LPARAM)&tbb);
/////////////////////////////////////////////////////////////////////////////////
// Create Status bar
// create our status bar and assign its properties
hStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0,
g_pGlob->hWnd, (HMENU)IDC_MAIN_STATUS, GetModuleHandle(NULL), NULL);
// send the messages to get our status bar started...
SendMessage(hStatus, SB_SETPARTS, sizeof(statwidths)/sizeof(int), (LPARAM)statwidths);
SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)"Hi there :)");
/////////////////////////////////////////////////////////////////////////////////
// Place DarkGDK inside a MDI window....
HWND hChild = CreateNewGDKMDIChild(g_hMDIClient);
// This is the MAGIC function - void dbOverrideHWND(HWND hwnd); it allows us to pass the
// handle to another window, in this case an MDI child window, but even a dialog window
// will work, modal or modeless doesnt matter.. the trick is to make sure you have ALL
// of your window classes registered and to create the window and call this function BEFORE
// you call ANY OTHER GDK commands.. If you sync or set the display or call anything GDK related
// that draws the window(setting vars like the window title is ok, just not syncing or display modes etc)
// then the window will be created and placed in the main window by default and thats where it will be stuck
dbOverrideHWND(hChild);
//SetWindowPos(hChild, NULL, GetSystemMetrics(SM_CXSCREEN)/2-(DISPLAYWIDTH/2), GetSystemMetrics(SM_CYSCREEN)/2-(DISPLAYHEIGHT/2), DISPLAYWIDTH, DISPLAYHEIGHT, SWP_NOZORDER);
// this sens a message to the MDI client to tile the child windows on it, just makes the GDK one look tidier lol
SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
// This is where our MODELESS dialog is created, it is hidden by default at the start,
// but you can show it with the DIALOG->Show/Hide commands from the main menu
g_hToolbar = CreateDialog(g_pGlob->hInstance, MAKEINTRESOURCE(IDD_TOOLBAR),
g_pGlob->hWnd, ToolDlgProc);
if(g_hToolbar != NULL)
{
ShowWindow(g_hToolbar, SW_HIDE);
}
else
{
MessageBox(g_pGlob->hWnd, "CreateDialog returned NULL", "Warning!", MB_OK | MB_ICONINFORMATION);
}
// Now that we have set out dbOverrideHWND, we can initialize the GDK display, by calling the SyncOn() command
// and setting its size and position with the relevant commands....
// NOTE : Is is very important that these commands are NOT called before the overrideHWND command is called, if they are
// then the GDK window will not be created where it should.
dbSyncOn();
dbSyncRate(0);
// set our window resolution and postion it in the center of the screen.
dbSetDisplayMode(DISPLAYWIDTH, DISPLAYHEIGHT, DISPLAYDEPTH);
dbSetWindowPosition(GetSystemMetrics(SM_CXSCREEN)/2-(DISPLAYWIDTH/2),
GetSystemMetrics(SM_CYSCREEN)/2-(DISPLAYHEIGHT/2));
dbSetWindowTitle("This is the MDI Child Window containing DarkGDK");
dbRandomize(dbTimer());
// this line is important because it tells our main window that along with whatever normal
// message handling it does, it will also be using the wndproc that we create MainWndProc
// to have some of its messages processed and acted upon
lpPrevWndProc = (WNDPROC)SetWindowLong(g_pGlob->hWnd, GWL_WNDPROC, (LONG)MainWndProc);
// this line tells our window to make the menu from our resource IDR_MENU1
SetMenu(g_pGlob->hWnd, MyMenu);
// make a cube that will spin on the spot to prove that GDK is working...
dbMakeObjectCube(1, 1.0);
// main loop
while( LoopGDK() )
{
// here we have a critical little conditional....
// what we are doing here is checking the message pump queue and telling it to send its messages off where ever
// they would be going normally, unless we want to do something to them... basically, we need this to remove messages
// that we dont want GDK's internal message pump to work on, for our modal dialog and MDI client mainly
// We are using PeekMessage instead of GetMessage because getmessage will slow our app right down as it waits for
// message to be processed before returning, peekmessage allows us to process our messages and return control to the
// program right after, while the message gets processed.
if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) > 0 )
{
if((!IsDialogMessage(g_hToolbar, &Msg) && !TranslateMDISysAccel(g_hMDIClient, &Msg)))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
// this function is where the 2D text and drawing takes place, this is NOT CRITICAL, it just stop the
// 2D stuff from getting overwritten when we open menus or dialogs... 3D takes care of itself and will overwrite
// everything so its no problem.
PaintScreen();
// rotate our cube around and around and around and.... you get the picture ;)
dbRotateObject(1,
dbWrapValue(dbObjectAngleX(1)+1.0f),
dbWrapValue(dbObjectAngleY(1)+1.0f),
dbWrapValue(dbObjectAngleZ(1)+1.0f));
// open our TestDialog if the space bar is pressed, this dialog will allow you to enter both
// numerical data and string data, aswell as checking and unchecking a box, and it will remember
// its state inbetween being opened and closed, it will remember the data that was entered and will
// re-display it upon re-openeing after being closed...
if (dbSpaceKey())
DialogBox(NULL, MAKEINTRESOURCE(IDD_TESTDIALOG), g_pGlob->hWnd, (DLGPROC)TestProc);
// this line adds in the variable b_Exit to our exit clause, it is set when the user clicks
// the "Exit" menu item... this way our app will exit when its told to from the menu.
if(dbEscapeKey() | (int)b_Exit)
{ return; }
// display the FPS - done here to demonstrate the paintscreen() function. the actual FPS data will be wiped when
// a menu or dialog is open, whereas the actual text "FPS" will not...
dbText(55, 100, dbStr(dbScreenFPS()));
// draw everything and update the screen
dbSync ( );
}
return;
}
// rendering function for when the menu is open, caught both in main DarkGDK Loops and in WM_PAIMT loop.
void PaintScreen()
{
dbText(0, 100, "FPS: ");
if (MenuTicked)
{
dbText(10, 10, "Woo! I'm ticked!");
}
};
///////////////////////////////////////////////////////////////////////////////////////////
// MAIN WINDOW'S MESSAGE HANDLING FUNCTION - USES APIENTRY SO WE CAN TELL IT TO RETURN
// CONTROL TO THE HANDLER BEFORE IT WHEN ITS DONE WITH THE MESSAGES
LRESULT APIENTRY MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
// resize the MDI client area and the controls inside it(scroll boxes etc) when the window is resized
case WM_SIZE:
{
HWND hTool;
RECT rcTool;
int iToolHeight;
HWND hStatus;
RECT rcStatus;
int iStatusHeight;
HWND hMDI;
int iMDIHeight;
RECT rcClient;
// Size toolbar and get height
hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
SendMessage(hTool, TB_AUTOSIZE, 0, 0);
GetWindowRect(hTool, &rcTool);
iToolHeight = rcTool.bottom - rcTool.top;
// Size status bar and get height
hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
SendMessage(hStatus, WM_SIZE, 0, 0);
GetWindowRect(hStatus, &rcStatus);
iStatusHeight = rcStatus.bottom - rcStatus.top;
// Calculate remaining height and size edit
GetClientRect(hwnd, &rcClient);
iMDIHeight = rcClient.bottom - iToolHeight - iStatusHeight;
hMDI = GetDlgItem(hwnd, IDC_MAIN_MDI);
SetWindowPos(hMDI, NULL, 0, iToolHeight, rcClient.right, iMDIHeight, SWP_NOZORDER);
}
break;
case WM_DESTROY:
DestroyWindow(g_hToolbar);
PostQuitMessage(0);
break;
// we call PaintScreen() from here so that when this window get a WM_PAINT call, it will draw our 2D stuff
case WM_PAINT:
SetWindowPos(g_pGlob->hWnd, NULL, GetSystemMetrics(SM_CXSCREEN)/2-(DISPLAYWIDTH/2), GetSystemMetrics(SM_CYSCREEN)/2-(DISPLAYHEIGHT/2), DISPLAYWIDTH, DISPLAYHEIGHT, SWP_NOZORDER);
PaintScreen();
SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
//dbFastSync();
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
// sets a variable when the menu item EXIT is clicked
case ID_FILE_EXIT:
b_Exit = true;
break;
// the next 2 case statements are responsible for creating new MDI windows when buttons are clicked
case ID_FILE_NEWGAME:
CreateNewMDIChild(g_hMDIClient);
SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
break;
case ID_FILE_OPENGAME:
{
HWND hChild = CreateNewMDIChild(g_hMDIClient);
SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
if(hChild)
{
DoFileOpen(hChild);
}
}
break;
case ID_FILE_CLOSEGAME:
{
HWND hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE,0,0);
if(hChild)
{
SendMessage(hChild, WM_CLOSE, 0, 0);
}
}
break;
// these 2 case statements are responsible for when the "tile" and "cascade" menu items are clicked.
case ID_WINDOW_TILE:
SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
break;
case ID_WINDOW_CASCADEALL:
SendMessage(g_hMDIClient, WM_MDICASCADE, 0, 0);
break;
// shows and hides our modeless dialog
case ID_DIALOG_SHOW:
ShowWindow(g_hToolbar, SW_SHOW);
break;
case ID_DIALOG_HIDE:
ShowWindow(g_hToolbar, SW_HIDE);
break;
// this case statement is the one that makes our Misc->Ticked item become checked and unchecked
case ID_MISC_TICKED:
if (MenuTicked)
{CheckMenuItem(MyMenu, ID_MISC_TICKED, MF_UNCHECKED);
MenuTicked = false;}
else
{CheckMenuItem(MyMenu, ID_MISC_TICKED, MF_CHECKED);
MenuTicked = true;}
break;
// these 2 messages open up their respective dialogs
case ID_MISC_ABOUT:
DialogBox(NULL, MAKEINTRESOURCE(IDD_ABOUT), hwnd, (DLGPROC)AboutProc);
break;
case ID_MISC_TESTDIALOG:
DialogBox(NULL, MAKEINTRESOURCE(IDD_TESTDIALOG), hwnd, (DLGPROC)TestProc);
break;
default:
{
if(LOWORD(wParam) >= ID_MDI_FIRSTCHILD)
{
//DefFrameProc(hwnd, g_hMDIClient, WM_COMMAND, wParam, lParam);
CallWindowProc(lpPrevWndProc, g_hMDIClient, WM_COMMAND, wParam, lParam);
}
else
{
HWND hChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE,0,0);
if(hChild)
{
SendMessage(hChild, WM_COMMAND, wParam, lParam);
}
}
}
break;
}
return 0;
default:
break;
}
return CallWindowProc(lpPrevWndProc, hwnd, uMsg, wParam, lParam);
}
//////////////////////////////////////////////////////////////////////////////////////
// CALLBACK FOR THE ABOUT DIALOG WINDOW - THIS IS A MODAL WINDOW SO WILL
// TAKE CONTROL FROM THE PARENT TILL IT IS CLOSED, THIS IS ITS MESSAGEHANDLER
// IF ITS NOT HERE, OR IN THE WINAPI IT AINT PROCESSED
BOOL CALLBACK AboutProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
// load the bitmap for the about dialog from the resource IDB_BITMAP2 - msgbox if fail
MyBitmap = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP2));
if(MyBitmap == NULL)
{
MessageBox(hwndDlg, "Could not load IDB_BITMAP1!", "Error", MB_OK | MB_ICONEXCLAMATION);
}
break;
case WM_PAINT:
// this case is responsible for drawing the bitmap to the dialog,
// it uses the GDI (Graphical Drawing Interface) to do this...
// the GDI is a windows API for working with graphics - this is pretty much
// straight from the Sephnroth tutorial
{
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwndDlg, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, MyBitmap);
GetObject(MyBitmap, sizeof(bm), (LPSTR)&bm);
BitBlt(hdc, 250, 15, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(hwndDlg, &ps);
}
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
DeleteObject(MyBitmap);
EndDialog(hwndDlg, wParam);
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
// CALLBACK FOR OUR TEST DIALOG - IT IS A MODAL DIALOG SO IT WILL TAKE CONTROL
// FROM THE PARENT WINDOW TILL IT IS CLOSED, THIS IS ITS ONLY MESSAGE HANDLER
// BESIDES THE WINDOWS DEFAULT MESSAGES, IT IS RESPONSIBLE FOR ALL
// OF THE FUNCTIONALITY IN THE TEST DIALOG
BOOL CALLBACK TestProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SETCURSOR:
// variable to hold the text to display on mouse-over of the controls...
char Temp2[256];
switch (GetDlgCtrlID((HWND) wParam))
{
// these cases are responsible for displaying the text that displays in the static bos
// when they are moused over
case IDC_CHTEST:
GetDlgItemText(hwndDlg, IDC_STMESSAGE, Temp2, 256);
if (strcmp(Temp2, "Click the box to tick/untick it.") != 0)
{
SetDlgItemText(hwndDlg, IDC_STMESSAGE, "Click the box to tick/untick it.");
}
break;
case IDC_EDTEXT:
GetDlgItemText(hwndDlg, IDC_STMESSAGE, Temp2, 256);
if (strcmp(Temp2, "Enter some text here.") != 0)
{
SetDlgItemText(hwndDlg, IDC_STMESSAGE, "Enter some text here.");
}
break;
case IDC_EDNUM:
GetDlgItemText(hwndDlg, IDC_STMESSAGE, Temp2, 256);
if (strcmp(Temp2, "Enter some numbers here.") != 0)
{
SetDlgItemText(hwndDlg, IDC_STMESSAGE, "Enter some numbers here.");
}
break;
default:
GetDlgItemText(hwndDlg, IDC_STMESSAGE, Temp2, 256);
if (strcmp(Temp2, "Hover over an item to receive help.") != 0)
{
SetDlgItemText(hwndDlg, IDC_STMESSAGE, "Hover over an item to receive help.");
}
break;
}
break;
case WM_INITDIALOG:
// This case is responsible for setting the dialog info when it is opened/re-opened
// note that the Numerical enterer can only remember 10 numerical characeters, more than that
// and it forgets
char Temp[33];
_itoa_s(DialogNumber, Temp, 10);
SetDlgItemText(hwndDlg, IDC_EDNUM, Temp);
SetDlgItemText(hwndDlg, IDC_EDTEXT, DialogText);
if (DialogChecked)
CheckDlgButton(hwndDlg, IDC_CHTEST, BST_CHECKED);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
// if OK is clicked, then all of the data in the dialog is saved into variables
// so that we can put it bak next time the window opens
if (IsDlgButtonChecked(hwndDlg, IDC_CHTEST) == BST_CHECKED)
{
DialogChecked = true;
}
else
{
DialogChecked = false;
}
GetDlgItemText(hwndDlg, IDC_EDTEXT, DialogText, 256);
DialogNumber = (int)GetDlgItemInt(hwndDlg, IDC_EDNUM, NULL, false);
EndDialog(hwndDlg, wParam);
return true;
case IDCANCEL:
// if CANCEL is pressed all of the dialog info is discarded.
EndDialog(hwndDlg, wParam);
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////
// CALLBACK FOR OUR MODELESS DIALOG BOX - THIS DIALOG, BEING MODELESS, WILL NOT
// TAKE CONTROL FROM THE PARENT WINDOW, THE PARENT WINDOW WILL CONTINUE TO PROCSESS
// MESSAGES AT THE SAME TIME AS THIS CALLBACK IS PROCSESING THE DIALOG'S.
BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
ShowWindow(g_hToolbar, SW_HIDE);
break;
case IDC_BUTTON1:
MessageBox(hwnd, "Hi!", "This is a message",
MB_OK | MB_ICONEXCLAMATION);
break;
case IDC_BUTTON2:
MessageBox(hwnd, "Bye!", "This is also a message",
MB_OK | MB_ICONEXCLAMATION);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////
// This function will load a text file into an MDI child window containing an
// edit control generated at runtime - it has only minimal error checking
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;
pszFileText = (LPSTR)GlobalAlloc(GPTR, dwFileSize + 1);
if(pszFileText != NULL)
{
DWORD dwRead;
if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead, NULL))
{
pszFileText[dwFileSize] = 0; // Add null terminator
if(SetWindowText(hEdit, pszFileText))
bSuccess = TRUE; // It worked!
}
GlobalFree(pszFileText);
}
}
CloseHandle(hFile);
}
return bSuccess;
}
/////////////////////////////////////////////////////////////////
// this function will save a file from the current edit window and save
// it as a text file
BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
HANDLE hFile;
BOOL bSuccess = FALSE;
hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
DWORD dwTextLength;
dwTextLength = GetWindowTextLength(hEdit);
// No need to bother if there's no text.
if(dwTextLength > 0)
{
LPSTR pszText;
DWORD dwBufferSize = dwTextLength + 1;
pszText = (LPSTR)GlobalAlloc(GPTR, dwBufferSize);
if(pszText != NULL)
{
if(GetWindowText(hEdit, pszText, dwBufferSize))
{
DWORD dwWritten;
if(WriteFile(hFile, pszText, dwTextLength, &dwWritten, NULL))
bSuccess = TRUE;
}
GlobalFree(pszText);
}
}
CloseHandle(hFile);
}
return bSuccess;
}
/////////////////////////////////////////////////////////////////
// this function will open a file open dialog and set some properties
void DoFileOpen(HWND hwnd)
{
OPENFILENAME ofn;
char szFileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Text Files (*.txt)*.txtAll Files (*.*)*.*";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = "txt";
if(GetOpenFileName(&ofn))
{
HWND hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);
if(LoadTextFileToEdit(hEdit, szFileName))
{
SendDlgItemMessage(g_hMainWindow, IDC_MAIN_STATUS, SB_SETTEXT, 0, (LPARAM)"Opened...");
SendDlgItemMessage(g_hMainWindow, IDC_MAIN_STATUS, SB_SETTEXT, 1, (LPARAM)szFileName);
SetWindowText(hwnd, szFileName);
}
}
}
/////////////////////////////////////////////////////////////////
// this function will open a file save dialog and set some properties
void DoFileSave(HWND hwnd)
{
OPENFILENAME ofn;
char szFileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Text Files (*.txt)*.txtAll Files (*.*)*.*";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrDefExt = "txt";
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
if(GetSaveFileName(&ofn))
{
HWND hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);
if(SaveTextFileFromEdit(hEdit, szFileName))
{
SendDlgItemMessage(g_hMainWindow, IDC_MAIN_STATUS, SB_SETTEXT, 0, (LPARAM)"Saved...");
SendDlgItemMessage(g_hMainWindow, IDC_MAIN_STATUS, SB_SETTEXT, 1, (LPARAM)szFileName);
SetWindowText(hwnd, szFileName);
}
}
}
////////////////////////////////////////////////////////////////
// Function will create a new MDI child that will contain an
// edit window
HWND CreateNewMDIChild(HWND hMDIClient)
{
MDICREATESTRUCT mcs;
HWND hChild;
mcs.szTitle = "[Untitled]";
mcs.szClass = g_szChildClassName;
mcs.hOwner = GetModuleHandle(NULL);;
mcs.x = mcs.cx = CW_USEDEFAULT;
mcs.y = mcs.cy = CW_USEDEFAULT;
mcs.style = MDIS_ALLCHILDSTYLES;
hChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LONG)&mcs);
if(!hChild)
{
MessageBox(hMDIClient, "MDI Child creation failed.", "Oh Oh...",
MB_ICONEXCLAMATION | MB_OK);
}
return hChild;
}
//////////////////////////////////////////////////////////////
// This function will register the MDI child window's class
// so that it can be initialised and created, this is the edit
// version from theForger's tutorial
BOOL SetUpMDIChildWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_SAVEBITS | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MDIChildWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(g_pGlob->hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szChildClassName;
wc.hIconSm = LoadIcon(g_pGlob->hInstance, MAKEINTRESOURCE(IDI_ICON1));
if(!RegisterClassEx(&wc))
{
MessageBox(0, "Could Not Register Child Window", "Oh Oh...",
MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}
else
return TRUE;
}
//////////////////////////////////////////////////////////////////////////////
// CALLBACK for the MDI child edit windows
LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
HFONT hfDefault;
HWND hEdit;
// Create Edit Control
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
0, 0, 100, 100, hwnd, (HMENU)IDC_CHILD_EDIT, GetModuleHandle(NULL), NULL);
if(hEdit == NULL)
MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);
hfDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));
}
break;
case WM_MDIACTIVATE:
{
HMENU hMenu, hFileMenu;
UINT EnableFlag;
hMenu = GetMenu(g_pGlob->hWnd);
if(hwnd == (HWND)lParam)
{ //being activated, enable the menus
EnableFlag = MF_ENABLED;
}
else
{ //being de-activated, gray the menus
EnableFlag = MF_GRAYED;
}
EnableMenuItem(hMenu, 1, MF_BYPOSITION | EnableFlag);
EnableMenuItem(hMenu, 2, MF_BYPOSITION | EnableFlag);
hFileMenu = GetSubMenu(hMenu, 0);
EnableMenuItem(hFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND | EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSEGAME, MF_BYCOMMAND | EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND | EnableFlag);
DrawMenuBar(g_pGlob->hWnd);
}
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_OPENGAME:
DoFileOpen(hwnd);
break;
case ID_FILE_SAVEAS:
DoFileSave(hwnd);
break;
case ID_EDIT_CUTE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_CUT, 0, 0);
break;
case ID_EDIT_COPYE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_COPY, 0, 0);
break;
case ID_EDIT_PASTEE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0, 0);
break;
}
break;
case WM_SIZE:
{
HWND hEdit;
RECT rcClient;
// Calculate remaining height and size edit
GetClientRect(hwnd, &rcClient);
hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);
SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER);
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
default:
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
return 0;
}
////////////////////////////////////////////////////////////////
// This function creates the MDI child window that will hold GDK
HWND CreateNewGDKMDIChild(HWND hMDIClient)
{
MDICREATESTRUCT mcs;
HWND hChild;
mcs.szTitle = "GDK MDI Window";
mcs.szClass = g_szGDKClassName;
mcs.hOwner = GetModuleHandle(NULL);
mcs.x = mcs.cx = CW_USEDEFAULT;
mcs.y = mcs.cy = CW_USEDEFAULT;
mcs.style = MDIS_ALLCHILDSTYLES;
hChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LONG)&mcs);
if(!hChild)
{
MessageBox(hMDIClient, "GDK MDI Child creation failed.", "Error...",
MB_ICONEXCLAMATION | MB_OK);
}
return hChild;
}
////////////////////////////////////////////////////////////////////////
// This registers the class for the MDI window that will hold the GDK surface
BOOL SetUpGDKMDIChildWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_SAVEBITS | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = GDKMDIChildWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(g_pGlob->hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szGDKClassName;
wc.hIconSm = LoadIcon(g_pGlob->hInstance, MAKEINTRESOURCE(IDI_ICON1));
if(!RegisterClassEx(&wc))
{
MessageBox(0, "Could Not Register GDK MDI Child Window", "Error...",
MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}
else
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
// CALLBACK to handle messages from our GDK window, this is because even though
// we can access it from the "void DarkGDK()" part of our program, if we have
// focus on this window, then only its messages are processed... im not going to bother
// coding something like that in this example though
LRESULT CALLBACK GDKMDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_MDIACTIVATE:
{
HMENU hMenu, hFileMenu;
UINT EnableFlag;
hMenu = GetMenu(g_pGlob->hWnd);
if(hwnd == (HWND)lParam)
{ //being activated, enable the menus
EnableFlag = MF_ENABLED;
}
else
{ //being de-activated, gray the menus
EnableFlag = MF_GRAYED;
}
EnableMenuItem(hMenu, 1, MF_BYPOSITION | EnableFlag);
EnableMenuItem(hMenu, 2, MF_BYPOSITION | EnableFlag);
hFileMenu = GetSubMenu(hMenu, 0);
EnableMenuItem(hFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND | EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSEGAME, MF_BYCOMMAND | EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND | EnableFlag);
DrawMenuBar(g_pGlob->hWnd);
}
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
}
break;
return DefMDIChildProc(hwnd, msg, wParam, lParam);
default:
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Here are some screenies of the app running :
Normal Startup look :
Modal dialog open :
Modeless dialog open :
Multiple Document Interface - editor windows with GDK window :
The attachment is in ZIP format, about 560k packed
If it ain't broke.... DONT FIX IT !!!