2007年07月10日

CListCtrl使用技巧

以下未经说明,listctrl默认view 风格为report


1. CListCtrl 风格

      LVS_ICON: 为每个item显示大图标
      LVS_SMALLICON: 为每个item显示小图标
      LVS_LIST: 显示一列带有小图标的item
      LVS_REPORT: 显示item详细资料

      直观的理解:windows资源管理器,“查看”标签下的“大图标,小图标,列表,详细资料”


2. 设置listctrl 风格及扩展风格

      LONG lStyle;
      lStyle = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//获取当前窗口style
      lStyle &= ~LVS_TYPEMASK; //清除显示方式位
      lStyle |= LVS_REPORT; //设置style
      SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//设置style
 
      DWORD dwStyle = m_list.GetExtendedStyle();
      dwStyle |= LVS_EX_FULLROWSELECT;//选中某行使整行高亮(只适用与report风格的listctrl)
      dwStyle |= LVS_EX_GRIDLINES;//网格线(只适用与report风格的listctrl)
      dwStyle |= LVS_EX_CHECKBOXES;//item前生成checkbox控件
      m_list.SetExtendedStyle(dwStyle); //设置扩展风格
 
      注:listview的style请查阅msdn
      http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp

 


3. 插入数据

      m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列
      m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 );
      int nRow = m_list.InsertItem(0, “11”);//插入行
      m_list.SetItemText(nRow, 1, “jacky”);//设置数据

 


4. 一直选中item

    选中style中的Show selection always,或者在上面第2点中设置LVS_SHOWSELALWAYS


5. 选中和取消选中一行

    int nIndex = 0;
    //选中
    m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
    //取消选中
    m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
 


6. 得到listctrl中所有行的checkbox的状态

      m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
      CString str;
      for(int i=0; i<m_list.GetItemCount(); i++)
      {
           if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i))
           {
                str.Format(_T("第%d行的checkbox为选中状态"), i);
                AfxMessageBox(str);
           }
      }


7. 得到listctrl中所有选中行的序号


      方法一:
      CString str;
      for(int i=0; i<m_list.GetItemCount(); i++)
      {
           if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
           {
                str.Format(_T("选中了第%d行"), i);
                AfxMessageBox(str);
           }
      }

      方法二:
      POSITION pos = m_list.GetFirstSelectedItemPosition();
      if (pos == NULL)
           TRACE0("No items were selected!\n");
      else
      {
           while (pos)
           {
                int nItem = m_list.GetNextSelectedItem(pos);
                TRACE1("Item %d was selected!\n", nItem);
                // you could do your own processing on nItem here
           }
      }


8. 得到item的信息

      TCHAR szBuf[1024];
      LVITEM lvi;
      lvi.iItem = nItemIndex;
      lvi.iSubItem = 0;
      lvi.mask = LVIF_TEXT;
      lvi.pszText = szBuf;
      lvi.cchTextMax = 1024;
      m_list.GetItem(&lvi);

      关于得到设置item的状态,还可以参考msdn文章
      Q173242: Use Masks to Set/Get Item States in CListCtrl
               http://support.microsoft.com/kb/173242/en-us


9. 得到listctrl的所有列的header字符串内容

      LVCOLUMN lvcol;
      char  str[256];
      int   nColNum;
      CString  strColumnName[4];//假如有4列

      nColNum = 0;
      lvcol.mask = LVCF_TEXT;
      lvcol.pszText = str;
      lvcol.cchTextMax = 256;
      while(m_list.GetColumn(nColNum, &lvcol))
      {
           strColumnName[nColNum] = lvcol.pszText;
           nColNum++;
      }


10. 使listctrl中一项可见,即滚动滚动条

    m_list.EnsureVisible(i, FALSE);


11. 得到listctrl列数

    int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();


12. 删除所有列

      方法一:
         while ( m_list.DeleteColumn (0))
       因为你删除了第一列后,后面的列会依次向上移动。

      方法二:
      int nColumns = 4;
      for (int i=nColumns-1; i>=0; i–)
          m_list.DeleteColumn (i);


13. 得到单击的listctrl的行列号

      添加listctrl控件的NM_CLICK消息相应函数
      void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           // 方法一:
           /*
           DWORD dwPos = GetMessagePos();
           CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
  
           m_list.ScreenToClient(&point);
  
           LVHITTESTINFO lvinfo;
           lvinfo.pt = point;
           lvinfo.flags = LVHT_ABOVE;
    
           int nItem = m_list.SubItemHitTest(&lvinfo);
           if(nItem != -1)
           {
                CString strtemp;
                strtemp.Format("单击的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem);
                AfxMessageBox(strtemp);
           }
          */
  
          // 方法二:
          /*
           NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
           if(pNMListView->iItem != -1)
           {
                CString strtemp;
                strtemp.Format("单击的是第%d行第%d列",
                                pNMListView->iItem, pNMListView->iSubItem);
                AfxMessageBox(strtemp);
           }
          */
           *pResult = 0;
      }

 


14. 判断是否点击在listctrl的checkbox上

      添加listctrl控件的NM_CLICK消息相应函数
      void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           DWORD dwPos = GetMessagePos();
           CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
  
           m_list.ScreenToClient(&point);
  
           LVHITTESTINFO lvinfo;
           lvinfo.pt = point;
           lvinfo.flags = LVHT_ABOVE;
    
           UINT nFlag;
           int nItem = m_list.HitTest(point, &nFlag);
           //判断是否点在checkbox上
           if(nFlag == LVHT_ONITEMSTATEICON)
           {
                AfxMessageBox("点在listctrl的checkbox上");
           }
           *pResult = 0;
      }


15. 右键点击listctrl的item弹出菜单

      添加listctrl控件的NM_RCLICK消息相应函数
      void CTest6Dlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
           if(pNMListView->iItem != -1)
           {
                DWORD dwPos = GetMessagePos();
                CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
   
                CMenu menu;
                VERIFY( menu.LoadMenu( IDR_MENU1 ) );
                CMenu* popup = menu.GetSubMenu(0);
                ASSERT( popup != NULL );
                popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
           }
           *pResult = 0;
  }

 


16. item切换焦点时(包括用键盘和鼠标切换item时),状态的一些变化顺序

      添加listctrl控件的LVN_ITEMCHANGED消息相应函数
      void CTest6Dlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
      {
           NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
           // TODO: Add your control notification handler code here
   
           CString sTemp;
 
           if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED &&
            (pNMListView->uNewState & LVIS_FOCUSED) == 0)
           {
                sTemp.Format("%d losted focus",pNMListView->iItem);
           }
           else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 &&
               (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED)
           {
                sTemp.Format("%d got focus",pNMListView->iItem);
           }
 
           if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED &&
            (pNMListView->uNewState & LVIS_SELECTED) == 0)
           {
                sTemp.Format("%d losted selected",pNMListView->iItem);
           }
           else if((pNMListView->uOldState & LVIS_SELECTED) == 0 &&
            (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED)
           {
                sTemp.Format("%d got selected",pNMListView->iItem);
           }
   
           *pResult = 0;
      }


17. 得到另一个进程里的listctrl控件的item内容

