//----------------------------------------------------------------
// 檔名:mfclite.cpp
// 作者:侯捷 J.J.Hou (jjhou), jjhou@jjhou.com, www.jjhou.com
// 用途:MFCLite 3.5,一個具體而微的 MFC-like application framework
// 本模組供應之功能:
// (1) Infrastructure: Dynamic/DynCreate/Serial
// (RTTI/Dynamic Creation/Persistence)
// (2) Application Framework
// (3) Message Map
// (4) Message Routing
// (5) MVC Model (Document-View)
// (6) Message Queue, Hotkey for menuitems and messages
// (7) File operations (New, Open, Save, SaveAs)
// (8) Multiple Documents
// (9) MDI
// (10) Subscribe-Notify protocol
// (11) OnWindowNew(), CMyView3, AfxWinTerm(), ExitInstance(), OnAppHotKeyHelp()
// (12) fixup resource leak in CDocument::OnOpenDocument,
// CDocument::OnSaveDocument
// and optimize CPtrList
// (thanks xioax)
// (13) optimize Document Read/Write (use tags)
// (14) window-close subsystem
//
// Ref.: MFC4.2 source
// objcore.cpp - CObject
// cmdtarg.cpp - CCmdTarget
// afxstate.cpp - AfxGetModuleState()
// appinit.cpp - AfxWinInit()
// appterm.cpp - AfxWinTerm()
// appmodul.cpp - _tWinMain()
// winmain.cpp - AfxWinMain()
// wincore.cpp - CWnd
// winfrm.cpp - CFrameWnd
// winmdi.cpp - CMDIFrameWnd, CMDIChildWnd
// viewcore.cpp - CView
// array_d.cpp - CDWordArray
// list_o.cpp - CObList
// list_p.cpp - CPtrList
// plex.cpp - CPlex
// thrdcore.cpp - CWinThread
// appcore.cpp - CWinApp
// appui.cpp - CWinAPp::OpenDocumentFile()
// appui2.cpp - CWinApp::AddDocTemplate(), CWinApp::CloseAllDocuments(), CWinApp::SaveAllModified()
// appdlg.cpp - CWinApp::OnFileNew(), CWinApp::OnFileOpen()
// doccore.cpp - CDocument
// docmgr.cpp - CDocManager
// doctempl.cpp - CDocTemplate
// docmulti.cpp - CMultiDocTemplate
// arccore.cpp - CArchive
// arcobj.cpp - CArchive for Object Persistence
// filecore.cpp - CFile
//----------------------------------------------------------------
#include <cstdio> // fopen, fclose, fread, fwrite, getchar, sprintf...
#include <cstring> // strlen, strcmp, memset, memcpy
#include <cstdlib> // abort, itoa(GCC 不支援,改用 sprintf)
#ifndef __GNUC__
#include <conio.h> // getch,getche 僅適用於 DOS-like,無 <cconio>;GCC 不支援。
#endif
#include <string> // 用於檔名, IDR_xxx
#include <iostream> //
#include <fstream> // 用於除錯檔 g_debugfile
#include <queue> // 用於訊息佇列(message queue)
#include <map> // 用於視窗管理, and hotKey-menuID table
#include <functional> // modulus<>()
#include "mfclite.h"
using namespace std;
///// 以下為 jjhou 增加之 global data and functions./////////
/////////////////////////////////////////////////////////////
static ofstream g_debugfile("debug.txt"); //6 用於測試 msg map
// 注意,由於這會自動開啟,所以如果某次執行時未曾按下 debug 命令,
// 原有的(以前執行所留下的)debug.txt 會被清空。
static queue<MSG> g_msgQueue; //6 模擬 message queue.
static map<char, unsigned int> g_MenuTable; //8 記錄所有表單按鍵與對應之識別碼
///////// 以下為 MFCLite 視窗管理系統 ///////////////////////
/* algorithm : 程式永遠(只)有一個 mainFrame,以及一對一對的
docFrame/View。每個 docFrame 雖為 CMDIChildFrame,但限制它
只擁有一個 view,以簡化狀態(而又堪稱實用),如此則 MFCLite
模擬之 CMDIChildFrame 可以簡易許多。
每個視窗產生之際其 hWnd 和對應之 CWnd* 即被當做 key/value 儲存
於 map<HWND, CWnd*> g_state 中,見 CWnd::CreateEx()。hWnd 為流水記號,
見 ::CreateWindowEx(),被摧毀後不再重複使用。每產生一個 docFrame 便會立即產生
一個 View,因此 hWnd 的規則是:mainFrame 1,docFrame 偶數,view 奇數;
g_state.size() 永遠為奇數 — 直到程式結束前 mainFrame 也被摧毀了。
Active window 的切換:如果 g_state.size()==1,表示只剩 mainFrame,
則切換無作用。如果 size()==3,切換亦無作用。否則,便在 g_state 中
從目前的 active docFrame 往下(iterator++)尋找下一個 docFrame。
如果走到尾端仍未找到,調頭從 g_state.begin()++ 開始搜尋。
視窗的摧毀:MFCLite 不支援實像視窗,只有邏輯概念上的視窗。所以
::DestroyWindow() 裡面沒有摧毀視窗的實際動作,只有視窗管理系統的維護。
維護方式是,如果 g_state.size()==1,刪除後 active docFrame/view 為 0/0,
active mainFrame 為 0。如果 g_state.size()==3,刪除後 active docFrame/view
為 0/0,active mainFrame 為 1。否則,如果欲刪除之 docFrame 與 active docFrame
不同,則直接刪除之,不必重設 active docFrame/view;若相同則先切換 active,
再刪除。
*/
static map<HWND, CWnd*> g_state; //6 記錄所有 window handles/CWnd object
// MFCLite中的所有視窗的 hWnd 必是:1 為mainFrame,奇數為view,偶數為docFrame
// 例如 1,2,3,4,5,6,7,8,9。此乃基於「每個docFrame之內只允許一個view」之假設。
static HWND g_activeFrame = (HWND)NULL; // docFrame, not mainFrame
static HWND g_activeView = (HWND)NULL;
static inline HWND JJGetMainFrame() { return g_state.begin()->first; }
// also work: { return AfxGetApp()->m_pMainWnd->m_hWnd; }
static inline HWND JJGetActiveFrame() { return g_activeFrame; }
static inline HWND JJGetActiveView() { return g_activeView; }
static inline void JJSetActiveFrame(HWND hWnd) { g_activeFrame = hWnd; }
static inline void JJSetActiveView(HWND hWnd) { g_activeView = hWnd; }
static void JJSetNextActive()
{
// 此函式將 g_state 視為環狀,次第切換。
// 例如:切換前 1 2 3 4 5,active docFrame/View 為 2,3
// 切換後 1 2 3 4 5,active docFrame/View 為 4,5
// 例如:切換前 1 2 3 4 5,active docFrame/View 為 4,5
// 切換後 1 2 3 4 5,active docFrame/View 為 2,3
// 例如:切換前 1 2 3,active docFrame/View 為 2,3
// 切換後 1 2 3,active docFrame/View 為 2,3
// 例如:切換前 1,active docFrame/View 為 0,0
// 切換後 1,active docFrame/View 為 0,0
if ((g_state.size()==0) || (g_state.size()==1) || (g_state.size()==3))
return; // 不做任何改變
// 在 g_state 中,從 active docFrame 開始,往下尋找 IsKindOf(CFrameWnd) 者,
// 找到後令為 active docFrame,並令其 m_pViewActive 為 active View。
map<HWND, CWnd*>::iterator posG = g_state.find(JJGetActiveFrame());
map<HWND, CWnd*>::iterator pos = posG;
for (++pos; pos != g_state.end(); ++pos) {
if ((pos->second)->IsKindOf(RUNTIME_CLASS(CFrameWnd))) { // docFrame, not view.
JJSetActiveFrame(pos->first);
CFrameWnd* pFrame = (CFrameWnd*)pos->second;
JJSetActiveView(pFrame->GetActiveView()->m_hWnd);
return; // 找到就回返
}
}
// 進行至此,表示從 current active docFrame 開始至 map 結束止,沒有找到
// 任何其他的 docFrame 可供切換。於是回頭找。
// 注意,g_state 的第一元素永為 mainFrame 而非 docFrame。
for (pos = g_state.begin(), ++pos; pos != posG; ++pos) {
if ((pos->second)->IsKindOf(RUNTIME_CLASS(CFrameWnd))) { // docFrame, not view.
JJSetActiveFrame(pos->first);
CFrameWnd* pFrame = (CFrameWnd*)pos->second;
JJSetActiveView(pFrame->GetActiveView()->m_hWnd);
return; // 找到就回返
}
}
}
static void JJDelElemAndSetNextActive(HWND delFrame)
{
// 例如:呼叫前 1 2 3 4 5,active docFrame/View 為 2,3, delFrame 為 2
// 呼叫後 1 4 5,active docFrame/View 為 4,5
// 例如:呼叫前 1 2 3 4 5,active docFrame/View 為 4,5, delFrame 為 2
// 呼叫後 1 4 5,active docFrame/View 為 4,5
// 例如:呼叫前 1 2 3,active docFrame/View 為 2,3, delFrame 為 2
// 呼叫後 1,active docFrame/View 為 0,0
// 例如:呼叫前 1,active docFrame/View 為 0,0, delFrame 為 1
// 呼叫後 x,active docFrame/View 為 0,0
if ((g_state.size()==1) && (delFrame==1)) { // 只剩 mainFrame 而待刪除的正是 mainFrame
g_state.erase(g_state.find(delFrame)); // 刪除之
return; // 不做改變。只剩 mainFrame 時,active docFrame