在新建SDI文档的过程中,框架指针为空(pFrame = NULL),需要根据新创建的文档指针pDocument来CreateNewFrame;在新建或打开MDI文档的过程中,则一直需要CreateNewFrame。
1 CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,BOOL bMakeVisible)
{
pDocument = CreateNewDocument();///创建一个新文档
...
CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);
...
}
2 CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)
{
if (pDoc != NULL)
ASSERT_VALID(pDoc);
// create a frame wired to the specified document
ASSERT(m_nIDResource != 0); // must have a resource ID to load from
CCreateContext context;
context.m_pCurrentFrame = pOther;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
if (m_pFrameClass == NULL)
{
TRACE0("Error: you must override CDocTemplate::CreateNewFrame./n");
ASSERT(FALSE);
return NULL;
}
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();//利用CRunTimeClass真正创建框架指针
if (pFrame == NULL)
{
TRACE1("Warning: Dynamic create of frame %hs failed./n",
m_pFrameClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CFrameWnd, pFrame);
if (context.m_pNewViewClass == NULL)
TRACE0("Warning: creating frame with no default view./n");
// create new from resource
if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
{
TRACE0("Warning: CDocTemplate couldn't create a frame./n");
// frame will be deleted in PostNcDestroy cleanup
return NULL;
}
}
3 BOOL CFrameWnd/CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
// only do this once
ASSERT_VALID_IDR(nIDResource);
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)
CString strFullString;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(m_strTitle, strFullString, 0); // first sub-string
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
// attempt to create the window
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
LPCTSTR lpszTitle = m_strTitle;
if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault,
pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
{
return FALSE; // will self destruct on failure normally
}
// save the default menu handle
ASSERT(m_hWnd != NULL);
m_hMenuDefault = ::GetMenu(m_hWnd);
// load accelerator resource
LoadAccelTable(MAKEINTRESOURCE(nIDResource));
if (pContext == NULL) // send initial update
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);//对于单文档,由于视图已经建立,发出WM_INITIALUPDATE消息,调用OnInitialUpdate()。
return TRUE;
}
BOOLCMDIChildWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)
{
// only do this once
ASSERT_VALID_IDR(nIDResource);
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)
// parent must be MDI Frame (or NULL for default)
ASSERT(pParentWnd == NULL || pParentWnd->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd)));
// will be a child of MDIClient
ASSERT(!(dwDefaultStyle & WS_POPUP));
dwDefaultStyle |= WS_CHILD;
// if available - get MDI child menus from doc template
CMultiDocTemplate* pTemplate;
if (pContext != NULL &&
(pTemplate = (CMultiDocTemplate*)pContext->m_pNewDocTemplate) != NULL)
{
ASSERT_KINDOF(CMultiDocTemplate, pTemplate);
// get shared menu from doc template
m_hMenuShared = pTemplate->m_hMenuShared;
m_hAccelTable = pTemplate->m_hAccelTable;
}
else
{
TRACE(traceAppMsg, 0, "Warning: no shared menu/acceltable for MDI Child window./n");
// if this happens, programmer must load these manually
}
CString strFullString, strTitle;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(strTitle, strFullString, 0); // first sub-string
ASSERT(m_hWnd == NULL);
if (!Create(GetIconWndClass(dwDefaultStyle, nIDResource),
strTitle, dwDefaultStyle, rectDefault,
(CMDIFrameWnd*)pParentWnd, pContext))
{
return FALSE; // will self destruct on failure normally
}
// it worked !
return TRUE;
}
4.1 LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)
{
ASSERT_VALID_IDR(nIDResource);
HINSTANCE hInst = AfxFindResourceHandle(
ATL_MAKEINTRESOURCE(nIDResource), ATL_RT_GROUP_ICON);
HICON hIcon = ::LoadIconW(hInst, ATL_MAKEINTRESOURCEW(nIDResource));
if (hIcon != NULL)
{
CREATESTRUCT cs;
memset(&cs, 0, sizeof(CREATESTRUCT));
cs.style = dwDefaultStyle;
PreCreateWindow(cs);//第一次调用
// will fill lpszClassName with default WNDCLASS name
// ignore instance handle from PreCreateWindow.
WNDCLASS wndcls;
if (cs.lpszClass != NULL &&
AfxCtxGetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndcls) &&
wndcls.hIcon != hIcon)
{
// register a very similar WNDCLASS
return AfxRegisterWndClass(wndcls.style,
wndcls.hCursor, wndcls.hbrBackground, hIcon);
}
}
return NULL; // just use the default
}
4.1.2 用MFC的全局函数AfxRegisterWndClass注册WNDCLASS,不需要定义所有字段,只需要关注其4个参数值。函数原型:LPCTSTR AfxRegisterWndClass(UINT nClassStyle,HCURSOR hCursor=0,HBRUSH hbrBackground=0,HICON hIcon=0);
第一个参数指定类样式,定义窗口的某种操作特性;第二个参数指定窗口识别“类光标”;第三个参数指定窗口默认背景颜色,可以传递一个画刷句柄,也可以指定一个预定义的Windows系统颜色并加1,第四个参数指定windows用来在桌面上、任务栏和其它地方代表应用程序的图标句柄,可以自定义图标然后加载,也可以加载系统图标。
该函数返回一个包含WNDCLASS名称的非空结尾字符串的指针,作为CreateEX函数的参数,用以创建窗口。
如:
CString strWndClass=AfxRegisterWndClass(CS_DBLCLKS, AfxGetApp()->LoadStandardCursor(IDC_ARROW), (HBRUSH)(COLOR_3DFACE+1), AfxGetApp()->LoadStandardIcon(IDI_WINLOGO));
CreateEx(0,strWndClass,_T("my program"),
WS_OVERLAPPED|WS_SYSMENU|WS_CAPTION|WS_MINIMIZEBOX,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
NULL,NULL);
4.2 BOOL CMDIChildWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect, CMDIFrameWnd* pParentWnd,
CCreateContext* pContext)
{
if (pParentWnd == NULL)
{
CWinThread *pThread = AfxGetThread();
ENSURE_VALID(pThread);
CWnd* pMainWnd = pThread->m_pMainWnd;
ENSURE_VALID(pMainWnd);
ASSERT_KINDOF(CMDIFrameWnd, pMainWnd);
pParentWnd = (CMDIFrameWnd*)pMainWnd;
}
ASSERT(::IsWindow(pParentWnd->m_hWndMDIClient));
// insure correct window positioning
pParentWnd->RecalcLayout();
// first copy into a CREATESTRUCT for PreCreate
CREATESTRUCT cs;
cs.dwExStyle = 0L;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = rect.left;
cs.y = rect.top;
cs.cx = rect.right - rect.left;
cs.cy = rect.bottom - rect.top;
cs.hwndParent = pParentWnd->m_hWnd;
cs.hMenu = NULL;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = (LPVOID)pContext;
if (!PreCreateWindow(cs))//CChildFrame第二次调用
{
PostNcDestroy();
return FALSE;
}
// extended style must be zero for MDI Children (except under Win4)
ASSERT(cs.hwndParent == pParentWnd->m_hWnd); // must not change
// now copy into a MDICREATESTRUCT for real create
MDICREATESTRUCT mcs;
mcs.szClass = cs.lpszClass;
mcs.szTitle = cs.lpszName;
mcs.hOwner = cs.hInstance;
mcs.x = cs.x;
mcs.y = cs.y;
mcs.cx = cs.cx;
mcs.cy = cs.cy;
mcs.style = cs.style & ~(WS_MAXIMIZE | WS_VISIBLE);
mcs.lParam = (LPARAM)cs.lpCreateParams;
// create the window through the MDICLIENT window
AfxHookWindowCreate(this);
HWND hWnd = (HWND)::SendMessage(pParentWnd->m_hWndMDIClient,
WM_MDICREATE, 0, (LPARAM)&mcs);//发出WM_MDICREATE(WM_NCCREATEHE WM_CREATE)消息,会调用CMDIChildWnd::OnNcCreate和CMDIChildWnd::OnCreate
if (!AfxUnhookWindowCreate())
PostNcDestroy(); // cleanup if MDICREATE fails too soon
if (hWnd == NULL)
return FALSE;
// special handling of visibility (always created invisible)
if (cs.style & WS_VISIBLE)//在前面PreCreateWindow中修改的cs属性必须有WS_VISIBLE,WS_MAXIMIZE才能生效的原因在此
{
// place the window on top in z-order before showing it
::BringWindowToTop(hWnd);
// show it as specified
if (cs.style & WS_MINIMIZE)
ShowWindow(SW_SHOWMINIMIZED);//如果可见,子框架窗口显示,在ActivateFrame中将再次ShowWindow
else if (cs.style & WS_MAXIMIZE)
ShowWindow(SW_SHOWMAXIMIZED);
else
ShowWindow(SW_SHOWNORMAL);
// make sure it is active (visibility == activation)
pParentWnd->MDIActivate(this);
// refresh MDI Window menu
::SendMessage(pParentWnd->m_hWndMDIClient, WM_MDIREFRESHMENU, 0, 0);
}
ASSERT(hWnd == m_hWnd);
return TRUE;
}
BOOL CFrameWnd/CMDIFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext)
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd./n");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}
m_strTitle = lpszWindowName; // save title for later
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd./n");
if (hMenu != NULL)
DestroyMenu(hMenu);
return FALSE;
}
return TRUE;
}
5 BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
ASSERT(lpszClassName == NULL || AfxIsValidString(lpszClassName) ||
AfxIsValidAtom(lpszClassName));
ENSURE_ARG(lpszWindowName == NULL || AfxIsValidString(lpszWindowName));
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))//主框架窗口的第二次调用
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = ::AfxCtxCreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);//调用API函数::CreateWindowEx真正创建框架窗口,创建过程中会发出WM_CREATE和WM_NCCREATE等消息。接收WM_CREATE消息的消息处理函数是CMainFrame::OnCreate()。
#ifdef _DEBUG
if (hWnd == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Window creation failed: GetLastError returns 0x%8.8X/n",
GetLastError());
}
#endif
if (!AfxUnhookWindowCreate())
PostNcDestroy(); // cleanup if CreateWindowEx fails too soon
if (hWnd == NULL)
return FALSE;
ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
return TRUE;
}
6 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此处通过修改
// CREATESTRUCT cs 来修改窗口类或样式
return TRUE;
}
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
}
if (cs.style & FWS_ADDTOTITLE)
cs.style |= FWS_PREFIXTITLE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
7
BOOL CMDIChildWnd::OnNcCreate(LPCREATESTRUCT lpCreateStruct)
{
if (!CFrameWnd::OnNcCreate(lpCreateStruct))
return FALSE;
// handle extended styles under Win4
// call PreCreateWindow again just to get dwExStyle
VERIFY(PreCreateWindow(*lpCreateStruct));//CChildFrame第三次调用
SetWindowLong(m_hWnd, GWL_EXSTYLE, lpCreateStruct->dwExStyle);
return TRUE;
}
int CMDIChildWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
// call base class with lParam context (not MDI one)
MDICREATESTRUCT* lpmcs;
lpmcs = (MDICREATESTRUCT*)lpCreateStruct->lpCreateParams;
CCreateContext* pContext = (CCreateContext*)lpmcs->lParam;
return OnCreateHelper(lpCreateStruct, pContext);//此函数调用的是CFrameWnd的OnCreateHelper,因此子框架窗口也将产生一个视图
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("未能创建工具栏/n");
return -1; // 未能创建
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("未能创建状态栏/n");
return -1; // 未能创建
}
// TODO: 如果不需要可停靠工具栏,则删除这三行
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
{
ENSURE_ARG(lpcs != NULL);
CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
return OnCreateHelper(lpcs, pContext);
}
8 int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
if (CWnd::OnCreate(lpcs) == -1)
return -1;
// create special children first
if (!OnCreateClient(lpcs, pContext))
{
TRACE(traceAppMsg, 0, "Failed to create client pane/view for frame./n");
return -1;
}
// post message for initial message string
PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);
// make sure the child windows have been properly sized
RecalcLayout();
return 0; // create ok
}
9
BOOL CMDIFrameWnd::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext*)
{
MDICREATESTRUCT* lpmcs;
lpmcs = (MDICREATESTRUCT*)lpCreateStruct->lpCreateParams;
CCreateContext* pContext = (CCreateContext*)lpmcs->lParam;
return CreateClient(lpcs, pMenu);
}
BOOL CMDIFrameWnd::CreateClient(LPCREATESTRUCT lpCreateStruct,
CMenu* pWindowMenu)//对于主框架窗口MainFrm调用,则不会新建视图
{
ASSERT(m_hWnd != NULL);
ASSERT(m_hWndMDIClient == NULL);
DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_BORDER |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
MDIS_ALLCHILDSTYLES; // allow children to be created invisible
//此处加入了WS_VISIBLE,因此对于MainFrm在PreCreateWindow中不需要加入该属性,至只要加入WS_MAXIMIZED就能实现最大化。而对于ChildFrm由于没有加入WS_VISIBLE,只有默认属性WS_OVERLAPPEDWINDOW|FWS_ADDTOTITILE,因此在PreCreateWindow中必须加入WS_VISIBLE和WS_MAXIMIZED,才能正确的最大化!
DWORD dwExStyle = 0;
// will be inset by the frame
// special styles for 3d effect on Win4
dwStyle &= ~WS_BORDER;
dwExStyle = WS_EX_CLIENTEDGE;
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu = pWindowMenu->GetSafeHmenu();
// set hWindowMenu for MFC V1 backward compatibility
// for MFC V2, window menu will be set in OnMDIActivate
ccs.idFirstChild = AFX_IDM_FIRST_MDICHILD;
if (lpCreateStruct->style & (WS_HSCROLL|WS_VSCROLL))
{
// parent MDIFrame's scroll styles move to the MDICLIENT
dwStyle |= (lpCreateStruct->style & (WS_HSCROLL|WS_VSCROLL));
// fast way to turn off the scrollbar bits (without a resize)
ModifyStyle(WS_HSCROLL|WS_VSCROLL, 0, SWP_NOREDRAW|SWP_FRAMECHANGED);
}
// Create MDICLIENT control with special IDC
if ((m_hWndMDIClient = ::AfxCtxCreateWindowEx(dwExStyle, _T("mdiclient"), NULL,
dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)AFX_IDW_PANE_FIRST,
AfxGetInstanceHandle(), (LPVOID)&ccs)) == NULL)
{
TRACE(traceAppMsg, 0, _T("Warning: CMDIFrameWnd::OnCreateClient: failed to create MDICLIENT.")
_T(" GetLastError returns 0x%8.8X/n"), ::GetLastError());
return FALSE;
}
// Move it to the top of z-order
::BringWindowToTop(m_hWndMDIClient);
return TRUE;
}
BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)//对于子框架窗口ChildFrm调用的是父类的,则产生一个新的视图
{
// default create client will create a view if asked for it
if (pContext != NULL && pContext->m_pNewViewClass != NULL)
{
if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
return FALSE;
}
return TRUE;
}
10 CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
{
ASSERT(m_hWnd != NULL);
ASSERT(::IsWindow(m_hWnd));
ENSURE_ARG(pContext != NULL);
ENSURE_ARG(pContext->m_pNewViewClass != NULL);
// Note: can be a CWnd with PostNcDestroy self cleanup
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();//真正创建的视图对象
if (pView == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Dynamic create of view type %hs failed./n",
pContext->m_pNewViewClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CWnd, pView);
// views are always created with a border!
if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), this, nID, pContext))
{
TRACE(traceAppMsg, 0, "Warning: could not create view for frame./n");
return NULL; // can't continue without a view
}
if (pView->GetExStyle() & WS_EX_CLIENTEDGE)
{
// remove the 3d style from the frame, since the view is
// providing it.
// make sure to recalc the non-client area
ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);
}
return pView;
}
在完成创建后,就要调用IntitalUpdateFrame()来显示框架窗口和视图了,详见下节。