http://www.codeproject.com/threads/int64_memsteal.asp


18. 选中listview中的item

Q131284: How To Select a Listview Item Programmatically
http://support.microsoft.com/kb/131284/en-us


19. 如何在CListView中使用CListCtrl的派生类

http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/


20. listctrl的subitem添加图标

      m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
      m_list.SetItem(..); //具体参数请参考msdn

 


21. 在CListCtrl显示文件,并根据文件类型来显示图标

      网上找到的代码,share
      BOOL CTest6Dlg::OnInitDialog()
      {
           CDialog::OnInitDialog();
  
           HIMAGELIST himlSmall;
           HIMAGELIST himlLarge;
           SHFILEINFO sfi;
           char  cSysDir[MAX_PATH];
           CString  strBuf;
 
           memset(cSysDir, 0, MAX_PATH);
  
           GetWindowsDirectory(cSysDir, MAX_PATH);
           strBuf = cSysDir;
           sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("\\")+1));
 
           himlSmall = (HIMAGELIST)SHGetFileInfo ((LPCSTR)cSysDir, 
                      0, 
                      &sfi,
                      sizeof(SHFILEINFO), 
                      SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
  
           himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 
                      0, 
                      &sfi, 
                      sizeof(SHFILEINFO), 
                      SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
  
           if (himlSmall && himlLarge)
           {
                ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                             (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
                ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                             (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
           }
           return TRUE;  // return TRUE  unless you set the focus to a control
      }
 
      void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument)
      {
           int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE);
           CString strSize;
           CFileFind filefind;
 
           //  get file size
           if (filefind.FindFile(lpszFileName))
           {
                filefind.FindNextFile();
                strSize.Format("%d", filefind.GetLength());
           }
           else
                strSize = "0";
  
           // split path and filename
           CString strFileName = lpszFileName;
           CString strPath;
 
           int nPos = strFileName.ReverseFind(‘\\’);
           if (nPos != -1)
           {
                strPath = strFileName.Left(nPos);
                strFileName = strFileName.Mid(nPos + 1);
           }
  
           // insert to list
           int nItem = m_list.GetItemCount();
           m_list.InsertItem(nItem, strFileName, nIcon);
           m_list.SetItemText(nItem, 1, strSize);
           m_list.SetItemText(nItem, 2, strFileName.Right(3));
           m_list.SetItemText(nItem, 3, strPath);
      }
 
      int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected)
      {
           SHFILEINFO sfi;
           memset(&sfi, 0, sizeof(sfi));
  
           if (bIsDir)
           {
            SHGetFileInfo(lpszPath, 
                         FILE_ATTRIBUTE_DIRECTORY, 
                         &sfi, 
                         sizeof(sfi), 
                         SHGFI_SMALLICON | SHGFI_SYSICONINDEX |
                         SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0)); 
            return  sfi.iIcon;
           }
           else
           {
            SHGetFileInfo (lpszPath, 
                         FILE_ATTRIBUTE_NORMAL, 
                         &sfi, 
                         sizeof(sfi), 
                         SHGFI_SMALLICON | SHGFI_SYSICONINDEX | 
                         SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0));
            return   sfi.iIcon;
           }
           return  -1;
      }


22. listctrl内容进行大数据量更新时,避免闪烁

      m_list.SetRedraw(FALSE);
      //更新内容
      m_list.SetRedraw(TRUE);
      m_list.Invalidate();
      m_list.UpdateWindow();
 
或者参考

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_cwnd.3a3a.setredraw.asp


23. listctrl排序

Q250614:How To Sort Items in a CListCtrl in Report View
http://support.microsoft.com/kb/250614/en-us


24. 在listctrl中选中某个item时动态改变其icon或bitmap

Q141834: How to change the icon or the bitmap of a CListCtrl item in Visual C++
http://support.microsoft.com/kb/141834/en-us


25. 在添加item后,再InsertColumn()后导致整列数据移动的问题

Q151897: CListCtrl::InsertColumn() Causes Column Data to Shift
http://support.microsoft.com/kb/151897/en-us


26. 关于listctrl第一列始终居左的问题

解决办法:把第一列当一个虚列,从第二列开始插入列及数据,最后删除第一列。
     
具体解释参阅   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp

 


27. 锁定column header的拖动

http://msdn.microsoft.com/msdnmag/issues/03/06/CQA/


28. 如何隐藏clistctrl的列

    把需隐藏的列的宽度设为0,然后检测当该列为隐藏列时,用上面第27点的锁定column 的拖动来实现


29. listctrl进行大数据量操作时,使用virtual list   

http://www.codeguru.com/cpp/controls/listview/advanced/article.php/c4151/
http://www.codeproject.com/listctrl/virtuallist.asp


30. 关于item只能显示259个字符的问题

解决办法:需要在item上放一个edit。


31. 响应在listctrl的column header上的鼠标右键单击

Q125694: How To Find Out Which Listview Column Was Right-Clicked
http://support.microsoft.com/kb/125694/en-us


32. 类似于windows资源管理器的listview

Q234310: How to implement a ListView control that is similar to Windows Explorer by using DirLV.exe
http://support.microsoft.com/kb/234310/en-us

 


33. 在ListCtrl中OnTimer只响应两次的问题

Q200054:
PRB: OnTimer() Is Not Called Repeatedly for a List Control
http://support.microsoft.com/kb/200054/en-us


34. 以下为一些为实现各种自定义功能的listctrl派生类

          (1)    拖放       
                   http://www.codeproject.com/listctrl/dragtest.asp

                   在CListCtrl和CTreeCtrl间拖放
                   http://support.microsoft.com/kb/148738/en-us
 
          (2)    多功能listctrl
                   支持subitem可编辑,图标,radiobutton,checkbox,字符串改变颜色的类
                   http://www.codeproject.com/listctrl/quicklist.asp
 
                   支持排序,subitem可编辑,subitem图标,subitem改变颜色的类
                   http://www.codeproject.com/listctrl/ReportControl.asp

          (3)    subitem中显示超链接
                   http://www.codeproject.com/listctrl/CListCtrlLink.asp

          (4)    subitem的tooltip提示
                   http://www.codeproject.com/listctrl/ctooltiplistctrl.asp

          (5)    subitem中显示进度条   
                   http://www.codeproject.com/listctrl/ProgressListControl.asp
                   http://www.codeproject.com/listctrl/napster.asp
                   http://www.codeguru.com/Cpp/controls/listview/article.php/c4187/

          (6)    动态改变subitem的颜色和背景色
                    http://www.codeproject.com/listctrl/highlightlistctrl.asp
                    http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
 
          (7)    类vb属性对话框
                    http://www.codeproject.com/listctrl/propertylistctrl.asp
                    http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c995/
                    http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c1041/
 
          (8)    选中subitem(只高亮选中的item)
                    http://www.codeproject.com/listctrl/SubItemSel.asp
                    http://www.codeproject.com/listctrl/ListSubItSel.asp
 
          (9)    改变行高
                    http://www.codeproject.com/listctrl/changerowheight.asp
 
          (10)   改变行颜色
                    http://www.codeproject.com/listctrl/coloredlistctrl.asp
 
          (11)   可编辑subitem的listctrl
                    http://www.codeproject.com/listctrl/nirs2000.asp
                    http://www.codeproject.com/listctrl/editing_subitems_in_listcontrol.asp
 
          (12)   subitem可编辑,插入combobox,改变行颜色,subitem的tooltip提示
                    http://www.codeproject.com/listctrl/reusablelistcontrol.asp
 
          (13)   header 中允许多行字符串
                    http://www.codeproject.com/listctrl/headerctrlex.asp
 
          (14)   插入combobox
                    http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/article.php/c979/
 
          (15)   添加背景图片
                    http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c4173/
                    http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c983/
                    http://www.vchelp.net/vchelp/archive.asp?type_id=9&class_id=1&cata_id=1&article_id=1088&search_term=
   
          (16)  自适应宽度的listctrl
                    http://www.codeproject.com/useritems/AutosizeListCtrl.asp

          (17)  改变ListCtrl高亮时的颜色(默认为蓝色)
                   处理 NM_CUSTOMDRAW
          
