MFC基础控件说明

16 篇文章 0 订阅

目录

        一. button 控件

      1. CButton类

      2. CSplitButton类

      3.  CMFCButton类

      4. CMFCColorButton类

      5. CMFCMenuButton类

      6.  CRadioButton类

      7. CCheckBox类

二.  StaticText

        1. CStatic类

        2.  CLinkCtrl类

三.  CComboBox

        1. CComboBox类

        2. CComboBoxEx类

        3. CMFCFontComboBox

四.  CEdit/CRichEdit

       1.  CEdit类       

       2.  CMFCEditBrowseCtrl类

      3.  CRichEdit

五. Pic Ctrl

六.  Slider/Progress

七.  SPinCtrl

八. CHotKey

九. CAnimalCtrl

十. CDateTime

          1. CDateTime

          2. CMonthCtrl

十一. IP Ctrl

         1. CIPAddressCtrl

         2. CNetAddressCtrl

十二.  List Ctrl

         1.  CListBox

         2.  CVSListBox

         3.  CListCtrl

十三. Tree Ctrl

十四. Shell List/Tree

十五.  CPropertyGrid

十六. TabCtrl

十七.  CMenu

十八. CToolBar

十九. Modal Dialog

二十. 窗口子类化

 二十一. 总结        


        一. 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类:①可以设置背景色;②还是"图标"+"文本",只是图标和文本都可以设置三种状态normalhot(鼠标进入按钮), 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 filestrue 

        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) ListBoxitem比较简单,可能满足不了我们的需求,那我们就可以派生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是容器, CMFCPropertyGridPropertyitem

	    //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. 仅以常用的子界面的切换为例进行说明,需要切换的子界面的属性设置:bordernone, stylechild

                2. 由于程序需要用到的子界面很多,这里采用"工厂模式"的方式来创建子界面,所以在此封装了一个工厂类CPage.h/.cpp, 下面的说明也在此基础上进行,由于选项卡一行不够用,需要设置属性:multi_selecttrue multitrue

                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) 方式一:加载到窗口上,设置主窗口的属性:MenuIDR_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. 静态加载消息响应:在需要加载工具栏的窗口,右键选择"类向导", 找到要响应的工具栏的itemID号,选择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/.cppbutton进行了简单的重绘,实现修改背景色和文本色的功能。

          5. 窗口超类化:用的比较少,简单聊下看法:本质是获取原来的窗口类,修改部分属性,名字和新的窗口过程,再重新注册得到一个新的窗口类。在子类化的基础上,能够修改窗口类的相关属性,而其实这些属性都可以通过API去修改,可能最大的目的是得到一个新的窗口类吧。                   

 二十一. 总结        

         1. 写这个的目的是为了方便查阅,平时用的少,偶尔用到百度+MSDN感觉麻烦,就是懒;

         2. 都是一些基础用法,除了button,都没有重绘处理,想要重绘例子,可以去CodeProject

         3. 没有图片进行说明,是因为都写好了例子,可以在release查看效果。

下载地址:https://download.csdn.net/download/zhoumin4576/19999286?spm=1001.2014.3001.5501

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

千里修行

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值