目录
一. button 控件
1. CButton类
(1) 设置/获取 文本 : SetWindowText/GetWindowText
(2) 设置图标:button类是不能设置背景色或背景图片的,只能设置"图标" + “文本” 的格式
//设置button图标icon
CButton* pButton = static_cast<CButton*>(GetDlgItem(IDC_BTN_ICO));
ASSERT(nullptr != pButton);
pButton->ModifyStyle(0, BS_TOP); //添加内容的对齐方式:BS_TOP, BS_LEFT等
//设置图标
HICON hIcon = ::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_BTN));
pButton->SetIcon(hIcon);
//调整文本位置
CRect stRect = { 10, 10, 0, 10 };
pButton->SetTextMargin(&stRect);//设置文本外边距
(3) 从资源加载位图:LoadBitmap/LoadImage只能加载bmp格式,完全满足不了现在的需求 ,所以在MFC里我们使用CImage来加载,如果要使用GDI+ 编程,就就使用Gdiplus::Image, Win32里可以使用CxImag类:
https://www.codeproject.com/Articles/1300/CxImage
CImage objImage;
objImage.Load(_T(".//res//button.png"));
//HBITMAP hBitmap = objImage.Detach();//可行
CBitmap objBitmap;
objBitmap.Attach(objImage.Detach());
pBitBtn->SetBitmap(objBitmap);
objBitmap.DeleteObject();
(4) 一般我们修改控件背景色,文本色都是通过OnCtrlColor来实现,但是实践后你会发现此法对Button无用,那如何去改变呢?一种方式是使用 CMFCButton,它可以轻松地修改背景色和文本色;另一种方法就是派生CButton类,重载DrawItem,实现自绘。
2. CSplitButton类
(1) “按钮” + “菜单”的组合方式:关于菜单的相关操作,在讲菜单的时候再进行说明
m_objSplitBtn.SetDropDownMenu(IDR_MENU_BTN, 0);//菜单资源
3. CMFCButton类
(1) 对比Button类:①可以设置背景色;②还是"图标"+"文本",只是图标和文本都可以设置三种状态normal, hot(鼠标进入按钮), pushed(按钮按下);③设置提示文本
//直接给变量出来?为什么不设计函数,不解
m_objMFCBtn.m_nFlatStyle = CMFCButton::BUTTONSTYLE_NOBORDERS;//按钮样式:没有边框
m_objMFCBtn.m_nAlignStyle = CMFCButton::ALIGN_LEFT;//文本对齐方式
m_objMFCBtn.m_bTopImage = TRUE;//图片和文本呈现方式
//m_objMFCBtn.m_bTransparent = TRUE; //设置透明度,TRUE时SetFaceColor无效
//设置图片
//m_objMFCBtn.SetImage(IDB_PNG_NORMAL, IDB_PNG_HOT, IDB_PNG_DISABLE);//设置按钮的normal, hot, disabled状态图
//m_objMFCBtn.SizeToContent();//按钮自适应图片和文本大小
//设置文本色
m_objMFCBtn.SetFaceColor(RGB(0, 191, 255));
m_objMFCBtn.SetTextColor(RGB(255, 255, 255));
m_objMFCBtn.SetTextHotColor(RGB(255, 105, 180));
//设置提示
//需要把CMFCButton属性里的fulltooltips(大工具窗口)设置为false
//SetToolTip会造成内存泄露,有一个m_pToolTip没有被释放,解决办法动态创建
m_objMFCBtn.SetTooltip(_T("提示我是一个MFC按钮"));
4. CMFCColorButton类
(1) MFC自己做的颜色拾取器,基本不需要再重新设计,设置下相关属性就行了
m_objMFCColorBtn.EnableAutomaticButton(_T("Default"), afxGlobalData.clrBtnText);//设置颜色说明文本
m_objMFCColorBtn.EnableOtherButton(_T("Other..."));//设置颜色拾取器的标题
m_objMFCColorBtn.SetColor((COLORREF)-1);//设置默认颜色
m_objMFCColorBtn.SetColorName((COLORREF)-1, _T("Default Color"));//设置弹出窗口的颜色说明文本
m_objMFCColorBtn.SetColumnsNumber(6);//设置弹出窗口的颜色列数
5. CMFCMenuButton类
(1) 和CSplitButton类似,也是“按钮” + “菜单”的格式,但是可以设置菜单弹出的位置:“下” 或 “右”
//CMFMenuButton: 这里的menu一定不要是局部变量,原因是菜单消息被转到MenuButton内部处理
m_objMenu.LoadMenu(IDR_MENU_BTN);
m_objMFCMenuBtn.m_hMenu = m_objMenu.GetSubMenu(0)->GetSafeHmenu();//关联menu
m_objMFCMenuBtn.m_bOSMenu = FALSE;
m_objMFCMenuBtn.m_bRightArrow = TRUE;
6. CRadioButton类
(1) button设计分组:eg. 6个按钮, "Ctrl + D" 设置6个按钮序号连续(eg.1,2,3,4,5,6),1和4设置了"group"属性,那么,1,2,3一组, 4,5,6一组
(2) 怎么获取按钮被选中呢?两种方式实现
① 消息响应
ON_BN_CLICKED(IDC_RADIO1, &CDlgButton::OnBnClickedRadio1)
... ...
void CDlgButton::OnBnClickedRadio1()
{
// TODO: 在此添加控件通知处理程序代码
m_iRadio = 1;
}
② API检测哪个按钮被选中:IsDlgButtonChecked
UINT auiCtrlID[] = { IDC_RADIO4, IDC_RADIO5, IDC_RADIO6 };
for (int iCount = 0; iCount < ARRAYSIZE(auiCtrlID); iCount++)
{
if (BST_CHECKED == IsDlgButtonChecked(auiCtrlID[iCount]))
{
CString strRadio;
strRadio.Format(_T("Radio %d is selected"), iCount + 4);
AfxMessageBox(strRadio);
}
}
7. CCheckBox类
(1) 可以多选,其他用法和CRadioButton差不多,这里不再进行说明
二. StaticText
1. CStatic类
(1) 更多地用来设置提醒文字,我们还是看下如何修改它的背景色和文本色
ON_WM_CTLCOLOR()
... ...
HBRUSH CDlgText::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: 在此更改 DC 的任何特性
if ((CTLCOLOR_STATIC == nCtlColor) && (&m_objStcText == pWnd))
{
//pDC->SetBkMode(TRANSPARENT);
pDC->SetBkColor(RGB(255, 255, 0));//设置字体背景色
pDC->SetTextColor(RGB(255, 0, 0));//设置字体颜色
pDC->SelectObject(&m_objFont);//设置字体大小,注意销毁字体
//return (HBRUSH)::GetStockObject(WHITE_BRUSH);//选择白色画刷,不用系统画刷 可选颜色太少了
return ::CreateSolidBrush(RGB(0, 255, 255));//自定义画刷,这样就可以画控件的背景色了
}
// TODO: 如果默认的不是所需画笔,则返回另一个画笔
return hbr;
}
2. CLinkCtrl类
(1) 对比CStatic类,多一个"超链接"文本设置, 设置控件颜色还是使用OnCtrlColor
m_objFont.CreatePointFont(180, _T("宋体"));
//link text
m_objLinkFont.CreatePointFont(180, _T("宋体"));
m_objLinkCtrl.SetFont(&m_objLinkFont);//设置字体大小
m_objLinkCtrl.SetWindowText(_T("国内搜索用度娘:<a href=\"http://www.baidu.com\">百度搜索</a> \
国外搜索用谷歌:<a href=\"http://www.google.com\">谷歌搜索</a> "));
//设置超链接信息:这样设置之后就能修改超链接的文本颜色了
LITEM stItem = { 0 };
stItem.mask = LIF_ITEMINDEX | LIF_STATE;
stItem.state = LIS_DEFAULTCOLORS; // 修改默认超链接颜色需要vista以上系统才支持.
stItem.stateMask = LIS_DEFAULTCOLORS;
//
stItem.iLink = 0;
m_objLinkCtrl.SetItem(&stItem);//链接1
stItem.iLink = 1;
m_objLinkCtrl.SetItem(&stItem);//链接2
(2) 点击超链接文本的消息响应
ON_NOTIFY(NM_CLICK, IDC_SYSLINK_TEXT, &CDlgText::OnNMClickSyslinkText)
... ...
void CDlgText::OnNMClickSyslinkText(NMHDR *pNMHDR, LRESULT *pResult)
{
PNMLINK pNMlink = (PNMLINK)pNMHDR;
::ShellExecute(NULL, _T("open"), pNMlink->item.szUrl, NULL, NULL, SW_SHOWNORMAL);
*pResult = 0;
}
三. CComboBox
1. CComboBox类
(1) 用法比较简单,添加字符AddString,插入指定位置字符InsertString, 设置选择序号SetCurSel
LPCTSTR alpString[] = { _T("abcd"), _T("abdef"), _T("abefg"), _T("abcdddd"), _T("abcc") };
for (int iCount = 0; iCount < ARRAYSIZE(alpString); iCount++)
{
m_objComboBox.AddString(alpString[iCount]);//添加
}
m_objComboBox.InsertString(3, _T("在河上一起游泳"));//插入
m_objComboBox.SetCurSel(0);//选择第几条列表项
2. CComboBoxEx类
(1) 在CComboBox类的基础上,添加了"图标" + "文本"的结合方式, 这里使用了CImageList类,用来绑定CComBoxEx类,CImageList需要手动释放
//ComboBoxEx
m_objImageList.Create(16, 16, ILC_COLOR32, 3, 3);//背景透明,默认为ILC_COLORDDB
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO1));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO2));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO3));
m_objComboBoxEx.SetImageList(&m_objImageList);//设置图像列表
//comboBox item
COMBOBOXEXITEMW stComboItem = { 0 };
LPTSTR alpFruit[] = {_T("蓝梅"), _T("棒棒糖"), _T("开心果")};
for (int iNum = 0; iNum < m_objImageList.GetImageCount(); iNum++)
{
stComboItem.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
stComboItem.iItem = iNum;
stComboItem.iImage = iNum;
stComboItem.iSelectedImage = iNum;
stComboItem.pszText = alpFruit[iNum];
m_objComboBoxEx.InsertItem(&stComboItem);
}
m_objComboBoxEx.SetCurSel(0);
3. CMFCFontComboBox
(1) MFC自己做了一个系统字体选择的Combox,可以直接使用
m_objFontComboBox.SelectFont(_T("宋体"));
四. CEdit/CRichEdit
1. CEdit类
(1) 有一些功能可以直接在"属性"栏设置,eg,只能输入数字,只读,密码输入方式等,其中OEM字符说明:
https://www.cnblogs.com/KevinYang/archive/2010/06/18/1760597.html
(2) 设置背景色,文本色等,使用OnCtrlColor实现
HBRUSH CDlgEdit::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
if (GetDlgItem(IDC_EDIT_NORMAL)->m_hWnd == pWnd->m_hWnd)
{
pDC->SetBkColor(RGB(255, 255, 0));
pDC->SetTextColor(RGB(255, 0, 0));
}
// TODO: 如果默认的不是所需画笔,则返回另一个画笔
return hbr;
}
(3) 多行显示:SetSel 和 ReplaceSel 实现
CString strEdit;
m_objEditMulti.GetWindowText(strEdit);
DWORD dwEditLen = m_objEditMulti.GetWindowTextLength();
if (0 >= dwEditLen)
{
strEdit = _T("我想测试一下换行功能\r\n");
}
//m_objEditMulti.SetSel(0, -1);//选择编辑器所有文本
m_objEditMulti.SetSel(-1, 0);//不选择文本
m_objEditMulti.ReplaceSel(strEdit);
m_objEditMulti.ScrollWindow(0, 0);//光标始终停在尾部
(4) 变量类型:UpdateData(FALSE):显示变量在界面上 UpdateData(TRUE):获取变量的值
m_dbEdit = 3.14159;
UpdateData(FALSE);//在edit上显示
... ...
CString strData;
UpdateData(TRUE);//获取Edit上内容
strData.Format(_T("%lf"), m_dbEdit);
AfxMessageBox(strData);
(5) 文件拖放到CEdit上显示: CEdit的父窗口属性:Accept files为true
ON_WM_DROPFILES()
... ...
void CDlgEdit::OnDropFiles(HDROP hDropInfo)//拖放文件处理
{
CString strFilePath;
int iDropCount = DragQueryFile(hDropInfo, -1, NULL, 0);
if (1 == iDropCount)//单个文件
{
DragQueryFile(hDropInfo, 0, strFilePath.GetBuffer(MAX_PATH), MAX_PATH);//获取文件名
//文件读写及网络传输尽量使用ANSI:数据量小,传送快
CFile objFile;
if (TRUE == objFile.Open(strFilePath, CFile::modeRead))
{
UINT uiBuffLen = (UINT)objFile.GetLength() + 1;//给文件一个'\0'结束这符
char* pcBuffer = new char[uiBuffLen];
ZeroMemory(pcBuffer, uiBuffLen);
objFile.Read(pcBuffer, uiBuffLen);//读文本
#if _UNICODE
ATL::CA2W objA2W(pcBuffer);
TCHAR* pcBuffW = (TCHAR*)objA2W;
m_objEditMulti.SetWindowText(pcBuffW);
#else
m_objEditMulti.SetWindowText(pcBuffer);
#endif
objFile.Close();
delete[]pcBuffer;
pcBuffer = nullptr;
}
}
else if (1 < iDropCount)//文件夹
{
//暂时不处理
}
CDialogEx::OnDropFiles(hDropInfo);
}
(6) ANSI, UNICODE乱码在编码时使用位置: 文件也要是ANSI格式,ANSI 文件操作及网络数据传输时使用:数据量小一倍,传送快
2. CMFCEditBrowseCtrl类
(1) "Edit" + "Button"组合模式:用来做选择文件/文件夹的组合框,也不需要写button的响应函数,系统都帮我们做好了,非常方便
//m_objEditBrowse.EnableBrowseButton(FALSE);//是否显示选择按钮
m_objEditBrowse.EnableFileBrowseButton(nullptr, _T("zip Files(*.zip)|*.zip|"));//使用"选择文件"模式,可以过滤文件后缀名
//m_objEditBrowse.EnableFolderBrowseButton();//使用"选择文件夹"模式
3. CRichEdit
(1) 对比Edit设置文本色使用OnCtrlColor, CRichEdit不用,而是使用CHARFORMAT2来设置,并且可以设置某一部分的文本大小和文本色
m_objRichEdit.SetWindowText(TEXT("做一个富文本编辑框例子\n"));
CHARFORMAT2 cf;
ZeroMemory(&cf, sizeof(cf));
cf.cbSize = sizeof(CHARFORMAT2);
cf.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE/* | CFM_BACKCOLOR*/; //
cf.crBackColor = RGB(0, 255, 0); // 背景色
cf.crTextColor = RGB(255, 0, 0); //文字颜色
cf.yHeight = 440;
_tcscpy_s(cf.szFaceName, _T("微软雅黑"));
m_objRichEdit.SetSel(1, 6); // 选中区域文字
m_objRichEdit.SetSelectionCharFormat(cf);
m_objRichEdit.SetSel(-1, -1);
(2) CRichEdit最大的特点是可以加载OLE控件,这样就大大丰富了CRichEdit可以使用的场景,像word,excel,pdf,图像,视频等都可以被加载进来,codeproject 上有一个例子加载rtf文件:https://www.codeproject.com/Articles/9541/A-Rich-Edit-Control-That-Displays-Bitmaps-and-Othe
这里不使用派生,加载一下图像(bmp,pgn,jpg)
void CDlgEdit::AddImage(CRichEditCtrl& objRichEdit, LPCTSTR strFilePath)
{
//获取位图
HBITMAP hBmp = NULL;
Gdiplus::Bitmap * pBmp = Gdiplus::Bitmap::FromFile(strFilePath);
pBmp->GetHBITMAP(0, &hBmp);
STGMEDIUM stgm;
stgm.tymed = TYMED_GDI;
stgm.hBitmap = hBmp;
stgm.pUnkForRelease = NULL;
FORMATETC fm;
fm.cfFormat = CF_BITMAP;
fm.ptd = NULL;
fm.dwAspect = DVASPECT_CONTENT;
fm.lindex = -1;
fm.tymed = TYMED_GDI;
COleDataSource oleDataSource;
oleDataSource.CacheData(CF_BITMAP, &stgm);
LPDATAOBJECT dataObject = (LPDATAOBJECT)oleDataSource.GetInterface(&IID_IDataObject);
if (OleQueryCreateFromData(dataObject) != OLE_S_STATIC)
return;
LPOLECLIENTSITE oleClientSite;
if (S_OK != objRichEdit.GetIRichEditOle()->GetClientSite(&oleClientSite))
return;
// 内存分配
LPLOCKBYTES lockBytes = NULL;
if (S_OK == CreateILockBytesOnHGlobal(NULL, TRUE, &lockBytes) && lockBytes)
{
IStorage *storage = NULL;
if (S_OK == StgCreateDocfileOnILockBytes(lockBytes, STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_READWRITE, 0, &storage) && storage)
{
IOleObject *oleObject = NULL;
if (S_OK == OleCreateStaticFromData(dataObject, IID_IOleObject, OLERENDER_FORMAT, &fm, oleClientSite, storage, (void **)&oleObject) && oleObject)
{
CLSID clsid;
if (S_OK == oleObject->GetUserClassID(&clsid))
{
REOBJECT reobject = { sizeof(REOBJECT) };
reobject.clsid = clsid;
reobject.cp = REO_CP_SELECTION;
reobject.dvaspect = DVASPECT_CONTENT;
reobject.poleobj = oleObject;
reobject.polesite = oleClientSite;
reobject.pstg = storage;
// 插入OLE对象
objRichEdit.GetIRichEditOle()->InsertObject(&reobject);
}
oleObject->Release();
}
storage->Release();
}
lockBytes->Release();
}
oleClientSite->Release();
}
... ...
AddImage(m_objRichEdit, _T(".\\res\\button.png"));
m_objRichEdit.SetSel(-1, -1);
m_objRichEdit.ReplaceSel(_T("\n"));
AddImage(m_objRichEdit, _T(".\\res\\1.gif"));
m_objRichEdit.SetSel(-1, -1);
m_objRichEdit.ReplaceSel(_T("\n"));
AddImage(m_objRichEdit, _T(".\\res\\2.jpg"));
五. Pic Ctrl
1. picCtrl也是CStatic类,在Static类里不讲是为了让功能专一,加载位图(bmp, png, jpg等), 结合CImage使用
void CDlgPic::ShowImage(CString strFilePath)
{
if (INVALID_FILE_ATTRIBUTES != GetFileAttributes(strFilePath))//文件路径合法
{
CImage objImage;
HRESULT hr = objImage.Load(strFilePath);
if (SUCCEEDED(hr))
{
CRect stPicRect = { 0 };
m_objPic.GetClientRect(stPicRect);//获取picControl的大小
//设置图片坐标
CRect stImagePos = { 0 };
AdjustImageRect(stImagePos, stPicRect, objImage.GetWidth(), objImage.GetHeight());
//开始绘图
CDC* pDC = CDC::FromHandle(::GetDC(m_objPic.m_hWnd));
pDC->FillRect(stPicRect, CBrush::FromHandle(::CreateSolidBrush(GetSysColor(COLOR_3DFACE))));//背景填充
SetStretchBltMode(pDC->m_hDC, COLORONCOLOR);//在调用strechblt前需要设置设备上下文为拉伸模式
objImage.StretchBlt(pDC->m_hDC, stImagePos.left, stImagePos.top,
stImagePos.right - stImagePos.left, stImagePos.bottom - stImagePos.top, SRCCOPY);//可以拉伸
::ReleaseDC(m_objPic.m_hWnd, pDC->m_hDC);
}
}
}
2. 结合GDI+ 和 OnTime 加载.gif图片
void CDlgPic::DrawGif(LPCTSTR lpFilePath)
{
if (nullptr == lpFilePath) return;
m_pGifImage = Gdiplus::Image::FromFile(lpFilePath);//加载图片
if (nullptr != m_pGifImage)
{
//获取picControl的大小
CRect stPicRect = { 0 };
m_objPic.GetClientRect(stPicRect);
//设置图片坐标
CRect stImagePos;
AdjustImageRect(stImagePos, stPicRect, m_pGifImage->GetWidth(), m_pGifImage->GetHeight());
//坐标转换
m_stGifPos.X = (Gdiplus::REAL)stImagePos.left;
m_stGifPos.Y = (Gdiplus::REAL)stImagePos.top;
m_stGifPos.Width = (Gdiplus::REAL)stImagePos.Width();
m_stGifPos.Height = (Gdiplus::REAL)stImagePos.Height();
//绘制
m_uiCurFrame = 0;
SetTimer(1, 100, NULL);
}
}
... ...
void CDlgPic::OnTimer(UINT_PTR nIDEvent)
{
PlayGif();
CDialogEx::OnTimer(nIDEvent);
}
... ...
void CDlgPic::PlayGif()
{
//获取PIC CONTROL大小
CRect stPicRect = { 0 };
m_objPic.GetClientRect(stPicRect);
//获取gif信息
GUID acGuidBuf[MAX_PATH] = { 0 };
UINT uiCount = m_pGifImage->GetFrameDimensionsCount();//GIF文件的帧的维数
m_pGifImage->GetFrameDimensionsList(acGuidBuf, uiCount);// 获得图像帧的GUID
UINT uiFrameSize = m_pGifImage->GetFrameCount(&acGuidBuf[0]);//动图的总画面数
//绘制
CDC* pDC = CDC::FromHandle(::GetDC(m_objPic.m_hWnd));
Gdiplus::Graphics graphics(pDC->GetSafeHdc());
if (0 == m_uiCurFrame) pDC->FillRect(stPicRect, CBrush::FromHandle(::CreateSolidBrush(GetSysColor(COLOR_3DFACE))));//背景填充
graphics.DrawImage(m_pGifImage, m_stGifPos, 0, 0, (Gdiplus::REAL)m_pGifImage->GetWidth(), (Gdiplus::REAL)m_pGifImage->GetHeight(), Gdiplus::UnitPixel);
//设置gif下一帧
GUID pageGuid = Gdiplus::FrameDimensionTime;
m_uiCurFrame = (++m_uiCurFrame) % uiFrameSize;
m_pGifImage->SelectActiveFrame(&pageGuid, m_uiCurFrame);//获取下一帧
::ReleaseDC(m_objPic.m_hWnd, pDC->m_hDC);
}
六. Slider/Progress
1. 这两个控件可以简单,基本用法也差不多,初始化一般就设置其范围:SetRange
m_objSlider.SetRange(0, 100);
m_objProgress.SetRange(0, 100);
2. Slider滑块移动的响应消息
ON_NOTIFY(NM_CUSTOMDRAW, IDC_SLIDER_PRO, &CDlgSliderPro::OnNMCustomdrawSliderPro)
... ...
void CDlgSliderPro::OnNMCustomdrawSliderPro(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
int iPos = m_objSlider.GetPos();
m_objProgress.SetPos(iPos);
*pResult = 0;
}
七. SPinCtrl
1. 这个按钮一般是与CEdit结合使用的,那么怎么将两个控件关联呢?SetBuddy
m_objSpinButton.SetBuddy(GetDlgItem(IDC_EDIT_SPIN));
2. 处理整数:将Edit属性的“Number”设为true, 这样就可以上/下增/减1
//interger
m_objSpinButton.SetBuddy(GetDlgItem(IDC_EDIT_SPIN));
m_objSpinButton.SetRange(0, 100);
m_objSpinButton.SetPos(50);
3. 处理Double:将Edit设置为double类型,不知道的可以回看CEdit类,再修改上/下按钮的响应
//double
m_objSpinDouble.SetBuddy(GetDlgItem(IDC_EDIT_DOUBLE));
m_objSpinDouble.SetRange(-10, 10);
m_dbEdit = 0.1;
UpdateData(FALSE);//在Edit显示
... ...
ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN_DOUBLE, &CDlgSpin::OnDeltaposSpinDouble)
... ...
void CDlgSpin::OnDeltaposSpinDouble(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
int iMin, iMax;
m_objSpinDouble.GetRange(iMin, iMax);
UpdateData(TRUE);//获取edit数据
if (-1 == pNMUpDown->iDelta)
{
m_dbEdit = (iMin < m_dbEdit) ? (m_dbEdit - 0.1) : iMin;
}
else
{
m_dbEdit = (iMax > m_dbEdit) ? (m_dbEdit + 0.1) : iMax;
}
UpdateData(FALSE);
*pResult = 0;
}
4. spin可以设置范围,但是Edit还没有限制,虽然 可以设置超出范围的值,那怎么办?
ON_EN_UPDATE(IDC_EDIT_DOUBLE, &CDlgSpin::OnEnUpdateEditDouble)
... ...
void CDlgSpin::OnEnUpdateEditDouble()//处理超出范围的数字
{
int iMin, iMax;
m_objSpinDouble.GetRange(iMin, iMax);
UpdateData(TRUE);
if (iMin > m_dbEdit)
{
m_dbEdit = iMin;
UpdateData(FALSE);
}
if (iMax < m_dbEdit)
{
m_dbEdit = iMax;
UpdateData(FALSE);
}
}
八. CHotKey
1. 注册系统热键
void CDlgHotkey::OnBnClickedBtnHotkey()
{
if (0 != m_iHotkeyID) ::UnregisterHotKey(m_objHotkey.m_hWnd, m_iHotkeyID);//注销热键
WORD wModifiers = 0;
WORD wVirtualKeyCode = 0;
m_iHotkeyID = ::GlobalAddAtom(m_objHotkey.GetHotKeyName());//获取一个全局的唯一标识符
m_objHotkey.GetHotKey(wVirtualKeyCode, wModifiers);//获取系统热键
wModifiers = HotkeyToMod(wModifiers);//转换系统热键:系统热键shift,alt与传统键shift,alt不一样
::RegisterHotKey(GetSafeHwnd(), m_iHotkeyID, wModifiers, wVirtualKeyCode);//注册在hotkey所在的窗口
}
2. 这里有一点特别要注意,键盘的虚拟键码与热键的虚拟键码shift,alt位置相反,所以需要转换一下
UINT CDlgHotkey::HotkeyToMod(UINT fsModifiers)
{
if ((fsModifiers & HOTKEYF_SHIFT) && !(fsModifiers & HOTKEYF_ALT)) // shift转alt
{
fsModifiers &= ~HOTKEYF_SHIFT;
fsModifiers |= MOD_SHIFT;
}
else if (!(fsModifiers & HOTKEYF_SHIFT) && (fsModifiers & HOTKEYF_ALT)) // alt转shift
{
fsModifiers &= ~HOTKEYF_ALT;
fsModifiers |= MOD_ALT;
}
return fsModifiers;
}
3.响应系统热键:如果注册了很多的系统热键,就要在这里对键码值进行判断了
ON_WM_HOTKEY()
... ...
void CDlgHotkey::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2)
{
if (nHotKeyId == m_iHotkeyID)
{
AfxMessageBox(_T("响应系统热键"), MB_TOPMOST);//窗口置顶
}
CDialogEx::OnHotKey(nHotKeyId, nKey1, nKey2);
}
九. CAnimalCtrl
1. 操作很简单,open, play,stop三个函数,但是对视频要求比较高,首先只能是.avi格式且不能被压缩,不能播放声音等等,适合做一些简单的动画效果,要加载复杂的视频可以使用ActiveX控件
CString strFilePath;
m_objEditBrowse.GetWindowText(strFilePath);
if (INVALID_FILE_ATTRIBUTES != GetFileAttributes(strFilePath))//文件路径合法
{
if (TRUE == m_objAnimate.Open(strFilePath))
{
m_objAnimate.Play(0, -1, -1);
}
}
else//直接加载内存avi
{
if (TRUE == m_objAnimate.Open(IDR_AVI_ANIMATE))
{
m_objAnimate.Play(0, -1, -1);
}
}
十. CDateTime
1. CDateTime
(1) 设置日期的格式
m_objDateTime.SetFormat(_T("yyy-MM-dd HH:mm:ss"));
(2) 获取本地时间
CString strTime;
tm stLocalTime = { 0 };
CTime objTime = CTime::GetCurrentTime();
objTime.GetLocalTm(&stLocalTime);//本地时间
strTime.Format(_T("Local: %d-%d-%d %d:%d:%d"), stLocalTime.tm_year + 1900, stLocalTime.tm_mon + 1, stLocalTime.tm_mday,
stLocalTime.tm_hour, stLocalTime.tm_min, stLocalTime.tm_sec);
AfxMessageBox(strTime);
(3) 格林尼治时间:位于本初子午线的时间,与本地时间相差了时区数个小时
CString strTime;
tm stUTCTime = { 0 };
CTime objTime = CTime::GetCurrentTime();
objTime.GetGmtTm(&stUTCTime);//格林尼治时间 与本地时间相差时区个小时数
strTime.Format(_T("UTC: %d-%d-%d %d:%d:%d"), stUTCTime.tm_year + 1900, stUTCTime.tm_mon + 1, stUTCTime.tm_mday,
stUTCTime.tm_hour, stUTCTime.tm_min, stUTCTime.tm_sec);
AfxMessageBox(strTime);
2. CMonthCtrl
(1) 系统给我们画了一个月历控件,获取当前点击的时间响应消息
ON_NOTIFY(MCN_SELECT, IDC_MONTH, &CDlgDatetime::OnMcnSelectMonth)
... ...
void CDlgDatetime::OnMcnSelectMonth(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMSELCHANGE pSelChange = reinterpret_cast<LPNMSELCHANGE>(pNMHDR);
CString strTime;
strTime.Format(_T("%d-%d-%d"), pSelChange->stSelStart.wYear, pSelChange->stSelStart.wMonth, pSelChange->stSelStart.wDay);
AfxMessageBox(strTime);
*pResult = 0;
}
十一. IP Ctrl
1. CIPAddressCtrl
(1) 设置/获取 地址:很方便,但也有限制,就是只能使用ipv4格式
m_objIP.SetAddress(192, 168, 0, 1);//设置,只能设置IpV4格式
... ...
if (!m_objIP.IsBlank())
{
DWORD dwIpAddr;
CString strIpAddr;
m_objIP.GetAddress(dwIpAddr);
strIpAddr.Format(_T("ip地址:%d.%d.%d.%d"), (dwIpAddr >> 24) & 0xff, (dwIpAddr >> 16) & 0xff, (dwIpAddr >> 8) & 0xff, dwIpAddr & 0xff, (dwIpAddr >> 8) & 0xff);
AfxMessageBox(strIpAddr);
}
2. CNetAddressCtrl
(1) 派生出CEdit类,设置比较自由:ipv4, ipv6, 网址均可,还可以限制其设置的类型
m_objNetAddr.SetAllowType(NET_STRING_IPV4_ADDRESS);
//m_objNetAddr.SetAllowType(NET_STRING_IPV6_ADDRESS);
//m_objNetAddr.SetAllowType(NET_STRING_NAMED_ADDRESS);
... ...
void CDlgIp::OnBnClickedButton2()//获取 MSDN例子
{
NC_ADDRESS m_na;
NET_ADDRESS_INFO m_nai;
m_na.pAddrInfo = &m_nai;
HRESULT rslt = m_objNetAddr.GetAddress(&m_na);
if (rslt != S_OK)
m_objNetAddr.DisplayErrorTip();
else
MessageBox(_T("Success!"), _T("Validation Results"));
}
十二. List Ctrl
1. CListBox
(1)属性里可以设置“多选”, “排序”, "多列"这里的多列表示一列不够显示时,用第N列帮忙显示。添加item还是AddString
m_objListBox.AddString(_T("棒棒糖"));
for (int iNum = 0; iNum < 100; iNum++)
{
CString strData;
strData.Format(_T("开心果%d"), iNum);
m_objListBox.AddString(strData);
}
(2) 设置/获取多选项
//设置多选列表项
for (int iCount = 0; iCount < 100; iCount += 3)
{
m_objListBox.SetSel(iCount);
}
... ...
//获取多选项的内容
CArray<int, int> arySelData;
int iSelNum = m_objListBox.GetSelCount();
arySelData.SetSize(iSelNum);//
m_objListBox.GetSelItems(iSelNum, arySelData.GetData());//获取选项的序号
//遍历
for (int iSer = 0; iSer < arySelData.GetSize(); iSer++)
{
CString strData;
m_objListBox.GetText(arySelData[iSer], strData);
//do something;
//if (2 == iSer)//打印一下其中一行被选中的项
//{
// CString strText;
// strText.Format(_T("Row: %d text: "), arySelData[iSer]);
// strText += strData;
// AfxMessageBox(strText);
//}
}
(3) ListBox的item比较简单,可能满足不了我们的需求,那我们就可以派生CListBox重载DrawItem达到加载位图,改变文本色的效果,这里有一个MSDN的例子,可以参考
//MSDN的例子
//void CMyODListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
//{
// ASSERT(lpDrawItemStruct->CtlType == ODT_LISTBOX);
// LPCTSTR lpszText = (LPCTSTR)lpDrawItemStruct->itemData;
// ASSERT(lpszText != NULL);
// CDC dc;
// dc.Attach(lpDrawItemStruct->hDC);
// // Save these value to restore them when done drawing.
// COLORREF crOldTextColor = dc.GetTextColor();
// COLORREF crOldBkColor = dc.GetBkColor();
// // If this item is selected, set the background color
// // and the text color to appropriate values. Also, erase
// // rect by filling it with the background color.
// if ((lpDrawItemStruct->itemAction | ODA_SELECT) &&
// (lpDrawItemStruct->itemState & ODS_SELECTED))
// {
// dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
// dc.SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
// dc.FillSolidRect(&lpDrawItemStruct->rcItem,
// ::GetSysColor(COLOR_HIGHLIGHT));
// }
// else
// {
// dc.FillSolidRect(&lpDrawItemStruct->rcItem, crOldBkColor);
// }
// // If this item has the focus, draw a red frame around the
// // item's rect.
// if ((lpDrawItemStruct->itemAction | ODA_FOCUS) &&
// (lpDrawItemStruct->itemState & ODS_FOCUS))
// {
// CBrush br(RGB(255, 0, 0));
// dc.FrameRect(&lpDrawItemStruct->rcItem, &br);
// }
// // Draw the text.
// dc.DrawText(
// lpszText,
// (int)_tcslen(lpszText),
// &lpDrawItemStruct->rcItem,
// DT_CENTER | DT_SINGLELINE | DT_VCENTER);
// // Reset the background color and the text color back to their
// // original values.
// dc.SetTextColor(crOldTextColor);
// dc.SetBkColor(crOldBkColor);
// dc.Detach();
//}
2. CVSListBox
(1)设置/获取item:这又是系统为我们提供了一个listbox,像VS添加库文件时就使用到了这个控件
void CDlgListTree::InitVSListBox()
{
//设置信息
m_objListBoxVS.SetWindowTextW(_T("水果库存信息"));
for (int iNum = 0; iNum < 30; iNum++)
{
CString strData;
strData.Format(_T("开心果%d"), iNum);
m_objListBoxVS.AddItem(strData);
}
//获取信息
CString strText(_T(""));
for (int iCount = 0; iCount < m_objListBoxVS.GetCount(); iCount++)
{
CString strTemp = m_objListBoxVS.GetItemText(iCount);
strText += strTemp;
}
//AfxMessageBox(strText);
}
3. CListCtrl
(1) 设置表信息:属性里的style:report报表模式,
//设置表信息
DWORD dwStyleEx = m_objListCtrl.GetExStyle();
dwStyleEx &= ~LVS_EX_CHECKBOXES;//系统生成的listctrl会有这一项,暂时不用
m_objListCtrl.SetExtendedStyle(dwStyleEx | LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);//扩展样式:网格线 | 选择一行 | 图片
(2) 设置表头:CHeaderCtrl , 这里给表头加了图标,里面关于列交换这一块放到后面讲
//插入表头
for (int iColumn = 0; iColumn < 4; iColumn++) m_objListCtrl.InsertColumn(iColumn, _T(""));
... ...
//可以设置表头的宽,高, "icon + text", 全选checkbox
//如果要设置表头文本色,背景图片,背景色则要派生CHeaderCtrl,在DrawItem里绘制即可
void CDlgListTree::SetHeaderCtrl(CListCtrl* pobjListCtrl)
{
if (nullptr == pobjListCtrl) return;
//获取表头
CHeaderCtrl* pHeaderCtrl = pobjListCtrl->GetHeaderCtrl();
ASSERT(nullptr != pHeaderCtrl);
//设置模式
pHeaderCtrl->ModifyStyle(0, HDS_CHECKBOXES);//保证HDF_CHECKBOX有效
//设置图片列表
m_objImageList.Create(16, 16, ILC_COLOR32, 3, 3);//背景透明,默认为ILC_COLORDDB
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO1));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO2));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO3));
pHeaderCtrl->SetImageList(&m_objImageList);//设置图像列表
//=======================================================================
//如果子项要加载位图而且第一列不想加载位图时,用此法解决第一列默认预留一个位图位置
CArray<int, int> aryColumn;
aryColumn.SetSize(pHeaderCtrl->GetItemCount());
pobjListCtrl->GetColumnOrderArray(aryColumn.GetData(), pHeaderCtrl->GetItemCount());
//我们要交换的是第0列和第2列
int iTemp = aryColumn[0];
aryColumn[0] = aryColumn[2];
aryColumn[2] = iTemp;
pobjListCtrl->SetColumnOrderArray(pHeaderCtrl->GetItemCount(), aryColumn.GetData());//设置列序号
//=======================================================================
//设置表头:区别于InsertColumn()
LPTSTR alpInfo[] = { _T("性别"), _T("姓名"), _T("序号"), _T("成绩") };
for (int iNum = 0; iNum < pHeaderCtrl->GetItemCount(); iNum++)
{
HDITEM stHdItem = { 0 };
pHeaderCtrl->GetItem(iNum, &stHdItem);
if (2 == iNum)//对第一列进行不同设置
{
stHdItem.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;
stHdItem.fmt = HDF_STRING | HDF_CHECKBOX | HDF_CENTER;//解决首列字体不能居中的问题, HDF_CHECKBOX必须关联HDS_CHECKBOXES
stHdItem.cxy = 60;
}
else
{
stHdItem.mask = HDI_TEXT | HDI_IMAGE | HDI_FORMAT | HDI_WIDTH;
stHdItem.fmt = HDF_STRING | HDF_CENTER /*| HDF_BITMAP_ON_RIGHT*/; //居中, 位图在右边
stHdItem.iImage = (0 == iNum) ? (iNum + 1) : iNum - 1;
stHdItem.cxy = 97;//表头宽
}
stHdItem.pszText = alpInfo[iNum];//文本
pHeaderCtrl->SetItem(iNum, &stHdItem);
}
}
(3) 禁止拖动表头:
BOOL CXXXX::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
// TODO: Add your specialized code here and/or call the base class
//屏蔽两个消息通知码,使得禁止拖动List表头
NMHEADER* pNMHeader = (NMHEADER*)lParam;
if(((pNMHeader->hdr.code == HDN_BEGINTRACKW) |
(pNMHeader->hdr.code == HDN_DIVIDERDBLCLICKW)))
{
*pResult = TRUE;
return TRUE;
}
return CDialog::OnNotify(wParam, lParam, pResult);
}
(4) 插入单元格
//插入列
for (int iColumn = 0; iColumn < 4; iColumn++) m_objListCtrl.InsertColumn(iColumn, _T(""));
... ...
//插入行及单元格
//正常插入单元格
//for (int iRow = 0; iRow < 100 * 1000; iRow++)
//{
// CString strRow;
// strRow.Format(_T("%d"), iRow + 1);
// m_objListCtrl.InsertItem(iRow, strRow);//添加行
// m_objListCtrl.SetItemText(iRow, 1, _T("张三"));
// m_objListCtrl.SetItemText(iRow, 2, _T("男"));
// m_objListCtrl.SetItemText(iRow, 3, _T("90"));
//}
(5) 设置每行的文本背景色或文本色, 设置某个单元格的文本背景色或文本色
ON_NOTIFY(NM_CUSTOMDRAW, IDC_LIST_CTRL, &CDlgListTree::OnNMCustomdrawListCtrl)
... ...
//设置每行文本色或单元格文本色
void CDlgListTree::OnNMCustomdrawListCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
*pResult = CDRF_DODEFAULT;
NMLVCUSTOMDRAW * lplvdr = (NMLVCUSTOMDRAW*)pNMHDR;
switch (lplvdr->nmcd.dwDrawStage)
{
case CDDS_PREPAINT://让其发出项改变的消息.
{
*pResult = CDRF_NOTIFYITEMDRAW;//控制每行的信息.
}
break;
case CDDS_ITEMPREPAINT://按行处理信息
{
if (5 == (int)lplvdr->nmcd.dwItemSpec)//行处理
{
lplvdr->clrTextBk = RGB(0, 255, 0);
}
//*pResult = CDRF_DODEFAULT;//只修改行, 则返回defaut.
*pResult = CDRF_NOTIFYSUBITEMDRAW; //控制每个单元格的信息.
}
break;
case CDDS_SUBITEM | CDDS_ITEMPREPAINT://按项处理信息(单元格)
{
//lplvdr->iSubItem只有CDRF_NOTIFYSUBITEMDRAW时才有效,否则为0
if ((3 == (int)lplvdr->nmcd.dwItemSpec) && (3 == lplvdr->iSubItem))//3 行 3 列
{
lplvdr->clrText = RGB(255, 0, 0);//文本色
lplvdr->clrTextBk = RGB(255, 255, 0);//文本背景色
}
*pResult = CDRF_DODEFAULT;
}
break;
}
}
(6) 假如我们要从数据读取100万条数据在List显示,大家可以试试需要加载多久,然后再拖动滚动条再试试?那怎么才能像几十条数据那样丝滑般流畅呢?这里使用虚表:始终从List里取N条数据进行显示,不管你怎么拖动鼠标,看到的永远只有N条数据。
① 使用虚拟技术时,需要将CListCtrl控件的Owner Data属性设置为ture。
② 给虚拟列表添加元素时,不需要使用InserItem函数,通过调用SetItemCount设置数据总个数,然后由系统产生不同的消息, 在相应的消息响应函数中完成插入工作。
③ 虚拟列表向父窗口发送的消息有三种: ⑴ 当它需要数据时,发送LVN_GETDISPINFO消息; ⑵ 当用户试图查找某个元素时,发送LVN_ODFINDITEM消息; ⑶当需要缓冲数据时,发送 LVN_ODCACHEHINT消息。
ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST_CTRL, &CDlgListTree::OnLvnGetdispinfoListCtrl)
... ...
m_objListCtrl.SetItemCount(1000 * 1000);//100万条数据
... ...
void CDlgListTree::OnLvnGetdispinfoListCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
int iRow = pDispInfo->item.iItem;//获取行号
if ((pDispInfo->item.mask & LVIF_TEXT) || (pDispInfo->item.mask & LVIF_IMAGE))//文本或图片
{
switch (pDispInfo->item.iSubItem)//获取列
{
case 0://_stprintf_s用来判断文本大小:cchTextMax
pDispInfo->item.iImage = 0;
//_stprintf_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, _T("%d"), iRow + 1);
break;
case 1:
_stprintf_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, _T("张三"));
break;
case 2:
//pDispInfo->item.iImage = 0;
_stprintf_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, _T("%d"), iRow + 1);
//_stprintf_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, _T("男"));
break;
case 3:
_stprintf_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, _T("90"));
break;
default:
break;
}
}
*pResult = 0;
}
(7) 点击表头进行排序:由于虚拟列表的设计与List排序是冲突的,这里只能提供网上的例子,不好进行实践
//初始化列表视图控件
BOOL CDataAnalysis::InitListCtl()
{
//其他处理,包括设置风格,插入列等等
//插入行
for(int i=0; i<LineNum; i++)
{
//要将char*转换为wchar_t*
mbstowcs_s(&converted, wStr, 30, m_analysis[i].Date, _TRUNCATE);
m_listAnalysis.InsertItem(i, wStr); //日期
mbstowcs_s(&converted, wStr, 30, m_analysis[i].Time, _TRUNCATE);
m_listAnalysis.SetItemText(i, 1, wStr); //时间
mbstowcs_s(&converted, wStr, 30, m_analysis[i].ID, _TRUNCATE);
m_listAnalysis.SetItemText(i, 2, wStr); //ID
m_listAnalysis.SetItemText(i, 3, m_analysis[i].lpszEvent); //事件
//设置回调函数的参数
m_listAnalysis.SetItemData(i, (LPARAM)(m_analysis+i));
}
return TRUE;
}
//点击表头事件
void CDataAnalysis::OnHdnItemclickAnalysisList(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
// TODO: Add your control notification handler code here
//设置回调函数的参数和入口地址
m_listAnalysis.SortItems(SortFunc, phdr->iItem);
*pResult = 0;
}
//排序的回调函数
int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
int result; //返回值
//两行的参数,用于比较
ANALYSISFORMAT* pAnalysis1 = (ANALYSISFORMAT*)lParam1;
ANALYSISFORMAT* pAnalysis2 = (ANALYSISFORMAT*)lParam2;
//排序
switch(lParamSort)
{
case 0: //日期
result = strcmp(pAnalysis1->Date, pAnalysis2->Date);
break;
case 1: //时间
result = strcmp(pAnalysis1->Time, pAnalysis2->Time);
break;
case 2: //ID
result = strcmp(pAnalysis1->ID, pAnalysis2->ID);
break;
case 3: //事件
result = wcscmp(pAnalysis1->lpszEvent, pAnalysis2->lpszEvent);
break;
default:
break;
}
return result;
}
(8) 单击/双击单元格响应消息
//鼠标单击事件
void CDlgListTree::OnNMClickListCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
//获取被点击的行和列
int iRow = pNMItemActivate->iItem;//行
int iColumn = pNMItemActivate->iSubItem;//列
if ((3 == iRow) && (3 == iColumn))
AfxMessageBox(_T("你点击了3行3列"));
*pResult = 0;
}
//鼠标双击单元格
void CDlgListTree::OnNMDblclkListCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
//获取被点击的行和列
int iRow = pNMItemActivate->iItem;//行
int iColumn = pNMItemActivate->iSubItem;//列
if ((5 == iRow) && (3 == iColumn))
AfxMessageBox(_T("你双击了这个单元格"));
*pResult = 0;
}
(9) 单元格加载位图:下面给出的是虚表的加载的方式,正常的加载方式可以参考表头的设置代码。加载完后大家会发现第一列会多一个图标的空白位置,这是为什么?CImageList是和CListCtrl的逻辑上的第一列绑定的,这就导致第一列一定会预留一个位图的位置,如果不想第一列显示位图怎么办?把要加载的位图的列 与 第 1 列交换位置。
//设置图片列表
m_objImageList.Create(16, 16, ILC_COLOR32, 3, 3);//背景透明,默认为ILC_COLORDDB
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO1));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO2));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO3));
pHeaderCtrl->SetImageList(&m_objImageList);//设置图像列表
... ...
//这里是虚表的加载方式在OnLvnGetdispinfoListCtrl
pDispInfo->item.iImage = 0;
... ...
//如果子项要加载位图而且第一列不想加载位图时,用此法解决第一列默认预留一个位图位置
CArray<int, int> aryColumn;
aryColumn.SetSize(pHeaderCtrl->GetItemCount());
pobjListCtrl->GetColumnOrderArray(aryColumn.GetData(), pHeaderCtrl->GetItemCount());
//我们要交换的是第0列和第2列
int iTemp = aryColumn[0];
aryColumn[0] = aryColumn[2];
aryColumn[2] = iTemp;
pobjListCtrl->SetColumnOrderArray(pHeaderCtrl->GetItemCount(), aryColumn.GetData());//设置列序号
(10) 右键菜单:具体的菜单可以查看菜单的说明
void CDlgListTree::OnNMRClickListCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
int iRow = pNMItemActivate->iItem;//行
int iColumn = pNMItemActivate->iSubItem;//列
CMenu mMenu, *pMenu = NULL;
mMenu.LoadMenu(IDR_MENU_BTN);
pMenu = mMenu.GetSubMenu(0);
CPoint pt;
GetCursorPos(&pt);
pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
*pResult = 0;
}
十三. Tree Ctrl
1. 插入节点
//插入根节点
HTREEITEM hRoot1 = m_objTreeCtrl.InsertItem(_T("中国"), TVI_ROOT, TVI_LAST);
//插入子节点
HTREEITEM hProvince = m_objTreeCtrl.InsertItem(_T("湖北"), hRoot1);
HTREEITEM hCity = m_objTreeCtrl.InsertItem(_T("武汉"), hProvince);
m_objTreeCtrl.InsertItem(_T("汉阳"), hCity);
2. 展开根节点下的所有节点
void CDlgListTree::ExpandTree(CTreeCtrl* pobjTreeCtrl, HTREEITEM hTreeItem)
{
if (nullptr == pobjTreeCtrl) return;
if (!pobjTreeCtrl->ItemHasChildren(hTreeItem)) return;
HTREEITEM hNextItem = pobjTreeCtrl->GetChildItem(hTreeItem);
while (hNextItem)
{
ExpandTree(pobjTreeCtrl, hNextItem);
hNextItem = pobjTreeCtrl->GetNextItem(hNextItem, TVGN_NEXT);//获取兄弟节点
}
pobjTreeCtrl->Expand(hTreeItem, TVE_EXPAND);
}
3. 插入位图:和List差不多
//插入位图
m_objTreeList.Create(16, 16, ILC_COLOR32, 3, 3);//背景透明,默认为ILC_COLORDDB
m_objTreeList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO1));
m_objTreeList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO2));
m_objTreeList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO3));
m_objTreeCtrl.SetImageList(&m_objTreeList, TVSIL_NORMAL);//设置图像列表
HTREEITEM hRoot = m_objTreeCtrl.InsertItem(_T("水果"), TVI_ROOT, TVI_LAST);
m_objTreeCtrl.InsertItem(_T("蓝梅"), 0, 0, hRoot);
m_objTreeCtrl.InsertItem(_T("棒棒糖"), 1, 1, hRoot);
m_objTreeCtrl.InsertItem(_T("开心果"), 2, 2, hRoot);
//设置Item属性:tree与imagelist关联,每一个节点都是逻辑上的第一个,所以每个节点都会显示位图
static TVITEM stTvItem = { 0 };
stTvItem.mask = TVIF_TEXT | TVIF_STATE;
stTvItem.hItem = hRoot;
stTvItem.state = TVIS_DROPHILITED | TVIS_BOLD;//拖放/粗体
stTvItem.pszText = _T("水果摊子");
m_objTreeCtrl.SetItem(&stTvItem);
4. 获取被选中的节点
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_CTRL, &CDlgListTree::OnTvnSelchangedTreeCtrl)
... ...
//选中某个节点时响应
void CDlgListTree::OnTvnSelchangedTreeCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
//这样就可以得到选中项的节点了
AfxMessageBox(m_objTreeCtrl.GetItemText(pNMTreeView->itemNew.hItem));
*pResult = 0;
}
十四. Shell List/Tree
1. 是系统为我们构建了一个资源管理器,还帮我们屏蔽了右键菜单功能,这样我们就能设计右键菜单去实现我们想要的功能,如何关联呢?
m_objShellTreeCtrl.Expand(m_objShellTreeCtrl.GetRootItem(), TVE_EXPAND);//只展开根节点
m_objShellTreeCtrl.SetRelatedList(&m_objShellListCrl);//关联shelllist
2. 获取当前选中的文件
ON_NOTIFY(NM_CLICK, IDC_MFCSHELLLIST1, &CDlgShellListTree::OnNMClickMfcshelllist1)
...
void CDlgShellListTree::OnNMClickMfcshelllist1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
int iRow = pNMItemActivate->iItem;//行
int iColumn = pNMItemActivate->iSubItem;//列
CString strFileName = m_objShellListCrl.GetItemText(iRow, iColumn);
//AfxMessageBox(strFileName);
CString strPrint, strPath;
m_objShellListCrl.GetCurrentFolderName(strPath);
strPrint = _T("文件夹名:") + strPath + _T("\n");
m_objShellListCrl.GetCurrentFolder(strPath);
strPrint += _T("文件夹路径:") + strPath + _T("\n");
strPrint += _T("文件名:") + strFileName + _T("\n");
m_objShellListCrl.GetItemPath(strPath, iRow);
strPrint += _T("文件夹路径:") + strPath + _T("\n");
AfxMessageBox(strPrint);
*pResult = 0;
}
十五. CPropertyGrid
1. CPropertyGrid填充表格层次上有点难读,而且new出来的对象不需要释放,看着会很怪,所以作了一下简单封装:MyPropertyGrid.h/.cpp, 下面是在此基础上进行讲解
2. 设置表属性:CPropertyGrid是容器, CMFCPropertyGridProperty是item
//m_objPropertyGrid.EnableHeaderCtrl(FALSE);//要不要显示表头
//m_objPropertyGrid.SetVSDotNetLook(FALSE);//描述是否按照.NET的样式绘制控件,外观不一样
m_objPropertyGrid.MarkModifiedProperties(TRUE);//指定如何显示修改后的属性,修改后字体加粗,区别于常规
//m_objPropertyGrid.SetAlphabeticMode(TRUE);//设置或重置字母模式, 用字母进行排序,且不显示“组选项”
//m_objPropertyGrid.EnableDescriptionArea(FALSE);//启用或禁用在属性列表下方显示的说明区域
//m_objPropertyGrid.SetDescriptionRows(3);//设置描述区域的行数
m_objPropertyGrid.SetShowDragContext();//指定当用户调整列的大小时,框架是否重绘当前属性网格控件的名称和值列
3. 设置表头:给表头加载位图,和List一样
void CDlgPropertyGrid::SetHeaderCtrl(CMFCPropertyGridCtrl* pobjGrid)
{
if (nullptr == pobjGrid) return;
CMFCHeaderCtrl& objHeaderCtrl = pobjGrid->GetHeaderCtrl();
//加载位图
m_objImageList.Create(16, 16, ILC_COLOR32, 2, 2);//背景透明,默认为ILC_COLORDDB
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO1));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO2));
objHeaderCtrl.SetImageList(&m_objImageList);//设置图像列表
//设置item属性
LPTSTR alpHeaderText[] = {_T("灯具类型"), _T("灯具属性")};
for (int iNum = 0; iNum < objHeaderCtrl.GetItemCount(); iNum++)
{
HDITEM stHdItem = { 0 };
objHeaderCtrl.GetItem(iNum, &stHdItem);
stHdItem.mask = HDI_TEXT | HDI_IMAGE | HDI_FORMAT | HDI_WIDTH;
stHdItem.fmt = HDF_STRING | HDF_IMAGE;
stHdItem.iImage = iNum;
stHdItem.cxy = 200;//表头宽
stHdItem.pszText = alpHeaderText[iNum];//文本
objHeaderCtrl.SetItem(iNum, &stHdItem);
}
}
4. 插入节点:(1)插入组节点和子节点的方式是不同的,看下面代码,另外插入完节点后一定要记得关联容器
(2) 这里有一个很特别的类型类COleVariant,和Java里的Any类很像,所以设置Value值时我们尽量用我们想用的类型,这样GetValue出来就不用再进行转换了,特别涉及到double类型的数据时
//创建根节点1
CMFCPropertyGridProperty* pRoot = CMyPropertyGrid::GetInstance().CreateRoot(_T("灯具属性"));
//======================================================================
//在根上插入子节点
CMyPropertyGrid::GetInstance().AddItem(pRoot, _T("名称"), _T("白炽灯"), _T("灯具名称"));
CMyPropertyGrid::GetInstance().AddItem(pRoot, _T("功率"), (long)100, _T("灯具功率"), TRUE);//edit
... ...
//-------------------------------------------------
//在根节点上创建组
CMFCPropertyGridProperty* pGroup = CMyPropertyGrid::GetInstance().AddGroup(pRoot, _T("坐标信息"), 0, TRUE);
//在组上创建子节点
CMyPropertyGrid::GetInstance().AddItem(pGroup, _T("X"), (long)100, _T("坐标X值"), TRUE);//edit
CMyPropertyGrid::GetInstance().AddItem(pGroup, _T("Y"), (long)100, _T("坐标Y值"), TRUE);//edit
//-------------------------------------------------
//关联容器
m_objPropertyGrid.AddProperty(pRoot);
5. 插入ComboBox节点
ARRAYCOMBO aryCombo;
LPCTSTR alpComboValue[] = { _T("8.2"), _T("10.25"), _T("12.38") };
aryCombo.SetSize(ARRAYSIZE(alpComboValue));
for (int iCount = 0; iCount < ARRAYSIZE(alpComboValue); iCount++)
{
aryCombo.SetAt(iCount, alpComboValue[iCount]);
}
CMyPropertyGrid::GetInstance().AddItem(pRoot, _T("价格"), 8.2, _T("灯具价格"), TRUE, TRUE, &aryCombo);//edit + combo
6. 插入系统 Font选择节点
CFont* font = CFont::FromHandle((HFONT)GetStockObject(DEFAULT_GUI_FONT));
LOGFONT lf;
font->GetLogFont(&lf);
_tcscpy_s(lf.lfFaceName, _T("宋体, Arial"));
CMyPropertyGrid::GetInstance().AddItemFont(pRoot3, _T("字体选择"), lf, _T("选择系统字体"));
7. 插入系统Color拾取器节点
CMyPropertyGrid::GetInstance().AddItemColor(pRoot3, _T("颜色选择"), RGB(255, 0, 0), _T("系统调色板"));
8. 插入File选择节点
LPCTSTR lpFilder = _T("image files(*.jpeg; *.jpg; *.bmp; *.png) | *.jpeg; *.jpg; *.bmp; *.png||");
CMyPropertyGrid::GetInstance().AddItemFile(pRoot3, _T("文件选择"), _T("1.jpg"), _T("在弹出窗口选择文件"), lpFilder);
9. 插入Folder选择节点
CMyPropertyGrid::GetInstance().AddItemFolder(pRoot3, _T("文件夹选择"), _T("C:\\"), _T("在弹出窗口选择文件夹"));
10. 设置表格外观颜色
//可以设置颜色: 背景色,文本色, 组背景色,组文本色, 描述框背景色,描述框文本色, 网格线色
m_objPropertyGrid.SetCustomColors(RGB(255, 255, 224), RGB(255, 0, 255), (COLORREF)-1, (COLORREF)-1, RGB(0, 233, 131), RGB(211, 0, 232), (COLORREF)-1);
11. 遍历表格的所有节点:需要结合MyPropertyGrid里遍历函数
//遍历回调
void CDlgPropertyGrid::OnEnumItems(CMFCPropertyGridProperty* pobjItem)
{
//举个例子,所有的item都在这里处理
if (0 == _tcscmp(pobjItem->GetName(), _T("颜色选择")))
{
CString strColor;
COLORREF color = ((CMFCPropertyGridColorProperty*)pobjItem)->GetColor();
strColor.Format(_T("R: %d G: %d B: %d"), GetRValue(color), GetGValue(color), GetBValue(color));
AfxMessageBox(strColor);
}
}
//遍历
void CDlgPropertyGrid::OnBnClickedBtnEnum()
{
//遍历所有节点
for (int iNum = 0; iNum < m_objPropertyGrid.GetPropertyCount(); iNum++)
{
CMyPropertyGrid::GetInstance().EnumItems(m_objPropertyGrid.GetProperty(iNum));
}
}
十六. TabCtrl
1. 仅以常用的子界面的切换为例进行说明,需要切换的子界面的属性设置:border为none, style为child
2. 由于程序需要用到的子界面很多,这里采用"工厂模式"的方式来创建子界面,所以在此封装了一个工厂类CPage.h/.cpp, 下面的说明也在此基础上进行,由于选项卡一行不够用,需要设置属性:multi_select为true, multi为true。
3. 添加页(子界面)过程
void CMFCCtrlPlayDlg::InitTabCtrl()
{
//设置TabCtrl的大小和位置
CRect stRect;
GetClientRect(&stRect);
m_objTabCtrl.AdjustRect(FALSE, &stRect);
m_objTabCtrl.MoveWindow(&stRect, TRUE);
//设置Pages;
m_objPage.SetTabCtrl(&m_objTabCtrl);//设置tabctrl指针
m_objPage.CreatePages();//创建pages
m_objPage.InsertPages();//插入pages
m_objPage.ShowPage(0, 0);//显示page
//设置TabCurSel
m_objTabCtrl.SetCurSel(m_iCurSel);//设置焦点位置
}
4. 切换响应消息
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_CTRL, &CMFCCtrlPlayDlg::OnTcnSelchangeTabCtrl)
... ...
void CMFCCtrlPlayDlg::OnTcnSelchangeTabCtrl(NMHDR *pNMHDR, LRESULT *pResult)
{
int iNewSel = m_objTabCtrl.GetCurSel();
m_objPage.ShowPage(m_iCurSel, iNewSel);//显示page
m_iCurSel = iNewSel;
*pResult = 0;
}
十七. CMenu
1. 静态加载:首先需要在资源管理器上添加"Menu"资源
(1) 方式一:加载到窗口上,设置主窗口的属性:Menu为IDR_MENU_XXX, 这样就可以看到在标题栏下面看到一组菜单栏。
(2) 方式二:按钮或右键弹出
//静态加载2
void CDlgMenu::OnBnClickedBtnMenu()
{
CMenu mMenu, *pMenu = NULL;
mMenu.LoadMenu(IDR_MENU_DLG);
pMenu = mMenu.GetSubMenu(1);//右键菜单只能选择一列,和窗口菜单不一样
CPoint pt;
GetCursorPos(&pt);
pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
}
2. 动态加载:
//一级菜单
pRootMenu = new CMenu;
pRootMenu->CreatePopupMenu();
pRootMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_1, _T("item1"));
pRootMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_2, _T("item2"));
//pRootMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3, _T("item3"));
pRootMenu->AppendMenu(MF_SEPARATOR, NULL);//添加分隔符
pRootMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_4, _T("item4"));
pRootMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_5, _T("item5"));
//添加二级菜单
pGroupMenu = new CMenu;
pGroupMenu->CreatePopupMenu();
pRootMenu->InsertMenu(2, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT)pGroupMenu->m_hMenu, _T("item3"));//插入二级
pGroupMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_1, _T("item31"));
//pGroupMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_2, _T("item32"));
pGroupMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_3, _T("item33"));
pGroupMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_4, _T("item34"));
//添加三级菜单
pSubMenu = new CMenu;
pSubMenu->CreatePopupMenu();
pGroupMenu->InsertMenu(1, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT)pSubMenu->m_hMenu, _T("iItem32"));//插入三级
pSubMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_2_1, _T("item321"));
pSubMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_2_2, _T("item322"));
pSubMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_2_3, _T("item323"));
pSubMenu->AppendMenu(MF_STRING | MF_ENABLED, EN_MENU_3_2_4, _T("item324"));
3. 菜单按钮的响应:
(1) 静态加载的方式:可以在资源管理上选中要添加消息响应的item,右键添加"事件消息处理"选择COMMAN
ON_COMMAND(ID_32787, &CDlgMenu::On32787)
... ...
//菜单响应
void CDlgMenu::On32787()
{
AfxMessageBox(_T("响应菜单按钮"));
}
(2) 那么要是动态创建的菜单怎么办?我们先重载OnCommand
//动态创建菜单的响应
BOOL CDlgMenu::OnCommand(WPARAM wParam, LPARAM lParam)
{
UINT uiMenuID = LOWORD(wParam);
if (EN_MENU_5 == uiMenuID)
{
AfxMessageBox(_T("点击了item5"));
}
else if (EN_MENU_3_2_4 == uiMenuID)
{
AfxMessageBox(_T("点击了item324"));
}
else if (EN_MENU_2 == uiMenuID)
{
static bool s_bCheck = false;
UINT uiCheck = MF_BYCOMMAND;
uiCheck = (s_bCheck) ? (uiCheck | MF_CHECKED) : (uiCheck | MF_UNCHECKED);
pRootMenu->CheckMenuItem(EN_MENU_2, uiCheck);
s_bCheck = !s_bCheck;
}
return CDialogEx::OnCommand(wParam, lParam);
}
(3) 设置Menu的外观属性
//设置item属性=====================================
MENUINFO mi;
mi.cbSize = sizeof(MENUINFO);
mi.fMask = MIM_BACKGROUND;
mi.hbrBack = ::CreateSolidBrush(RGB(255, 255, 255));
SetMenuInfo(pRootMenu->m_hMenu, &mi);//设置菜单背景色
CImage objImage;
objImage.Load(_T(".\\res\\3.ico"));
m_bitmap.Attach(objImage.Detach());
pRootMenu->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bitmap, &m_bitmap);//添加位图
pRootMenu->CheckMenuItem(1, MF_BYPOSITION | MF_CHECKED);//添加选择check
pRootMenu->EnableMenuItem(4, MF_BYPOSITION | MF_DISABLED);//禁用menu按钮
pRootMenu->SetDefaultItem(5);//每个子菜单最多只能有一个缺省菜单:响应回车键
十八. CToolBar
1. 静态加载:在资源管理器上添加"ToolBar", 双击你创建的IDR_TOOLBAR, 再双击上面的小格子,设置item属性:width, height, ID, Prompt:提示框,格式如下:打开文件\nOpen File, 设置完了会自生成第二个item,再去设置,需要几个就设置几个。再添加一张你设计好的工具栏位图bmp
void CDlgToolbar::InitToolbar()
{
m_objToolbar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_objToolbar.LoadToolBar(IDR_TOOLBAR);
m_objToolbar.LoadBitmap(IDB_BITMAP_TOOL);
m_objToolbar.SetBarStyle(CBRS_ALIGN_TOP | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
//显示
m_objToolbar.ModifyStyle(0, TBSTYLE_TRANSPARENT);//背景透明
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
}
2. 动态加载:
//动态创建工具栏
void CDlgToolbar::InitAutoToolbar()
{
//button按钮
m_objImageList.Create(16, 16, ILC_COLOR32, 3, 3);//背景透明,默认为ILC_COLORDDB
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO1));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO2));
m_objImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_COMBO3));
//创建toolbar设置大小
m_objAutoToolbar.Create(this);
//m_objAutoToolbar.GetToolBarCtrl().SetButtonWidth(16, 24);
m_objAutoToolbar.SetSizes(CSize(24, 24), CSize(16, 16));//设置大小,系统默认button(24, 23), image(16, 15), 不能比它小
m_objAutoToolbar.GetToolBarCtrl().SetImageList(&m_objImageList);//设置图像列表
//m_objAutoToolbar.GetToolBarCtrl().SetHotImageList(&m_objImageList);//设置hot button
UINT auiBtnID[] = { EN_TOOLBAR_ID1, EN_TOOLBAR_ID2, EN_TOOLBAR_ID3 };
m_objAutoToolbar.SetButtons(auiBtnID, 3);//设置按钮ID
//设置文本: 根据需求设置
//m_objAutoToolbar.SetButtonText(0, _T("蓝梅"));
//m_objAutoToolbar.SetButtonText(1, _T("棒棒糖"));
//m_objAutoToolbar.SetButtonText(2, _T("开心果"));
//显示
m_objAutoToolbar.ModifyStyle(0, TBSTYLE_TRANSPARENT);//背景透明
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
}
3. 有人发现静态加载的Toolbar没有提示框,那怎么办? 那就采用动态的方式来加载。
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CDlgToolbar::OnToolTipNotify)
... ...
BOOL CDlgToolbar::OnToolTipNotify(UINT id, NMHDR*pNMHDR, LRESULT*pResult)
{
TOOLTIPTEXT*pTTT = (TOOLTIPTEXT*)pNMHDR;
UINT uID = pNMHDR->idFrom; //获取工具栏按钮ID
if (pTTT->uFlags & TTF_IDISHWND) uID = ::GetDlgCtrlID((HWND)uID);
if (uID == NULL) return FALSE;
switch (uID)//toolbar button ID
{
case ID_TOOL1: pTTT->lpszText = _T("打开文件"); break;
case ID_TOOL2: pTTT->lpszText = _T("打开文件夹"); break;
case ID_TOOL3: pTTT->lpszText = _T("复制"); break;
case ID_TOOL4: pTTT->lpszText = _T("粘贴"); break;
case ID_TOOL5: pTTT->lpszText = _T("保存"); break;
case ID_TOOL6: pTTT->lpszText = _T("另存为"); break;
case ID_TOOL7: pTTT->lpszText = _T("打印"); break;
case ID_TOOL8: pTTT->lpszText = _T("帮助"); break;
default:break;
}
return TRUE;
}
4. 静态加载消息响应:在需要加载工具栏的窗口,右键选择"类向导", 找到要响应的工具栏的item的ID号,选择COMMAND, 生成函数
void CDlgToolbar::OnTool1()
{
AfxMessageBox(_T("点击了工具栏按钮"));
}
5. 动态加载消息响应:
//ID1 --- ID3 要连续
ON_COMMAND_RANGE(EN_TOOLBAR_ID1, EN_TOOLBAR_ID3, &CDlgToolbar::OnBnClickedToolBar)
... ...
//动态创建的toolbar的消息响应
void CDlgToolbar::OnBnClickedToolBar(UINT nID)
{
if (EN_TOOLBAR_ID1 == nID)
{
AfxMessageBox(_T("响应toolbar消息"));
}
}
6. 工具栏容器:CReBar, 用来装载ToolBar及组合其他控件
(1) 添加控件
m_objReBar.Create(this);
m_objReBar.AddBar(&m_objToolbar);
//添加edit
m_objEdit.Create(WS_CHILD | WS_VISIBLE , CRect(0, 0, 100, 18), this, 0);//添加Edit
m_objEdit.SetFont(m_objAutoToolbar.GetFont());//m_objEdit默认的字体不好看,反正我不喜欢
m_objReBar.AddBar(&m_objEdit, NULL, NULL, RBBS_NOGRIPPER);//消除band
//
m_objReBar.AddBar(&m_objAutoToolbar);
//添加ComboBox
m_objComboBox.Create(WS_CHILD | WS_VISIBLE | CBS_DROPDOWN, CRect{ 0, 0, 100, 20 }, this, 0);
m_objComboBox.AddString(_T("蓝梅"));
m_objComboBox.AddString(_T("棒棒糖"));
m_objComboBox.AddString(_T("开心果"));
m_objComboBox.SetFont(m_objAutoToolbar.GetFont());
m_objReBar.AddBar(&m_objComboBox, NULL, NULL, RBBS_NOGRIPPER);//消除band
//添加splitbtn
m_objReBar.AddBar(&m_objSplitBtn, NULL, NULL, RBBS_NOGRIPPER);//消除band
... ...
//显示
CRect stRect;
::GetClientRect(GetParent()->m_hWnd, &stRect);
m_objReBar.SetWindowPos(NULL, 0, 0, 260, 60, SWP_SHOWWINDOW);//多行显示
m_objReBar.SetWindowPos(NULL, 0, 0, stRect.Width(), 30, SWP_SHOWWINDOW);//一行显示
(2) 给CReBar添加位图背景:这里原本是想设计给CReBar添加背景色,最后出来的始终为白色,原因没找到
//加载CReBar的背景图片
CImage objImage;
objImage.Load(_T(".\\res\\1.gif"));
REBARBANDINFO info;
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(REBARBANDINFO);
info.fMask = RBBIM_BACKGROUND | RBBIM_IMAGE /*| RBBIM_COLORS*/;//这里的color无效,原因?
//info.clrFore = RGB(0, 123, 123);
//info.clrBack = RGB(0, 123, 134);
info.hbmBack = objImage.Detach();
for (UINT uiNum = 0; uiNum < m_objReBar.GetReBarCtrl().GetBandCount(); uiNum++)
{
m_objReBar.GetReBarCtrl().SetBandInfo(uiNum, &info);
}
十九. Modal Dialog
1. 非模态对话框的创建和销毁:主对话框类:CDlgModal, 非模态对话框类:CTestModalOrNo
//在CDlgModal类里创建
CTestModalOrNo* pTestNoModal = new CTestModalOrNo;
pTestNoModal->Create(CTestModalOrNo::IDD, this);
pTestNoModal->ShowWindow(SW_SHOW);//显示
pTestNoModal->SetForegroundWindow();//置顶
... ...
//在CTestModalOrNo类里销毁
DestroyWindow();
delete this;
2. 模态对话框的创建和销毁: 主对话框类:CDlgModal, 非模态对话框类:CTestModalOrNo
//在CDlgModal里创建
CTestModalOrNo objTestModal;
objTestModal.DoModal();
... ...
//在CTestModalOrNo里销毁
CDialogEx::OnCancel();//模态对话框
二十. 窗口子类化
1. 子类化的本质:给窗口过程换一个新地址,新窗口过程处理用户感兴趣的消息,其他消息交给原窗口过程处理。
2. 子类化的目的:在不修改原代码的条件下,扩展窗口功能。eg. 前面我们讲到button按钮没有办法像CStatic一样使用OnCtrlColor去修改背景色和文本色,而我们能处理的WM_PAINT消息已经把button画好了,也就是说我们要去MFC源码里面修改WM_PAINT里的代码。显然,这是不被允许的,也是不合理的,那么怎么办?子类化技术就出现了,你可以设置一个新的窗口过程来处理
WM_PAINT消息,也完全遵从了面向对象的“OCP“开闭原则(对扩展开放,对修改关闭)。
3. 子类化最本质的调用:
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (INT_PTR)AfxGetAfxWndProc());这一点在WIN32上看就很清晰,那么在MFC上呢?估计找得眼睛疼,那下面我们就聊下。
4. MFC的窗口子类化:有两种方式
(1) 用button举例,先派生一个button类,然后在窗口上拖一个button,并添加变量,再把变量的类名改为派生button类就可以了,本质是使用DDX_Control(pDX, IDC_BTN_XXX, m_objTabCtrl)函数,我们跟进,调用到了SubclassDlgItem,也是我们下面要说的。
(2) 用button举例,先派生一个button类,再在OnInitDialog里
m_objSubButton.SubclassDlgItem(IDC_BTN_SUB, this);
(3)上面两种方式最终都是调用了SubclassDlgItem,里面以调用了SetWindowLongPtr
最后到OnWndMsg,再路由到包装好的的各个消息处理函数,这里涉及到命令路由机制,感兴趣的可以去看下《深入浅出MFC》里面有讲。MSDN里讲到button的绘制要重载DrawItem,也就是
WM_PAINT消息处理封装到了DrawItem.
(4) 程序里SubButton.h/.cpp对button进行了简单的重绘,实现修改背景色和文本色的功能。
5. 窗口超类化:用的比较少,简单聊下看法:本质是获取原来的窗口类,修改部分属性,名字和新的窗口过程,再重新注册得到一个新的窗口类。在子类化的基础上,能够修改窗口类的相关属性,而其实这些属性都可以通过API去修改,可能最大的目的是得到一个新的窗口类吧。
二十一. 总结
1. 写这个的目的是为了方便查阅,平时用的少,偶尔用到百度+MSDN感觉麻烦,就是懒;
2. 都是一些基础用法,除了button,都没有重绘处理,想要重绘例子,可以去CodeProject
3. 没有图片进行说明,是因为都写好了例子,可以在release查看效果。
下载地址:https://download.csdn.net/download/zhoumin4576/19999286?spm=1001.2014.3001.5501