http://www.codeproject.com/listctrl/lvcustomdraw.asp

2006年08月29日

关于文档视图关系的整理

、应用程序对象有一个文档模板管理器CDocManager* m_pDocManager
   (第一次调用AddDocTemplate时new出来)

2、文档模板管理器有一个文档模板对象列表CPtrList m_templateList
   (AddDocTemplate 函数负责添加该列表)

3、文档模板对象拥有文档、视图、框架的静态CRuntimeClass成员指针
   用于动态创建,还有一个m_nIDResource用来表示应采用的UI对象

4、每个文档模板对象拥有 m_pOnlyDoc 或 m_docList (文档指针或文档指针列表)
   OnFileNew 和 OnFileOpen都调用文档模板对象的OpenDocumentFile
   OpenDocumentFile 调用文档模板的 CreateNewDocument
   CreateNewDocument再调用文档模板的 AddDocument 填充该文档列表或文档指针

5、文档对象有一个文档模板指针 m_pDocTemplate (回指文档对象所属模板对象)
   同上,也是文档模板的 AddDocument 成员函数把 this 指针(文档模板自身)
   塞给刚刚创建的文档对象

6、文档对象有一个 m_viewList(视图列表)
   OnFileNew 和 OnFileOpen 都调用文档模板对象的OpenDocumentFile
   该函数调用 CreateNewDocument 创建文档
   然后调用 CreateNewFrame 创建框架对象
   CreateNewFrame 构造CCreateContext对象
   CCreateContext两个重要字段:(1)刚创建的文档指针(2)视图的CRuntimeClass指针

   CreateNewFrame 创建框架对象后由该对象调用 LoadFrame
   LoadFrame 的最后一个参数即为 CCreateContext 指针
   LoadFrame 调用 Create,Create 再调用 CreateEx 最后一个参数均为此CCreateCon
text指针
   Create的调用由消息映射表引发CFrameWnd::OnCreate被调用
   OnCreate的LPCREATESTRUCT的一个字段lpCreateParams 仍然是这个CCreateContext
指针
   则在CFrame::OnCreate中,由这个CCreateContext的CRuntimeClass(视图的)来调用C
reateObject
   产生视图对象后,由该对象调用Create(最后一个参数仍然是这个CCreateContext指针
)
   视图对象的Create由消息映射表引发视图对象的OnCreate被调用
   视图的OnCreate的参数 LPCREATESTRUCT 的 lpCreateParams 还是这个CCreateCont
ext指针)
   于是利用 CCreateContext 的成员 m_pCurrentDoc (当前文档)
   来调用 CDocument::AddView 把视图加入文档的视图列表

7、视图有一个文档指针m_pDocument (指向所属文档)
   同上,也是CDocument::AddView函数初始化的,如下所示:
   pView->m_pDocument = this;

8、框架有一个m_pViewActive(活动视图)
   由框架的SetActiveView进行设置

 
CWinApp中有一个CDocManager对象指针  m_pDocManager
(第一次调用AddDocTemplate时new出来)
CDocManager有一个指针链表 CPtrList m_templateList
用来保存一系列文档模板
文档模板有三个成员变量
CRuntimeClass* m_pDocClass
CRuntimeClass* m_pFrameClass
CRuntimeClass* m_pViewClass   //用于动态创建对象
CSingleDocTemplate有一个文档指针m_pOnlyDoc
CMultiDocTemplate有一个文档指针列表 CPtrList m_docList  //用来保存一系列文档
文档有一个成员CDocTemplate* m_pDocTemplate 指向其文档模板
文档还有一个CPtrList m_viewList用来保存其对应的一系列视图
视图有一个成员变量CDocument* m_pDocument指向其相关的文档
框架有一个成员 CView* m_pViewActive
指向当前活动的View

几个View_Frame_Document迭代函数:(已知一个求另一个的Get函数)

CWinApp::GetFirstDocTemplatePosition
CWinApp::GetNextDocTemplate
CDocTemplate::GetFirstDocPosition
CDocTemplate::GetNextDoc
CDocument::GetFirstViewPosition
CDocument::GetNextView
CDocument::GetDocTemplate
CView::GetDocument
CView::GetParent
CView::GetParentFrame
CFrameWnd::GetActiveView
CFrameWnd::GetActiveDocument

2006年06月15日

动态链接库DLL的链接

  应用程序使用DLL可以采用两种方式:一种是隐式链接,另一种是显式链接。在使用DLL之前首先要知道DLL中函数的结构信息。Visual C++6.0在VC\bin目录下提供了一个名为Dumpbin.exe的小程序,用它可以查看DLL文件中的函数结构。另外,Windows系统将遵循下面的搜索顺序来定位DLL: 1.包含EXE文件的目录,2.进程的当前工作目录, 3.Windows系统目录, 4.Windows目录,5.列在Path环境变量中的一系列目录。

  1.隐式链接

  隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中。实现隐式链接很容易,只要将导入函数关键字_declspec(dllimport)函数名等写到应用程序相应的头文件中就可以了。下面的例子通过隐式链接调用MyDll.dll库中的Min函数。首先生成一个项目为TestDll,在DllTest.h、DllTest.cpp文件中分别输入如下代码:


//Dlltest.h
#pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include
#include"Dlltest.h"
void main()
{int a;
a=min(8,10)
printf("比较的结果为%d\n",a);
}


  在创建DllTest.exe文件之前,要先将MyDll.dll和MyDll.lib拷贝到当前工程所在的目录下面,也可以拷贝到windows的System目录下。如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C"。TestDll.h文件中的关键字Progam commit是要Visual C+的编译器在link时,链接到MyDll.lib文件,当然,开发人员也可以不使用#pragma comment(lib,"MyDll.lib")语句,而直接在工程的Setting->Link页的Object/Moduls栏填入MyDll.lib既可。

  2.显式链接

  显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。不过实现显式链接要麻烦一些。在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过显式链接调用DLL中的Max函数的例子。


#include
#include
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max
HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
Printf("比较的结果为%d\n",a);
FreeLibrary(hDLL);//卸载MyDll.dll文件;
}

  在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。

  使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,"Min")改为GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。

编程时用ad.h,ad.lib,放在项目当前目录里

