現在,我們還沒有看字體枚舉的回調函數是如何工作的。回調函數裏每次回調一個新的字體就需要創建一個CFontInfo對象,並寫入相應的信息,然後添加到CFontComboBox的成員m_pFontVec中。在這個回調的靜態函數裏就需要訪問CFontComboBox 對象,原來在調用這個函數的時候我們把一個CFontComboBox 對象的this指針通過LPARAM參數傳入了回調函數。一切就都不是問題了。
int CFontComboBox::EnumFontProc(ENUMLOGFONTEX *lpelfe,
NEWTEXTMETRICEX *lpntme,
DWORD FontType,
LPARAM lParam)
{
CFontComboBox *pThis = reinterpret_cast<CFontComboBox*>(lParam);
CFontInfo *pInfo = new CFontInfo;
pInfo->SetFontType(FontType);
pInfo->SetFontName(lpelfe->elfLogFont.lfFaceName);
DWORD dwFontType = FontType;
if (FontType & TRUETYPE_FONTTYPE)
{
DWORD dwFontFlags = lpntme->ntmTm.ntmFlags;
if (dwFontFlags & NTM_PS_OPENTYPE)
dwFontType |= PS_OPENTYPE_FONTTYPE;
else
dwFontType |=0;
if (dwFontFlags & NTM_TT_OPENTYPE)
dwFontType |= TT_OPENTYPE_FONTTYPE;
else
dwFontType |=0;
dwFontType |= (dwFontFlags & NTM_TYPE1 ? TYPE1_FONTTYPE : 0);
}
switch(dwFontType & 0x70007)
{
case (TRUETYPE_FONTTYPE | PS_OPENTYPE_FONTTYPE):
case (TRUETYPE_FONTTYPE | TT_OPENTYPE_FONTTYPE):
case (TRUETYPE_FONTTYPE | TYPE1_FONTTYPE):
pInfo->SetImage(3); break;
case RASTER_FONTTYPE:
case DEVICE_FONTTYPE:
case NULL: pInfo->SetImage(0XFF); break;
case TRUETYPE_FONTTYPE:
default: pInfo->SetImage(0); break;
}
pThis->m_pFontVec.push_back(pInfo);
return TRUE;
}
注意在上面使用的字體類型定義,需要在EnumFontProc函數前面直接加上如下的預編譯定義:
#ifndef NTM_PS_OPENTYPE
#define NTM_PS_OPENTYPE 0x00020000
#endif
#ifndef NTM_TT_OPENTYPE
#define NTM_TT_OPENTYPE 0x00040000
#endif
#ifndef PS_OPENTYPE_FONTTYPE
#define PS_OPENTYPE_FONTTYPE 0x10000
#define TT_OPENTYPE_FONTTYPE 0x20000
#define TYPE1_FONTTYPE 0x40000
#endif
接下來我們看看如何設置下面列表框的寬度,當用戶點擊小箭頭顯示下拉框時,會發出CBN_DROPDOWN消息,我們需要在這個消息函數裏設置下拉框的寬度。先在MFC的消息宏中添加消息映射:
BEGIN_MESSAGE_MAP(CFontComboBox, CComboBox)
ON_CONTROL_REFLECT(CBN_DROPDOWN, OnDropdown)
ON_WM_DESTROY()
END_MESSAGE_MAP()
在OnDropdown函數裏我們要計算所有字體名稱字符串的長度,用最大值做爲下拉框的顯示寬度,最好的顯示寬度還要加上滾動條和左邊的字體圖片的寬度,爲了方便使用,還定義了兩個字體預覽圖的寬度、高度值:
#define FNTIMG_X 20
#define FNTIMG_Y 12
void CFontComboBox::OnDropdown()
{
int nNumEntries = GetCount();
int nWidth = 0;
CString str;
CClientDC dc(this);
int nSave = dc.SaveDC();
dc.SelectObject(GetFont());
int nScrollWidth = ::GetSystemMetrics(SM_CXVSCROLL); //取滾動條寬度
for (int i = 0; i < nNumEntries; i++)
{
GetLBText(i, str);
int nLength = dc.GetTextExtent(str).cx + nScrollWidth;
nWidth = max(nWidth, nLength);
}
nWidth += dc.GetTextExtent("0").cx;
dc.RestoreDC(nSave);
if (!m_pFontVec.empty())
SetDroppedWidth(nWidth + FNTIMG_XSIZE); //設置寬度值
}
還記得上面枚舉函數裏我們用new動態申請了很多的字體信息CFontInfo對象,這些對象在窗口退出時需要釋放,否則會內存泄漏。釋放這些東西的位置就在窗口WM_DESTROY消息函數裏。這個消息是最理想的清除地方。
應用程序關閉過程: 當用戶按下菜單的close命令時,系統發出WM_CLOSE,通常程序的窗口函數不攔截這個消息,於是DefWinodwProc處理它,DefWinodwProc收到WM_CLOSE後,調用DestroyWindow把窗口清除,DestroyWindow本身會送出WM_DESTROY,程序對WM_DESTROY的標準反應就是調用PostQuitMessage,PostQuitMessage沒有其他的操作,就只送出WM_QUIT消息,而消息循環GetMessage得到這個消息後返回0,而結束了消息循環,再接着結束整個程序。在實際運用中,有的時候我們的程序窗口關閉了,但是在任務管理器裏還有進程存在,這個問題有時候就是因爲沒有調用PostQuitMessage(0);引起的。 |
void CFontComboBox::OnDestroy()
{
for (int i=0; i<m_pFontVec.size(); ++i)
delete m_pFontVec[i];
m_pFontVec.erase(m_pFontVec.begin(), m_pFontVec.end());
CComboBox::OnDestroy();
}
在釋放STL的很多指針容器時,容器對象的erase函數只是釋放每個指針所佔用的控件,並且釋放我們new的指針對象,所以在erase之前要先delete每個對象指針。