From dbe097d0d48c74fce9dfb82623a4eee44d3b71b9 Mon Sep 17 00:00:00 2001 From: Martin Payne Date: Tue, 15 Aug 2017 11:42:50 +0100 Subject: [PATCH] Prepare for version 1.0. --- .gitignore | 3 + License.txt | 24 ++++ Makefile | 53 +++++++++ Readme.md | 51 ++++++++ include/AboutDialog.h | 12 ++ include/Globals.h | 15 +++ include/MDIChildWindow.h | 31 +++++ include/MainWindow.h | 21 ++++ include/Resource.h | 25 ++++ res/Application.ico | Bin 0 -> 23558 bytes res/Application.manifest | 30 +++++ res/Resource.rc | 92 ++++++++++++++ src/AboutDialog.c | 39 ++++++ src/MDIChildWindow.c | 250 +++++++++++++++++++++++++++++++++++++++ src/MainWindow.c | 237 +++++++++++++++++++++++++++++++++++++ src/WinMain.c | 72 +++++++++++ 16 files changed, 955 insertions(+) create mode 100644 .gitignore create mode 100644 License.txt create mode 100644 Makefile create mode 100644 Readme.md create mode 100644 include/AboutDialog.h create mode 100644 include/Globals.h create mode 100644 include/MDIChildWindow.h create mode 100644 include/MainWindow.h create mode 100644 include/Resource.h create mode 100644 res/Application.ico create mode 100644 res/Application.manifest create mode 100644 res/Resource.rc create mode 100644 src/AboutDialog.c create mode 100644 src/MDIChildWindow.c create mode 100644 src/MainWindow.c create mode 100644 src/WinMain.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc001bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Ignore compiled binaries +/bin/ +/obj/ \ No newline at end of file diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..ea89a5d --- /dev/null +++ b/License.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..82c87b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +# This Makefile will build the MinGW Win32 MDI Example application + +# Object files to create for the executable +OBJS = obj/WinMain.o obj/MainWindow.o obj/MDIChildWindow.o obj/AboutDialog.o obj/Resource.o + +# Warnings to be raised by the C compiler +WARNS = -Wall + +# Names of tools to use when building +CC = gcc +RC = windres + +# Compiler flags. Compile ANSI build only if CHARSET=ANSI. +ifeq (${CHARSET}, ANSI) + CFLAGS = -O2 -std=c99 -D _WIN32_IE=0x0500 -D WINVER=0x0500 ${WARNS} -Iinclude +else + CFLAGS = -O2 -std=c99 -D UNICODE -D _UNICODE -D _WIN32_IE=0x0500 -D WINVER=0x0500 ${WARNS} -Iinclude +endif + +# Linker flags +LDFLAGS = -s -lcomctl32 -lcomdlg32 -Wl,--subsystem,windows + +.PHONY: all clean + +# Build executable by default +all: bin/Win32MDIApp.exe + +# Delete all build output +clean: + if exist bin\* del /q bin\* + if exist obj\* del /q obj\* + +# Create build output directories if they don't exist +bin obj: + @if not exist "$@" mkdir "$@" + +# Compile object files for executable +obj/%.o: src/%.c | obj + ${CC} ${CFLAGS} -c "$<" -o "$@" + +# Build the resources +obj/Resource.o: res/Resource.rc res/Application.manifest res/Application.ico include/Resource.h | obj + ${RC} -I./include -I./res -i "$<" -o "$@" + +# Build the exectuable +bin/Win32MDIApp.exe: ${OBJS} | bin + ${CC} -o "$@" ${OBJS} ${LDFLAGS} + +# C header dependencies +obj/AboutDialog.o: include/AboutDialog.h include/Resource.h include/Globals.h +obj/MainWindow.o: include/MainWindow.h include/MDIChildWindow.h include/AboutDialog.h include/Resource.h include/Globals.h +obj/MDIChildWindow.o: include/MDIChildWindow.h include/MainWindow.h include/Resource.h include/Globals.h +obj/WinMain.o: include/MainWindow.h include/MDIChildWindow.h include/Resource.h include/Globals.h diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..6571dab --- /dev/null +++ b/Readme.md @@ -0,0 +1,51 @@ +# MinGW Win32 MDI Application + +## Table of Contents + +- [Introduction](#introduction) +- [Terms of Use](#terms-of-use) +- [Problems?](#problems) +- [Changelog](#changelog) + +## Introduction + +This application is an example Windows MDI GUI application, written to demonstrate how this can be done using MinGW. It +accompanies the [Win32 Apps with MinGW](http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/) article on +[Transmission Zero](http://www.transmissionzero.co.uk/). The compiled application is a fully functional, with the +exception that opening and saving files does not read or write the files, and the MDI child windows do not have any code +to display documents. However, by adding this functionality you can quickly create a functioning MDI application. + +To build the application on a Windows machine, open a command prompt, change to the directory containing the Makefile, +and type "mingw32-make". The application should be compiled, linked, and output as "bin\Win32MDIApp.exe". + +To compile an ANSI build (i.e. if you want the application to run under Windows 9x), run "mingw32-make CHARSET=ANSI" +from the command prompt. + +To build under another operating system, the Makefile will probably require some small changes. For example, under +Fedora the C compiler and resource compiler are named "i686-pc-mingw32-gcc" and "i686-pc-mingw32-windres". Also, your +version of the make utility may be named differently--please check the documentation which came with your MinGW +packages. + +It should also be possible to build the application using any C or C++ compiler which supports targeting Windows, for +example Open Watcom. You will of course need to set the projects up for yourself if you do that. It can also be built +with Visual C++, however you are advised to use the +[MSVC Win32 MDI Application](https://github.com/TransmissionZero/MSVC-Win32-MDI-Application) because that's ready to use +with MSVC. No source code modifications are required if you want to build a 64 bit version of the application. + +## Terms of Use + +Refer to "License.txt" for terms of use. + +## Problems? + +If you have any problems or questions, please ensure you have read this readme file and the +[Win32 Apps with MinGW](http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/) article. If you are still +having trouble, you can [get in contact](http://www.transmissionzero.co.uk/contact/). + +## Changelog + +1. 2017-08-14: Version 1.0 + - Initial release. + +Transmission Zero +2017-08-14 diff --git a/include/AboutDialog.h b/include/AboutDialog.h new file mode 100644 index 0000000..cf7aa23 --- /dev/null +++ b/include/AboutDialog.h @@ -0,0 +1,12 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +/* Dialog procedure for our "about" dialog */ +INT_PTR CALLBACK AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); + +/* Show our "about" dialog */ +void ShowAboutDialog(HWND owner); + +#endif diff --git a/include/Globals.h b/include/Globals.h new file mode 100644 index 0000000..65620ae --- /dev/null +++ b/include/Globals.h @@ -0,0 +1,15 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +#include + +/* Global instance handle */ +extern HINSTANCE g_hInstance; + +/* Global main window handle */ +extern HWND g_hMainWindow; + +/* Global MDI client window handle */ +extern HWND g_hMDIClient; + +#endif diff --git a/include/MDIChildWindow.h b/include/MDIChildWindow.h new file mode 100644 index 0000000..d4a996c --- /dev/null +++ b/include/MDIChildWindow.h @@ -0,0 +1,31 @@ +#ifndef MDICHILDWINDOW_H +#define MDICHILDWINDOW_H + +#include + +/* Window procedure for our main window */ +LRESULT CALLBACK MDIChildWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +/* Register a class for our main window */ +BOOL RegisterMDIChildWindowClass(void); + +/* Create a new MDI child window */ +void MDIChildNew(HWND hMDIClient); + +/* Open a document in a new MDI child window */ +void MDIChildOpen(HWND hMDIClient); + +/* Save a document in an MDI child window */ +void MDIChildSave(HWND hMDIChild); + +/* Save a document in an MDI child window with a filename */ +void MDIChildSaveAS(HWND hMDIChild); + +/* Instance data for the MDI child window */ +typedef struct tagMdiChildData +{ + /* Flag to determine whether this is a new document which hasn't been saved */ + BOOL IsUnSaved; +} MdiChildData; + +#endif diff --git a/include/MainWindow.h b/include/MainWindow.h new file mode 100644 index 0000000..eceab41 --- /dev/null +++ b/include/MainWindow.h @@ -0,0 +1,21 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +/* Window procedure for our main window */ +LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +/* Register a class for our main window */ +BOOL RegisterMainWindowClass(void); + +/* Create an instance of our main window */ +HWND CreateMainWindow(void); + +/* Callback to close all child windows */ +BOOL CALLBACK CloseAllProc(HWND hWnd, LPARAM lParam); + +/* First ID Windows should use for menu items it attaches to the "Window" menu */ +#define ID_MDI_FIRSTCHILD 50000 + +#endif diff --git a/include/Resource.h b/include/Resource.h new file mode 100644 index 0000000..758812c --- /dev/null +++ b/include/Resource.h @@ -0,0 +1,25 @@ +#ifndef RESOURCE_H +#define RESOURCE_H + +#define IDI_APPICON 101 +#define IDR_MAINMENU 102 +#define IDR_ACCELERATOR 103 +#define IDD_ABOUTDIALOG 104 +#define ID_FILE_NEW 40001 +#define ID_FILE_OPEN 40002 +#define ID_FILE_SAVE 40003 +#define ID_FILE_SAVEAS 40004 +#define ID_FILE_CLOSE 40005 +#define ID_FILE_CLOSEALL 40006 +#define ID_FILE_EXIT 40007 +#define ID_WINDOW_TILE 40008 +#define ID_WINDOW_CASCADE 40009 +#define ID_WINDOW_ARRANGE 40010 +#define ID_HELP_ABOUT 40011 +#define ID_MDI_FIRSTCHILD 50000 + +#ifndef IDC_STATIC + #define IDC_STATIC -1 +#endif + +#endif diff --git a/res/Application.ico b/res/Application.ico new file mode 100644 index 0000000000000000000000000000000000000000..1073447c20ca46dca11c7f8ed0297cdf1188a5e0 GIT binary patch literal 23558 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 S2uFK+=#hr_`y5^6R{sMo*qd?y literal 0 HcmV?d00001 diff --git a/res/Application.manifest b/res/Application.manifest new file mode 100644 index 0000000..a7f6420 --- /dev/null +++ b/res/Application.manifest @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/Resource.rc b/res/Resource.rc new file mode 100644 index 0000000..0dd5960 --- /dev/null +++ b/res/Resource.rc @@ -0,0 +1,92 @@ +#include +#include "Resource.h" + +/* Win32 application icon */ +IDI_APPICON ICON "Application.ico" + +/* Our main menu */ +IDR_MAINMENU MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&New\tCtrl+N", ID_FILE_NEW + MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN + MENUITEM "&Save\tCtrl+S", ID_FILE_SAVE, GRAYED + MENUITEM "&Save As...", ID_FILE_SAVEAS, GRAYED + MENUITEM SEPARATOR + MENUITEM "&Close\tCtrl+F4", ID_FILE_CLOSE, GRAYED + MENUITEM "&Close All", ID_FILE_CLOSEALL, GRAYED + MENUITEM SEPARATOR + MENUITEM "E&xit", ID_FILE_EXIT + END + POPUP "&Window" GRAYED + BEGIN + MENUITEM "&Tile\tShift+F5", ID_WINDOW_TILE + MENUITEM "&Cascade\tShift+F4", ID_WINDOW_CASCADE + MENUITEM "Arrange &Icons", ID_WINDOW_ARRANGE + END + POPUP "&Help" + BEGIN + MENUITEM "&About", ID_HELP_ABOUT + END +END + +/* Application manifest */ +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "Application.manifest" + +/* Executable version information */ +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE +#else + FILEFLAGS 0 +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "CompanyName", "Transmission Zero" + VALUE "FileDescription", "Win32 MDI Example application" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "Win32MDIApp" + VALUE "LegalCopyright", "©2017 Transmission Zero" + VALUE "OriginalFilename", "Win32MDIApp.exe" + VALUE "ProductName", "Win32 MDI Example application" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + +/* Our "about" dialog */ +IDD_ABOUTDIALOG DIALOGEX 0, 0, 147, 67 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + ICON IDI_APPICON,IDC_STATIC,7,7,20,20 + LTEXT "Win32 MDI Example application.",IDC_STATIC,34,7,98,8 + LTEXT "©2017 Transmission Zero",IDC_STATIC,34,17,86,8 + DEFPUSHBUTTON "OK",IDOK,90,46,50,14,WS_GROUP +END + +/* Our accelerators */ +IDR_ACCELERATOR ACCELERATORS +BEGIN + "A", ID_HELP_ABOUT, VIRTKEY, ALT, NOINVERT + "N", ID_FILE_NEW, VIRTKEY, CONTROL, NOINVERT + "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT + "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT + VK_F4, ID_WINDOW_CASCADE, VIRTKEY, SHIFT, NOINVERT + VK_F5, ID_WINDOW_TILE, VIRTKEY, SHIFT, NOINVERT +END diff --git a/src/AboutDialog.c b/src/AboutDialog.c new file mode 100644 index 0000000..4e11c54 --- /dev/null +++ b/src/AboutDialog.c @@ -0,0 +1,39 @@ +#include "AboutDialog.h" +#include "Resource.h" +#include "Globals.h" + +/* Dialog procedure for our "about" dialog */ +INT_PTR CALLBACK AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + { + WORD id = LOWORD(wParam); + + switch (id) + { + case IDOK: + case IDCANCEL: + { + EndDialog(hwndDlg, (INT_PTR)id); + return (INT_PTR)TRUE; + } + } + break; + } + + case WM_INITDIALOG: + { + return (INT_PTR)TRUE; + } + } + + return (INT_PTR)FALSE; +} + +/* Show our "about" dialog */ +void ShowAboutDialog(HWND owner) +{ + DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_ABOUTDIALOG), owner, &AboutDialogProc); +} diff --git a/src/MDIChildWindow.c b/src/MDIChildWindow.c new file mode 100644 index 0000000..018dd13 --- /dev/null +++ b/src/MDIChildWindow.c @@ -0,0 +1,250 @@ +#include +#include "MDIChildWindow.h" +#include "MainWindow.h" +#include "Resource.h" +#include "Globals.h" + +/* MDI child window class and title */ +static LPCTSTR MDIChildWndClass = TEXT("Win32 MDI Example Application - Child"); + +/* Window procedure for our MDI child window */ +LRESULT CALLBACK MDIChildWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_COMMAND: + { + WORD id = LOWORD(wParam); + + switch (id) + { + case ID_FILE_SAVE: + { + MDIChildSave(hWnd); + return 0; + } + + case ID_FILE_SAVEAS: + { + MDIChildSaveAS(hWnd); + return 0; + } + } + + break; + } + + /* An MDI child window is being activated */ + case WM_MDIACTIVATE: + { + HMENU hParentMenu, hParentFileMenu; + UINT enableMenu; + HWND hActivatedChild = (HWND)lParam; + + /* If this window is the one being activated, enable its menus. */ + if (hWnd == hActivatedChild) + { + enableMenu = MF_ENABLED; + } + else + { + enableMenu = MF_GRAYED; + } + + /* Get menu of MDI frame window */ + hParentMenu = GetMenu(g_hMainWindow); + + /* Enable / disable the "window" menu */ + EnableMenuItem(hParentMenu, 1, MF_BYPOSITION | enableMenu); + + /* Enable / disable the save and close menu items */ + hParentFileMenu = GetSubMenu(hParentMenu, 0); + EnableMenuItem(hParentFileMenu, ID_FILE_SAVE, MF_BYCOMMAND | enableMenu); + EnableMenuItem(hParentFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND | enableMenu); + EnableMenuItem(hParentFileMenu, ID_FILE_CLOSE, MF_BYCOMMAND | enableMenu); + EnableMenuItem(hParentFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND | enableMenu); + + /* Redraw the updated menu */ + DrawMenuBar(g_hMainWindow); + + return 0; + } + + case WM_CREATE: + { + /* Allocate child window data */ + MdiChildData* childData = calloc(1, sizeof(MdiChildData)); + + /* Fail window creation if allocation failed */ + if (!childData) + return -1; + + /* Associate child window data with window */ + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)childData); + + return 0; + } + + case WM_DESTROY: + { + /* Free child window data */ + MdiChildData* childData = (MdiChildData*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + + if (childData) + free(childData); + + return 0; + } + } + + return DefMDIChildProc(hWnd, msg, wParam, lParam); +} + +/* Register a class for our main window */ +BOOL RegisterMDIChildWindowClass() +{ + WNDCLASSEX wc; + + wc.cbSize = sizeof(wc); + wc.style = 0; + wc.lpfnWndProc = &MDIChildWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = g_hInstance; + wc.hIcon = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | + LR_DEFAULTCOLOR | LR_SHARED); + wc.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); + wc.lpszClassName = MDIChildWndClass; + wc.hIconSm = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); + + return (RegisterClassEx(&wc)) ? TRUE : FALSE; +} + +/* Create a new MDI child window */ +void MDIChildNew(HWND hMDIClient) +{ + MDICREATESTRUCT mcs; + static unsigned int counter = 0; + TCHAR title[16]; + HWND hWndChild; + MdiChildData* childData; + + /* Increment counter, but ensure it doesn't become longer than the buffer */ + counter = (counter % 9999) + 1; + + /* Create the window title */ + wsprintf(title, TEXT("Untitled - %u"), counter); + + /* Set MDI child properties */ + mcs.szTitle = title; + mcs.szClass = MDIChildWndClass; + mcs.hOwner = g_hInstance; + mcs.x = mcs.cx = CW_USEDEFAULT; + mcs.y = mcs.cy = CW_USEDEFAULT; + mcs.style = MDIS_ALLCHILDSTYLES; + + /* Create the MDI child */ + hWndChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LPARAM)&mcs); + + if (hWndChild) + { + /* Mark document as unsaved */ + childData = (MdiChildData*)GetWindowLongPtr(hWndChild, GWLP_USERDATA); + childData->IsUnSaved = TRUE; + } + else + MessageBox(NULL, TEXT("Error creating new document."), TEXT("Error"), MB_ICONERROR | MB_OK); +} + +/* Open a document in a new MDI child window */ +void MDIChildOpen(HWND hMDIClient) +{ + OPENFILENAME ofn = { 0 }; + TCHAR fileName[MAX_PATH] = TEXT(""); + TCHAR fileTitle[MAX_PATH] = TEXT(""); + MDICREATESTRUCT mcs; + + /* Set open file dialog properties */ + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hMDIClient; + ofn.lpstrFilter = TEXT("Text Documents (*.txt)\0.txt\0All Files (*.*)\0*.*\0"); + ofn.lpstrFile = fileName; + ofn.lpstrFileTitle = fileTitle; + ofn.nMaxFileTitle = MAX_PATH; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_NOTESTFILECREATE | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = TEXT("txt"); + + /* Show open file dialog */ + if (GetOpenFileName(&ofn)) + { + HWND hWndChild; + + /* Set MDI child properties */ + mcs.szTitle = fileTitle; + mcs.szClass = MDIChildWndClass; + mcs.hOwner = g_hInstance; + mcs.x = mcs.cx = CW_USEDEFAULT; + mcs.y = mcs.cy = CW_USEDEFAULT; + mcs.style = MDIS_ALLCHILDSTYLES; + + /* Create the MDI child */ + hWndChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LPARAM)&mcs); + + if (hWndChild) + { + /* TODO: Add file opening code */ + } + else + MessageBox(NULL, TEXT("Error opening document."), TEXT("Error"), MB_ICONERROR | MB_OK); + } +} + +/* Save a document in an MDI child window */ +void MDIChildSave(HWND hMDIChild) +{ + MdiChildData* childData = (MdiChildData*)GetWindowLongPtr(hMDIChild, GWLP_USERDATA); + + /* If this is an unsaved document, do a save as */ + if (childData->IsUnSaved) + { + MDIChildSaveAS(hMDIChild); + } + else + { + /* TODO: Add file saving code */ + } +} + +/* Save a document in an MDI child window with a filename */ +void MDIChildSaveAS(HWND hMDIChild) +{ + OPENFILENAME ofn = { 0 }; + TCHAR fileName[MAX_PATH] = TEXT(""); + TCHAR fileTitle[MAX_PATH] = TEXT(""); + + /* Set save file dialog properties */ + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hMDIChild; + ofn.lpstrFilter = TEXT("Text Documents (*.txt)\0.txt\0All Files (*.*)\0*.*\0"); + ofn.lpstrFile = fileName; + ofn.lpstrFileTitle = fileTitle; + ofn.nMaxFileTitle = MAX_PATH; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_NOTESTFILECREATE | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; + ofn.lpstrDefExt = TEXT("txt"); + + if (GetSaveFileName(&ofn)) + { + /* TODO: Add file saving code */ + + /* Update window title with the filename */ + SetWindowText(hMDIChild, ofn.lpstrFileTitle); + + /* Mark document as saved */ + MdiChildData* childData = (MdiChildData*)GetWindowLongPtr(hMDIChild, GWLP_USERDATA); + childData->IsUnSaved = FALSE; + } +} diff --git a/src/MainWindow.c b/src/MainWindow.c new file mode 100644 index 0000000..de71527 --- /dev/null +++ b/src/MainWindow.c @@ -0,0 +1,237 @@ +#include "MainWindow.h" +#include "MDIChildWindow.h" +#include "AboutDialog.h" +#include "Resource.h" +#include "Globals.h" + +/* Main window class and title */ +static LPCTSTR MainWndClass = TEXT("Win32 MDI Example Application"); + +/* Global MDI client window handle */ +HWND g_hMDIClient = NULL; + +/* Window procedure for our main window */ +LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_COMMAND: + { + WORD id = LOWORD(wParam); + + switch (id) + { + /* Create new MDI child with an empty document */ + case ID_FILE_NEW: + { + MDIChildNew(g_hMDIClient); + return 0; + } + + /* Create new MDI child with an existing document */ + case ID_FILE_OPEN: + { + MDIChildOpen(g_hMDIClient); + return 0; + } + + /* Send close message to MDI child on close menu item */ + case ID_FILE_CLOSE: + { + HWND hWndChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE, 0, 0); + + if (hWndChild) + { + SendMessage(hWndChild, WM_CLOSE, 0, 0); + return 0; + } + + break; + } + + /* Close all MDI children on close all menu item */ + case ID_FILE_CLOSEALL: + { + EnumChildWindows(g_hMDIClient, &CloseAllProc, 0); + return 0; + } + + /* Show "about" dialog on about menu item */ + case ID_HELP_ABOUT: + { + ShowAboutDialog(hWnd); + return 0; + } + + case ID_FILE_EXIT: + { + DestroyWindow(hWnd); + return 0; + } + + case ID_WINDOW_TILE: + { + SendMessage(g_hMDIClient, WM_MDITILE, 0, 0); + return 0; + } + + case ID_WINDOW_CASCADE: + { + SendMessage(g_hMDIClient, WM_MDICASCADE, 0, 0); + return 0; + } + + case ID_WINDOW_ARRANGE: + { + SendMessage(g_hMDIClient, WM_MDIICONARRANGE, 0, 0); + return 0; + } + + default: + { + /* If the ID is less than ID_MDI_FIRSTCHILD, it's probably a message for a child window's menu */ + if (id < ID_MDI_FIRSTCHILD) + { + HWND hWndChild = (HWND)SendMessage(g_hMDIClient, WM_MDIGETACTIVE, 0, 0); + + if (hWndChild) + { + SendMessage(hWndChild, WM_COMMAND, wParam, lParam); + return 0; + } + } + + break; + } + } + + break; + } + + case WM_GETMINMAXINFO: + { + /* Prevent our window from being sized too small */ + MINMAXINFO *minMax = (MINMAXINFO*)lParam; + minMax->ptMinTrackSize.x = 220; + minMax->ptMinTrackSize.y = 110; + + return 0; + } + + case WM_SIZE: + { + /* Ensure MDI client fills the whole client area */ + RECT rcClient; + + GetClientRect(hWnd, &rcClient); + SetWindowPos(g_hMDIClient, NULL, 0, 0, rcClient.right, rcClient.bottom, SWP_NOZORDER); + + return 0; + } + + /* Item from system menu has been invoked */ + case WM_SYSCOMMAND: + { + WORD id = LOWORD(wParam); + + switch (id) + { + /* Show "about" dialog on about system menu item */ + case ID_HELP_ABOUT: + { + ShowAboutDialog(hWnd); + return 0; + } + } + + break; + } + + case WM_CREATE: + { + /* Create the MDI client window */ + CLIENTCREATESTRUCT ccs; + HWND hMDIClient; + + ccs.hWindowMenu = GetSubMenu(GetMenu(hWnd), 1); + ccs.idFirstChild = ID_MDI_FIRSTCHILD; + + hMDIClient = CreateWindowEx(0, TEXT("MDICLIENT"), NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | + WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWnd, NULL, + g_hInstance, (LPVOID)&ccs); + + /* Fail the window creation if the MDI client creation failed */ + if (!hMDIClient) + { + MessageBox(NULL, TEXT("Error creating MDI client."), TEXT("Error"), MB_ICONERROR | MB_OK); + return -1; + } + + /* Set global MDI client handle */ + g_hMDIClient = hMDIClient; + + return 0; + } + + case WM_DESTROY: + { + PostQuitMessage(0); + return 0; + } + } + + return DefFrameProc(hWnd, g_hMDIClient, msg, wParam, lParam); +} + +/* Close all child windows */ +BOOL CALLBACK CloseAllProc(HWND hWnd, LPARAM lParam) +{ + if (GetWindow(hWnd, GW_OWNER)) + return TRUE; + + SendMessage(hWnd, WM_CLOSE, 0, 0); + + return TRUE; +} + +/* Register a class for our main window */ +BOOL RegisterMainWindowClass() +{ + WNDCLASSEX wc; + + wc.cbSize = sizeof(wc); + wc.style = 0; + wc.lpfnWndProc = &MainWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = g_hInstance; + wc.hIcon = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | + LR_DEFAULTCOLOR | LR_SHARED); + wc.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED); + wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); + wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); + wc.lpszClassName = MainWndClass; + wc.hIconSm = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); + + return (RegisterClassEx(&wc)) ? TRUE : FALSE; +} + +/* Create an instance of our main window */ +HWND CreateMainWindow() +{ + HWND hWnd; + HMENU hSysMenu; + + hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, MainWndClass, MainWndClass, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, + CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, g_hInstance, NULL); + + if (hWnd) + { + /* Add "about" to the system menu */ + hSysMenu = GetSystemMenu(hWnd, FALSE); + InsertMenu(hSysMenu, 5, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); + InsertMenu(hSysMenu, 6, MF_BYPOSITION, ID_HELP_ABOUT, TEXT("About")); + } + + return hWnd; +} diff --git a/src/WinMain.c b/src/WinMain.c new file mode 100644 index 0000000..a59b6b7 --- /dev/null +++ b/src/WinMain.c @@ -0,0 +1,72 @@ +#include +#include +#include "MainWindow.h" +#include "MDIChildWindow.h" +#include "Resource.h" +#include "Globals.h" + +/* Global instance handle */ +HINSTANCE g_hInstance = NULL; + +/* Global main window handle so that child windows can access it */ +HWND g_hMainWindow = NULL; + +/* Our application entry point */ +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + INITCOMMONCONTROLSEX icc; + HWND hWnd; + HACCEL hAccelerators; + MSG msg; + + /* Assign global HINSTANCE */ + g_hInstance = hInstance; + + /* Initialise common controls */ + icc.dwSize = sizeof(icc); + icc.dwICC = ICC_WIN95_CLASSES; + InitCommonControlsEx(&icc); + + /* Register our main window class */ + if (!RegisterMainWindowClass()) + { + MessageBox(NULL, TEXT("Error registering main window class."), TEXT("Error"), MB_ICONERROR | MB_OK); + return 0; + } + + /* Register our MDI child window class */ + if (!RegisterMDIChildWindowClass()) + { + MessageBox(NULL, TEXT("Error registering MDI child window class."), TEXT("Error"), MB_ICONERROR | MB_OK); + return 0; + } + + /* Create our main window */ + if (!(hWnd = CreateMainWindow())) + { + MessageBox(NULL, TEXT("Error creating main window."), TEXT("Error"), MB_ICONERROR | MB_OK); + return 0; + } + + /* Set global main window handle */ + g_hMainWindow = hWnd; + + /* Load accelerators */ + hAccelerators = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); + + /* Show main window and force a paint */ + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + /* Main message loop */ + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + if (!TranslateMDISysAccel(g_hMDIClient, &msg) && !TranslateAccelerator(hWnd, hAccelerators, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return (int)msg.wParam; +}