在头文件中加入#include "ad.h"

在Project Setting–>Link–>Object/library modules
加入ad.lib

执行时将ad.dll跟你的程序放在同一目录

我们在编写程序,开发软件的过程中如果能利用已有的程序的功能,那么可以大大减轻开发过程中程序员的工作量,同时达到事半功倍的效果。例如在工程中,许多软件需要文字处理功能,虽然MFC提供了一些方法,但是具体实现起来既费事,又有一定的困难,如果我们可以直接使用OFFICE提供的功能,岂不美哉!要实现这一目的,只需要利用微软的ActiveX Automation技术就可以轻松实现。本例主要讲述了自动化的概念,并通过一个操作Word文档的程序来帮助读者朋友理解Visual C++编程中如何实现自动化。

  一、实现方法

  1.自动化的概念

  在Windows程序开发中,自动化是基于COM/DCOM之上的技术:它可以使一个应用程序来操纵另外一个程序中的对象,使用该程序提供的丰富的功能;或者是一个应用程序通过提供一些对象及对象的方法和属性来允许另外一个程序使用它提供的各种功能。其中,根据程序是受惠者还是施惠者这一原则,将提供对象和对象属性方法的应用程序称为自动化服务器端;使用对象属性方法的应用程序叫作自动化的客户端。自动化服务器让其它程序告诉它做些什么,它揭示的函数和数据称为方法(Methods)和属性(Properties)。例如Microsoft Office套件中的成员Word、Excel等都是自动化服务器。根据自动化的客户端和服务器端的不同位置,又可以将自动化分为两类:

  1)如果作为服务器端和客户端的两个不同的应用程序都在同一台计算机上,叫作本地自动化;

  2)如果作为服务器端和客户端的两个不同的应用程序是基于网络的,分别处于不同的计算机上,叫作远程自动化。

  需要读者朋友注意的是,由于利用了COM技术,客户端无法直接获得服务器端的对象来实现对它的访问,它必须通过获取一个指向接口的指针来使用对象提供的功能。这个接口称为IDispatch,它是一个简化的使用多种不同语言的特殊接口(包括象Visual Basic这样不能使用指针的语言)。这部分内容读者可以在下面的程序实现过程中细细体味。

  说到自动化,不能不提一下ActiveX控件,它其实是在进程中装入的极小自动化服务器。这意味着它们的执行速度极快,它们原来被称为OLE自定义控件,用来替代VB和Visual C++中使用的16位的VBX控件。由于ActiveX控件通常被保存为.OCX文件,所以又称它为OCX控件。因为控件是一个小型化的自动化服务器,它们需要在自动化客户端使用,我们又将这时的客户端应用程序称为容器应用程序。ActiveX控件除了属性和方法外,还包含事件(events),当一些事情需要容器应用程序注意时,控件才会触发一个事件,如用户的单击等。

  2.自动化中的数据类型

  在实现自动化编程中,最重要的一个问题是程序员要明白在自动化的客户端和服务器端数据是如何传递的。如何提供一个统一安全的机制来实现数据传递呢?在Visual C++开发平台上,它提供了一个叫VARIANT的数据类型来解决这个问题。该数据类型有两部分组成,第一部分为数据的类型,第二部分才是具体的数据的数值。在VARIANT数据类型的基础上,VC提供了ColeVariant类,它对VARIANT数据类型进行了封装,这意味着在所有使用VARIANT的地方都可以使用ColeVariant类的对象。在操作日期和货币变量时,Visual C++又提供了两个类:ColeCurrency和ColeDateTime,具体的内容,读者可以参考MSDN。

  本实例的代码演示了如何操作Word2000实现自动化,由于我们主要是为了辅助解释说明Visual C++编程中怎样实现自动化,帮助读者对自动化的实现过程有一个感性的认识,所以为了简单起见,该程序的功能是仅仅选择一个Word2000的*.Doc文档,并将它的内容都在该程序的"视"中显示出来。为了达到这个目的,就需要启动Word2000打开用户所选择的文件,进行全选和剪贴板操作,然后将数据粘贴到"视"中。

  在这个例子的具体实现中,我们的应用程序为自动化的客户端,Word2000为自动化的服务器端。首先生成一个新的单文档应用程序,视的基类选择CEditView,由于Office中的许多操作是由VBA语言来实现的,所以我们在使用自动化操作Word2000的过程中,为了清楚Word2000的工作过程,我们要对VBA有所了解,这些知识可以通过在Word中录制宏来实现。录制宏的步骤如下:启动Word2000,选择工具菜单下的录制宏子菜单,为一个新的宏起个名字为Macro1,开始录制宏,这时候窗口出现一个浮动工具条,点击上面的按钮可以停止或暂停宏的录制。下面是实现打开一个Word2000文档、全部选择文档内容并将数据存放到剪贴板上的操作的宏代码:


‘ Sub Macro1()
” 宏在 02-5-1 由 LiuTao 录制

Documents.Open FileName:="基于Visual C.doc", ConfirmConversions:=False, _
ReadOnly:=False, AddToRecentFiles:=False, PasswordDocument:="", _
PasswordTemplate:="", Revert:=False, WritePasswordDocument:="", _
WritePasswordTemplate:="", Format:=wdOpenFormatAuto
Selection.WholeStory
Selection.Copy
End Sub


  查看宏代码可以点击Word2000中工具菜单下的Visual Basic 编辑器。从上面的代码中可以看出对Word2000的这几个操作用到了Documents、Selection这两个自动化服务器端显示出来的对象和它们的方法。

  为了在Visual C++中编程操作Word2000,需要使用微软提供的类型库,这个类型库可以在Msword9.olb文件中找到,Msword9.olb这个文件通常情况下位于Microsoft Office的安装目录下面。利用Visual C++中的Class Wizard可以方便的向应用程序添加新的类型库,在应用程序的项目代码中启动Class Wizard ,选择Add class…. \From a type library …\C:\OFFICE\msword 9.olb,在弹出的对话框中选择_Application,_Documents,Selection,这样就可以为客户端程序中使用到的每个Word2000对象建立相应的C++类。这些类的定义和实现分别在msword9.h和msword9.cpp文件中给出。

  为了使生成程序的项目代码支持自动化,我们需要要在项目中的StdAfx.h的文件末尾添加上下面一行:#include "afxdisp.h";在应用程序的InitInstance函数的开始添加下面的调用:


if (CoInitialize(NULL)!=0)
{
 AfxMessageBox("初始化COM支持库失败!");
 exit(1);
}

 

或者加入这句话也可以:AfxOleInit();
  在操作Word2000的过程中,首先通过ColeDispatchDriver类的成员函数CreateDispatch()创建一个Word2000的Applicaion对象,然后通过COleDispatchDriver的成员函数AttachDispatch()将Documents和Selection对象与Application对象提供的IDispatch接口关联起来,在程序对上述对象使用完毕后,要使用COleDispatchDriver的成员函数ReleaseDispatch()来释放对象和接口之间的连接。

  二、编程步骤

  1、 启动Visual C++6.0,生成一个单文档的应用程序,视图的基类选择CEditView类,将该程序命名为"OperateWord";

  2、 启动Word2000,根据所要执行的Word操作录制宏;

  3、 使用Class Wizard为应用程序的项目代码中添加操作Word2000的类型库Msword9.olb;

  4、 添加代码,编译运行程序。


