默认情况下,MFC的主窗口标题由文档标题和Frame标题组成,格式为:file - frame
。
设置标题
设置窗口标题用 CWnd::SetWindowText 方法。
设置文档标题
新建一个文档时,MFC 会使用字符串资源AFX_IDS_UNTITLED
作为文档的默认标题,接着会触发 CDocument::OnNewDocument 方法,我们可以在文件新建成功后使用 CDocument::SetTitle 方法来设置标题。
1 2 3 4 5 6 7 8
| BOOL CMFCApplication2Doc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE;
this->SetTitle(_T("新文件")); return TRUE; }
|
如果是打开文档
就不能在 CDocument::OnOpenDocument 中处理,因为 MFC 在打开文档后会使用文件名作为标题
1 2 3 4 5
| ... TCHAR szTitle[_MAX_FNAME]; if (AfxGetFileTitle(szFullPath, szTitle, _MAX_FNAME) == 0) SetTitle(szTitle); ...
|
最早的修改时机是在 CDocument::SetPathName 中,我们可以重载它
1 2 3 4 5
| void SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU) override { CDocument::SetPathName(lpszPathName, bAddToMRU); this->SetTitle(_T("new title")); }
|
但是更建议在 CDocument::OnDocumentEvent 中处理
1 2 3 4 5 6
| void OnDocumentEvent(DocumentEvent deEvent) override { if (deEvent == onAfterNewDocument || deEvent == onAfterOpenDocument) { this->SetTitle(_T("new title")); } }
|
固定窗口标题
我们有时候希望应用程序标题是固定的,不会随着文档变化,可以通过以下几种方式来做。
方式一
因为文档在设置标题后会触发 CFrameWndEx::OnUpdateFrameTitle 方法,而这个方法会改变窗口标题,所以我们覆盖它即可避免被影响。
在 CFrameWndEx::OnCreate 中设置固定标题
1
| this->SetWindowText(_T("title"));
|
重载 CFrameWndEx::OnUpdateFrameTitle 方法,不调用父类操作。
1 2 3 4
| void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle) { }
|
方式二
跟进OnUpdateFrameTitle
后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void CFrameWnd::OnUpdateFrameTitle(BOOL bAddToTitle) { if ((GetStyle() & FWS_ADDTOTITLE) == 0) return;
if (m_pNotifyHook != NULL && m_pNotifyHook->OnUpdateFrameTitle()) return;
CDocument* pDocument = GetActiveDocument(); if (bAddToTitle && pDocument != NULL) UpdateFrameTitleForDocument(pDocument->GetTitle()); else UpdateFrameTitleForDocument(NULL); }
|
这里发现了一个新东西FWS_ADDTOTITLE
,如果窗口样式不包含它的话就退出函数了,所以我们也可以在创建Frame
时去除这个Flag
1 2 3 4 5 6 7 8
| BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if (!CFrameWndEx::PreCreateWindow(cs)) return FALSE;
cs.style &= ~FWS_ADDTOTITLE; return TRUE; }
|
所以这个方式和方式一是一样的,本质上就是忽略OnUpdateFrameTitle
的默认行为。
方式三
继续跟进源码,创建窗口的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| 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));
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 = CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
#ifdef _DEBUG if (hWnd == NULL) { TRACE(traceAppMsg, 0, "Warning: Window creation failed: GetLastError returns 0x%8.8X\n", GetLastError()); } #endif
if (!AfxUnhookWindowCreate()) PostNcDestroy();
if (hWnd == NULL) return FALSE; ASSERT(hWnd == m_hWnd); return TRUE; }
|
在调用CreateWindowEx
创建窗口之前调用过了一次PreCreateWindow
,然后用cs.lpszName
作为窗口标题,那么就可以在PreCreateWindow
中设置固定标题
1 2 3 4 5 6 7 8 9 10 11 12 13
| BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if (!CFrameWnd::PreCreateWindow(cs)) return FALSE;
if (cs.hInstance) { cs.style &= ~FWS_ADDTOTITLE; cs.lpszName = _T("Hello"); }
return TRUE; }
|
比前两种方式简单直观,推荐使用这种方式。
Frame 默认标题从哪来?
如果我们不做任何设置,Frame 始终会有一个默认标题,这个标题字符串从哪来?还是看源码
1 2 3 4 5 6 7 8 9
| BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext) { ... CString strFullString; if (strFullString.LoadString(nIDResource)) AfxExtractSubString(m_strTitle, strFullString, 0); ... }
|
原来在创建 MFC 工程时,资源文件中生成了它
1 2 3 4
| STRINGTABLE BEGIN IDR_MAINFRAME "MFCApplication2\n\nMFCApplication2\n\n\nMFCApplication2.Document\nMFCApplication2.Document" END
|
这是一个用\n
分隔的字符串列表,第一个子串就是默认标题。
关于其他子串的解释可以查看 CDocTemplate::GetDocString 的解释。