三、程序代码

////////////////////////////////////////////////////////////////////////////
void COperateWordView::OnShowDoc()
{
 // TODO: Add your command handler code here
 static char BASED_CODE szFilter[]="Word Files(*doc)|*.DOC||";
 CFileDialog fd(true,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,szFilter,NULL);
 CString strFilePath;//用来保存打开的文件名;
 if(fd.DoModal()==IDOK)
  strFilePath=fd.GetPathName();//获取要打开的Word文档的名字;
  _Application m_App;//定义Word提供的应用程序对象;
 Documents m_Docs;//定义Word提供的文档对象;
 Selection m_Sel;//定义Word提供的选择对象;
 m_Docs.ReleaseDispatch();
 m_Sel.ReleaseDispatch();
 m_App.m_bAutoRelease=true;
 if(!m_App.CreateDispatch("Word.Application"))
 {
  AfxMessageBox("创建Word2000服务失败!");
  exit(1);
 }
 //下面是定义VARIANT变量;
 COleVariant varFilePath(strFilePath);
 COleVariant varstrNull("");
 COleVariant varZero((short)0);
 COleVariant varTrue(short(1),VT_BOOL);
 COleVariant varFalse(short(0),VT_BOOL);
 m_Docs.AttachDispatch(m_App.GetDocuments());
 //将Documents类对象m_Docs和Idispatch接口关联起来;
 m_Docs.Open(varFilePath,varFalse,varFalse,varFalse,
  varstrNull,varstrNull,varFalse,varstrNull,
  varstrNull,varTrue,varTrue,varTrue);
 //打开Word文档;
 m_Sel.AttachDispatch(m_App.GetSelection());
 //将Selection类对象m_Sel和Idispatch接口关联起来;
 m_Sel.WholeStory ();//选择文档中的全部内容;
 m_Sel.Copy();//将数据拷贝到剪贴板
 this->GetEditCtrl( ).Paste ();//将数据粘贴到程序的"视"中;
 m_Docs.ReleaseDispatch();//断开关联;
 m_Sel.ReleaseDispatch();
}


  四、小结

  本实例主要讲述了基于自动化的技术如何操作Word2000,为读者学习自动化客户端程序的开发起到抛砖引玉的作用,读者可以在此基础上,实现对Word2000、Excel2000、PowerPoint2000等服务器端进行更复杂的操作。最后补充一句,如果读者朋友想操作OFFICE98版本的应用程序,只需要引入微软提供的8.0版本的类型库就可以了。

2006年05月09日

刚接触VC编程的朋友往往对许多数据类型的转换感到迷惑不解,本文将介绍一些常用数据类型的使用。

我们先定义一些常见类型变量借以说明

int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠沈静";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;

一、其它数据类型转换为字符串

短整型(int)
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); ///按二进制方式转换
长整型(long)
ltoa(l,temp,10);

二、从其它包含字符串的变量中获取指向该字符串的指针
CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);

三、字符串转换为其它数据类型
strcpy(temp,"123");

短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);

四、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:

整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;

五、BSTR、_bstr_t与CComBSTR

CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);

六、VARIANT 、_variant_t 与 COleVariant

VARIANT的结构可以参考头文件VC98\Include\OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4;///指明整型数据
va.lVal=a; ///赋值

对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:

unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF

variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;

COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;

七、其它

对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
LPARAM lParam;
WORD loValue = LOWORD(lParam);///取低16位
WORD hiValue = HIWORD(lParam);///取高16位
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);///取低8位
BYTE hiValue = HIBYTE(wValue);///取高8位

2006年04月21日

讲解: 很多人在使用VC串口控件编写程序的时候,经常会遇到当串口接受数据量比较大时,从Windows 2000任务管理器里,可以看到程序占用的内存会不断增大,只好转用API从新编写,本文主要解决这个问题.

一般编写串口控件接收数据时,是这样写的:

//初始化设置

m_msComm.SetCommPort(1)//打开COM1
m_msComm.SetPortOpen(true);//打开串口
m_msComm.SetSettings("115200,n,8,1");//串口参数设置 
m_msComm.SetInputMode(1);//comInputModeBinary设置Binary缓冲区输入方式
m_msComm.SetRThreshold(5);//每接收5个字符则激发OnComm()事件
m_msComm.SetInputLen(5);//每次读取5个字符

//接收数据

void CCOMDlg::OnComm() 
{
VARIANT variant_inp;
COleSafeArray safearray_inp;
LONG len,k;
BYTE rxdata[5]; //设置BYTE数组
CString strtemp;

switch(m_msComm.GetCommEvent())
{
case 2://事件值为2表示接收缓冲区内有字符


variant_inp=m_msComm.GetInput(); //读缓冲区——————————–*


safearray_inp = variant_inp; //VARIANT型变量转换为ColeSafeArray型变量


len=safearray_inp.GetOneDimSize(); //验证得到有效数据长度
assert(len == 5);


for(k=0;k<len;k++)//len is 5 in normal case
safearray_inp.GetElement(&k,rxdata+k);//转换为BYTE型数组

HandleCommand(rxdata);//这是接收数据处理函数
break;

default:
break;
}
}

//发送

void CCOMDlg::SendCommand(unsigned char commandID,int para1,int para2)
{
BYTE strBuf[5],tempbyte; 
CByteArray OutBuf; 
COleVariant varOutput; 

//命令字
strBuf[0] = commandID;

//地址高位
tempbyte = (para1>>8) & 0xff;
strBuf[1] = tempbyte;

//地址低位
tempbyte = para1 & 0xff;
strBuf[2] = tempbyte;

//数据高位
tempbyte = (para2>>8) & 0xff;
strBuf[3] = tempbyte;

//数据低位
tempbyte = para2 & 0xff;
strBuf[4] = tempbyte;

OutBuf.SetSize(5); 
for(int i=0;i<5;i++) 
OutBuf[i] = strBuf[i]; 
varOutput = OutBuf; 
m_msComm.SetOutput(varOutput); 
}

问题出在VARIANT型变量上,当执行到*号的这一行时,VARIANT型变量就会使内存增加,可以用下面的方法解决:

void CCOMDlg::OnComm() 
{

long len,k;
COleVariant myVar;
COleSafeArray safearray_inp;
BYTE rxdata[5]; //设置BYTE数组

switch(m_msComm.GetCommEvent())
{
case 2://事件值为2表示接收缓冲区内有字符

myVar.Attach (m_msComm.GetInput());————————————–*

safearray_inp = myVar; //COleVariant型变量转换为ColeSafeArray型变量

len=safearray_inp.GetOneDimSize(); //验证得到有效数据长度
assert(len == 5);

for(k=0;k<len;k++) //len is 5 in normal case
safearray_inp.GetElement(&k,rxdata+k); //转换为BYTE型数组

HandleCommand(rxdata); );//这是接收数据处理函数

break;

2006年03月03日

直接通过ODBC读写Excel表格文件
译者:徐景周(原作:Alexander Mikula)

下载本文示例代码

想要通过ODBC直接读、写Excel表格文件,首先,应确保ODBC中已安装有Excel表格文件的驱动"MICROSOFT EXCEL DRIVER (*.XLS)"。然后,可根据下面步骤进行:

1. 在StdAfx.h文件中加入

#include <afxdb.h>
#include <odbcinst.h>

2. 通过ODBC直接创建Excel文件并在表中插入数据(暂定文件名:Demo.xls)

//创建并写入Excel文件
void CRWExcel::WriteToExcel()
{
  CDatabase database;
  CString sDriver = "MICROSOFT EXCEL DRIVER (*.XLS)"; // Excel安装驱动
  CString sExcelFile = "c:\\demo.xls";                // 要建立的Excel文件
  CString sSql;

  TRY
  {
    // 创建进行存取的字符串
    sSql.Format("DRIVER={%s};DSN='''';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=\"%s\";DBQ=%s",
                sDriver, sExcelFile, sExcelFile);

    // 创建数据库 (既Excel表格文件)
    if( database.OpenEx(sSql,CDatabase::noOdbcDialog) )
    {
      // 创建表结构(姓名、年龄)
      sSql = "CREATE TABLE demo (Name TEXT,Age NUMBER)";
      database.ExecuteSQL(sSql);

      // 插入数值
      sSql = "INSERT INTO demo (Name,Age) VALUES (''徐景周'',26)";
      database.ExecuteSQL(sSql);

      sSql = "INSERT INTO demo (Name,Age) VALUES (''徐志慧'',22)";
      database.ExecuteSQL(sSql);

      sSql = "INSERT INTO demo (Name,Age) VALUES (''郭徽'',27)";
      database.ExecuteSQL(sSql);
    }      

    // 关闭数据库
    database.Close();
  }
  CATCH_ALL(e)
  {
    TRACE1("Excel驱动没有安装: %s",sDriver);
  }
  END_CATCH_ALL;
}

3. 通过ODBC直接读取Excel文件(暂定文件名:Demo.xls)

// 读取Excel文件
void CRWExcel::ReadFromExcel()
{
    CDatabase database;
    CString sSql;
    CString sItem1, sItem2;
    CString sDriver;
    CString sDsn;
    CString sFile = "Demo.xls"; 			// 将被读取的Excel文件名

    // 检索是否安装有Excel驱动 "Microsoft Excel Driver (*.xls)"
    sDriver = GetExcelDriver();
    if (sDriver.IsEmpty())
    {
        // 没有发现Excel驱动
        AfxMessageBox("没有安装Excel驱动!");
        return;
    }

    // 创建进行存取的字符串
    sDsn.Format("ODBC;DRIVER={%s};DSN='''';DBQ=%s", sDriver, sFile);

    TRY
    {
        // 打开数据库(既Excel文件)
        database.Open(NULL, false, false, sDsn);

        CRecordset recset(&database);

        // 设置读取的查询语句.
        sSql = "SELECT Name, Age "
               "FROM demo "
               "ORDER BY Name ";

        // 执行查询语句
        recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly);

        // 获取查询结果
        while (!recset.IsEOF())
        {
            //读取Excel内部数值
            recset.GetFieldValue("Name ", sItem1);
            recset.GetFieldValue("Age", sItem2);

            // 移到下一行
            recset.MoveNext();
        }

        // 关闭数据库
        database.Close();

    }
    CATCH(CDBException, e)
    {
        // 数据库操作产生异常时...
        AfxMessageBox("数据库错误: " + e->m_strError);
    }
    END_CATCH;
}

4. 获取ODBC中Excel驱动的函数

CString CRWExcel::GetExcelDriver()
{
    char szBuf[2001];
    WORD cbBufMax = 2000;
    WORD cbBufOut;
    char *pszBuf = szBuf;
    CString sDriver;

    // 获取已安装驱动的名称(涵数在odbcinst.h里)
    if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut))
        return "";

    // 检索已安装的驱动是否有Excel...
    do
    {
        if (strstr(pszBuf, "Excel") != 0)
        {
            //发现 !
            sDriver = CString(pszBuf);
            break;
        }
        pszBuf = strchr(pszBuf, ''\0'') + 1;
    }
    while (pszBuf[1] != ''\0'');

    return sDriver;
}

作者信息:
姓名:徐景周(未来工作室 Future Studio)
EMAIL:jingzhou_xu@163.net

2005年12月14日

VC项目文件说明

.opt 工程关于开发环境的参数文件。如工具条位置等信息;

.aps (AppStudio File),资源辅助文件,二进制格式,一般不用去管他;

.clw ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW文件可以解决.如果此文件不存在的话,每次用ClassWizard的时候绘提示你是否重建;

.dsp (DeveloperStudio Project):项目文件,文本格式,不过不熟悉的话不要手工修改.dsw(DeveloperStudio Workspace)是工作区文件,其他特点和DSP差不多;

.plg 是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大.在Tools->Options里面有个选项可以控制这个文件的生成;

.hpj (Help Project)是生成帮助文件的工程,用microsfot  Help Compiler可以处理;

.mdp (Microsoft DevStudio Project)是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的DSP格式;

.bsc 是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件.如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,可以加快编译速度;

.map 是执行文件的映像信息纪录文件,除非对系统底层非常熟悉,这个文件一般用不着;

.pch (Pre-Compiled File)是预编译文件,可以加快编译速度,但是文件非常大;

.pdb (Program Database)记录了程序有关的一些数据和调试信息,在调试的时候可能有用;

2005年11月29日

我们在进行程序的界面设计时,常常希望将位图的关键部分,也既是图像的前景显示在界面上,而将位图的背景隐藏起来,将位图与界面很自然的融合在一起,本实例介绍了透明位图的制作知识,并将透明位图在一个对话框中显示了出来,界面效果如图一所示:



图一、对话框界面上透明显示位图


  一、 实现方法

  绘制"透明"位图是指绘制某一位图中除指定颜色外的其余部分,我们称这种颜色为"透明色"。通过将位图的背景色指定为"透明色",在绘制时,不绘制这部分背景,而仅绘制图像,这样就可以将位图中图像透明地绘制到窗口上。

  绘制"透明"位图的关键是创建一个"掩码"位图(mask bitmap),"掩码"位图是一个单色位图,它是位图中图像的一个单色剪影。在Windows编程中,绘图都要用到设备描述表,我们需创建两个内存设备描述表:位图设备描述表(image DC)和"掩码"位图设备描述表(mask DC)。位图设备描述表用来装入位图,而"掩码"位图设备描述表用来装入"掩码"位图。在"掩码"位图设备描述表中制作"掩码"位图的方式是:先创建一个单色的Bitmap,装入mask DC,然后,以"SRCCOPY"的方式将装有位图的位图设备描述表绘制(BitBlt)到mask DC上。这样,mask DC的显示平面中的位图即是"掩码"位图。

  一般情况下,绘制"透明"位图的实际操作步骤如下:

  1、 位图设备描述表以"SRCINVERT"的方式绘制(BitBlt)到显示设备描述表上;

  2、 "掩码"位图设备描述表以"SRCAND"的方式绘制(BitBlt)到显示设备描述表上;

  3、 再将位图设备描述表以"SRCINVERT"的方式绘制(BitBlt)到显示设备描述表上。这样除"透明色"外的其余位图部分(图像部分)就被绘制到窗口上了。

  上述操作中需要用到的显示函数BitBlt的原型和说明如下:


BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );


  函数的参数说明如下:int x表示贴到目的地的左上角X坐标;int y表示/贴到目的地的左上角Y坐标;int nWidth表示贴到目的地的区域宽度;int nHeight表示贴到目的地的区域高度;CDC* pSrcDC表示存储源位图的设备描述表;int xSrc表示源位图的左上角X坐标;int ySrc表示源位图的左上角Y坐标;DWORD dwRop为柵格运算标志,一般我们选择SRCCOPY,直接拷贝源位图到目标。还可以让源位图和目标位图进行XOR,AND,OR等等的操作。大家可以查看MSDN。

  二、 编程步骤

  1、 启动Visual C++6.0,生成一个基于对话框架的应用程序,讲程序命名为"TransPrarentImageTest";

  2、 添加位图资源,其ID为IDB_DRAGON,然后在对话框上添加一个IDC_STATIC控件,在其属性设置里选择显示该资源图像;

  3、 使用Class Wizard自定义类CtransparentImage,其基类选择Cstatic;

  4、 添加代码,编译运行程序。

  三、 程序代码


//////////////////////////////////////////////////////////
#ifndef __TRANSPARENTIMAGE_H_TRANSPARENTIMAGE_42A6E395_97E4_11D3_B6F0_005004024A9E
#define __TRANSPARENTIMAGE_H_TRANSPARENTIMAGE_42A6E395_97E4_11D3_B6F0_005004024A9E
#if _MSC_VER >= 1000
#pragma once
#endif
class CTransparentImage : public CStatic
{
 public:
  CTransparentImage() ;
  virtual ~CTransparentImage() ;
 protected:
  //{{
AFX_MSG( CTransparentImage )
    afx_msg void OnPaint() ;
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
} ;
//{{AFX_INSERT_LOCATION}}
#endif

//////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "TransparentImage.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__ ;
#endif

CTransparentImage::CTransparentImage()
{}

CTransparentImage::~CTransparentImage()
{}

BEGIN_MESSAGE_MAP( CTransparentImage, CStatic )
 //{{AFX_MSG_MAP( CTransparentImage )
  ON_WM_PAINT()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CTransparentImage::OnPaint()
{
 HBITMAP l_hbmpBitmap = GetBitmap() ;
 if( l_hbmpBitmap == NULL )
 {
  Default() ;
  return ;
 }
 CPaintDC l_PaintDC( this ) ;
 // Prepare everything for drawing
 CRect l_rcClient ;
 GetClientRect( &l_rcClient ) ;
 CDC l_BufferDC ;
 l_BufferDC.CreateCompatibleDC( &l_PaintDC ) ;
 CBitmap l_BufferBitmap ;
 l_BufferBitmap.CreateCompatibleBitmap(&l_PaintDC,l_rcClient.Width(), l_rcClient.Height() ) ;
 CBitmap* l_pOldBufferBitmap = l_BufferDC.SelectObject( &l_BufferBitmap ) ;
 CDC l_MaskDC ;
 l_MaskDC.CreateCompatibleDC( &l_PaintDC ) ;
 CBitmap l_MaskBitmap ;
 l_MaskBitmap.CreateBitmap( l_rcClient.Width(), l_rcClient.Height(), 1, 1, NULL ) ;
 CBitmap* l_pOldMaskBitmap = l_MaskDC.SelectObject( &l_MaskBitmap ) ;
 #define SRCMASK 0×00220326
 // Fill with transparent
color
 l_BufferDC.FillSolidRect( &l_rcClient, RGB( 255, 0, 255 ) ) ;
 // Blit the bitmap to the buffer
 CDC l_MemoryDC ;
 l_MemoryDC.CreateCompatibleDC( &l_PaintDC ) ;
 CBitmap* l_pOldMemoryBitmap = l_MemoryDC.SelectObject( CBitmap::FromHandle( l_hbmpBitmap ) ) ;
 l_BufferDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_MemoryDC,0, 0, SRCCOPY ) ;
 l_MemoryDC.SelectObject( l_pOldMemoryBitmap ) ;
 // Create the mask.
 COLORREF l_crOldBack = l_BufferDC.SetBkColor( RGB( 255, 0, 255 ) ) ;
 l_MaskDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_BufferDC,0, 0, SRCCOPY ) ;
 l_BufferDC.SetBkColor( l_crOldBack ) ;
 // Draw the bitmap transparently now;
 if( ! l_PaintDC.MaskBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(),&l_BufferDC, 0, 0, l_MaskBitmap, 0, 0,ROP4_TRANSPARENTBLIT ) )
 {
  CDC l_CopyDC ;
  l_CopyDC.CreateCompatibleDC( &l_PaintDC ) ;
  CBitmap l_CopyBitmap ;
  l_CopyBitmap.CreateCompatibleBitmap( &l_PaintDC, l_rcClient.Width(),l_rcClient.Height() ) ;
  CBitmap* l_pOldCopyBitmap = l_CopyDC.SelectObject( &l_CopyBitmap ) ;
  l_CopyDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_PaintDC,0, 0, SRCCOPY ) ;
  l_CopyDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_MaskDC,0, 0, SRCAND ) ;
  l_BufferDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_MaskDC,0, 0, SRCINVERT ) ;
  l_CopyDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_BufferDC,0, 0, SRCPAINT ) ;
  l_PaintDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_CopyDC,0, 0, SRCCOPY ) ;
  l_CopyDC.SelectObject( l_pOldCopyBitmap ) ;
 }
 // Clean up.
 l_MaskDC.SelectObject( l_pOldMaskBitmap ) ;
 l_BufferDC.SelectObject( l_pOldBufferBitmap ) ;
}


  四、 小结

  本实例介绍了如何在对话框中实现透明位图的显示,读者朋友可能感觉到文中介绍的方法和代码的实现过程仿佛不太一致,其实这些只是表面现象,读者朋友在吃透实例代码的实现过程后,就会发现两者之间并没有什么冲突。

2005年11月25日

如何实现双缓冲
首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:

CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象

//随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(NULL);
//这时还不能绘图,因为没有地方画 ^_^
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);

//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);

//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));

//绘图
MemDC.MoveTo(……);
MemDC.LineTo(……);

//将内存中的图拷贝到屏幕上进行显示
pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);

//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();

禁止系统擦掉原来的图象
可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。如
BOOL CMyWin::OnEraseBkgnd(CDC* pDC)
{
  return TRUE;
  //return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。
}


多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案。
MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,
只要使用方法得当,再加上一些技巧,用MFC可以得到效率很高的绘图程序。
我想就我长期(呵呵当然也只有2年多)使用MFC绘图的经验谈谈
我的一些观点。
1、显示的图形为什么会闪烁?
    我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏
幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,
总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容
反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来
在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。
当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来
绘制的图形进行清除,而又叠加上了新的图形。
    有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,
其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。
例如在OnDraw(CDC *pDC)中这样写:
 pDC->MoveTo(0,0);
 pDC->LineTo(100,100);
这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见
闪烁。其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的
时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。
比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪
烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画
只会闪烁一次。这个也可以试验,在OnDraw(CDC *pDC)中这样写:
 for(int i=0;i<100000;i++)
 {
  pDC->MoveTo(0,i);
  pDC->LineTo(1000,i);
 }
呵呵,程序有点变态,但是能说明问题。
    说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么
闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要
闪得厉害一些,但是闪烁频率要低。
    那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,
闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间
的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,
不闪才怪呢。

2、如何避免闪烁
    在知道图形显示闪烁的原因之后,对症下药就好办了。首先当然是去掉MFC
提供的背景绘制过程了。实现的方法很多,
  * 可以在窗口形成时给窗口的注册类的背景刷付NULL
  * 也可以在形成以后修改背景
 static CBrush brush(RGB(255,0,0));
 SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);
  * 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE
    这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,
变得一团乱。怎么办?这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有
图形进行显示以外,在内存中也有图形在绘制。我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。

3、如何实现双缓冲
    首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:
 CDC MemDC; //首先定义一个显示设备对象
 CBitmap MemBitmap;//定义一个位图对象
 //随后建立与屏幕显示兼容的内存显示设备
 MemDC.CreateCompatibleDC(NULL);
 //这时还不能绘图,因为没有地方画 ^_^
 //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
 MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
 
 //将位图选入到内存显示设备中
 //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
 CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
 //先用背景色将位图清除干净,这里我用的是白色作为背景
 //你也可以用自己应该用的颜色
 MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
 //绘图
 MemDC.MoveTo(……);
 MemDC.LineTo(……);
 
 //将内存中的图拷贝到屏幕上进行显示
 pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
 //绘图完成后的清理
 MemBitmap.DeleteObject();
 MemDC.DeleteDC();
上面的注释应该很详尽了,废话就不多说了。

4、如何提高绘图的效率
    我主要做的是电力系统的网络图形的CAD软件,在一个窗口中往往要显示成千上万个电力元件,而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件,可想而知这个过程是非常漫长的。如果加上了图形的浏览功能,鼠标拖动图形滚动时需要进行大量的重绘,速度会慢得让用户将无法忍受。怎么办?只有再研究研究MFC的绘图过程了。
    实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你
在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。


关键字 双缓冲
原作者姓名 戚高

介绍
在论坛中经常见到关于刷新时界面闪烁的帖子,如何控制在进行高效绘图时不出现界面闪烁的感觉呢,下文就双缓冲方法进行讲解.

正文
图形为什么会闪烁的原因是:我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。
如何实现双缓冲:在OnDraw(CDC *pDC)中:
      CDC MemDC; //首先定义一个显示设备对象
      CBitmap MemBitmap;//定义一个位图对象

      //随后建立与屏幕显示兼容的内存显示设备
      MemDC.CreateCompatibleDC(NULL);
      //这时还不能绘图,因为没有地方画 ^_^
      //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
      MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
      //将位图选入到内存显示设备中
      //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
      CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
      //先用背景色将位图清除干净,这里我用的是白色作为背景
      //你也可以用自己应该用的颜色
      MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
      //绘图
      MemDC.MoveTo(……);
      MemDC.LineTo(……);

      //将内存中的图拷贝到屏幕上进行显示
      pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
      //绘图完成后的清理
      MemBitmap.DeleteObject();
      MemDC.DeleteDC();


以论坛的一个帖子例子为例来说明一些具体如何解决问题.
帖子那容是:

我想让一个区域动起来,
如何解决窗口刷新时区域的闪烁。
void CJhkljklView::OnDraw(CDC* pDC)
{
    CJhkljklDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
            int i;
    int x[20],y[20];
    CPen hPen;
     POINT w[5];
    
             x[0]=a/100+10;
             x[1]=a/100+30;
                 x[2]=a/100+80;
                 x[3]=a/100+30;
                 x[4]=a/100+10;

                     y[0]=10;
                  y[1]=10;
                   y[2]=25;
                    y[3]=40;
                     y[4]=40;    

      for (i=0;i<5;i++)
      {         w[i].x=x[i];
      w[i].y=y[i];
      }
      //CClientDC dc(this);
             //hPen=CreatePen(PS_SOLID,1,RGB(255,0,0));
      CRgn argn,Brgn;
        CBrush abrush(RGB(40,30,20));
        argn.CreatePolygonRgn(w, 5, 1);// point为CPoint数组,
        pDC->FillRgn(&argn, &abrush);
         abrush.DeleteObject();
}

void CJhkljklView::OnTimer(UINT nIDEvent)
{
    // TODO: Add your message handler code here and/or call default
        InvalidateRect(NULL,true);
        UpdateWindow();
        a+=100;
    CView::OnTimer(nIDEvent);
}

int CJhkljklView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    // TODO: Add your specialized creation code here
    SetTimer(1,10,NULL);
    return 0;
}


利用定时器直接进行10毫秒的屏幕刷新,这样效果会出现不停的闪烁的情况.

解决方法利用双缓冲,首先触发WM_ERASEBKGND,然后修改返回TRUE;
定义变量:
CBitmap *m_pBitmapOldBackground ;
CBitmap m_bitmapBackground ;
CDC m_dcBackground;

//绘制背景
if(m_dcBackground.GetSafeHdc()== NULL|| (m_bitmapBackground.m_hObject == NULL))
    {
        m_dcBackground.CreateCompatibleDC(&dc);
        m_bitmapBackground.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height()) ;
        m_pBitmapOldBackground = m_dcBackground.SelectObject(&m_bitmapBackground) ;
        //DrawMeterBackground(&m_dcBackground, rect);
        CBrush brushFill, *pBrushOld;        
        // 背景色黑色
        brushFill.DeleteObject();
        brushFill.CreateSolidBrush(RGB(255, 255, 255));
        pBrushOld = m_dcBackground.SelectObject(&brushFill);
        m_dcBackground.Rectangle(rect);
        m_dcBackground.SelectObject(pBrushOld);
    }
    memDC.BitBlt(0, 0, rect.Width(), rect.Height(),
                       &m_dcBackground, 0, 0, SRCCOPY) ;

    //绘制图形
    int i;
    int x[20],y[20];
    CPen hPen;
    POINT w[5];
    
    x[0]=a/100+10;
    x[1]=a/100+30;
    x[2]=a/100+80;
    x[3]=a/100+30;
    x[4]=a/100+10;
    
    y[0]=10;
    y[1]=10;
    y[2]=25;
    y[3]=40;
    y[4]=40;
    
    
    
    for (i=0;i<5;i++)
    {         w[i].x=x[i];
    w[i].y=y[i];
    }
    //CClientDC dc(this);
    //hPen=CreatePen(PS_SOLID,1,RGB(255,0,0));
    CRgn argn,Brgn;
    CBrush abrush(RGB(40,30,20));
    argn.CreatePolygonRgn(w, 5, 1);// point为CPoint数组,
    memDC.FillRgn(&argn, &abrush);
    abrush.DeleteObject();
}


这样编译运行程序就会出现屏幕不闪烁的情况了.