MFC技术资料大全与汇总

其他同类贴子:

VC,MFC开发技巧收集 http://www.usidcbbs.com/read.php?tid=1566

Windows平台VC(MFC)多线程编程 http://www.usidcbbs.com/read.php?tid=3367



树形控件可以用于树形的结构,其中有一个根接点(Root)然后下面有许多子结点,而每个子结点上有允许有一个或多个或没有子结点。MFC中使用CTreeCtrl类来封装树形控件的各种操作。通过调用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些树形控件的专用风格:

TVS_HASLINES 在父/子结点之间绘制连线
TVS_LINESATROOT 在根/子结点之间绘制连线
TVS_HASBUTTONS 在每一个结点前添加一个按钮,用于表示当前结点是否已被展开
TVS_EDITLABELS 结点的显示字符可以被编辑
TVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点
TVS_DISABLEDRAGDROP 不允许Drag/Drop
TVS_NOTOOLTIPS 不使用ToolTip显示结点的显示字符
在树形控件中每一个结点都有一个句柄(HTREEITEM),同时添加结点时必须提供的参数是该结点的父结点句柄,(其中根Root结点只有一个,既不可以添加也不可以删除)利用
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一个结点,pszItem为显示的字符,hParent代表父结点的句柄,当前添加的结点会排在hInsertAfter表示的结点的后面,返回值为当前创建的结点的句柄。

下面的代码会建立一个如下形式的树形结构:
+--- Parent1
     +--- Child1_1
     +--- Child1_2
     +--- Child1_3
+--- Parent2
+--- Parent3


HTREEITEM hItem,hSubItem;
hItem = m_tree.InsertItem("Parent1",TVI_ROOT);在根结点上添加Parent1
hSubItem = m_tree.InsertItem("Child1_1",hItem);//在Parent1上添加一个子结点
hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上添加一个子结点,排在Child1_1后面
hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem);

hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem);   
hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem);   

如果你希望在每个结点前添加一个小图标,就必需先调用CImageList* SetImageList( CImageList * pImageList, int nImageListType );
CImageList指向一个CImageList对象,如果这个值为空,则CTreeCtrl中的Image将被移除。
nImageListType有两种,TVSIL_NORMAL-包含选择和被选择两个状态的Image,TVSIL_STATE-用户定义状态的Image。
在调用完成后控件中使用图片以设置的ImageList中图片为准。然后调用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加结点,
nImage为结点没被选中时所使用图片序号,nSelectedImage为结点被选中时所使用图片序号。
下面的代码演示了ImageList的设置。

m_list.Create(IDB_TREE,16,4,RGB(0,0,0));
m_tree.SetImageList(&m_list,TVSIL_NORMAL);
m_tree.InsertItem("Parent1",0,1);//添加,选中时显示图标1,未选中时显示图标0


Example2:

CImageList imglist;
CBitmap     bitmap;
imglist.Create(16, 16, ILC_MASK, 1, 1);
bitmap.LoadBitmap( IDB_COMPUTER );
imglist.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();
treectrl.SetImageList(&m_imgList, TVSIL_NORMAL);


此外CTreeCtrl还提供了一些函数用于得到/修改控件的状态。
HTREEITEM GetSelectedItem( );将返回当前选中的结点的句柄。BOOL SelectItem( HTREEITEM hItem );将选中指明结点。
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某结点所使用图标索引。
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一结点的显示字符。
BOOL DeleteItem( HTREEITEM hItem );用于删除某一结点,BOOL DeleteAllItems( );将删除所有结点。

此外如果想遍历树可以使用下面的函数:
HTREEITEM GetRootItem( );得到根结点。
HTREEITEM GetChildItem( HTREEITEM hItem );得到子结点。
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明结点的上/下一个兄弟结点。
HTREEITEM GetParentItem( HTREEITEM hItem );得到父结点。

树形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于树形控件可能取值和对应的数据结构为:

TVN_SELCHANGED 在所选中的结点发生改变后发送,所用结构:NMTREEVIEW
TVN_ITEMEXPANDED 在某结点被展开后发送,所用结构:NMTREEVIEW
TVN_BEGINLABELEDIT 在开始编辑结点字符时发送,所用结构:NMTVDISPINFO
TVN_ENDLABELEDIT 在结束编辑结点字符时发送,所用结构:NMTVDISPINFO
TVN_GETDISPINFO 在需要得到某结点信息时发送,(如得到结点的显示字符)所用结构:NMTVDISPINFO
关于ON_NOTIFY有很多内容,将在以后的内容中进行详细讲解。

关于动态提供结点所显示的字符:首先你在添加结点时需要指明lpszItem参数为:LPSTR_TEXTCALLBACK。在控件显示该结点时会通过发送TVN_GETDISPINFO来取得所需要的字符,在处理该消息时先将参数pNMHDR转换为LPNMTVDISPINFO,然后填充其中item.pszText。但是我们通过什么来知道该结点所对应的信息呢,我的做法是在添加结点后设置其lParam参数,然后在提供信息时利用该参数来查找所对应的信息。下面的代码说明了这种方法:

char szOut[8][3]={"No.1","No.2","No.3"};

//添加结点
HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...)
m_tree.SetItemData(hItem, 0 );
hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...)
m_tree.SetItemData(hItem, 1 );
//处理消息
void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR;
pTVDI->item.pszText=szOut[pTVDI->item.lParam];//通过lParam得到需要显示的字符在数组中的位置
*pResult = 0;
}


关于编辑结点的显示字符:首先需要设置树形控件的TVS_EDITLABELS风格,在开始编辑时该控件将会发送TVN_BEGINLABELEDIT,你可以通过在处理函数中返回TRUE来取消接下来的编辑,在编辑完成后会发送TVN_ENDLABELEDIT,在处理该消息时需要将参数pNMHDR转换为LPNMTVDISPINFO,然后通过其中的item.pszText得到编辑后的字符,并重置显示字符。如果编辑在中途中取消该变量为NULL。下面的代码说明如何处理这些消息:

//处理消息 TVN_BEGINLABELEDIT
void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR;
if(pTVDI->item.lParam==0);//判断是否取消该操作
   *pResult = 1;
else
   *pResult = 0;
}
//处理消息 TVN_BEGINLABELEDIT
void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR;
if(pTVDI->item.pszText==NULL);//判断是否已经取消取消编辑
   m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置显示字符
*pResult = 0;
}

上面讲述的方法所进行的消息映射必须在父窗口中进行(同样WM_NOTIFY的所有消息都需要在父窗口中处理)。

文章转自:http://blog.csdn.net/dongle2001/articles/429704.aspx


1.树视图风格:
TVS_HASBUTTONS;    //在父项旁边显示(+)和(-)
TVS_HASLINES;     //使用线条显示各项之间的层次
TVS_LINESATROOT;//使用线条链接树视图控件根部各项

2.处理单击事件:TVN_SELCHANGED
void CTreeCtrlDlg::OnTvnSelchangedTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
      LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR);
      // TODO: 在此添加控件通知处理程序代码
      HTREEITEM ht=m_treeCtrl.GetSelectedItem();
      CString strSelect=m_treeCtrl.GetItemText(ht);
      m_strTreeVal=strSelect;
      UpdateData(FALSE);
      *pResult = 0;
}

3.同时让自己派生的CMyTreeCtrl类和对话框处理TVN_SELCHANGED消息:
只须在CMyTreeCtrl中处理以下消息,并返回FALSE就OK了ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnTvnSelchanged) OnTvnSelchanged的签名如下
BOOL CMyTreeCtrl::OnTvnSelchanged(NMHDR *pNMHDR,LRESULT *pResult)

4.编辑标签:要允许编辑树视图控件中的文本,可以设置以下三个步骤
      (1).设置树视图的TVS_EDITLABELS风格
TVS_EDITLABE可以通过资源编辑框内部修改(Edit labels),也可以用代码的方式修改,如下
long lStyle=::GetWindowLong(m_treeCtrl.GetSafeHwnd(),GWL_STYLE);
      lStyle|=TVS_EDITLABELS;
      ::SetWindowLong(m_treeCtrl.GetSafeHwnd(),GWL_STYLE,lStyle)
      (2).处理TVN_BEGINLABELEDIT通知消息
       //设置相关限制,如限制编辑框字符串长度
      CEdit*pEdit=GetEditControl();     //获取当前选中结点编辑框
      ASSERT(pEdit);
      if (pEdit)
      {
          pEdit->LimitText(15);//设置编辑框文本长度为15个字符串
          *pResult = 0;      
      }
      (3).处理TVN_ENDLABLEEDIT通知消息
      HTREEITEM hParent=GetParentItem(pTVDispInfo->item.hItem); //获取父结点 
      HTREEITEM hChild=GetChildItem(hParent?hParent:TVI_ROOT); //获取第一个根结点
      hChild=GetNextSiblingItem(hChild);            //获取下一个兄弟节点
      if (pTVDispInfo->item.hItem!=hChild)          //判断是否与当前节点相同
      pTVDispInfo->item.pszText                     //获取当前节点的字符串
      CString strText=GetItemText(hChild);          //获取节点的字符串

5.让树视图处理Esc和Enter键
重载PreTranslateMessage函数
BOOL bHandleMsg=FALSE;
      switch(pMsg->message) {
      case VK_ESCAPE:
      case VK_RETURN:
          if (::GetKeyState(VK_CONTROL)&0x8000)
          {
              break;
          }
          if (GetEditControl())
          {
              ::TranslateMessage(pMsg);
              ::DispatchMessage(pMsg);
              bHandleMsg=TRUE;
          }
          break;
      }

4.实现上下文菜单
在WM_RBUTTONDOWN消息处理函数上实现上下文菜单

5.展开和收起树视图结点:
HTREEITEM hItem=GetRootItem();                //获取根结点,可能会有多个根结点
ItemHasChildren(hParent)                      //判断结点是否有孩子结点
hItem=GetChildItem(hParent);                  //获取第一个子结点
hItem=GetNextSiblingItem(hItem));             //获取下一个兄弟结点结点
Expand(hItem,bExpand?TVE_EXPAND:TVE_COLLAPSE);//展开/叠起结点
Select(hItem,TVGN_FIRSTVISIBLE);                  //设置选中结点
CString str=GetItemText(hChild);              //获取结点字符串信息
HTREEITEM hCurrSel = GetSelectedItem();       //获取当前选中结点
SelectItem(hNewSel);
HTREEITEM hNewSel = HitTest(pt, &nFlags);         //判断坐标是否在当前结点范围内
HTREEITEM hItem=InsertItem(dlg.m_strItemText,hItemParent);    //插入结点


#pragma once
//定义文件MyTreeCtrl.h
// CMyTreeCtrl
class CMyTreeCtrl : public CTreeCtrl
{
      DECLARE_DYNAMIC(CMyTreeCtrl)
public:
      CMyTreeCtrl();
      virtual ~CMyTreeCtrl();
protected:
      DECLARE_MESSAGE_MAP()
      void ExpandBranch(HTREEITEM hItem,BOOL bExpand =TRUE);
public:
      void ExpandAllBranches(BOOL bExpand =TRUE);
      BOOL DoesItemExist(HTREEITEM hItemParent, CString const& strItem);
      afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
      afx_msg void OnAddItem();
      virtual BOOL PreTranslateMessage(MSG* pMsg);
      afx_msg void OnTvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
      afx_msg void OnTvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
   
      afx_msg BOOL OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult);
};
// MyTreeCtrl.cpp : 实现文件
#include "stdafx.h"
#include "TreeCtrl.h"
#include "MyTreeCtrl.h"
#include ".mytreectrl.h"
#include "AddItemDlg.h"
// CMyTreeCtrl
IMPLEMENT_DYNAMIC(CMyTreeCtrl, CTreeCtrl)
CMyTreeCtrl::CMyTreeCtrl()
{
}
CMyTreeCtrl::~CMyTreeCtrl()
{
}
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)
      ON_WM_RBUTTONDOWN()
      ON_COMMAND(IDR_ADD_ITEM, OnAddItem)
      ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT, OnTvnEndlabeledit)
      ON_NOTIFY_REFLECT(TVN_BEGINLABELEDIT, OnTvnBeginlabeledit)
      ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnTvnSelchanged)
END_MESSAGE_MAP()

// CMyTreeCtrl 消息处理程序
void CMyTreeCtrl::ExpandBranch(HTREEITEM hItem,BOOL bExpand )
//展开
{
      if (ItemHasChildren(hItem))    //判断是否有孩子结点
      {
          Expand(hItem,bExpand?TVE_EXPAND:TVE_COLLAPSE);
          //展开/叠起结点
          hItem=GetChildItem(hItem);//获取第一个子结点
          do{
              ExpandBranch(hItem);
          } while(hItem=GetNextSiblingItem(hItem));//获取兄弟结点
      }
}
void CMyTreeCtrl::ExpandAllBranches(BOOL bExpand )
{
      HTREEITEM hItem=GetRootItem();//获取根结点,可能会有多个根结点
      do{
          ExpandBranch(hItem,bExpand);
      } while(hItem=GetNextSiblingItem(hItem));
      Select(hItem,TVGN_FIRSTVISIBLE);//设置选中结点
}
BOOL CMyTreeCtrl::DoesItemExist(HTREEITEM hItemParent,
                                  CString const& strItem)
{
      BOOL bDoesItemExist=FALSE;
      ASSERT(strItem.GetLength());
      HTREEITEM hChild=GetChildItem(hItemParent?hItemParent:TVI_ROOT);
      while (NULL!=hChild&&!bDoesItemExist)
      {
          CString str=GetItemText(hChild);//获取结点字符串信息
          if (0==str.CompareNoCase(strItem))
          {
              bDoesItemExist=TRUE;
          }
          else
          {
              hChild=GetNextSiblingItem(hChild);
          }
      }
      return bDoesItemExist;
}
void CMyTreeCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
      // TODO: 在此添加消息处理程序代码和/或调用默认值   
      // set focus to the tree control
      SetFocus();

      // map the point that is passed to the
      // function from client coordinates
      // to screen coordinates
      ClientToScreen(&point);
      // Get the currently selected item
      HTREEITEM hCurrSel = GetSelectedItem();//获取当前选中结点
      // Figure out which item was right clicked
      CPoint pt(0, 0);
      ::GetCursorPos(&pt);
      ScreenToClient (&pt);
      HTREEITEM hNewSel = HitTest(pt, &nFlags);
      // Set the selection to the item that the
      // mouse was over when the user right
      // clicked
      if (NULL == hNewSel)
      {
          SelectItem(NULL);
      }
      else if (hCurrSel != hNewSel)
      {
          SelectItem(hNewSel);
          SetFocus();
      }

      // Load the context menu
      CMenu Menu;
      if (Menu.LoadMenu(IDM_CONTEXT_MENU))
      {
          CMenu* pSubMenu = Menu.GetSubMenu(0);
          if (pSubMenu!=NULL)
          {
              // Display the context menu
              pSubMenu->TrackPopupMenu(
                  TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                  point.x, point.y, this);
          }
      }  
}
void CMyTreeCtrl::OnAddItem()
//添加上下文菜单
{
      // TODO: 在此添加命令处理程序代码
      HTREEITEM hItemParent=GetSelectedItem();
      //获取当前选中结点
      CAddItemDlg dlg;
      if (dlg.DoModal()==IDOK)
      {
          if (!DoesItemExist(hItemParent,dlg.m_strItemText))
          {
              HTREEITEM hItem=InsertItem(dlg.m_strItemText,hItemParent);
              //插入结点
              SelectItem(hItem);
          }
          else
          {
              AfxMessageBox("已存在相同结点");
          }
      }
  
}
BOOL CMyTreeCtrl::PreTranslateMessage(MSG* pMsg)
{
      // TODO: 在此添加专用代码和/或调用基类
      BOOL bHandledMsg = FALSE;

      switch (pMsg->message)
      {
          case WM_KEYDOWN:
          {
              switch (pMsg->wParam)
              {
              case VK_ESCAPE:
              case VK_RETURN:   
                  if (::GetKeyState(VK_CONTROL) & 0x8000)
                  {
                      break;
                  }
                  if (GetEditControl())
                  {
                      ::TranslateMessage(pMsg);
                      ::DispatchMessage(pMsg);
                      bHandledMsg = TRUE;
                  }
                  break;
              default: break;
              } // switch (pMsg->wParam)
          } // WM_KEYDOWN
          break;
      default: break;
      } // switch (pMsg->message)                  
      // continue normal translation and dispatching             
      return (bHandledMsg ?TRUE : CTreeCtrl::PreTranslateMessage(pMsg));

}
void CMyTreeCtrl::OnTvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
      LPNMTVDISPINFO pTVDispInfo = reinterpret_cast(pNMHDR);
      // TODO: 在此添加控件通知处理程序代码
      *pResult=1;
      CEdit*pEdit=GetEditControl();
      ASSERT(pEdit);
      if (pEdit)
      {
          pEdit->LimitText(15);
          *pResult=0;
      }  
}
void CMyTreeCtrl::OnTvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
      LPNMTVDISPINFO pTVDispInfo = reinterpret_cast(pNMHDR);
      // TODO: 在此添加控件通知处理程序代码

      BOOL bValidItem=FALSE;
      CString strItem=pTVDispInfo->item.pszText;
      if (0       {
          HTREEITEM hParent=GetParentItem(pTVDispInfo->item.hItem);
          bValidItem=!DoesItemExist(hParent,strItem);    
      }
      *pResult = bValidItem;
}
BOOL CMyTreeCtrl::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
      LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR);
      // TODO: 在此添加控件通知处理程序代码
      TRACE(GetItemText(pNMTreeView->itemNew.hItem));
      TRACE(" ");
      *pResult = 0;
      return FALSE;         //返回FALSE可以让父窗口进行进一步的处理
}


usidc5 2010-07-18 21:57

CTreeCtrl m_TripleTree;
CImageList m_imgList;
CImageList m_imgState; 
/*
CImageList ImgTree;
 ImgTree.Create (16,16,ILC_COLOR|ILC_MASK,3,3);//创建图标列表  图标为单个图标
 ImgTree.Add (AfxGetApp()->LoadIcon (IDI_CK));
 ImgTree.Add (AfxGetApp()->LoadIcon (IDI_BASE_INFO));
 ImgTree.Add (AfxGetApp()->LoadIcon (IDI_INPUT));
*/
 m_imgList.Create(IDB_BITMAP_LIST,16, 1, RGB(255,255,255));//创建图标列表 为连续的16*16图标
 //m_imgState.Create(IDB_BITMAP_STATE,16, 1, RGB(255,255,255));//创建状态图标
 m_TripleTree.SetImageList(&m_imgList,TVSIL_NORMAL);//选择与非选择
 //m_TripleTree.SetImageList(&m_imgState,TVSIL_STATE);//用户定义
HTREEITEM hItem1A=m_TripleTree.InsertItem(_T("根部"),0,0);
      
 HTREEITEM hItem2_1B=m_TripleTree.InsertItem(_T("根上一"),1,2,hItem1A,TVI_LAST);
 m_TripleTree.InsertItem(_T("根上二"1,2,hItem2_1B); 
 m_TripleTree.InsertItem(_T("根上二"),1,2,hItem2_1B); 

 HTREEITEM hItem2_2B=m_TripleTree.InsertItem(_T("根上一"),1,2,hItem1A,TVI_LAST);
 m_TripleTree.InsertItem(_T("根上一"),1,2,hItem2_2B); 
 m_TripleTree.InsertItem(_T("根上一"),1,2,hItem2_2B); 

  m_TripleTree.InsertItem(_T("根上一"),1,2,hItem1A,TVI_LAST);
 m_TripleTree.InsertItem(_T("根上一"),1,2,hItem1A,TVI_LAST);
    
    //设置显示风格
 m_TripleTree.SetBkColor (RGB(220,200,220));//背景颜色
 m_TripleTree.Expand(hItem1A,TVE_EXPAND);//根部展开
 DWORD dwStyle=GetWindowLong(m_TripleTree.m_hWnd ,GWL_STYLE);//获得树的信息
 dwStyle|=TVS_HASBUTTONS|TVS_HASLINES|TVS_LINESATROOT;//设置风格
 ::SetWindowLong (m_TripleTree.m_hWnd ,GWL_STYLE,dwStyle);

usidc5 2010-08-16 14:35

当一个工程很大的时候,恰当的路径设置可以将不同模块的动态链接库和工程很好的组织起来。

在VC中的设置如下:
include : project->setting->C/C++->Preprocessor: Additional include directories: ../../inc
dll : project->setting->Link->General: Output file name: ../../bin/moduld
.dll
pdb : project->setting->Link->Customize: Program database name: ../../bin/moduld.pdb
lib : 应该是在project->setting->Link->OutPut下,但是没有找到,所以更改Project Option:/implib:"../../lib/moduld.lib"

一般来讲,把所有的动态链接库的头文件放在inc目录下,而cpp文件放在src目录下,生成文件和pdb文件(方便进入动态链接库调试)放在bin目录下。这样可以满足不同项目调用同一个动态链接库,而且实时更新动态链接库。
ps:
definition of dllimport static data member not allowed
有可能是重复包含了文件,定义了和链接库中已经定义的同名变量

usidc5 2010-08-16 18:45
方案一


在对话框上放置一个Tab Control的控件,再在对话框上放置所需的控件(本例放置了2个按钮,试图在每个标签中显示一个)。然后利用Class Wizard来为Tab Control控件创建一个控件变量,该变量是CTabCtrl类的,再为其他控件也创建相应的控件类。 在主对话框的初始函数中CProperty1Dlg::OnInitDialog()加入如下代码:


//本例插入两个标签,实际运用中可通过循环插入所需个数的标签,运行后默认第一个标签被选中


m_tab.InsertItem( 0, _T("Tab1") );


m_tab.InsertItem( 1, _T("Tab2") );


//将不是第一个标签的控件隐藏掉,只留下你要的控件


m_button2.ShowWindow( SW_HIDE );


再利用ClassWizard处理Tab Control的 TCN_SELCHANGE 的消息。在消息处理函数中,利用CWnd::ShowWindow来使相应的控件显示和隐藏。


void CProperty1Dlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)


{


//GetCurSel返回当前被选中的标签的索引号(以0为基础算起)


int sel = m_tab.GetCurSel();


switch(sel)


{


case 0:


m_button1.ShowWindow( SW_SHOW );


m_button2.ShowWindow( SW_HIDE );


break;


case 1:


m_button2.ShowWindow( SW_SHOW );


m_button1.ShowWindow( SW_HIDE );


break;


}


*pResult = 0;


}





方案二


本这个方案中,我将使用MFC中现成的CPropertySheet和CPropertyPage类来完成将控件分散到各个对话框类中。


首先加入两个(或数个)对话框资源。修改各对话框资源的属性,将对话框的Caption属性改为你要在标签上所显示的文字。将对话框的Style属性改为:Child, Border属性改为:Thin, 只选中Title Bar复选框,去掉其他复选框。然后你可以在这些对话框中加入要分开显示的各个控件。


为上述对话框资源分别制作一个对话框类,该对话框类是从CPropertyPage继承。这样一来各子对话框类就好了,主对话框类可以直接使用CPropertySheet类。使用如下代码即可:


CPropertySheet sheet("属性页对话框");


CPage1 page1;


CPage2 page2;


//加入子对话框作为一个属性页


sheet.AddPage(&page1);


sheet.AddPage(&page2);


//产生一个模态对话框,也可以使用Create方法来产生一个非模态对话框(具体参见MSDN)


sheet.DoModal();


如何在主对话框中放置其他控件呢?如果直接使用CPropertySheet的话,是不可以的,但是别忘了我们可以从CPropertySheet类继承自己的类啊!


方案三


首先还是要创建那些要在属性页中的显示的子对话框类,创建步骤和方案二一样,都是从CPropertyPage继承。


这次我们将从CPropertySheet类继承自己的类(假设类名为CMySheet)。我们要在这里放上一个button控件。那么现在先在CMySheet中加入一个CButton类的成员变量m_button。


在CMySheet类中的OnInitDialog()函数里,这样写:


BOOL bResult = CPropertySheet::OnInitDialog();


//取得属性页的大小


CRect rectWnd;


GetWindowRect(rectWnd);


//调整对话框的宽度


SetWindowPos(NULL, 0, 0,rectWnd.Width() + 100,rectWnd.Height(),SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);


CRect rectButton(rectWnd.Width() + 25, 25,rectWnd.Width()+75, 75);


//用程序创建一个按钮


m_button.Create("Button", BS_PUSHBUTTON, CRect(rectWnd.Width(), 25,rectWnd.Width()+75, 50) , this, 1);


//显示这个按钮


m_button.ShowWindow( SW_SHOW );


CenterWindow();


return bResult;


 使用方案三虽然能在主对话框中加入控件,但是也比较麻烦,首先所加的控件只能在属性页的右边或下边。并且用程序来产生控件比较烦琐,位置与大小不易控制。那么还有其他方法,既能在对话框中加入属性页,又能在主对话框随意添加控件?


方案四


不从CPropertySheet继承自己的类,还是直接使用它。各属性页的子对话框类还是需要的,创建方法和上述两个方案相同。


首先我们新建一个基于对话框的工程。在编辑已有的一个主对话框中可以自由加一些所需的控件,但是得留出一定的空间用于放置属性页。


在主对话框类里加入一个CPropertySheet类的一个成员变量(m_sheet)代表整个属性页。再加入一些各子对话框类的实例作为成员变量(m_page1、m_page2……)。


在主对话框类的OnInitDialog()函数中加入:


//加入标签,标签名由各个子对话框的标题栏决定


m_sheet.AddPage(&m_page1);


m_sheet.AddPage(&m_page2);


//用Create来创建一个属性页


m_sheet.Create(this, WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT);


RECT rect;


m_sheet.GetWindowRect(&rect);


int width = rect.right - rect.left;


int height = rect.bottom - rect.top;


//调整属性页的大小和位置


m_sheet.SetWindowPos(NULL, 20, 50, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);


这个方案可以自由在主对话框中加一些必要的控件,而且属性页中的控件也都分散在了各个子对话框类中,使用非常方便。


方案五


使用Tab Control,并且从CTabCtrl控件类继承自己的类(CTabSheet)来处理。


首先我先介绍一下如何使用CTabSheet。


先要制作子对话框类,这次的子对话框类不要从CPropertyPage继承,而是直接从CDialog继承。并且各个子对话框资源的属性应设置为:Style为Child, Border为None。


在主对话框资源中,加入一个Tab Control,并且适当调整位置和大小。利用ClassWizard来为这个Tab Control创建一个CTabSheet的控件变量。


在主对话框的OnInitDialog()加入:


m_sheet.AddPage("tab1", &m_page1, IDD_DIALOG1);


m_sheet.AddPage("tab2", &m_page2, IDD_DIALOG2);


m_sheet.Show();


就这样就可以在对话框上制作出一个完美的属性页了。效果和上图完全一样。


下面我就来讲讲CTabSheet类的细节内容。


CTabSheet是从CTabCtrl继承来的,用于Tab Control的控件类。在类中有一个成员变量用来记录各子对话框的指针CDialog* m_pPages[MAXPAGE]; MAXPAGE是该类所能加载的标签的最大值。


类中有一个AddPage方法,用于记录子对话框的指针和所使用对话框资源的ID号。


BOOL CTabSheet::AddPage(LPCTSTR title, CDialog *pDialog,UINT ID)


{


if( MAXPAGE == m_nNumOfPages )


return FALSE;


//保存目前总的子对话框数


m_nNumOfPages++;


//记录子对话框的指针、资源ID、要在标签上显示的文字


m_pPages[m_nNumOfPages-1] = pDialog;


m_IDD[m_nNumOfPages-1] = ID;


m_Title[m_nNumOfPages-1] = title;


return TRUE;


}


在使用AddPage加入了若干子对话框后,必须调用CTabSheet的Show方法来真正生成标签和子对话框。


void CTabSheet::Show()


{


//利用CDialog::Create来创建子对话框,并且使用CTabCtrl::InsertItem来加上相应的标签


for( int i=0; i < m_nNumOfPages; i++ )


{


m_pPages[ii]->Create( m_IDD[ii], this );[ii][ii]


InsertItem( i, m_Title[ii] );[ii]


}


//由于对话框显示时默认的是第一个标签被选中,所以应该让第一个子对话框显示,其他子对话框隐藏


m_pPages[0]->ShowWindow(SW_SHOW);


for( i=1; i < m_nNumOfPages; i++)


m_pPages[ii]->ShowWindow(SW_HIDE);[ii]


SetRect();


}


生成好标签和子对话框后,调用CTabSheet::SetRect来计算并调整属性页的大小。


void CTabSheet::SetRect()


{


CRect tabRect, itemRect;


int nX, nY, nXc, nYc;


//得到Tab Control的大小


GetClientRect(&tabRect);


GetItemRect(0, &itemRect);


//计算出各子对话框的相对于Tab Control的位置和大小


nX=itemRect.left;


nY=itemRect.bottom+1;


nXc=tabRect.right-itemRect.left-2;


nYc=tabRect.bottom-nY-2;


//利用计算出的数据对各子对话框进行调整


m_pPages[0]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_SHOWWINDOW);


for( int nCount=1; nCount < m_nNumOfPages; nCount++ )


m_pPages[nCount]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_HIDEWINDOW);


}


在单击标签栏后,应该是相应的子对话框显示,正在显示的子对话框应该隐藏。因此利用ClassWizard来处理WM_LBUTTONDOWN消息。


void CTabSheet::OnLButtonDown(UINT nFlags, CPoint point)


{


CTabCtrl::OnLButtonDown(nFlags, point);


//判断是否单击了其他标签


if(m_nCurrentPage != GetCurFocus())


{


//将原先的子对话框隐藏


m_pPages[m_nCurrentPage]->ShowWindow(SW_HIDE);


m_nCurrentPage=GetCurFocus();


//显示当前标签所对应的子对话框


m_pPages[m_nCurrentPage]->ShowWindow(SW_SHOW);


}


}


这样利用CTabSheet这个类就可以轻松地在对话框上放置自己的属性页了,并且控件都分散在各子对话框类中,符合对象封装的思想。而且用这个方法来制作属性页就可以利用ClassWizard来轻松地生成消息映射处理Tab Control的消息了。例如:可以处理TCN_SELCHANGE消息来对切换了标签时进行一些动作。


  方案五另一写法
           思路:当我们调用InsertItem()这个函数的时候,选项卡控件将会添加一个标签页,这个时候,我们将自己的对话框的窗体的指针与此标签页关联起来,当用户进行标签页的切换的时候,我们根据当前是哪个标签页,显示哪个对话框,不是与当前标签页关联的对话框,我们将其隐藏即可.这样我们便可以实现选项卡控件.
        第一步:新建一个自己的类CTabSheet继承CTabCtrl.
        第二步:定义有用的成员变量
        CDialog* m_dlgWnd[MAXTABPAGE]; //这个是存放对话框指针的指针数组
        int m_curTabNumber; //记录当前用户添加了几个标签页
        int m_selTabID;  //当前用户点击的标签页的ID
        第三步:添加成员函数
        //通过这个函数,可以将一个对话框指针与添加的标签页关联起来,insWnd是创建的非模式对话框的指针,wndID是对话框的ID,pageText是标签页的标题
        void CreateTabPage(CWnd *insWnd, int wndID,CString pageText) 
       //添加控件的点击事件的处理,当点击后得到当前点击的标签页的ID,然后将与此标签页相关的对话框显示,其它的隐藏即可
        void OnLButtonDown(UINT nFlags, CPoint point) 
       通过添加以上的成员变量及成员函数即可实现一个简单的选项卡控件的用法
       下面我将这两个成员函数的代码贴出来,并详细讲解
        //创建并且增加一个标签页
    //创建并且增加一个标签页
void CTabSheet::CreateTabPage(CWnd *insWnd, int wndID,CString pageText)
{
     if (m_curTabNumber >= MAXTABPAGE)
     {
         MessageBox("标签页己经达到最大!","创建出错!",MB_OK);
         return;
     }
        //首先new一个对话框的指针,但是不要调用create函数,再将些指针当成参数传进来即可,创建己由此函数做完
     if (NULL == insWnd)
     {
         MessageBox("标签页为空","创建出错",MB_OK);
         return;
     }
                 //创建对话框,并且增加标签页
     CDialog* curDlg = (CDialog*)insWnd;
     curDlg->Create(wndID,this);
     int suc = InsertItem(m_curTabNumber,pageText);
     if (-1 == suc)
     {
         MessageBox("插入标签页失败","失败",MB_OK);
         return;
     }
     curDlg->ShowWindow(SW_SHOW);
     //将这个对应的窗体指针存放起来
     m_dlgWnd[m_curTabNumber] = curDlg;
     //此时选择当前页面
     SetCurSel(m_curTabNumber);
     m_selTabID = m_curTabNumber;
     m_curTabNumber ++;
}
//点击左键事件,处理
void CTabSheet::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    CTabCtrl::OnLButtonDown(nFlags, point);
    //得到当前用户点击的标签页的ID
    int curSelect = GetCurSel();


    //得到当前标签页的位置以便设置对话框显示的位置


    CRect curRect;
    GetClientRect(curRect);
    if (-1 == curSelect)
    {
        return;
    }
               //查找标签页,将与当前用户点击的标签页相关的对话框显示出来,其它的对话框隐藏
    for (int i = 0; i < m_curTabNumber; i ++)
    {
        if (i == curSelect)
        {
                                       m_dlgWnd[ii]->SetWindowPos(NULL,0,20,curRect.Width(),curRect.bottom,SWP_SHOWWINDOW);[ii]
        }
        else
        {
               m_dlgWnd[ii]->SetWindowPos(NULL,0,20,curRect.Width(),curRect.bottom,SWP_HIDEWINDOW);[ii]
        }
    }
    m_selTabID = curSelect;
    Invalidate();
    //CTabCtrl::OnLButtonDown(nFlags, point);
}


以上为关键的两个函数,下面介绍调用的方法
创建非模式的对话框
CTabSheet m_tabSheet;
CMyDlg* m_dlg = new CMyDlg;
m_tabSheet.CreateTabPage(m_dlg ,IDD_DLG_ID,"第一个标签页");
这样就可以产生一个标签页了,当然还可以继续调用此函数添加标签页 

usidc5 2010-08-21 01:51
在 Windows 平台上窗口化子系统鼓励开发人员处理上下文菜单请求在顶级窗口。 传统 Windows 编程范例因此集中的所有上下文菜单请求在几个窗口和对话框过程处理的代码。 但是,与对象的方向编程另一种解决方案可以被采纳,即要处理控件内的 WM_CONTEXTMENU 消息从而创建独立的实体和对象本身。 窗口和对话框,此方法允许在多个只是一个父控件的重复使用。


在树的视图控件的情况下它将嵌入的拖放功能要求以获得从所在的窗口的独立性采取特殊步骤。树视图控件接收 WM_RBUTTONDOWN 消息后,邮件是不由控件转发,直到收到 WM_RBUTTONUP 消息。 如果拖放操作时,或者不初始化,将决定该控件。
如果用户未启动拖放操作,USER32 生成 WM_CONTEXTMENU 消息,并将其发送到树视图控件。 如果树视图控件不能处理此邮件然后将默认窗口过程转发它到控件的父窗口。
如果该用户不启动拖放操作然后将该控件的窗口过程发送 WM_NOTIFY (代码 NM_RCLICK) 邮件,然后 WM_CONTEXTMENU 消息向父窗口。
因此,请考虑以下三种情况:
当按下 SHIFT + F10,并在控件获得焦点时 WM_CONTEXTMENU 消息发送到树视图控件。
当用户执行一个拖放操作 WM_CONTEXTMENU 消息被发送到树视图控件中。
当用户右击、 WM_NOTIFY (代码 NM_RCLICK) 消息通过树控件发送到控件的父级。 如果您要处理反射的通知如下所示,控件将在所有情况下显示上下文菜单。
若要实现树视图控件的上下文菜单建议 WM_CONTEXTMENU 和反射的 WM_NOTIFY (NM_RCLICK) 消息的消息处理程序由该控件实现。 例如:


BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)
//{{AFX_MSG_MAP(CMyTreeCtrl)
ON_NOTIFY_REFLECT(NM_RCLICK, OnRClick)
ON_WM_CONTEXTMENU()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


void CMyTreeCtrl::OnRClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
TRACE0("CMyTreeCtrl::OnRClick()\n");
// Send WM_CONTEXTMENU to self
SendMessage(WM_CONTEXTMENU, (WPARAM) m_hWnd, GetMessagePos());
// Mark message as handled and suppress default handling
*pResult = 1;
}


void CMyTreeCtrl::OnContextMenu(CWnd* pWnd, CPoint ptMousePos) 
{
// if Shift-F10
if (ptMousePos.x == -1 && ptMousePos.y == -1)
ptMousePos = (CPoint) GetMessagePos();


ScreenToClient(&ptMousePos);


UINT uFlags;
HTREEITEM htItem;

htItem = HitTest( ptMousePos, &uFlags );


if( htItem == NULL )
return;

m_hActiveItem = htItem;


CMenu menu;
CMenu* pPopup;


// the font popup is stored in a resource
menu.LoadMenu(IDR_TREEITEM_CONTEXTMENU);
pPopup = menu.GetSubMenu(0);
ClientToScreen(&ptMousePos);
pPopup->TrackPopupMenu( TPM_LEFTALIGN, ptMousePos.x, ptMousePos.y, this );
}

usidc5 2010-08-22 23:57

开发环境:VS7,Windows XP,Windows 2K 

  在VS7中添加了一种新的对话框类:CDHtmlDialog,顾名思义就是能够显示DHTML内容的对话框,但不同与以前的CHTMLView不同的是添加了对DHTML的支持,能够响应各种DHTML的事件,而且能够方便的得到网页上的各种内容和输入。在这以前要完成这些功能必须通过复杂的COM接口来完成,而现在MS MFC已经为我们做好了这一切。 


  在下面我会按照下面的顺序讲解CDHtmlDialog的用法。但本文也只能对DHTML对话框的功能进行部分的讲解,更多更全的说明需要大家自己摸索和查找资料。本文的目的是在于讲解如何得到和设置网页上的各种元素的值,如何处理事件。 


  此外在阅读本文前你必须对HTML和DHTML有所了解。

类成员函数介绍 

  构造函数

  CDHtmlDialog( );
  CDHtmlDialog(
   LPCTSTR lpszTemplateName,
   LPCTSTR szHtmlResID,
   CWnd *pParentWnd = NULL 
  );
  CDHtmlDialog(
   UINT nIDTemplate,
   UINT nHtmlResID = 0,
   CWnd *pParentWnd = NULL 
  );

  你可以看到和CDialog不同的就在于第二个参数,表示在对话框创建时是否自动加载HTML,这里必须说明的是HTML必须以资源的形式存放,这个参数指明的是资源的ID或名称。


或者你可以利用 

  BOOL LoadFromResource(
     LPCTSTR lpszResource 
  );
  BOOL LoadFromResource(
     UINT nRes 
  );
  将
HTML内容在后期进行装载。

  页面浏览 

  此外一些函数如:OnNavigateComplete,OnBeforeNavigate,Navigate等用于页面转换的函数,在以前的CHtmlView中就有这里就不再作讲解。 

  
得到当前URL 

  void GetCurrentUrl(
     CString& szUrl 
  );

  
得到网页中的DHTML元素的指定接口 

  HRESULT GetElementInterface(
     LPCTSTR szElementId,
     REFIID riid,
     void** ppvObj 
  );

  第一个参数指定,第二个参数指定接口ID,第三个参数返回接口指针。 

  
得到网页中的DHTML元素的IHTMLElement接口 

  HRESULT GetElement(
     LPCTSTR szElementId,
     IHTMLElement **pphtmlElement 
  );
  相当于调用 GetElementInterface(szElementId,IHTMLElement,pphtmlElement);

  这个函数非常的重要,因为如果要得到DHTML的内容就必须通过页面上的各个元素的属性来得到,例如:对于Input type=text的属性value就是输入的值,对于p的属性innerText就是段落中的内容。

  函数的第二个参数就是元素的名称。 

  函数的第二个参数,是一个指向指针的指针,通过它得到IHTMLElement的接口。函数返回值是HRESULT其值的定义符合COM中对返回值的定义。(如果你不了解,可以简单的检测返回值是否等于S_OK)


得到元素的innerText和innerHTML的属性 

  innerHTML属性:
  BSTR GetElementHtml(
     LPCTSTR szElementId 
  );
  innerText属性:
  BSTR GetElementText(
     LPCTSTR szElementId 
  );
  相当于调用IHTMLElement接口的gett_innerHTML和get_innerText方法

  与之对应的是设置元素的innerText和InnerHTML属性:
  innerHTML属性:
  void SetElementHtml(
     LPCTSTR szElementId,
     BSTR bstrText 
  );
  innerText属性:
  void SetElementText(
     LPCTSTR szElementId,
     BSTR bstrText 
  );
  相当于调用IHTMLElement接口的put_innerHTML和put_innerText方法

  
示范代码 

  假设页面上的代码为:

test

,执行下面代码可以显示原来的内容和将新内容设置为:abcdefg 

  CComPtr spP1;
  HRESULT hr = S_OK;

   // Use the template overload
   hr = GetElementInterface("p2", &spP1);
  // 或者 hr = GetElement("p2", &spP1);
  // 或者 hr = GetElementInterface("p2", IID_IHTMLElement, reinterpret_cast(&spP1));
  if(S_OK == hr)
   {
    BSTR bStr;
    spP1->get_innerHTML(&bStr);
    CString szTemp;
    szTemp = bStr;
    AfxMessageBox(szTemp);

   CString strTable="abcdefg";
    BSTR bstrTable = strTable.AllocSysString();
    spP1->put_innerHTML(bstrTable);
   }
  或者利用SetElementHtml和SetElementText来进行设置:
   BSTR bStr;
   bStr = GetElementHtml("p2");
   CString szTemp;
   szTemp = bStr;
   AfxMessageBox(szTemp);
   CString strTable="ABCDEFG";
   BSTR bstrTable = strTable.AllocSysString();
   //spP1->put_innerHTML(bstrTable);
   SetElementHtml("p2",bstrTable);


事件处理映射宏 

  
基本格式

  
BEGIN_DHTML_EVENT_MAP(className )
  DHTML_EVENT_ONCLICK(elemName, memberFxn ) //处理onclick事件
  DHTML_EVENT_ONFOCUS(elemName, memberFxn ) //处理onfocus事件
  DHTML_EVENT_ONKEYDOWN(elemName, memberFxn ) //处理onkeydown事件
  DHTML_EVENT_ONMOUSEMOVE(elemName, memberFxn ) //处理mousemove事件
  DHTML_EVENT_ONMOUSEOUT(elemName, memberFxn ) //处理mousemoveout事件
  等等………
  END_DHTML_EVENT_MAP()

  更详细的说明可以查阅MSDN中DHTML Event Map Macros部分。MSDN中对可以处理的事件进行了详细的说明。DHTML中的事件与
Windows中消息不是同一个概念,虽然映射宏的形式很相同。 

  
添加映射处理代码 

  我在VC7中没有发现自动添加各种事件映射的方法,只能通过手工添加代码的方式。

  定义事件处理函数:
   函数原型为:HRESULT urClass::OnXXXXX(IHTMLElement* /*pElement*/)
  添加消息映射:
   BEGIN_DHTML_EVENT_MAP(urClass)
    DHTML_EVENT_ONCLICK(_T("id name"), OnXXXXX)
   END_DHTML_EVENT_MAP()

  下面是一段示范代码:
  // mydlg.h
  class CmydhtmlDlg : public CDHtmlDialog
  {
  // 构造
  public:
   CmydhtmlDlg(CWnd* pParent = NULL); // 标准构造函数

  // 对话框数据
   enum { IDD = IDD_MYDHTML_DIALOG, IDH = IDR_HTML_MYDHTML_DIALOG };

   protected:
   virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持


HRESULT OnButtonOK(IHTMLElement *pElement);
   HRESULT OnButtonCancel(IHTMLElement *pElement);
   HRESULT OnButtonTest1(IHTMLElement *pElement);
   HRESULT OnButtonTest2(IHTMLElement *pElement);
   HRESULT OnButtonTest3(IHTMLElement *pElement);
   HRESULT OnSelectTest1(IHTMLElement *pElement);
   HRESULT OnDivMouseMove1(IHTMLElement *pElement);
   HRESULT OnDivMouseOut1(IHTMLElement *pElement);

  //mydlg.cpp
  BEGIN_DHTML_EVENT_MAP(CmydhtmlDlg)
  DHTML_EVENT_ONCLICK(_T("ButtonOK"), OnButtonOK)
  DHTML_EVENT_ONCLICK(_T("ButtonCancel"), OnButtonCancel)
   DHTML_EVENT_ONCLICK(_T("Test1"), OnButtonTest1)
   DHTML_EVENT_ONCLICK(_T("Test2"), OnButtonTest2)
   DHTML_EVENT_ONCLICK(_T("Test3"), OnButtonTest3)
   DHTML_EVENT_ONCHANGE(_T("s1"), OnSelectTest1)
   DHTML_EVENT_ONMOUSEMOVE(_T("d1"), OnDivMouseMove1 )
   DHTML_EVENT_ONMOUSEOUT(_T("d1"), OnDivMouseOut1 )
  END_DHTML_EVENT_MAP()

  HRESULT CmydhtmlDlg::OnButtonOK(IHTMLElement* /*pElement*/)
  {
   OnOK();
   return S_OK;
  }

  HRESULT CmydhtmlDlg::OnButtonCancel(IHTMLElement* /*pElement*/)
  {
   OnCancel();
   return S_OK;
  }

  HRESULT CmydhtmlDlg::OnButtonTest1(IHTMLElement* /*pElement*/)
  {
   AfxMessageBox("test1 button clicked");
   return S_OK;
  }

  HRESULT CmydhtmlDlg::OnSelectTest1(IHTMLElement* /*pElement*/)
  {
   TRACE("select1 changed\n");
   return S_OK;
  }

  HRESULT CmydhtmlDlg::OnDivMouseMove1(IHTMLElement* /*pElement*/)
  {
   TRACE("div1 mouse move\n");
   return S_OK;
  }

  HRESULT CmydhtmlDlg::OnDivMouseOut1(IHTMLElement* /*pElement*/)
  {
   TRACE("div1 mouse out\n");
   return S_OK;
  }

各种DDX帮助宏 

  
DDX宏介绍 

  
如同CDialog类一样,CHtmlDialog也提供各种DDX帮助宏来与HTML页面上的控件交换数据。

  遗憾的是VS7中无法为CDHTMLDialog 的子类自动添加DDX变量,必须通过手工添加。


设置innerText和innerHTML属性 

  DDX_DHtml_ElementInnerText(
     CDataExchange* dx,
     LPCTSTR name,
     CString& var 
     )
  和
   DDX_DHtml_ElementInnerHtml(
     CDataExchange* dx,
     LPCTSTR name,
     CString& var 
  )
  相当与前面提到的设置和获取innerText,innerHTML属性。

  获取和设置控件中的值

  在DHTML中利用“对象名程.value”可以得到控件中输入的值,利用DDX也同样可以得到。 
  
  DDX_DHtml_ElementValue(
     CDataExchange* dx,
     LPCTSTR name,
     var 
  )
  用于在控件和对象之间交换数据。

  使用方法

  假设HTML文件中代码如下 

  

p4 for ddx


  
  

  在H文件中添加变量定义:
  public: //DDX
   CString m_szP4,m_szInput1;
   int m_iInput2;

  在类的构造函数中赋初值:
  CmydhtmlDlg::CmydhtmlDlg(CWnd* pParent /*=NULL*/)
   : CDHtmlDialog(CmydhtmlDlg::IDD, CmydhtmlDlg::IDH, pParent)
   {
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_szP4 = "test for p4";
    m_szInput1= "test for input1";
    m_iInput2 = 101;
   }

  在CPP文件中的void CmydhtmlDlg::DoDataExchange(CDataExchange* pDX)函数中添加代码:
  void CmydhtmlDlg::DoDataExchange(CDataExchange* pDX)
  {
   CDHtmlDialog::DoDataExchange(pDX);
   //// for html ddx
   DDX_DHtml_ElementInnerHtml(pDX,"p4",m_szP4); //对应 p4
   DDX_DHtml_ElementValue(pDX,"input1",m_szInput1); //对应 input1
   DDX_DHtml_ElementValue(pDX,"input2",m_iInput2); //对应 input2
  }
  使用是与CDialog一样利用UpdateData。
  HRESULT CmydhtmlDlg::OnButtonTest4(IHTMLElement* /*pElement*/)
  {
   UpdateData();
   TRACE("p4=%s\n",m_szP4);
   CString szTemp=m_szP4;
   m_szP4 =m_szInput1;
   m_szInput1=szTemp; //对换p4和input1中内容
   m_iInput2 ++; //将input2中数字加一
   UpdateData(FALSE);
   return S_OK;
  }


一个简单的例子 

  最后介绍一下如何利用VC7创建一个利用CDHtmlDialog的工程。

  首先创建工程,进行如下设置:


  在资源管理中修改HTML文件:


  最后添加自己的代码。我提供的例子中所使用的函数在上面都已经提到。大家可以下载这份
例子去进行一定的参考。


usidc5 2010-08-23 00:05
在CDHtmlDialog类使用中,总是会遇到HTML不能正确解析资源的问题。我的经验如下:


1。使用绝对路径在资源里引入HTML网页和图片资源:
     使用RES://应用程序名称/资源类型/#资源号,
     例如:如果你的应用程序名为c.exe,html网页或资源保存在HTML类型下。并且查看资源标示号为133,便可以这样引用:res://c.exe/html/#133。或这样引用:res
://c:/c.exe/html/#133


2。使用相对路径引用:
    如果使用方法1,总得去寻找应用程序名称,如果引入的是html中的图片,有诸多不便,譬如:应用程序如果改变了名称,那调用便会无效。为了防止这些纠葛,有一个非常好的办法就是:把所有使用的资源(图片,CSS,JS等),统统放到HTML资源类型下,引用的时候写成 res://#资源号就可以了。譬如上例便可以这样引用:res:/#133。当然好像res:/ 和res:// 是通用的。


3。FLASH控件的支持:
   我没有在资源里引用flash成功,看了看flash的官方文档,好像必须为绝对路径才可以。例如movie=“d:\\flash\\test.swf”是可以成功的。


4。不在资源里引用HTML,直接在本地文件里使用。可以在CDHtmlDialog的继承类里添加一个函数来处理。


void CDHtmlViewSpec::GotoUrl(LPCTSTR pszUrl) { CString strPath,str; DWORD dwSize=MAX_PATH; ::GetModuleFileName(NULL,strPath.GetBuffer(MAX_PATH),dwSize); //AfxGetResourceHandle() strPath.ReleaseBuffer(dwSize); str=strPath.Left(strPath.ReverseFind('\\')+1); ASSERT(pszUrl!=NULL); if(pszUrl!=NULL) { CString strUrl; strUrl=str+pszUrl;
Navigate(_T("file:///")+strUrl); } }
本函数为了方便没有对路径进行更多处理,在使用的时候,可以以exe文件所在的目录为当前目录,以相对路径表示pszUrl。调用方法:
gotoUrl("html\\test.html")
gotoUrl("..\\html2\test.htm")
这样载入HTML网页,网页内的资源定位都是相对路径定义即可。图片和flash都可以正常载入。
不过这样的话,就有个问题,用户可以随时修改你的html文件。如果你有消息处理的话,很容易就使你的程序崩溃。
我一般的做法就是修改html的扩展名,迫使一般用户不轻易修改它,譬如改称.dat。

usidc5 2010-08-23 00:06
自从去年年底一次棘手的界面,开始研究用web做界面到现在大约1年,这一年间不是局限在实现层面,也并非一直研究这一个问题,有很多问题其实不是问题, 只是自己没有想清楚或者思想没放开。对于一个界面开发人员,想必拉的对话框不少于100个,腻味不必说,光是对话框大小改变导致控件跟着变化都需要一番功 夫,加上界面美观,界面的风格统一,界面的灵活多变......,头痛。在对话框里面加载位图,加载gif,超链接......,啊,没法控制了吧!在考 虑远点,现在.net3.0技术已经完全打破应用和桌面的界限,我们的界面html资源完全可以放在一个web站点上,这样界面是完全动态的。

其 间写过2篇这方面的文章,基于vc6实现,绕弯很大。在vc7.1、vc8里面要简单很多,主要是把几个以前为公开的类公开了,最重要的是在CWnd里面 加入了一个虚函数CreateControlSite使得有机会改变控件站点以修改控件行为。在mfc类层次上,CHTMLView和 CDHtmlDialog为开发者提供了创建webgui的一系列基础设施,包括事件机制、窗口行为、以及对html文档操纵接口。我们在此基础上实现 webgui很简单,然而仍然困惑我很久,经理也催过我几次我一直未肯决定最终方案。在我脑袋里一直琢磨是要应用程序完全操纵html文档,还是html 访问应用获取信息,其实就是它们之间的通信模式。一直到昨天我才定下方案,应用通过IWebBrowser2接口操纵html元素,html通过 vbscript、java script脚本响应本身事件,访问应用。主要是考虑通信自然畅通,而以前我一味想通过应用指令完全控制html元素,导致去 解析html文档,费力不讨好。下面开始我的想法:

写一个dll,封装CDHtmlDialog,提供一个类似html容器的对话框,功 能就是加载html网页,以及创建与html呼应的com组件。它本身不包含与应用功能有关代码,应用有关的部分是html页面和对于的com功能组件。 这里需要对CDHtmlDialog进行了适当的改造以适合自己的目标:

首先从CDHtmlDialog派生一个类CHTMLContainerDlg,默认情况下会生成一个网页资源,这个网页是这个对话框创建时加载的,我们需要的其实是一个容器而不是一个具体的对话框,所以删除网页资源,修改对话框头文件:
enum { IDD = IDD_HTMLCONTAINERDLG, IDH = 0 };
这里把IDH修改为0,因为我们删除了网页资源。然而在对话框创建后会加载该资源,在CDHtmlDialog的OnInitDialog函数里面我们可以看到:
if (m_nHtmlResID)
        LoadFromResource(m_nHtmlResID);
    else if (m_szHtmlResID)
        LoadFromResource(m_szHtmlResID);
    else if (m_strCurrentUrl)
        Navigate(m_strCurrentUrl);
结果就是对话框一出现就会出现加载一个无效地址的页面,出现无法打开链接的页面,为了避免这个问题,需要重载OnInitDialog函数。其实就是拷贝mfc代码然后去掉上面那段代码就ok,强制不加载页面。那么为了加载指定页面,需要一个函数:
void CHTMLContainerDlg::SetHtmlAndCom(CString strURL, CString strProg)
{
    HRESULT        hr        = NOERROR;
    m_strURL = strURL;
    hr = m_spComDisp.CoCreateInstance(strProg);
    if(FAILED(hr))
    {
        TRACE(_T("Some error when create com object\n"));
    }
    SetExternalDispatch(m_spComDisp);
}
指定html的url和对应功能组件的progid,这样在网页里面可以通过脚本window.external访问该com组件。
这样就可以加载html网页,但是html页面里面的元素风格却是2k风格(至少在ie7以下版本是如此),这个怕是没起到一点美观作用,为之我考虑了半天,问过做web的人是否有办法,最终还是灵感光临,误撞上了。重载GetHostInfo函数
STDMETHODIMP CHTMLContainerDlg::GetHostInfo(DOCHOSTUIINFO* pInfo)
{
    pInfo->dwFlags = DOCHOSTUIFLAG_THEME;
    return S_OK;
}
这个多得不说,^_^。
下面就可以演示了,在vs2005里面找个向导来show一下:
CHTMLContainerDlg    dlg;
    TCHAR                szPath[MAX_PATH] 
=   0 } ;
    CString                strPath;
    GetCurrentDirectory(MAX_PATH, szPath);
    strPath 
=  szPath;
    strPath 
+=  _T( " \\Default.htm " );
    dlg.SetHtmlAndCom(strPath, _T(
" TestWebCom.WebComCtrl " ));
    dlg.DoModal();

MFC技术资料大全与汇总_第1张图片
对话框标题其实可以通过解析html文档获取title标题设置,目前还未处理。下面看看html与应用交互的组件。
生成一个atl工程,TestWebCom,添加一个com组件WebComCtrl,添加方法处理上面那个带...的按钮(文件夹浏览按钮):
STDMETHODIMP CWebComCtrl::ShowFolderBrowser( void )
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    
// TODO: 在此添加实现代码
    AfxMessageBox(_T("In Com, you can show folder select dialog"));
    
return S_OK;
}

这里不作具体处理,只是象征性弹出一个对话框。好了,上面我们在对话框里面已经设置了com组件的progid,这里可以把html和组件关联上了,通过脚本可以访问com组件方法:
< BUTTON  CLASS ="buttonClass3Custom"  ID ="BrowseBtn"  TYPE ="BUTTON"  TITLE ="浏览头文件。"  onClick ="OnBrowseHeaderFile();" > BUTTON >
脚本如下:
function  OnBrowseHeaderFile()
{
    window.external.ShowFolderBrowser();
}
下面运行试一试,按下选择文件夹按钮会出现如下询问组件是否安全的对话框:
MFC技术资料大全与汇总_第2张图片
这个很恼人,用户可没有耐心忍受每次多弹出这个对话框询问组件是否安全。我开始打算将组件实现安全接口解决掉此问题,不过不知道缘何,没有成功,网上搜索一下好像说在ie7里面无效,没办法还是看mfc源码来解决问题。
CDHtmlDialog类获取external代码如下:
STDMETHODIMP CDHtmlDialog::GetExternal(IDispatch  ** ppDispatch)
{
    
if(ppDispatch == NULL)
        
return E_POINTER;
        
    
*ppDispatch = NULL;
    
if (m_spExternalDisp.p && CanAccessExternal())
    
{
        m_spExternalDisp.p
->AddRef();
        
*ppDispatch = m_spExternalDisp.p;
        
return S_OK;
    }

    
return E_NOTIMPL;
}
看到CanAccessExternal函数,肯定就是验证安全性的代码,找到函数声明,幸好是虚函数,重载直接返回TRUE:
BOOL CHTMLContainerDlg::CanAccessExternal()
{
    
// we trust all com object (haha, you can make virus)
 return TRUE;
}
有兴趣的朋友可以看下内部实现。
这下就好了,按下网页选择文件夹按钮,弹出对话框:
MFC技术资料大全与汇总_第3张图片
一套流程完备,方案个人觉得不错,各司其职,通信自然畅通,一个html配对一个com功能组件,功能组件化不仅使代码封装性好,而且可以用于多种语言。

由于此技术不用于公司开发,今整理提供下载

usidc5 2010-09-18 18:16

ID--HANDLE--HWND三者之间的互相转换
id->句柄        hWnd = ::GetDlgItem(hParentWnd,id);
id->指针        CWnd::GetDlgItem();
句柄->id        id = GetWindowLong(hWnd,GWL_ID);
句柄->指针    CWnd *pWnd=CWnd::FromHandle(hWnd);
指针->ID       id = GetWindowLong(pWnd->GetSafeHwnd,GWL_ID);
                                            GetDlgCtrlID();
指针->句柄     hWnd=cWnd.GetSafeHandle() or mywnd->m_hWnd;  

指针的使用在编程过程中至关重要,恰到好处并能正确无误的使用指针不但能够提高程序自身的运行效率,而且有助于节省程序执行所需要消耗的资源。指针对应着某个数据在内存空间中的地址,得到了指针就可以自由地修改该数据。句柄代表指针的“指针”,也可以将其比作表中数据项的索引值( 表对应某个进程自身的内存空间 )。句柄是间接的引用对象。

        指针和句柄的不同之处

  • 句柄所指的可以是一个很复杂的结构,并且很有可能与系统有关的,比如上面所说线程的句柄,它指向的就是一个类或者结构,它和系统有很密切的关系。当一个线程由于不可预料的原因而终止时,系统就可以通过句柄来回收它所占用的资料,如CPU,内存等等。反过来想,这些句柄中的某一些,是与系统进行交互用的。
  • 指针它也可以指向一个复杂的结构,但通常是由用户自我定义的,所以一些必需的工作都要由用户自己完成,特别是在删除的时候。
  • 另外需要注意的是句柄往往有自己的存在区限,比如一个进程,如果将其传递到另一个进程中,句柄也就失去了意义( 表中数据项的索引值,索引离开了具体的表也就失去了意义 )。

        具体转换

        ( 句柄转为指针 )

        CWnd* pWnd=FromeHandle(hMyHandle); 
        pWnd->SetWindowText("Hello World!"); 

        or 

        CWnd* pWnd; 
        pWnd->Attach(hMyHandle);  

        MFC类中有的还提供了标准方法,比如Window句柄:

        static CWnd* PASCAL FromHandle( 
        HWND hWnd 
        ); 
        HWND GetSafeHwnd( ) const; 

        对于位图: 
        static CBitmap* PASCAL FromHandle( 
       HBITMAP hBitmap 
         );  
         static CGdiObject* PASCAL FromHandle( 
       HGDIOBJ hObject 
         ); 
        HGDIOBJ GetSafeHandle( ) const;

        当然,更详细的信息需要在具体使用中自我查询。

建 议:指针和句柄的使用属于比较复杂、危险性较高的应用,在具体实践中,如果可以,尽量不要使用指针和句柄,最好选择现有的、封装完好的方式来实现,更别提指针同句柄的转换了,它更加危险。

        比如在操作字符串时,尽量使用CString类来实现,通过定义好的构造、析构函数来完成分配和回收,最好不要通过指针来动态操作。


usidc5 2010-09-18 18:17
摘要:指针,在VC++中是很常见的,这里我们并不打算去详细讲解在C++中那样的指针用法(我们会有另外的文章去详细讨论),这里主要讲一下VC++中常见的对指针获取的方法,包括:工具条、状态条、控件和窗口的指针。
     获取工具条的指针
     在缺省状态下,有一个默认的工具条AFX_IDW_TOOLBAR,我们可以根据相应的ID去获取工具条指针,方法如下:
     CToolBar* pToolBar=(CToolBar*)AfxGetMainWnd()->GetDescendantWindow(AFX_IDW_TOOLBAR);
     是不是很简单?
 
     获取状态条的指针
     在缺省状态下,有一个默认的状态条AFX_IDW_STATUS_BAR,我们自然也可以根据相应的ID去获取状态条指针,方法如下:
     CStatusBar* pToolBar=(CStatusBar*)AfxGetMainWnd()->GetDescendantWindow(AFX_IDW_STATUS_BAR);
     是不是同样很简单?

    获取控件的指针 
    这里有两种方法。
    一、调用CWnd: : GetDlgItem,获取一个CWnd*指针调用成员函数。例如,我们想获取CButton指针,方法如下:
    CButton* pButton=(CButton*) GetDlgItem (IDC_MYBUTTON);
    二、可以使用ClassWizard将控件和成员变量联系起来。在ClassWizard中简单地选择Member Variables标签,然后选择Add Variable …按钮。如果在对话资源编辑器中,按下Ctrl键并双击控件即可转到Add Member Variable对话。

    在文档类中调用视图类指针
    我们可以利用文档类的成员函数GetFirstView()和GetNextView()遍历视图。

    在视图类中调用文档类
    其实,在视图类中有一个现成的成员函数供我们使用,那就是:GetDocument();利用它我们可以很容易的得到文档类指针,我们先看一下GetDocument()函数的实现:
    CColorButtonDoc* CColorButtonView::GetDocument() 
   {
 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CColorButtonDoc)));
 return (CColorButtonDoc*)m_pDocument;
   }
   这里实际上是将m_pDocument强制转换成CColorButtonDoc*,也就是我们想要的。

   在框架类中调用文档类、视图类
   这里我们可以利用GetActiveXXXXX()去掉用当前激活的文档和视图:
   CMyDoc*  pDoc=(CMyDoc*)GetActiveDocument();
   CMyView* pView=(CMyView*)GetActiveView();

   获得应用程序指针
   这个很简单,一句话搞定:
   CMyApp* pApp=(CMyApp*)AfxGetApp();

   获得主框架指针
   在类CWinThread里面有一个公有的成员变量:CWnd* m_pMainWnd; 它存在的主要目的就是提供我们获得CWnd指针,我们可以利用它来达到我们的目的:
   CMainFrame* pFrame=(CMainFrame*)(AfxGetApp()->m_pMainWnd);
   
   
通过鼠标获得子窗口指针
   这里我们要用到一个不太常用的函数:ChildWindowFromPoint。他的原型如下:
   CWnd* ChildWindowFromPoint(POINT point) const;
   CWnd* ChildWindowFromPoint(POINT point,UINT nFlags) const;
   这个函数用于确定包含指定点的子窗口,如果指定点在客户区之外,函数返回NULL;如果指定点在客户区内,但是不属于任何一个子窗口,函数返回该CWnd的指针;如果有多个子窗口包含指定点,则返回第一个子窗口的指针。不过,这里还要注意的是:该函数返回的是一个伪窗口指针,不能将它保存起来供以后使用。
   对于第二个参数nFlags有几个含义:
   CWP_ALL             
file://不忽略任何子窗口
   CWP_SKIPNIVSIBLE    
file://忽略不可见子窗口
   CWP_SKIPDISABLED    
file://忽略禁止的子窗口
  CWP_SKIPRANSPARENT  
file://忽略透明子窗口

usidc5 2010-09-19 21:23

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       {
           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       {
           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
 


usidc5 2010-09-19 21:24

[设置属性]
首先用资源编辑器拖放一个ListCtrl控件到对话框中。
设置属性。
Always Show Selection: True // 即便失去焦点依然高亮选择
SingleSelection: True   // 只允许选中一行
View: Report            // 报表方式,较常用

[初始化]
// 在对话框的OnInitialDialog中初始化ListCtrl
// 1. 设置样式
m_ListCtrl.SetExtendedStyle( LVS_EX_FULLROWSELECT );
// LVS_EX_FULLROWSELECT: 表示选中整行,而非某一列
// LVS_EX_GRIDLINES: 显示网格线
// LVS_EX_CHECKBOXES: 第一列前面显示复选框
// LVS_EX_FLATSB: 比较平的滚动条

// 2. 插入列
m_ListCtrl.InsertColumn( 0, _T("列标题0"), LVCFMT_LEFT, 100 ); 
m_ListCtrl.InsertColumn( 1, _T("列标题1"), LVCFMT_LEFT, 100 );

// LVCFMT_LEFT表示列左对齐,还可以取LVCFMT_RIGHT,LVCFMT_CENTER
// 100为列宽

// 3. 插入行
// 可以在初始化时插入行,也可以动态插入行

m_ListCtrl.InsertItem( 0, _T("0 行 0 列") );
m_ListCtrl.SetItemText( 0, 1, _T("0 行 1 列") );
m_ListCtrl.InsertItem( 1, _T("1 行 0 列") );
m_ListCtrl.SetItemText( 1, 1, _T("0 行 1 列") );


[遍历所选]
POSITION selectItemPos = m_ListCtrl.GetFirstSelectedItemPosition();
 while ( selectItemPos != NULL ) {
  const int selectItemIndex = m_ListCtrl.GetNextSelectedItem( selectItemPos );  
  CString keyString = m_ListCtrl.GetItemText( selectItemIndex,  0 );
 //  selectItemIndex是基于0的索引
 }


[删除所选]
POSITION selectItemPos = m_ListCtrl.GetFirstSelectedItemPosition();
 int offset = 0;
 while ( selectItemPos != NULL ) {
  const int selectItemIndex = m_ListCtrl.GetNextSelectedItem( selectItemPos );
  m_ListCtrl.DeleteItem( selectItemIndex - offset );
  ++offset;
 }



usidc5 2010-09-19 21:25


1.创建图形列表并和CListCtrl关联:
 m_image_list.Create(IDB_CALLER2, 16, 10, RGB(192,192, 192));
 m_image_list.SetBkColor( GetSysColor( COLOR_WINDOW ) );
 m_caller_list.SetImageList( &m_image_list, LVSIL_SMALL);
2.为报表添加4列:
  char *szColumn[]={"昵称","IP地址","登陆时间","状态"};
  int widths[]={100,98,70,55};
  LV_COLUMN lvc;
  lvc.mask=LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
  lvc.fmt=LVCFMT_LEFT;
  for(int i=0;i<4;i++) {//插入各列
   lvc.pszText=szColumn;
   lvc.cx=widths;
   lvc.iSubItem=i;
   m_caller_list.InsertColumn(i,&lvc);
  }
3.为报表添加两项,以附加方式添加:
 char* data[4];
 data[0]="所有人";
 data[1]="0.0.0.0";
 data[3]="在线";
 data[2]=new char;
 CTime now=CTime::GetCurrentTime();
       CString temp = now.Format("%H:%M:%S");
 data[2]=temp.GetBuffer(1);
 LV_ITEM lvi;
 lvi.mask=LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM;
 lvi.iSubItem=0;
 lvi.pszText=(char *)data[0];
 lvi.iImage = 0;
 lvi.iItem=0;
 m_caller_list.InsertItem(&lvi);
 for (int j=0;j<4;j++) m_caller_list.SetItemText(count,j,data[j]);
 count++;
 lvi.iImage = 1;
 lvi.iItem=count;
 m_caller_list.InsertItem(&lvi);
 data[0]="cherami";
 data[1]="127.0.0.1"; 
 for (int n=0;n<4;n++) m_caller_list.SetItemText(count,n,data[n]);
 count++;
4.设置报表的样式
选中一整行:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_FULLROWSELECT);  
绘制表格:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_GRIDLINES); 
带复选框:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_CHECKBOXES); 
自动切换:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_TRACKSELECT);
选定一行:
设置CListCtrl的Show selection always选项
SetItemState (iIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED) 
 
选中一个或多个项目时,会发送LVN_ITEMCHANGED消息,可以使用
GetSelectedCount()方法得到被选定的项的数目。
点击列头的消息响应:
ON_NOTIFY(HDN_ITEMCLICKW, 0, ResponseFunc)
消息,需要自己添加 
或者:
ON_NOTIFY(LVN_COLUMNCLICK, ID_yourCtrl,  ResponseFunc)//向导添加
前者后响应,后者先响应
响应函数:
ResponseFunc(NMHDR *pNMHDR, LRESULT *pResult)
双击CListCtrl中的ITEM的消息是及消息函数:
ON_NOTIFY(NM_DBLCLK, ID_yourCtrl, ResponseFunc)
单击ITEM的消息响应:
ON_NOTIFY(NM_CLICK, ID_yourCtrl, ResponseFunc)
ResponseFunc(NMHDR *pNMHDR, LRESULT *pResult)

HDN_ITEMCLICK    就是Header control Notify message for mouse left click on the Header control!
而HDN_ITEMCLICK是当List View中存在一个Header Contrl时,Header Ctrl通知父窗口List View的!
CListCtrl中的Item被选中触发LBN_SELCHANGE(通过WM_COMMAND)消息!
删除CListCtrl中选定的项:
POSITION pos;
int nIndex;
for(; pos= GetFirstSelectedItemPosition();)
{
nIndex = GetNextSelectedItem(pos);
DeleteItem(nIndex);
}
5.在ListCtrl中进行排序
列表控件(CListCtrl)的顶部有一排按钮,用户可以通过选择不同的列来对记录进行排序。但是 CListCtrl并没有自动排序的功能,我们需要自己添加一个用于排序的回调函数来比较两个数据的大小,此外还需要响应排序按钮被点击的消息。下面讲述一下具体的做法。
CListCtrl提供了用于排序的函数,函数原型为:BOOL CListCtrl::SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData )。其中第一个参数为全局排序函数的地址,第二个参数为用户数据,你可以根据你的需要传递一个数据或是指针。该函数返回-1代表第一项排应在第二项前面,返回1代表第一项排应在第二项后面,返回0代表两项相等。
用于排序的函数原形为:int CALLBACK ListCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort),其中第三个参数为调用者传递的数据(即调用SortItems时的第二个参数dwData)。第一和第二个参数为用于比较的两项的ItemData,你可以通过DWORD CListCtrl::GetItemData( int nItem )/BOOL CListCtrl::SetItemData( int nItem, DWORD dwData )来对每一项的ItemData进行存取。在添加项时选用特定的CListCtrl::InsertItem也可以设置该值。由于你在排序时只能通过该值来确定项的位置所以你应该比较明确的确定该值的含义。
最后一点,我们需要知道什么时候需要排序,实现这点可以在父窗口中对LVN_COLUMNCLICK消息进行处理来实现。
下面我们看一个例子,这个例子是一个派生类,并支持顺序/倒序两种方式排序。为了简单我对全局数据进行排序,而在实际应用中会有多组需要排序的数据,所以需要通过传递参数的方式来告诉派序函数需要对什么数据进行排序。

//全局数据
struct DEMO_DATA
{
 char szName[20];
 int iAge;
}strAllData[5]={{"王某",30},{"张某",40},{"武某",32},{"陈某",20},{"李某",36}};
//CListCtrl派生类定义
class CSortList : public CListCtrl
{
// Construction
public:
 CSortList();
 BOOL m_fAsc;//是否顺序排序
 int m_nSortedCol;//当前排序的列
protected:
 //{{AFX_MSG(CSortList)
 //}}AFX_MSG
...
};
//父窗口中包含该CListCtrl派生类对象
class CSort_in_list_ctrlDlg : public CDialog
{
// Construction
public:
 CSort_in_list_ctrlDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
 //{{AFX_DATA(CSort_in_list_ctrlDlg)
 enum { IDD = IDD_SORT_IN_LIST_CTRL_DIALOG };
 CSortList m_listTest;
 //}}AFX_DATA
}
//在父窗口中定义LVN_COLUMNCLICK消息映射
BEGIN_MESSAGE_MAP(CSort_in_list_ctrlDlg, CDialog)
 //{{AFX_MSG_MAP(CSort_in_list_ctrlDlg)
 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST1, OnColumnclickList1)
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()
//初始化数据
BOOL CSort_in_list_ctrlDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 //初始化ListCtrl中数据列表
 m_listTest.InsertColumn(0,"姓名");
 m_listTest.InsertColumn(1,"年龄");
 m_listTest.SetColumnWidth(0,80);
 m_listTest.SetColumnWidth(1,80);
 for(int i=0;i<5;i++)
 {
  m_listTest.InsertItem(i,strAllData.szName);
  char szAge[10];
  sprintf(szAge,"%d",strAllData.iAge);
  m_listTest.SetItemText(i,1,szAge);
  //设置每项的ItemData为数组中数据的索引
  //在排序函数中通过该ItemData来确定数据
  m_listTest.SetItemData(i,i);
 }
 return TRUE;  // return TRUE  unless you set the focus to a control
}
//处理消息
void CSort_in_list_ctrlDlg::OnColumnclickList1(NMHDR* pNMHDR, LRESULT* pResult) 
{
 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
 //设置排序方式
 if( pNMListView->iSubItem == m_listTest.m_nSortedCol )
  m_listTest.m_fAsc = !m_listTest.m_fAsc;
 else
 {
  m_listTest.m_fAsc = TRUE;
  m_listTest.m_nSortedCol = pNMListView->iSubItem;
 }
 //调用排序函数
 m_listTest.SortItems( ListCompare, (DWORD)&m_listTest );        
 *pResult = 0;
}
//排序函数实现
int CALLBACK ListCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
 //通过传递的参数来得到CSortList对象指针,从而得到排序方式
 CSortList* pV=(CSortList*)lParamSort;
 
 //通过ItemData来确定数据
 DEMO_DATA* pInfo1=strAllData+lParam1;
 DEMO_DATA* pInfo2=strAllData+lParam2;
 CString szComp1,szComp2;
 int iCompRes;
 switch(pV->m_nSortedCol)
 {
 case(0):
  //以第一列为根据排序
  szComp1=pInfo1->szName;
  szComp2=pInfo2->szName;
  iCompRes=szComp1.Compare(szComp2);
  break;
 case(1):
  //以第二列为根据排序
  if(pInfo1->iAge == pInfo2->iAge)
   iCompRes = 0;
  else
   iCompRes=(pInfo1->iAge < pInfo2->iAge)?-1:1;
  break;
 default:
  ASSERT(0);
  break;
 }
 //根据当前的排序方式进行调整
 if(pV->m_fAsc)
  return iCompRes;
 else
  return iCompRes*-1;
}
排序最快:
CListCtrl::SortItems
Example
// Sort the item in reverse alphabetical order.
static int CALLBACK 
MyCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
  // lParamSort contains a pointer to the list view control.
  // The lParam of an item is just its index.
  CListCtrl* pListCtrl = (CListCtrl*) lParamSort;
  CString    strItem1 = pListCtrl->GetItemText(lParam1, 0);
  CString    strItem2 = pListCtrl->GetItemText(lParam2, 0);
  return strcmp(strItem2, strItem1);
}
void snip_CListCtrl_SortItems()
{
  // The pointer to my list view control.
  extern CListCtrl* pmyListCtrl;
  // Sort the list view items using my callback procedure.
  pmyListCtrl->SortItems(MyCompareProc, (LPARAM) pmyListCtrl);
}

If you don’t want to allow the users to sort the list by clicking on the header, you can use the style LVS_NOSORTHEADER. However, if you do want to allow sorting, you do not specify the LVS_NOSORTHEADER. The control, though, does not sort the items. You have to handle the HDN_ITEMCLICK notification from the header control and process it appropriately. In the code below, we have used the sorting function SortTextItems() developed in a previous section. You may choose to sort the items in a different manner. 
Step 1: Add two member variables
Add two member variables to the CListCtrl. The first variable to track which column has been sorted on, if any. The second variable to track if the sort is ascending or descending. 
        int nSortedCol; 
        BOOL bSortAscending; 
 
Step 2: Initialize them in the constructor.
Initialize nSortedCol to -1 to indicate that no column has been sorted on. If the list is initially sorted, then this variable should reflect that. 
  
        nSortedCol = -1; 
        bSortAscending = TRUE; 
  
Step 3: Add entry in message map to handle HDN_ITEMCLICK
Actually you need to add two entries. For HDN_ITEMCLICKA and HDN_ITEMCLICKW. Do not use the class wizard to add the entry. For one, you need to add two entries whereas the class wizard will allow you only one. Secondly, the class wizard uses the wrong macro in the entry. It uses ON_NOTIFY_REFLECT() instead of ON_NOTIFY(). Since the HDN_ITEMCLICK is a notification from the header control to the list view control, it is a direct notification and not a reflected one. 
ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHeaderClicked) 
ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHeaderClicked)
 Note that we specify the same function for both the notification. Actually the program will receive one or the other and not both. What notification it receives will depend on the OS. The list view control on Windows 95 will send the ANSI version and the control on NT will send the UNICODE version. 
Also, note that the second argument is zero. This value filters for the id of the control and we know that header control id is zero.
Step 4: Write the OnHeaderClicked() function
Here’s where you decide what to do when the user clicks on a column header. The expected behaviour is to sort the list based on the values of the items in that column. In this function we have used the SortTextItems() function developed in a previous section. If any of the columns displays numeric or date values, then you would have to provide custom sorting for them. 
  
void CMyListCtrl::OnHeaderClicked(NMHDR* pNMHDR, LRESULT* pResult) 
{
        HD_NOTIFY *phdn = (HD_NOTIFY *) pNMHDR;
        if( phdn->iButton == 0 )
        {
                // User clicked on header using left mouse button
                if( phdn->iItem == nSortedCol )
                        bSortAscending = !bSortAscending;
                else
                        bSortAscending = TRUE;
                nSortedCol = phdn->iItem;
                SortTextItems( nSortedCol, bSortAscending );
        }
        *pResult = 0;
}
让CListCtrl的SubItem也具有编辑功能:
要重载一个文本框,然后在LVN_BEGINLABELEDIT时改变文本框位置。
CInEdit m_InEdit;
    if( ( GetStyle() & LVS_TYPEMASK ) == LVS_REPORT && ( m_nEditSubItem != 0 ) )
    {
        HWND    hwndEdit;
        CRect    rtBound;
        CString strText;
        hwndEdit = (HWND)SendMessage( LVM_GETEDITCONTROL );
        GetSubItemRect( pDispInfo->item.iItem, m_nEditSubItem, LVIR_LABEL, rtBound );
        m_InEdit.SubclassWindow( hwndEdit );
        m_InEdit.m_left = rtBound.left;
        strText = GetItemText( pDispInfo->item.iItem, m_nEditSubItem );
        m_InEdit.SetWindowText( strText );
    }
void CInEdit::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
    CRect rtClient;
    lpwndpos->x = m_left;  // m_left在LVN_BEGINLABELEDIT中设置
    CEdit::OnWindowPosChanging(lpwndpos);
    
    // TODO: Add your message handler code here
}


usidc5 2010-09-19 21:26
方案1:窗口锁定技术。
m_list.LockWindowUpdate()
while()
  {
    //取出数据,插入到数据库中。
}
m_list.UnlockWindowUpdate()
程序在每次循环的时候不闪烁了,但是每个查询周期还是闪烁。
方案2:   不知道是否用对,每个查询周期还是要闪烁一次
SetWindowRedraw(hwnd,   FALSE);
    //过程同上
SetWindowRedraw(hwnd,   TRUE);
方案3:虚拟列表技术
  codePrject上下载的例子是针对一次插入大容量数据时候的不闪烁技术。更改以后,
每个周期改变数组的值,达到了要求。还是存在一些问题:
(1)虚拟列表技术必须要CListCtrl控件设置成自绘。原来的一个类用来设置每行的背景
        颜色,不能用了。是在CodePrject上下载了一个CListCtrl例子,类CReportCtrl例子,
        二者不能融合。调试时候发现是OnCustomdrawMyList   ()函数中错误?
          它使用了消息反射技术,如函数
          OnCustomdrawMyList   (   NMHDR*   pNMHDR,   LRESULT*   pResult   ),不知道和虚拟列表
        技术使用的LVN_GETDISPINFO消息(对应函数OnGetdispinfoList(NMHDR*   pNMHDR,      
          LRESULT*   pResult))二者是怎么样的对应关系?

usidc5 2010-09-20 14:51

很多基于对话框的应用程序都是不带框架的,也就是说对话框没有标题栏。众所周知,窗口的移动都是通过鼠标点住标题栏拖动窗口实现的,那么现在没有了标题栏,如何移动对话框呢?本文拟针对这个问题提出解决的办法。 
    解决这个问题有两种方案。一种很业余,另外一种比较专业。前者使用一种常规思路处理鼠标拖拽事件。当窗口获得WM_LBUTTONDOWN(OnLButtonDown)时,通过设置标志并调用SetCapture控制鼠标使应用程序进入移动模式。进入移动模式之后,只要有WM_MOUSEMOVE消息过来,就可以据此移动框架窗口。最后,当用户释放鼠标按钮,则WM_LBUTTONUP消息处理例程清除标志并调用ReleaseCapture函数将鼠标控制返还给Windows。之所以说这种方法业余,主要是因为比较繁琐,首先要决定窗口移到哪?然后要想好如何重绘窗口等等,而且根据屏幕显示属性对话框“效果”页中“视觉效果”项的“拖动实显示窗口内容”复选框是不是选中,拖动效果是不同的。那么你怎么知道的设置呢?(方法是调用SystemParametersInfo(SPI_GETDRAGFULLWINDOWS)。Windows要程序员来事务巨细地处理这些繁琐的事情真是太遭了。由于Windows本身知道通过鼠标点住标题栏可以移动窗口,那么能不能将鼠标在窗口客户区任何地方的点击拖动行为都模仿成好像是在标题栏中一样呢?


图五 显示器的属性对话框-“效果”标签

    答案是肯定的。有心的读者可能已经猜测到下一步该怎么做了答。实际上,用鼠标点住对话框背景进行拖动操作并不难,但是你必须了解在标题栏里拖动窗口的原理。Windows首先确定鼠标点中了那个窗口,然后向那个窗口发送一个WM_NCHITTEST消息找出此窗口的哪个“非客户区”(如边界、最大化/最小化按钮、菜单、标题等等)拥有鼠标光标。接着默认的窗口过程响应消息并返回一个特定的代码。如果鼠标指针落在标题栏中,那么这个神奇的特定代码就是HTCAPTIONA。如果WM_NCHITTEST返回HTCAPTION,那么Windows便进入拖拽模式,以便对窗口进行移动操作。所以要想在客户区里用鼠标拖动对话框,那么只要在客户区里模仿标题栏里的鼠标拖动行为即可。这个我们下面要介绍的比较专业的方法,其主要思路是处理WM_NCHITTEST消息:

UINT CMyDialog::OnNcHitTest(CPoint pt)
{
  CRect rc;
  GetClientRect(&rc);
  ClientToScreen(&rc);
  return rc.PtInRect(pt) ? HTCAPTION : CDialog::OnNcHitTest(pt);
}     
    上面这个代码很容易理解,当鼠标落在客户区内,函数返回HTCAPTION。对于一个简单的对话框来说,仅仅用这个代码就完全可以实现在对话框背景内的拖动操作。因为Windows使用z-order坐标来确定鼠标下是哪个窗口,所以对话框中其它的所有对象照常工作。如果用户单击某个控制,只要这个控制不是静态位图图像或者文本,那么Windows都将鼠标事件发送到该控制上,而不是对话框。由于静态位图图像或者文本对于对话框是透明的,所以鼠标在上面的拖动同样实现移动,而对于对话框中的编辑框、按钮、组合框等其它非静态控制则按通常的行为方式运行。
如图二是本文例子程序运行画面:


如图二

    用鼠标在背景上可以拖动对话框。如果应用不是一个纯粹的对话框程序,而是CFormView或其它非对话框视图,处理方法几乎是一样的,只需在视图代码中做一点小小的改动即可,因为Windows在发送WM_NCHITTEST消息时,是将它发送到鼠标光标下的框架/视图最顶层非透明窗口,由于视图首先获得WM_NCHITTEST消息。所以只要在视图的WM_NCHITTEST消息处理例程中返回HTTRANSPARENT,让视图对鼠标点击透明即可。注意是在在视图中,而不是框架中加上下面代码:
UINT CMyView::OnNcHitTest(CPoint pt)
{
  return HTTRANSPARENT;
}     
    这样做以后,Windows将忽略视图并继续搜索能接收WM_NCHITTEST的窗口。如果顺利的话,将找到父窗口,这时用与对话框相同的WM_NCHITTEST处理代码即可,即在客户区中的点击返回HTCAPTION。你甚至可以通过鼠标坐标的象素计算,在规定的局部范围内实现视图透明。
UINT CMyView::OnNcHitTest(CPoint pt)
{
  return PointLiesWithinDraggableRegion(pt) ?
    HTTRANSPARENT : CView::OnNcHitTest(pt);
}      
    这样拖拽/移动特性只能在PointLiesWithinDraggableRegion的非零窗口区域内有效。为此请参见另外一篇文章的基于框架/视图的例子:“MFC框架程序中全屏显示特性的实现”,其中就实现了用鼠标在客户区拖动整个框架窗口的效果。

usidc5 2010-09-24 14:04

以下代码主要总结了对组合框添加下拉选项、选择某一选项的基本应用。

    void  CNewcomCosScriptView::DoDataExchange(CDataExchange *  pDX)
{
    CFormView::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_Card, m_card);
}


// 添加ComboBox下拉选项
// ComboBox属性设置中有个sort选项,若为True则按首字母顺序添加,为False则每次添加到列表最后
void  CNewcomCosScriptView::OnCbnDropdownCard()
{
    m_card.ResetContent();

    m_card.AddString(
"SD");
    m_card.AddString(
"PCSC");
}


// 选择ComboBox选项
void  CNewcomCosScriptView::OnCbnSelchangeCard()
{
    UpdateData(FALSE);
    
    
int nCount = m_card.GetCount();                       //获取ComboBox中元素个数
// int nTmp = m_card.SetCurSel(nCount - 1);       //设置当前选项为最后一个元素    

    
//获取ComboBox的当前值
 int iPos = m_card.GetCurSel();                             //当前选中的元素索引
    CString str;
    m_card.GetLBText(m_card.GetCurSel(),str);    
//当前选中的字符串
    
    
char *pa = (char*)((LPCTSTR)str);

//三种判断均可:
//    if(!strcmp(pa,"SD"))
//    if (str == "SD")
 if (iPos == 1){
        
    }

    
else if (iPos == 0){
        
    }

}

usidc5 2010-09-24 14:05
Combo box controls are space savers. Wherever there is no need for a multi-select from a list of items, combo box is a good choice in such places. This article " CComboBox Example" explains how to use the MFC CComboBox class for manipulation of a list of strings.CComboBox Example - Initializing a Combo Box:

   It is assumed that the readers of the sample have already created a [color=blue !important][color=blue !important]dialog [color=blue !important]box
 (either in a dialog based application or SDI/MDI application) and placed a combo box control from the controls toolbox on the Resource Editor.
   After placing the combo box control on the dialog box, open the class wizard by pressing Ctrl + W keys or Menu --> View --> ClassWizard. In the Member Variables tab, Add a Variable for the CComboBox class. This CComboBox example assumes that the variable name is,
      CComboBox  m_cbExample;
   This m_cbExample will be used further in our CComboBox example MFC code.



CComboBox Example - Adding Items to a Combo Box:
   The function AddString is used for adding items to a combo box. If there is a constant set of data, these values can also be added in the Resource Editor itself. The Combo Box control properties dialog has a tab for adding data. Otherwise the data can be added as follows.
    m_cbExample.AddString("StringData1");
    m_cbExample.AddString("StringData2");
    m_cbExample.AddString("StringData3");
CComboBox Example - Retrieving Items from a Combo Box:
   Usually 
a requirement for retrieving items from the combo box will arise from selecting the data. This article also assumes the same. Now the data selected in a combo box needs to be retrieved.
   To do this, the first step is to find out the index of the selected item inside the combo box control.
Then the item at the corresponding position needs to be pulled out as follows.

    int nIndex = m_cbExample.GetCurSel();
    CString strCBText;

    m_cbExample.GetLBText(
nIndex, strCBText);

   In the above CComboBox example code, the value will be retrieved and stored in strCBText variable. There is another overloaded version for GetLBText. But the version which uses CString is the easiest one.


CComboBox Example - Finding Items inside a Combo Box:

   This kind of Find operations on a Combo box will most probably be useful in programs that dynamically modify the values in a combo box. The function FindStringExact is used to find the exact string match inside a combo box.

    int nIndex = m_cbExample.FindStringExact(0, "Value to be found");

   The string position inside the combo box control is the return value. It returns CB_ERR if it was unsuccessful in finding the string.


CComboBox Example - Deleting Items from a Combo Box:

    This operation can be done by using the CCombobox member function DeleteString. This function needs the index of the item inside the combo box.

     m_cbExample
.DeleteString(nIndex);

usidc5 2010-10-05 19:11
有三个API函数可以运行可执行文件WinExec、ShellExecute和CreateProcess。CreateProcess因为使用复杂,比较少用。WinExec主要运行EXE文件。如:WinExec(’Notepad.exe Readme.txt’, SW_SHOW);




ShellExecute不仅可以运行EXE文件,也可以运行已经关联的文件。
首先必须引用shellapi.pas单元:uses ShellAPI;


1、使用CreateProcess(好像不执行)


BOOL CMSChartuseDlg::RunCMD(LPCTSTR pszCommand)
{
// TCHAR szAppName[_MAX_PATH] = TEXT("fexplore.exe");//资源管理器程序
TCHAR szAppName[60] = TEXT("c:\\windows\\system32\\cmd.exe");//资源管理器程序
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
ZeroMemory(&pi,sizeof(pi));
si.cb=sizeof(si); 
si.dwFlags=STARTF_USESHOWWINDOW; 
si.wShowWindow=SW_HIDE; 
if(CreateProcess(szAppName,(char*)pszCommand,NULL,NULL,NULL,
0,NULL,NULL,&si,&pi)==0)
{
LPVOID lpMsgBuf; 
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, 
   NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),   //   Default   language 
   (LPTSTR)&lpMsgBuf,0,NULL);


CString strMsg; 
strMsg.Format("执行软件时出错:%s",(char*)lpMsgBuf); 
LocalFree(lpMsgBuf); 
MessageBox(strMsg); 
return FALSE;
}
else{
// system(pszCommand);
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
// WaitForSingleObject(pi.hProcess,INFINITE); 
return TRUE;
}
}


2、深入浅出ShellExecute


      Q:       如何打开一个应用程序?       ShellExecute(this->m_hWnd,"open","calc.exe","","",       SW_SHOW       );   
      或       ShellExecute(this->m_hWnd,"open","notepad.exe",   
              "c:\\MyLog.log","",SW_SHOW       );   
      正如您所看到的,我并没有传递程序的完整路径。   
      Q:       如何打开一个同系统程序相关连的文档?       ShellExecute(this->m_hWnd,"open",   
              "c:\\abc.txt","","",SW_SHOW       );   
      Q:       如何打开一个网页?       ShellExecute(this->m_hWnd,"open",   
              "http://www.google.com","","",       SW_SHOW       );   
      Q:       如何激活相关程序,发送EMAIL?       ShellExecute(this->m_hWnd,"open",   
              "mailto:[email protected]","","",       SW_SHOW       );   
      Q:       如何用系统打印机打印文档?       ShellExecute(this->m_hWnd,"print",   
              "c:\\abc.txt","","",       SW_HIDE);   
      Q:       如何用系统查找功能来查找指定文件?       ShellExecute(m_hWnd,"find","d:\\nish",   
              NULL,NULL,SW_SHOW);   
      Q:       如何启动一个程序,直到它运行结束?(打开一个程序,直到它关闭再回到主程序)


      SHELLEXECUTEINFO       ShExecInfo       =       {0};   
      ShExecInfo.cbSize       =       sizeof(SHELLEXECUTEINFO);   
      ShExecInfo.fMask       =       SEE_MASK_NOCLOSEPROCESS;   
      ShExecInfo.hwnd       =       NULL;   
      ShExecInfo.lpVerb       =       NULL;   
      ShExecInfo.lpFile       =       "c:\\MyProgram.exe";   
      ShExecInfo.lpParameters       =       "";   
      ShExecInfo.lpDirectory       =       NULL;   
      ShExecInfo.nShow       =       SW_SHOW;   
      ShExecInfo.hInstApp       =       NULL;   
      ShellExecuteEx(&ShExecInfo);   
      WaitForSingleObject(ShExecInfo.hProcess,INFINITE);   
      或:       PROCESS_INFORMATION       ProcessInfo;     
      STARTUPINFO       StartupInfo;       //This       is       an       [in]       parameter   
      ZeroMemory(&StartupInfo,       sizeof(StartupInfo));   
      StartupInfo.cb       =       sizeof       StartupInfo       ;       //Only       compulsory       field   
      if(CreateProcess("c:\\winnt\\notepad.exe",       NULL,     
              NULL,NULL,FALSE,0,NULL,   
              NULL,&StartupInfo,&ProcessInfo))   
      {     
              WaitForSingleObject(ProcessInfo.hProcess,INFINITE);   
              CloseHandle(ProcessInfo.hThread);   
              CloseHandle(ProcessInfo.hProcess);   
      }       
      else   
      {   
              MessageBox("The       process       could       not       be       started...");   
      }   

      Q:       如何显示文件或文件夹的属性?       SHELLEXECUTEINFO       ShExecInfo       ={0};   
      ShExecInfo.cbSize       =       sizeof(SHELLEXECUTEINFO);   
      ShExecInfo.fMask       =       SEE_MASK_INVOKEIDLIST       ;   
      ShExecInfo.hwnd       =       NULL;   
      ShExecInfo.lpVerb       =       "properties";   
      ShExecInfo.lpFile       =       "c:\\";       //can       be       a       file       as       well   
      ShExecInfo.lpParameters       =       "";     
      ShExecInfo.lpDirectory       =       NULL;   
      ShExecInfo.nShow       =       SW_SHOW;   
      ShExecInfo.hInstApp       =       NULL;     
      ShellExecuteEx(&ShExecInfo);  




     附:一些windows的系统命令:


winver---------检查Windows版本
wmimgmt.msc----打开windows管理体系结构(WMI)
wupdmgr--------windows更新程序
wscript--------windows脚本宿主设置
write----------写字板
winmsd---------系统信息
wiaacmgr-------扫描仪和照相机向导
winchat--------XP自带局域网聊天


mem.exe--------显示内存使用情况
Msconfig.exe---系统配置实用程序
mplayer2-------简易widnows media player
mspaint--------画图板
mstsc----------远程桌面连接
mplayer2-------媒体播放机
magnify--------放大镜实用程序
mmc------------打开控制台
mobsync--------同步命令


dxdiag---------检查DirectX信息
drwtsn32------ 系统医生
devmgmt.msc--- 设备管理器
dfrg.msc-------磁盘碎片整理程序
diskmgmt.msc---磁盘管理实用程序
dcomcnfg-------打开系统组件服务
ddeshare-------打开DDE共享设置
dvdplay--------DVD播放器


net stop messenger-----停止信使服务
net start messenger----开始信使服务
notepad--------打开记事本
nslookup-------网络管理的工具向导
ntbackup-------系统备份和还原
narrator-------屏幕“讲述人”
ntmsmgr.msc----移动存储管理器
ntmsoprq.msc---移动存储管理员*作请求
netstat -an----(TC)命令检查接口


syncapp--------创建一个公文包
sysedit--------系统配置编辑器
sigverif-------文件签名验证程序
sndrec32-------录音机
shrpubw--------创建共享文件夹
secpol.msc-----本地安全策略
syskey---------系统加密,一旦加密就不能解开,保护windows xp系统的双重密码
services.msc---本地服务设置
Sndvol32-------音量控制程序
sfc.exe--------系统文件检查器
sfc /scannow---windows文件保护


tsshutdn-------60秒倒计时关机命令
tourstart------xp简介(安装完成后出现的漫游xp程序)
taskmgr--------任务管理器


eventvwr-------事件查看器
eudcedit-------造字程序
explorer-------打开资源管理器


packager-------对象包装程序
perfmon.msc----计算机性能监测程序
progman--------程序管理器


regedit.exe----注册表
rsop.msc-------组策略结果集
regedt32-------注册表编辑器
rononce -p ----15秒关机
regsvr32 /u *.dll----停止dll文件运行
regsvr32 /u zipfldr.dll------取消ZIP支持


cmd.exe--------CMD命令提示符
chkdsk.exe-----Chkdsk磁盘检查
certmgr.msc----证书管理实用程序
calc-----------启动计算器
charmap--------启动字符映射表
cliconfg-------SQL SERVER 客户端网络实用程序
Clipbrd--------剪贴板查看器
conf-----------启动netmeeting
compmgmt.msc---计算机管理
cleanmgr-------好东西整理
ciadv.msc------索引服务程序


osk------------打开屏幕键盘
odbcad32-------ODBC数据源管理器
oobe/msoobe /a----检查XP是否激活
lusrmgr.msc----本机用户和组
logoff---------注销命令


iexpress-------木马捆绑工具,系统自带


Nslookup-------IP地址侦测器


fsmgmt.msc-----共享文件夹管理器


utilman--------辅助工具管理器


gpedit.msc-----组策略
xp下运行命令大全.


$Systemroot$Documents and Settingsusername 目录下


appwiz.cpl------------添加删除程序


control userpasswords2--------用户帐户设置


cleanmgr-------垃圾整理


CMD--------------命令提示符可以当作是 Windows 的一个附件,Ping,Convert 这些不能在图形环境下使用的功能要借助它来完成。


cmd------jview察看Java虚拟机版本。


command.com------调用的则是系统内置的 NTVDM,一个 DOS虚拟机。它完全是一个类似 Virtual PC 的虚拟环境,和系统本身联系不大。当我们在命令提示符下运行 DOS 程序时,实际上也 是自动转移到 NTVDM虚拟机下,和 CMD 本身没什么关系。


calc-----------启动计算器


chkdsk.exe-----Chkdsk磁盘检查


compmgmt.msc---计算机管理


conf-----------启动 netmeeting


control userpasswords2-----User Account 权限设置


devmgmt.msc--- 设备管理器


diskmgmt.msc---磁盘管理实用程序


dfrg.msc-------磁盘碎片整理程序


drwtsn32------ 系统医生


dvdplay--------启动Media Player


dxdiag-----------DirectX Diagnostic Tool


gpedit.msc-------组策略编辑器


gpupdate /target:computer /force 强制刷新组策略


eventvwr.exe-----事件查看器


explorer-------打开资源管理器


logoff---------注销命令


lusrmgr.msc----本机用户和组


msinfo32---------系统信息


msconfig---------系统配置实用程序


net start (servicename)----启动该服务


net stop (servicename)-----停止该服务


notepad--------打开记事本


nusrmgr.cpl-------同control userpasswords,打开用户帐户控制面板


Nslookup-------IP地址侦测器


oobe/msoobe /a----检查XP是否激活


perfmon.msc----计算机性能监测程序


progman--------程序管理器


regedit----------注册表编辑器


regedt32-------注册表编辑器


regsvr32 /u *.dll----停止dll文件运行


route print------查看路由表


rononce -p ----15秒关机


rsop.msc-------组策略结果集


rundll32.exe rundll32.exe %Systemroot%System32shimgvw.dll,ImageView_Fullscreen----启动一个空白的Windows 图片和传真查看器


secpol.msc--------本地安全策略


services.msc---本地服务设置


sfc /scannow-----启动系统文件检查器


sndrec32-------录音机


taskmgr-----任务管理器(适用于2000/xp/2003)


tsshutdn-------60秒倒计时关机命令


winchat--------XP自带局域网聊天


winmsd---------系统信息


winver-----显示About Windows 窗口


Windows XP的关机是由Shutdown.exe程序来控制的,位于Windows\System32文件夹中。如果想让Windows 2000也实现同样的效果,可以把Shutdown.exe复制到系统目录下。


比如你的电脑要在22:00关机,可以选择“开始→运行”,输入“at 22:00 Shutdown -s”,这样,到了22点电脑就会出现“系统关机”对话框,默认有30秒钟的倒计时并提示你保存工作。如果你想以倒计时的方式关机,可以输入“Shutdown.exe -s -t 3600”,这里表示60 分钟后自动关机,“3600”代表60分钟。


设置好自动关机后,如果想取消的话,可以在运行中输入“shutdown -a”。另外输入“shutdown -i”,则可以打开设置自动关机对话框,对自动关机进行设置。


Shutdown.exe的参数,每个都具有特定的用途,执行每一个都会产生不同的效果,比如 “-s”就表示关闭本地计算机,“-a”表示取消关机操作,下面列出了更多参数, 可以在Shutdown.exe中按需使用。


-f:强行关闭应用程序


-m \\计算机名:控制远程计算机


-i:显示图形用户界面,但必须是Shutdown的第一个选项


-l:注销当前用户


-r:关机并重启


-t 时间:设置关机倒计时


-c "消息内容":输入关机对话框中的消息内容(不能超127个字符)


有时候,我们需要定时关闭计算机,下面介绍一个在Windows XP下实现定时关机的简单方法。


指定系统在22分钟后自动关闭:点击“开始→运行”,在“打开”中输入命令“Shutdown -s -t 1320”(注意:引号不输入,参数之间有空格,1320的单位是秒),单击“确定”
按钮即可。


指定系统在某个时间(比如12:00)自动关闭:在“打开”中输入命令
“at 12:00 Shutdown -s”即可。


取消定时关机:在“打开”中输入命令“Shutdown -a”即可。

usidc5 2010-10-13 23:17
 spin控件和edit控件如图中所圈。当点击微调按钮时,编辑框中的数据就会增加或减少。


     首先设置spin控件的属性auto buddy 和set Buddy integer 为true。同时保证spin控件的tab顺序紧挨着edit控件的tab顺序,即spin控件的tab顺序为edit控件的tab顺序加1。


     添加变量。为edit 控件添加int类型变量m_edit,为spin控件添加CSpinButtonCtrl类型变量m_spin


     在OnInitalDialog中对spin控件进行必要的设置。


     m_spin.SetRange(0,100);


     m_spin.SetBuddy(GetDlgItem(IDC_EDIT1));


设置tab order 的方法是 ctrl+d ,然后用鼠标按个点击选择,就是按TAB键是焦点在窗体上的移动顺序

usidc5 2010-11-07 20:03
ON_NOTIFY不反射消息.如果自己处理不了,就传给上级窗口,如果再处理不了,在往上传.实在处理不了,由框架默认处理.

ON_NOTIFY_REFLECT 反射消息.把消息传给上级窗口处理,如果上级都处理不了,再反射回来,自己处理.

这就是MFC强大的消息反射机制.

usidc5 2010-11-07 20:16

CObject
 └CCmdTarget
    └CWnd
       └CListBox

CListBox类提供Windows列表框的功能。列表框显示项的列表,如用户可以见到和选择的文件名称。
在单选列表框里,用户只可选择一个项。在多选列表框里,可选择许多项。当用户选择某项时,其高亮显示且列表框给父窗口发送一个通知消息。
可从对话模板或直接在你的代码中创建列表框。直接创建时,构造CListBox对象,再调用Create成员函数创建Windows列表框控件并将其附加给CListBox对象。要在对话模板中使用列表框,可在对话框类中声明一个CListbox 变量,再在对话框类的DoDataExchange中使用DDX_Control连接成员变量到此控件(当向对话框类中添加控件变量时,ClassWizard自动为你实现)。
构造函数可以是从CListBox派生的类的一个单步进程。为派生类写构造函数并从中调用Create。
如果要处理由列表框发送到其父亲(通常为从CDialog派生的类)的Windows通知消息,为每个消息添加消息映射入口和消息处理成员函数到父类。
每个消息映射入口有以下形式:
ON_Notification( id,memberFxn )
id 指定发送通知的列表框控件的子窗口,memberFxn 是编写处理通知的父成员函数名的地方。
父函数原型如下:
afx_msg void memberFxn( );
下面是可能的消息映射入口列表和描述它们可能被发送到父亲的情况:
ON_LBN_DBLCLK 用户双击列表框中的字符串。只有LBS_NOTIFY风格的列表框才会发送此通知消息。
ON_LBN_ERRSPACE 列表框不能重新分配足够的内存来满足请求。
ON_LBN_KILLFOCUS 列表框正失去输入焦点。
ON_LBN_SELCANCEL 当前列表框选择被取消。此消息只有在列表框是LBS_NOTIFY风格时才发送。
ON_LBN_SELCHANGE 列表框中的选择可能改变。如果选择被CListBox::SetCurSel成员函数改变,则通知不发送。此通知只适用于LBS_NOTIFY风格的列表框。无论何时用户按下箭头键,即使选择未改变,LBN_SELCHANGE通知消息都被发送给多选列表框。
ON_LBN_SETFOCUS 列表框正在接收输入焦点。
ON_WM_CHARTOITEM 一个无字符串的自绘制列表接受WM_CHAR消息。
ON_WM_VKEYTOITEM LBS_WANTKEYBOARDINPUT风格的列表框接受WM_KEYDOWN消息。
如果在对话框中构造一个CListBox对象(通过对话资源),当用户关闭对话框时,CListBox对象自动毁弃。
如果在窗口中构造一个CListBox对象,可能需要毁弃CListBox对象。如果在栈上创建CListBox对象,它会自动毁弃。如果使用new函数在堆上创建CListBox对象,必须对此对象调用delete来在用户关闭父窗口时毁弃它。
如果在CListBox对象中分配内存,可覆盖CListBox析构程序释放分配的内存。
#include


CListBox类的成员

构造函数
CListBox 构造一个CListBox对象

初始化
Create 创建Windows列表框并附加给CListBox对象
InitStorage 为列表框的项和字符串预分配内存块

一般操作
GetCount 返回列表框中的字符串数目
GetHorizontalExtent 返回列表框的水平宽度,用像素表示
SetHorizontalExtent 设置列表框的水平宽度,用像素表示
GetTopIndex 返回列表框中第一个可见字符串的索引
SetTopIndex 设置列表框中第一个可见字符串的基于零的索引
GetItemData 返回与列表框有关的32位值
GetItemDataPtr 返回指向列表框的指针
SetItemData 设置列表框有关的32位值
SetItemDataPtr 设置指向列表框的指针
GetItemRect 返回当前显示的列表框项的相应矩形
ItemFromPoint 返回与某点最近的列表框项的索引
SetItemHeight 设置列表框中项的高度
GetItemHeight 确定列表框中项的高度
GetSel 返回列表框某项的选择
GetText 拷贝某列表框项到缓冲区
GetTextLen 返回列表框的字节长
SetColumnWidth 设置多列列表框的列宽
SetTabStops 设置列表框制表键停止位置
GetLocale 获取列表框的地点标识符
SetLocale 设置列表框的地点标识符

单选操作
GetCurSel 返回列表框中当前选择串的基于零的索引
SetCurSel 选择一个列表框字符串

多选操作
SetSel 在多选列表框中选择或不选某个列表框项
GetCaretIndex 确定在多选列表框中有焦点矩形的项的索引
SetCaretIndex 设置焦点矩形到多选列表框中的指定的索引项
GetSelCount 返回多选列表框中当前选择的字符串的数目
GetSelItems 返回列表框中当前选择的字符串的索引
SelItemRange 选择/不选多选列表框中的一些字符串
SetAnchorIndex 设置多选列表框的锚点以开始扩展选择
GetAnchorIndex 获取列表框当前锚点项的基于零的索引

字符串操作
AddString 添加一个字符串到列表框中
DeleteString 从列表框中删除一个字符串
InsertString 在列表框中指定位置插入一个字符串
ResetContent 清空列表框所有入口
Dir 从当前目录添加文件名称到列表框中
FindString 在列表框中查找一个字符串
FindStringExact 查找与指定的字符串匹配的第一个列表框字符串
SelectString 查找并选择单选列表框中的一个字符串

可覆盖的函数
DrawItem 当自绘制列表框的一个可视部分改变时,被框架调用
MeasureItem 当自绘制列表框创建时,被框架调用来确定列表框维数
CompareItem 被框架调用以确定一系列列表框中某新项的位置
DeleteItem 当用户从自绘制列表框中删除某项时,被框架调用
VKeyToItem 覆盖以提供LBS_WANTKEYBOARDINPUT风格列表框的设置所需的定制WM_KEYDOWN
CharToItem 覆盖以提供不含字符串的自绘制列表框定制WM_CHAR

usidc5 2010-11-07 20:17


ListBox窗口用来列出一系列的文本,每条文本占一行。创建一个列表窗口可以使用成员函数:


BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );


其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对列表控件指明专门的风格。
LBS_MULTIPLESEL 指明列表框可以同时选择多行
LBS_EXTENDEDSEL 可以通过按下Shift/Ctrl键选择多行
LBS_SORT 所有的行按照字母顺序进行排序


在列表框生成后需要向其中加入或是删除行,可以利用:
int AddString( LPCTSTR lpszItem )添加行,
int DeleteString( UINT nIndex )删除指定行,
int InsertString( int nIndex, LPCTSTR lpszItem )将行插入到指定位置。
void ResetContent( )可以删除列表框中所有行。
通过调用int GetCount( )得到当前列表框中行的数量。


如果需要得到/设置当前被选中的行,可以调用int GetCurSel( )/int SetCurSel(int iIndex)。如果你指明了选择多行的风格,你就需要先调用int GetSelCount( )得到被选中的行的数量,然后int GetSelItems( int nMaxItems, LPINT rgIndex )得到所有选中的行,参数rgIndex为存放被选中行的数组。通过调用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框内指定行的字符串。


此外通过调用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在当前所有行中查找指定的字符传的位置,nStartAfter指明从那一行开始进行查找。


int SelectString( int nStartAfter, LPCTSTR lpszItem )可以选中包含指定字符串的行。


在MFC 4.2版本中添加了CCheckListBox类,该类是由CListBox派生并拥有CListBox的所有功能,不同的是可以在每行前加上一个检查框。必须注意的是在创建时必须指明LBS_OWNERDRAWFIXED或LBS_OWNERDRAWVARIABLE风格。


通过void SetCheckStyle( UINT nStyle )/UINT GetCheckStyle( )可以设置/得到检查框的风格,关于检查框风格可以参考4.1 Button中介绍。通过void SetCheck( int nIndex, int nCheck )/int GetCheck( int nIndex )可以设置和得到某行的检查状态,关于检查框状态可以参考4.1 Button中介绍。


最后介绍一下列表框几种常用的消息映射宏:


ON_LBN_DBLCLK 鼠标双击
ON_EN_ERRSPACE 输入框无法分配内存时产生
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在输入框失去/得到输入焦点时产生
ON_LBN_SELCHANGE 选择的行发生改变
使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用列表框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。




GetDlgItem(IDC_LIST1)->EnableWindow(FALSE)提供控件的id.获得指向控件的指针
是CWnd的成员函数
->ShowWindow(SW_SHOW)
->ShowWindow(SW_HIDE)显示或隐藏窗口,也是CWnd的成员函数
CListBox::AddString()函数可以向列表框中添加项目
事例程序:
void COptionListDlg::OnAddButton()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
if(m_edit==""){
MessageBox("请输入添加的数据");
return;
}
switch(m_radio){
case 0:
m_listbox1.AddString(m_edit);
break;
case 1:
m_listbox2.AddString(m_edit);
break;
case 2:
m_listbox3.AddString(m_edit);
break;
}
UpdateData(FALSE);
}
CListBox::GetCurSel()返回当前列表框选择项目的序号
CListBox::GetText()函数可以根据当前列表框的序号读取当前选择的项目
程序:
void COptionListDlg::OnSelchangeList1()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
int index = m_listbox1.GetCurSel();
if(index == LB_ERR)
m_edit="";
else
m_listbox1.GetText(index,m_edit);
UpdateData(FALSE);
}


m_listbox1.DeleteString()
m_listbox1.InsertString()
程序:
void COptionListDlg::OnEditButton()
{
// TODO: Add your control notification handler code here
int index;
UpdateData(TRUE);
if(m_edit == ""){
MessageBox("请输入修改项目");
return;
}


switch(m_radio){
case 0:
index = m_listbox1.GetCurSel();
if(index == LB_ERR){
MessageBox("请选择要修改的项目");
return;
}
m_listbox1.DeleteString(index);
m_listbox1.InsertString(index,m_edit);
break;


case 1:
index = m_listbox2.GetCurSel();
if(index == LB_ERR){
MessageBox("请选择要修改的项目");
return;
}
m_listbox2.DeleteString(index);
m_listbox2.InsertString(index,m_edit);
break;


case 2:
index = m_listbox3.GetCurSel();
if(index == LB_ERR){
MessageBox("请选择要修改的项目");
return;
}
m_listbox3.DeleteString(index);
m_listbox3.InsertString(index,m_edit);
break;
}
}


usidc5 2010-11-11 18:10
我有一个类 CImageSortListCtrl (父类是CListCtrl
在MESSAGE_MAP里面有: 
ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag) 
目的是CImageSortListCtrl自己处理一些控件内部的拖动操作。 
但现在需要重用这个控件,我在CDialog中使用它,想使用 
ON_NOTIFY(LVN_BEGINDRAG, IDC_LST_IMAGE, OnBeginDragLstImage)来映射函数OnBeginDragLstImage,以使其支持控件之间的拖动操作。IDC_LST_IMAGE是一个CListCtrl的资源号。 
但我的ON_NOTIFY得不到响应,查阅MSDN TN062:消息反射觉得很含糊。 

如下: 
如果你在控件的父窗口类中为一个或一定范围特定的消息提供了一个处理函数,对于同样的消息,如果在您的处理中您并没有调用其基类的处理函数它就会覆盖掉被反射的消息处理。例如,如果你在一个对话框类中试图处理 WM_CTLCOLOR,您的处理将覆盖掉任何被反射的消息处理函数。 
If you have supplied a handler for a specific message, or for a range of messages, in your parent window''''s class, it will override reflected message handlers for the same message provided you don''''t call the base class handler function in your own handler. For example, if you handle WM_CTLCOLOR in your dialog box class, your handling will override any reflected message handlers. 

如果你在父窗口中为一个或一系列一定范围的特定的 WM_NOTIFY 消息提供一个处理函数,您的处理函数只有当这些发送消息的子控件通过ON_NOTIFY_REFLECT() 宏就不会有一个被反射消息处理了。如果该处理返回 TRUE,消息就也会给父窗口来处理,而如果它返回的是一个 FALSE值,就不会让父窗口来处理该消息。请注意:被反射的消息是在通知消息之前被处理。 

If, in your parent window class, you supply a handler for a specific WM_NOTIFY message or a range of WM_NOTIFY messages, your handler will be called only if the child control sending those messages does not have a reflected message handler through ON_NOTIFY_REFLECT(). If you use ON_NOTIFY_REFLECT_EX() in your message map, your message handler may or may not allow the parent window to handle the message. If the handler returns TRUE, the message will be handled by the parent as well, while a call that returns FALSE does not allow the parent to handle it. Note that the reflected message is handled before the notification message. 

当一个 WM_NOTIFY 消息被发送后,控件就得到了第一次的机会来处理它。如果任何其他的被反射消息被发送,父窗口会有第一个机会来处理之,而控件将能接收被反射的消息。为了达到这样的目的,在控件类消息映射中需要一个处理函数和一个合适的入口。 
When a WM_NOTIFY message is sent, the control is offered the first chance to handle it. If any other reflected message is sent, the parent window has the first chance to handle it and the control will receive the reflected message. To do so, it will need a handler function and an appropriate entry in the control''''s class message map.

usidc5 2010-11-12 21:39
译者:徐景周(原作:Mustafa   Demirhan) 

为了最大限度的发挥属性页的效用,首先让我们先从   CPropertySheet   继承一个新类,取名为   CMyPropSheet.
接着便可以进行下面的各种操作: 

  一、隐藏属性页默认按钮   
隐藏掉Apply应用按钮:   

propsheet.m_psh.dwFlags   |=   PSH_NOAPPLYNOW; 
或隐藏掉Cancel取消按钮:CWnd   *pWnd   =   GetDlgItem(   IDCANCEL   ); 
pWnd-> ShowWindow(   FALSE   ); 

  二、移动属性页按钮 
首先,要获取按钮的句柄,然后就可以象对待窗体一样处理它们了.   下面代码先隐藏掉Apply和Help铵钮,再把OK和Cancel按移动到右侧。   BOOL   CMyPropSheet::OnInitDialog   ()   

        BOOL   bResult   =   CPropertySheet::OnInitDialog(); 

        int   ids   []   =   {IDOK,   IDCANCEL};//,   ID_APPLY_NOW,   IDHELP   }; 
        
        //   Hide   Apply   and   Help   buttons 
        CWnd   *pWnd   =   GetDlgItem   (ID_APPLY_NOW); 
        pWnd-> ShowWindow   (FALSE); 
        pWnd   =   GetDlgItem   (IDHELP); 
        pWnd-> ShowWindow   (FALSE); 
        
        CRect   rectBtn; 
        int   nSpacing   =   6;                 //   space   between   two   buttons... 

        for(   int   i   =0;   i   <   sizeof(ids)/sizeof(int);   i++) 
        { 
                GetDlgItem   (ids   )-> GetWindowRect   (rectBtn); 
                
                ScreenToClient   (&rectBtn); 
                int   btnWidth   =   rectBtn.Width(); 
                rectBtn.left   =   rectBtn.left   +   (btnWidth   +   nSpacing)*   2; 
                rectBtn.right   =   rectBtn.right   +   (btnWidth   +   nSpacing)*   2; 

                GetDlgItem   (ids   )-> MoveWindow(rectBtn); 
        } 

        
        return   bResult; 



下面代码移动所有按钮到右侧,并且重新置属性页为合适的大小.BOOL   CMyPropSheet::OnInitDialog   ()   

        BOOL   bResult   =   CPropertySheet::OnInitDialog(); 

        
        int   ids[]   =   {   IDOK,   IDCANCEL,   ID_APPLY_NOW   }; 
        
        CRect   rectWnd; 
        CRect   rectBtn; 
        
        GetWindowRect   (rectWnd); 
        GetDlgItem   (IDOK)-> GetWindowRect   (rectBtn); 
        
        int   btnWidth   =   rectBtn.Width(); 
        int   btnHeight   =   rectBtn.Height(); 
        int   btnOffset   =   rectWnd.bottom   -   rectBtn.bottom; 
        int   btnLeft   =   rectWnd.right   -   rectWnd.left; 

        rectWnd.bottom   =   rectBtn.top; 
        rectWnd.right   =   rectWnd.right   +   btnWidth   +   btnOffset; 
        MoveWindow(rectWnd); 
        
        rectBtn.left   =   btnLeft; 
        rectBtn.right   =   btnLeft   +   btnWidth; 

        for   (int   i   =   0;   i   <   sizeof   (ids)   /   sizeof   (int);   i++) 
        { 
                rectBtn.top   =   (i   +   1)   *   btnOffset   +   btnHeight   *   i; 
                rectBtn.bottom   =   rectBtn.top   +   btnHeight; 
                GetDlgItem   (ids   )-> MoveWindow   (rectBtn); 
        } 
        
        return   bResult; 



  三、改变属性页上的标签文字 
首先修改TC_ITEM结构,然后用   SetItem   来修改标签文字,如下代码:TC_ITEM   item; 
item.mask   =   TCIF_TEXT; 
item.pszText   =   "New   Label "; 

//Change   the   label   of   the   first   tab   (0   is   the   index   of   the   first   tab)... 
GetTabControl   ()-> SetItem   (0,   &item); 

  四、改变属性页标签文字的字体属性 
代码如下m_NewFont.CreateFont   (14,   0,   0,   0,   800,   TRUE,   0,   0,   1,   0,   0,   0,   0,   _T( "Arial ")   ); 
        GetTabControl()-> SetFont   (&m_NewFont); 

  五、在属性页标签上显示位图   
可以用   CImageList   建立图像.   用   SetItem   来设置,如下代码所示:BOOL   CMyPropSheet::OnInitDialog   () 

        BOOL   bResult   =   CPropertySheet::OnInitDialog(); 

        m_imageList.Create   (IDB_MYIMAGES,   13,   1,   RGB(255,255,255)); 
        CTabCtrl   *pTabCtrl   =   GetTabControl   (); 
        pTabCtrl-> SetImageList   (&m_imageList); 
        
        TC_ITEM   item; 
        item.mask   =   TCIF_IMAGE; 
        for   (int   i   =   0;   i   <   NUMBER_OF_TABS;   i++) 
        { 
                item.iImage   =   i; 
                pTabCtrl-> SetItem   (i,   &item   ); 
        } 

        return   bResult; 



  六、在属性页左下角显示位图 
如下代码所示:   void   CMyPropSheet::OnPaint   ()   

        CPaintDC   dc(this);   //   device   context   for   painting 
        
        int   nOffset   =   6; 
        //   load   IDB_BITMAP1   from   our   resources 
        CBitmap   bmp; 
        if   (bmp.LoadBitmap   (IDB_BITMAP1)) 
        { 
                //   Get   the   size   of   the   bitmap 
                BITMAP   bmpInfo; 
                bmp.GetBitmap   (&bmpInfo); 
                
                //   Create   an   in-memory   DC   compatible   with   the 
                //   display   DC   we ' 're   using   to   paint 
                CDC   dcMemory; 
                dcMemory.CreateCompatibleDC   (&dc); 
                
                //   Select   the   bitmap   into   the   in-memory   DC 
                CBitmap*   pOldBitmap   =   dcMemory.SelectObject   (&bmp); 
                
                //   Find   a   bottom-left   point   for   the   bitmap   in   the   client   area 
                CRect   rect; 
                GetClientRect   (&rect); 
                int   nX   =   rect.left   +   nOffset; 
                int   nY   =   rect.top   +   (rect.Height   ()   -   bmpInfo.bmHeight)   -   nOffset;
                
                //   Copy   the   bits   from   the   in-memory   DC   into   the   on- 
                //   screen   DC   to   actually   do   the   painting.   Use   the   centerpoint 
                //   we   computed   for   the   target   offset. 
                dc.BitBlt   (nX,   nY,   bmpInfo.bmWidth,   bmpInfo.bmHeight,   &dcMemory,   
                        0,   0,   SRCCOPY); 
                
                dcMemory.SelectObject   (pOldBitmap); 
        } 

        //   Do   not   call   CPropertySheet::OnPaint()   for   painting   messages 



  七、在属性页右下角显示3D文字Logo 
代码如下:void   CMyPropSheet::OnPaint   ()   

        ///////////////////////////////////////////////////////////////////////// 
//在TAB按钮旁边显示3D文字提示,jingzhou   xu 
Cstring   m_LogoName   =   “属性页”; 
//    if(m_LogoName   ==   " ") 
//     return; 

GetWindowRect(rect); 
ScreenToClient(rect); 

LOGFONT   logFont; 
ZeroMemory((void*)&logFont,sizeof(logFont)); 
strcpy(logFont.lfFaceName, "宋体 "); 
logFont.lfHeight   =   -12; 
logFont.lfWeight   =   400; 
logFont.lfCharSet   =   GB2312_CHARSET; 
logFont.lfOutPrecision   =   3; 
logFont.lfClipPrecision   =   2;   
logFont.lfQuality   =   1; 
logFont.lfPitchAndFamily   =   2; 
m_font.CreateFontIndirect(&logFont); 
SetFont(&m_font); 
CFont    *pOldFont   =   pDC-> SelectObject(&m_font); 

rect.left   +=   6; 
rect.right   -=   6; 
rect.bottom   -=   1; 
rect.top   =   rect.bottom   -   ITEMBUTTON_HEIGHT   +   1; 


CFont   m_LogoFont; 
CString   sLogoString; 

m_LogoFont.CreateFont(rect.Height()*4/5,   0,   0,   0,   FW_BOLD,   1,   FALSE,   FALSE, 
DEFAULT_CHARSET,   OUT_DEFAULT_PRECIS,   CLIP_DEFAULT_PRECIS,   DEFAULT_QUALITY, 
FIXED_PITCH   |   FF_ROMAN,   "楷体_GB2312 "); 

sLogoString   =   m_LogoName; 

RECT   m_rDataBox; 
CopyRect(&m_rDataBox,&rect); 

TEXTMETRIC   tm; 
pDC-> GetTextMetrics(&tm); 
CFont*   oldFont   =   pDC-> SelectObject(&m_LogoFont); 
CSize   sz   =   pDC-> GetTextExtent(sLogoString,   sLogoString.GetLength()); 
//用GetTextExtent来计算字体logo大小,依靠于设备环境,使用logo位于右下角 
m_rDataBox.left   =   m_rDataBox.right     -   sz.cx   -   tm.tmAveCharWidth/2; 
m_rDataBox.top     =   m_rDataBox.bottom   -   sz.cy   -   tm.tmHeight/5; 
pDC-> SetBkMode(TRANSPARENT); 
//用3D字体显示,先黑后白,最后再用默认色 
COLORREF   oldColor   =   pDC-> SetTextColor(GetSysColor(COLOR_3DDKSHADOW)); 
pDC-> DrawText(sLogoString,   sLogoString.GetLength(),   &m_rDataBox,   DT_VCENTER   |   DT_SINGLELINE   |   DT_CENTER); 
m_rDataBox.left   -=   tm.tmAveCharWidth; 
pDC-> SetTextColor(GetSysColor(COLOR_3DHILIGHT)); 
pDC-> DrawText(sLogoString,   sLogoString.GetLength(),   &m_rDataBox,   DT_VCENTER   |   DT_SINGLELINE   |   DT_CENTER); 
m_rDataBox.left   +=   3*tm.tmAveCharWidth/5; 
pDC-> SetTextColor(RGB(0,0,255)); 
pDC-> DrawText(sLogoString,   sLogoString.GetLength(),   &m_rDataBox,   DT_VCENTER   |   DT_SINGLELINE   |   DT_CENTER); 

//释放资源 
pDC-> SelectObject(oldFont); 
pDC-> SetTextColor(oldColor);       
m_LogoFont.DeleteObject(); 
///////////////////////////////////////////////////////////////////////// 


  八、在属性页中动态加入其它控件 
下面演示如何在左下角加入一Edit控件: 
MyPropSheet.h中:public: 
        CEdit   m_edit; 

MyPropSheet.cpp中:BOOL   CMyPropSheet::OnInitDialog   () 

        BOOL   bResult   =   CPropertySheet::OnInitDialog   (); 

        
        CRect   rect; 
        
        int   nHeight   =   24; 
        int   nWidth   =   120; 
        int   nOffset   =   6; 
        
        GetClientRect   (&rect); 

        //   Find   a   bottom-left   point   for   the   edit   control   in   the   client   area 
        int   nX   =   rect.left   +   nOffset; 
        int   nY   =   rect.top   +   (rect.Height()   -   nHeight)   -   nOffset; 
        
        //   finally   create   the   edit   control 
        m_Edit.CreateEx   (WS_EX_CLIENTEDGE,   _T( "EDIT "),   NULL, 
                                          WS_CHILD   |   WS_VISIBLE   |   WS_TABSTOP   |   WS_BORDER,   
                nX,   nY,   nWidth,   nHeight,   m_hWnd,   0,   0   ); 

        return   bResult; 

usidc5 2010-11-19 21:50

属性表 CPropertySheet
  属性表是一个允许用户去查看和编辑项目的属性的窗口。例如,一个电子表格程序可能使用属性表去让用户设置字体和表格的边框属性,及设置设置的属性,例如一个磁盘驱动器,打印机或鼠标。 
  关于属性表 
  使用属性表 
  在Internet Explorer中属性表的更新 
  属性表手册 
  关于属性表 
  这个文档假设你已经十分的理解了对话框模板及对话框程序。如果不是这样,在继续下边的章节前你应该读一下平台SDK(Platform SDK)中的“对话框”(Dialog Boxes)。 
  要在你的应用程序中实现属性表,在你的项目中包含Prsht.h头文件。Prsht.h中包含了所有被属性表使用的标识符。 
  一个属性表包含的一个或多个层叠的窗口叫做页(pages),各自包含着一组相关属性的设置窗口。例如,一个页可以包含设置项目(item)字体的类型风格,大小,颜色等属性的控制。每页有一个标签(tab),用户可以使用它选择页,使用它移到属性表中的最显著的位置。下面的图解显示了一个查看和设置软盘驱动器属性的属性表。 
   
  属性表对话框 
  一个属性表和页实际上包含在对话框中。属性表是一个系统定义的对话框,它管理页及为它们提供一个公共容器。属性表对话框可以是模式的或非模式的。它包括一个框架,一个标题栏和四个按钮:确定(OK),取消(Cancel),应用(Apply Now)和帮助(Help)。(帮助(Help)按钮可能是被隐藏着的,象上面的图解)当用户单击了按钮时,对话框过程为页接收通知消息。 
  属性表中的每一页是应用程序定义的非模式对话在,它管理控制窗口被使用来查看和编辑项目(item)的属性。你提供对话框模板去建立每一页,就像对话框程序一样。它管理控制及设置相应项目(item)的属性。 
  当这页(page)获得或失去焦点时及当单击了确定(OK),取消(Cancel),应用(Apply Now)或帮助(Help)按钮时,属性表为这页发送通知消息到对话框过程。这个通知以 WM_NOTIFY 消息的形式发送。 lParam 参数是 NMHDR 结构的地址,包含了指向属性表对话框的窗口的句柄。 
  一些通知消息需要一个页(page)去返回TRUE或FALSE两者之一来响应 WM_NOTIFY 消息。要这么干,这个页(page)必须使用
  SetWindowLong 函数去为页对话框设置DWL_MSGRESULT值为TURE或FALSE之一。 
  页(Pages ) 
  一个属性表必须至少包含一个页(page),但它包含的页不能超过MAXPROPPAGES定义的值(定义在Win32头文件中)。每一页都有一个以0为开始的索引,这是属性表以添加的先后顺序分配的。这些索引被消息使用,发送到属性页。 
  一个属性页可以包含一个嵌套的对话框。如果这样做了,你必须为顶层对话框(top-level dialog box)包含WS_EX_CONTROLPARENT风格,并调用带有父对话框句柄的
  IsDialogMessage 函数。这确保了用户在嵌套对话框中可以使用记忆术和对话框导航键去移动焦点到控件 
  每一个页都有一个相应的图标(icon)和标签(label)。属性表为每一页建立了一个标签卡(tab),在这个标签卡(tab)中显示图标和标签(label)。所有属性表页被期望使用非粗体字体。要确保字体不是粗体,在对话框模板中指定DS_3DLOOK风格。 
  不要为每一页的对话框过程调用
  EndDialog 函数。这样做了将消毁整个属性表,不单单是这一页。 
  最小的属性页是212对话单位宽114对话单位高。如果一个页对话框比这小,那么这个页将被放大到最小属性页的大小。Prsht.h头文件包含了属性表页的默认的三个设置。PROP_SM_CXDLG和PROP_SM_CYDLG定义了被推荐的最小属性表页的尺寸。PROP_MED_CXDLG和PROP_MED_CYDLG定义了被推荐的中等大小属性表面页的尺寸。PROP_LG_CXDLG和PROP_LG_CYDLG定义了被推荐的最大属性表页的尺寸。Prsht.h也包含了属性表向导页的推荐尺寸。这些尺寸的说明参见 Wizard Property Sheets 。使用这些推荐尺寸将帮助你可视的连接你的应用程序和其它的微软Windows应用程序。 
  使用下列的值去设置你的属性表页的基础大小: 
   
  
  PROP_SM_CXDLG 
  小型属性表页的宽,对话单位 
  PROP_SM_CYDLG 
  小型属性表页的高,对话单位 
  PROP_MED_CXDLG 
  中型属性表页的宽,对话单位 
  PROP_MED_CYDLG 
  中型属性表页的高,对话单位 
  PROP_LG_CXDLG 
  大型属性表页的宽,对话单位 
  PROP_LG_CYDLG 
  大型属性表页的高,对话单位 
  属性表创造 
  在创造一个属性表前,你必须定义一个或多个页(pages)。这包含填充一个 PROPSHEETPAGE 结构(使用页图标,标签(label),对话框模板,对话框过程等相关信息填充),此结构用于调用 CreatePropertySheetPage 函数。这个函数返回指向HPROPSHEETPAGE类型的句柄,这是这页唯一的标识符。 
  要新建一个属性表,你在调用 PropertySheet 函数中指定一个 PROPSHEETHEADER 结构的地址。这个函数为属性表定义了图标和标题(title),也包含了HPROPSHEETPAGE句柄数组的地址。当 PropertySheet 新建属性表时,它包含了页在数组中的识别。这页在属性表中以在数组中包含的次序来显示。 
  另外新建一个属性表的途径是指定一个 PROPSHEETPAGE 结构的数组来代替HPROPSHEETPAGE句柄的数组。既然这样, PropertySheet 在添加页到属性表前为它们创造句柄。 
  当一个页(page)被建立时,对话框过程为页(page)接收一个WM_INITDIALOG消息。这个消息的 lParam 参数是被新建页的 PROPSHEETPAGE 结构的地址。对话框可以在结构或保存这些信息,在稍后使用它来修改这个页。 
  PropertySheet 自动设置属性表的大小和初始位置。当属性表被新建时,这个位置基于所有者窗口的位置,这个大小基于页的数组指定的大小。如果你想让页与属性表底部的四个按钮的宽相匹配,设置页宽为190对话单位。 
  添加和移去页 
  新建一个属性表后,一个应用程序可以使用 PSM_ADDPAGE 消息添加一个页。注意属性表的大小在它被建立后不能改变,因此新的页必须比属性表中当前的页要小。 
  一个应用程序使用 PSM_REMOVEPAGE 消息去移去一个页。当你定义了一个页,你可以指定一个 PropSheetPageProc 回调函数的地址来让属性表(当它正在新建或移去这个页时)调用。使用 PropSheetPageProc 给你一个机会去执行初始化和消除个别页的操作。 
  当一个属性表被消毁时,它自动消毁已经添加的所有页。从建立页时指定使用的数组中倒序取出页进行消毁。 要消毁一个通过 CreatePropertySheetPage 函数新建的但没有添加到属性表中的页,使用 DestroyPropertySheetPage 函数。 
  属性表标题 (Title)和页标签(Labels ) 
  你在 PROPSHEETHEADER 结构中指定一个属性表的标题(title)去新建属性表。如果 dwFlags 成员包含PSH_PROPTITLE值,属性表添加“Properties for”的前缀到指定的标题字符串。你可以在属性表新建完成后通过 PSM_SETTITLE 消息来改变这个标题。 
  默认,一个属性表使用在对话框模板中指定的名字字符串作为页的标签。你可以通过在 PROPSHEETPAGE 结构的 dwFlags 成员中包含PSP_USETITLE值来不考虑名字字符串。 pszTitle 成员必须包含页标签字符串的地址。 
  页激活 
  一个属性表在同一时间只拥有一个活动页。这个页是活动的在层叠页的最前边。用户通过选择它的标签卡(tab)来激活一页;一个应用程序通过使用 PSM_SETCURSEL 消息激活一个页。 
  属性表发送 PSN_KILLACTIVE 通知消息让页失去焦点。响应消息,确认用户对这页的改变。如果这页在失去焦点之前需要附加的用户输入,它应该使用
  SetWindowLong 函数去设置这页的DWL_MSGRESULT值为真(TRUE)。同样,这页应该显示一个消息框来描述问题及提供被推荐的动作。当它同意失去焦点时,这页应该设置DWL_MSGRESULT为假(FALSE) 
  在这页获得焦点可视以前,属性表发送 PSN_SETACTIVE 通知消息去这页。这页应该通过初始化它的控制窗口去响应。 
  帮助按钮 
  当一个页被激活时,属性表通过检查PSP_HASHELP风格来确定是否为这页打开(enable)或禁止(disable)帮助(Help)按钮。如果这页拥有这个风格,它将支持帮助(Help)按钮。如果PSP_HASHELP风格不存在,这个按钮将是禁止的。 
  当用户单击了帮助(Help)按钮,激活页接收 PSN_HELP 通知消息。这页应该通过显示帮助信息来响应,代表性的做法是调用 WinHelp 函数。 
  确定(OK),取消(Cancel)和应用(Apply Now)按钮 
  确定(OK)和应用(Apply Now)按钮是类似的;都是引导属性页去验证和应用用户对属性的修改。仅仅不同的是单击了确定(OK)按钮将在改变被应用后消毁属性表。 
  当用户单击了确定(OK)或应用(Apply Now)按钮时,属性表发送 PSN_KILLACTIVE 通过消息到活动页,给它一个机会去验证用户的修改。如果这页决定修改是有效的,它应该调用
  SetWindowLong 函数去为这页设置DWL_MSGRESULT值为假(FALSE)。既然这样,属性表发送 PSN_APPLY 通知消息到每一页,指导它们去应用新的属性到相应的项目(item)。如果这页决定用户的修改是无效的,它应该设置DWL_MSGRESULT为真(TRUE),并显示一个对话框问题信息给用户。这页的剩余物至到它在响应 PSN_KILLACTIVE 消息中设置DWL_MSGRESULT为假(FALSE)为止。一个应用程序可以使用 PSM_APPLY 消息来模拟选择了应用(Apply Now)按钮。 
  当一个页变成活动时,应用(Apply Now)按钮开始是无效的,标志着没有任何属性改变要去应用。当这页从它的控件中收到输入,标志着用户已经编辑了一个属性,这页应该发送 PSM_CHANGED 消息去属性表。这个消息引起属性表去激活应用(Apply Now)按钮。如果用户随后单击了应用(Apply Now)或取消(Cancel)按钮,这页应该重新初始化它的控件,并发送 PSM_UNCHANGED 消息重新禁止应用(Apply Now)按钮。 
  有时应用(Apply Now)按钮引起一个页变成一个属性表,并且改变是不可逆的。当这发生时,这页应该发送 PSM_CANCELTOCLOSE 消息去属性表。这个消息引起属性表改变确定(OK)按钮上的文本为“关闭(Close)”,这标志着应用的改变是不可以取消的。 
  有时一个页改变了系统的配置,需要Windows去重启动或在改变生效前需要重新启动。当发生这样的改变后,一个页应该发送 PSM_RESTARTWINDOWS 消息或 PSM_REBOOTSYSTEM 消息去属性表。这些消息引起属性表在消毁后, PropertySheet 函数返回ID_PSRESTARTWINDOWS或ID_PSREBOOTSYSTEM值。 
  当用户单击了取消按钮时,表发送 PSN_RESET 通知消息到所有的页,这标志着属性表马上就要被消毁了。一个页应该使用通知去执行消除操作。 
  向导属性表 
  你可以新建一个叫做向导( wizard )的特别类型的属性表,这由一组有次序的对话框组成,它指导用户有次序的一步一步的进行操作,这很象设置一个设置或新建一张生日。在向导属性表中,页没有标签卡(tab),并且在同一时间只有一个页是可视的。同样,一个向导属性表用上一步(Back)按钮,下一步(Next)或完成(Finish)按钮替代了确定(OK)和应用(Apply Now)按钮,取消(Cancel)按钮继续存在。要告诉属性表哪一个按钮是可用的,使用带有PSWIZB_BACK,PSWIZB_NEXT,PSWIZB_FINISH和PSWIZB_DISABLEDFINISH值的 PSM_SETWIZBUTTONS 消息。 
  就象标准属性表一样新建和初始化一个向导属性表,当然你必须在 PROPSHEETHEADER 结构的 dwFlags 成员中包含PSH_WIZARD值。系统忽略 pszCaption 成员;替代为,在属性表的标题栏中放置当前页的标签(label)。当用户从一个页到了下一个页时,系统更新使用当前页的标签更新标题。 
  使用下列的值去设置你的向导属性表的基础大小。使用这些值确保你的页符合Windows的标准。 
   
  
  WIZ_BODYCX 
  在向导属性表中页主体的宽。主体不包含位图范围。 
  WIZ_BODYX 
  在向导属性表中主体的左上角的水平坐标。为页主体的升起坐标使用0 
  WIZ_CXBMP 
  在向导属性表中位图范围的宽。使用WIZ_CYDLG设置位图范围的高。 
  WIZ_CXDLG 
  在向导属性表中页的宽。 
  WIZ_CYDLG 
  在向导属性表中面的高。 
  对话框过程为向导属性表中的页接收所有相同的通知消息,象标准的属性表页一样。另外,一人向导属性表页接收三个标准属性表不接收物通知消息: PSN_WIZBACK , PSN_WIZNEXT 和 PSN_WIZFINISH 。当用户单击了上一步(Back),下一步(Next)或完成(Finish)按钮时,向导页接收这些通知。 
  当用户单击了上一步(Back)或下一步(Next)按钮时,属性表前进到先前的页或下一页。一个应用程序可以通过在 PSN_WIZBACK 或 PSN_WIZNEXT 通知中设置DWL_MSGRESULT值为-1来防止属性表提前。要立即跳到先前的页或下一页,一个应用程序应该设置DWL_MSGRESULT到被显示的对话框的标识符。 
  当用户单击了完成(Finish)按钮时,系统自动消毁向导属性表。一个应用程序可以通过在 PSN_WIZFINISH 通知消息中设置DWL_MSGRESULT为非零值来防止向导被消毁。 
  属性页扩展 
  一个属性页扩展是一个动态链接库,添加一个或多个页到属性表,被其它的模块建立。这个模块创造的属性页中包含一个 AddPropSheetPageProc 回调函数,这是被扩展DLL调用来添加一页。函数接收指向一个页的句柄及应用程序定义的32位值。 
  扩展DLL同样包含一个叫做 ExtensionPropSheetPageProc 的回调函数,接收来自这个模块的 AddPropSheetPageProc 的地址来创造属性表。扩展DLL必须输出 ExtensionPropSheetPageProc 。 
  Windows头文件包含了两个为属性表回调函数定义的原型。要定义 AddPropSheetPageProc ,使用下列的原型: typedef BOOL (CALLBACK FAR * LPFNADDPROPSHEETPAGE)(HPROPSHEETPAGE, LPARAM);
  要定义 ExtensionPropSheetPageProc ,使用下列的原型: typedef BOOL (CALLBACK FAR * LPFNADDPROPSHEETPAGES)(LPVOID, LPFNADDPROPSHEETPAGE, LPARAM);
  使用属性表 
  这个部分包含了怎样新建一个属性表和处理通知消息的例子。 
  新建一个属性表 
  在这部分的例子中新建了一个属性表,它包含两个页,一个是设置电子表格程序中单元的字体属性,另一个是设置单元的边框属性。例子通过填充一双 PROPSHEETPAGE 结构定义了页(pages),在 PROPSHEETHEADER 函数中指定了它的地址,并调用了 PropertySheet 函数。对话框模板,图标和页的标签(label)被从应用程序的可执行文件中的资源装入。属性表的图标也同样被从应用程序的资源中装入。 // DoPropertySheet - 新建一个包含两个页的属性表
  // hwndOwner - 指向属性表的所有者窗口的句柄。
  //
  // 全局变量
  // g_hinst - 实例句柄
  extern HINSTANCE g_hinst;
  VOID DoPropertySheet(HWND hwndOwner)
  {
   PROPSHEETPAGE psp[2];
   PROPSHEETHEADER psh;
   psp[0].dwSize = sizeof(PROPSHEETPAGE);
   psp[0].dwFlags = PSP_USEICONID | PSP_USETITLE;
   psp[0].hInstance = g_hinst;
   psp[0].pszTemplate = MAKEINTRESOURCE(DLG_FONT);
   psp[0].pszIcon = MAKEINTRESOURCE(IDI_FONT);
   psp[0].pfnDlgProc = FontDialogProc;
   psp[0].pszTitle = MAKEINTRESOURCE(IDS_FONT)
   psp[0].lParam = 0;
   psp[0].pfnCallback = NULL;
   psp[1].dwSize = sizeof(PROPSHEETPAGE);
   psp[1].dwFlags = PSP_USEICONID | PSP_USETITLE;
   psp[1].hInstance = g_hinst;
   psp[1].pszTemplate = MAKEINTRESOURCE(DLG_BORDER);
   psp[1].pszIcon = MAKEINTRESOURCE(IDI_BORDER);
   psp[1].pfnDlgProc = BorderDialogProc;
   psp[1].pszTitle = MAKEINTRESOURCE(IDS_BORDER);
   psp[1].lParam = 0;
   psp[1].pfnCallback = NULL;
   psh.dwSize = sizeof(PROPSHEETHEADER);
   psh.dwFlags = PSH_USEICONID | PSH_PROPSHEETPAGE;
   psh.hwndParent = hwndOwner;
   psh.hInstance = g_hinst;
   psh.pszIcon = MAKEINTRESOURCE(IDI_CELL_PROPERTIES);
   psh.pszCaption = (LPSTR) "Cell Properties";
   psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
   psh.nStartPage = 0;
   psh.ppsp = (LPCPROPSHEETPAGE) &psp;
   psh.pfnCallback = NULL;
   PropertySheet(&psh);
   return;
  }
  处理通知消息 
  一个属性表发送 WM_NOTIFY 消息去从页中获取信息,并报告用户动作的页。这个消息的 lParam 参数是 NMHDR 结构的地址,这个地址包含指向属性表对话框的句柄,指向页对话框的句柄和一个通知代码。这个页必须通过设置页的DWL_MSGRESULT值为真(TRUE)或假(FALSE)来响应一些通知消息。 
  下列的例子是页对话框过程中的代码片段。它显示了怎样处理 PSN_HELP 通知消息 case WM_NOTIFY:
   switch (((NMHDR FAR *) lParam)->code) {
   case PSN_HELP:
   {
   char szBuf[FILE_LEN]; // buffer for name of help file
   // Display help for the font properties page.
   LoadString(g_hinst, IDS_HELPFILE, &szBuf, FILE_LEN)
   WinHelp(((NMHDR FAR *) lParam)->hwndFrom, &szBuf,
   HELP_CONTEXT, IDH_FONT_PROPERTIES);
   break;
   }
   .
   . // 在这处理其它的属性表通知
   .
   }
  在Internet Explorer中属性表 的更新 
  属性表在Microsoft® Internet Explorer中支持下列的新特性。 
  新的通知 
  PSN_GETOBJECT 通知允许一个页进行OLE拖放对象。 
  更新的结构 
  PROPSHEETHEADER 和 PROPSHEETPAGE 结构已经被更新来支持新的特性。请参见这些结构的参考。



属性表及属性页问题集
  一、“应用”按钮的ID
  “应用”的ID为:ID_APPLY_NOW (实际上是0x3021)。 
  在VC++的include目录中的Afxres.h文件中,定义如下: 
  ...
  #define ID_APPLY_NOW 0x3021
  #define ID_WIZBACK 0x3023
  #define ID_WIZNEXT 0x3024
  #define ID_WIZFINISH 0x3025
  ...
  二、添加一个控件到属性表
  每一个属性页有它自己的控件设置。只不过控件共享在属性表中的标准按钮。如果你想去添加一个编辑控件,一个预览按钮或其它的控件,你只好在运行时去做它。下面的代码显示了你怎样去添加一个编辑控件。 
  第一步:从CPropertySheet派生你自己的类我们不能直接使用CPropertySheet类,因为我们需要添加一个成员变量。如果你在派生这个以前没有使用CPropertySheet的子类。 
  第二步:添加成员变量添加成员变量到CPropertySheet派生类。编辑控件将被新建并使用这个成员访问。 public:
  CEdit m_edit;
  第三步:在OnInitDialog中新建编辑控件重载OnInitDialog()函数并在这个函数中添加新建编辑控件的代码。在做派生类其它的细节前去调用函数的基类版本是一个好的想法。 
  属性表首行调整大小去容纳新的编辑控件。编辑在你渴望的位置被建立。WS_EX_CLIENTEDGE给定它是一个3D风格。编辑控件比其它控件多出了可以使用不同的字体来建立。我们调用SetFont()来安装这个属性,并设置设置与属性表相同。 
  编辑控件中的文本值可以使用m_edit对象来设置或取得。 BOOL CMyPropSheet::OnInitDialog()
  {
  BOOL bResult = CPropertySheet::OnInitDialog();
  CRect rectWnd;
  GetWindowRect(rectWnd);
  SetWindowPos(NULL, 0, 0,
  rectWnd.Width() + 100,
  rectWnd.Height(),
  SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
  m_edit.CreateEx( WS_EX_CLIENTEDGE, _T("EDIT"), NULL,
  WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER, 
  rectWnd.Width(), 20, 80, 24, m_hWnd, 0, 0 );
  m_edit.SetFont( GetFont() );
  CenterWindow();
  return
  bResult;
  }
  三、改变Tab的标签字体
  我们可以使用Tab控件改变字体,在CWnd类中使用SetFont()函数重绘这个标签。下面的代码最常的简单,不用为恢复Tab控件或属性表的大小而费心。这个工作设置的字体比Tab控件的字体小或一样大。在CPropertySheet的派生类中的OnInitDialog()是放置这些代码最好的地方。 // m_fontEdit是一个成员变量
  // 新建一个粗体字体
  m_fontEdit.CreateFont( -8, 0, 0, 0, 700, 0, 0, 0, 1, 
  0, 0, 0, 0, _T("MS Sans Serif") );
  GetTabControl()->SetFont( &m_fontEdit );
  四、改变Tab标签的文本
  标签是被属性表中的Tab控件使用的,是从属性表的标题中取得的。要在运行时改变这个标签,我们只好首先取得指向Tab控件对象的指针,然后调用它的SetItem()函数。下面的代码告诉改变了第一个Tab标签。To change this label at runtime, we have to first get a pointer to the tab control object and call its function. The following code changes the label of the first tab. CString sCaption = _T("New Caption");
  TC_ITEM tcItem;
  tcItem.mask = TCIF_TEXT;
  tcItem.pszText = (LPTSTR)((LPCTSTR)sCaption);
  GetTabControl()->SetItem(0, &tcItem );
  五、隐藏“应用”按钮 
  当属性表显示时,默认“淫”按钮是可见并无儿的。常常,我们在全部程序中不需要“应用”按钮。怎样移去它呢?在你新建属性表对象后加入PSH_NOAPPLYNOW标记。 
  propsheet.m_psh.dwFlags |= PSH_NOAPPLYNOW;
  六、隐藏标准按钮
  下列的代码隐藏了“OK”按钮。标准按钮的ID是IDOK,IDCANCEL,IDHELP和ID_APPLY_NOW。 
  CWnd *pWnd = GetDlgItem( IDOK );
  pWnd->ShowWindow( FALSE );

usidc5 2010-11-22 21:04

属性页上的“应用”、“帮助”我们可能用不上,所以要去掉它们,要不多影响美观~

去掉“应用”按钮很简单:


CPropertySheet properSht("Setting");

properSht.m_psh.dwFlags |= PSH_NOAPPLYNOW;    //去掉了“应用”按钮


要去掉“帮助”按钮,需要注意的一点是,必须同时设置CPropertyPage的dwFlags,要不然是去不掉的(MSDN里写有):

假设有两个CPropertyPage对象:


CPropertySheet properSht("Setting");

properSht.m_psh.dwFlags &= ~(PSH_HASHELP);

CPropertyPage1 page1;    //页一

page1.m_psp.dwFlags &= ~(PSP_HASHELP); //设置页一的帮助标志

CPropertyPage2 page2;    //页二

page2.m_psp.dwFlags &= ~(PSP_HASHELP); //设置页二的帮助标志

——在Onintdialg前调用



   另一种方法 在OnInitDialog()中加入:
   GetDlgItem(ID_APPLY_NOW)->DestroyWindow();//不要“应用按扭”
   GetDlgItem(IDOK)->SetWindowText("生成标准件");//将“确定”改为“生成标准件”


usidc5 2010-12-02 13:46

在VC++中,如果想要输入IP地址可使用IP地址控件,该控件是一个与编辑控件类似的控件,可用于输入IP地址。

在对话框中放入此控件后,可以为控件定义一个 CIPAddressCtrl类 的变量对它进行控制。

CIPAddressCtrl类 的主要成员函数有:

void ClearAddress();
清除IP地址控件中的内容。

BOOL IsBlank();
如果IP地址控件的所有域均为空,返回非0值;否则返回0。

void SetAddress(BYTE nField0,BYTE nField1,BYTE nField2,BYTE nField3);
void SetAddress(DWORD dwAddress);
设置IP地址控件中的地址值。
第一种形式是用4个0~255的整数分别设置IP地址各个域的值。
第二种形式是用1个长整数设置IP地址值。

int GetAddress(BYTE& nField0,BYTE& nField1,BYTE& nField2,BYTE& nField3);
int GetAddress(DWORD& dwAddress);
获取IP地址控件中的地址值。
第一种形式是把IP地址的4个域填充到用4个引用中。
第二种形式是把IP地址填充到1个长整数的引用中。
返回值:IP地址控件中非空域的数量。

void SetFieldFocus(WORD nField);
把焦点设置在指定的域中。nField取值为0~3,如果大于3,则焦点设置到第一个空域中,若所有域均非空,则焦点设置在第一个域中。

void SetFieldRange(int nField,BYTE nLower,BYTE nUpper);
设置指定域中数值的取值范围。
nField:域索引,取值0~3;
nLower:域的下限;
nUpper:域的上限。

下面的例子摹仿为计算机设置IP地址的对话框看一下IP地址控件的用法。

对话框建立后,创建一个CSetIPDialog类来控制该对话框。

为对话框中的各控件添加相应的控制变量。

SetIPDialog.h:

//{{AFX_DATA(CSetIPDialog)
enum { IDD = IDD_SETIP_DIALOG };
CIPAddressCtrl m_IPAddressCtrl3;
CIPAddressCtrl m_IPAddressCtrl2;
CIPAddressCtrl m_IPAddressCtrl1;
CStatic m_IPStatic3;
CStatic m_IPStatic2;
CStatic m_IPStatic1;
int IP_From;
//}}AFX_DATA

SetIPDialog.cpp:

void CSetIPDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CSetIPDialog)
    DDX_Control(pDX, IDC_IPADDRESS3, m_IPAddressCtrl3);
    DDX_Control(pDX, IDC_IPADDRESS2, m_IPAddressCtrl2);
    DDX_Control(pDX, IDC_IPADDRESS1, m_IPAddressCtrl1);
    DDX_Control(pDX, IDC_IP_STATIC3, m_IPStatic3);
    DDX_Control(pDX, IDC_IP_STATIC2, m_IPStatic2);
    DDX_Control(pDX, IDC_IP_STATIC1, m_IPStatic1);
    DDX_Radio(pDX, IDC_IPFROM_RADIO1, IP_From);
    //}}AFX_DATA_MAP
}

m_IPAddressCtrl1对应输入IP地址的控件;

m_IPAddressCtrl2对应输入子网掩码的控件;

m_IPAddressCtrl3对应输入默认网关的控件;

变量IP_From对应于单选按钮的位置,当IP_From==0时,表示IP地址是自动获取的,此时,各IP地址控件为灰色显示,不能进行设置。当IP_From==1时,可以设置IP地址。函数EnableIP()可根据IP_From的值设置各IP地址控件的可用性:

void CSetIPDialog::EnableIP()
{
    BOOL show = (IP_From==1);
    m_IPStatic1.EnableWindow( show );
    m_IPStatic2.EnableWindow( show );
    m_IPStatic3.EnableWindow( show );
    m_IPAddressCtrl1.EnableWindow( show );
    m_IPAddressCtrl2.EnableWindow( show );
    m_IPAddressCtrl3.EnableWindow( show );
}

用ClassWizard添加单选按钮的响应函数:

//消息函数:自动获取(单选按钮)
void CSetIPDialog::OnIPFromRadio1() 
{
    IP_From = 0;
    m_IPAddressCtrl1.ClearAddress();
    m_IPAddressCtrl2.ClearAddress();
    m_IPAddressCtrl3.ClearAddress();
    EnableIP();
}

//消息函数:手工配置(单选按钮)
void CSetIPDialog::OnIPFromRadio2() 
{
    IP_From = 1;
    EnableIP();
    m_IPAddressCtrl1.SetFieldFocus( 0 );
}

当选中“自动获取”的单选按钮时,用ClearAddress()函数清除各IP地址控件的内容,然后把控件设置为灰色。

当选中“手工配置”的单选按钮时,让各IP地址控件可用,并且把输入焦点设置第1个IP地址控件的第0个域中。

子网掩码的输入:

按照习惯,当输入了IP地址后,只要单击输入子网掩码的IP地址控件就可以自动生成默认的子网掩码。为了实现这一点,可以用添加一个新类CSubnetMaskCtrl,基类为CIPAddressCtrl。

在该类中加入一个变量CIPAddressCtrl* p_IPAddressCtrl;

该变量是指向输入IP地址的控件的指针,用于获取控件中输入的IP地址。

在CSubnetMaskCtrl中添加一个消息函数OnSetfocus(),当子网掩码控件获得输入焦点时会执行此函数。

(注:此函数响应的是EN_SETFOCUS消息,在ClassWizard中没有此消息,所以只能手工添加。)

SubnetMaskCtrl.h:

protected:
    //{{AFX_MSG(CSubnetMaskCtrl)
    // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG
    afx_msg void OnSetfocus();
    DECLARE_MESSAGE_MAP()

SubnetMaskCtrl.cpp:

BEGIN_MESSAGE_MAP(CSubnetMaskCtrl, CIPAddressCtrl)
    //{{AFX_MSG_MAP(CSubnetMaskCtrl)
        // NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    ON_CONTROL_REFLECT(EN_SETFOCUS, OnSetfocus)
END_MESSAGE_MAP()

void CSubnetMaskCtrl::OnSetfocus() 
{
    if( p_IPAddressCtrl && IsBlank() )
    {
        BYTE field0, field1, field2, field3;
        p_IPAddressCtrl->GetAddress( field0, field1, field2, field3 );
        if( field0>=1 && field0<=127 )
            SetAddress( 255, 0, 0, 0 );
        else if( field0>=128 && field0<=191 )
            SetAddress( 255, 255, 0, 0 );
        else if( field0>=192 && field0<=223 )
            SetAddress( 255, 255, 255, 0 );
    }
}

在函数中,如果子网掩码为空,就通过p_IPAddressCtrl指针获取用户输入的IP地址,然后根据IP地址的第1个数的取值设置本控件中的子网掩码默认值。

回到CSetIPDialog对话框,把用于输入子网掩码的IP地址控件的类型改为CSubnetMaskCtrl。

SetIPDialog.h:

//{{AFX_DATA(CSetIPDialog)
enum { IDD = IDD_SETIP_DIALOG };
CIPAddressCtrl m_IPAddressCtrl3;
CSubnetMaskCtrl m_IPAddressCtrl2;
CIPAddressCtrl m_IPAddressCtrl1;
CStatic m_IPStatic3;
CStatic m_IPStatic2;
CStatic m_IPStatic1;
int IP_From;
//}}AFX_DATA

用ClassWizard添加对话框的初始化函数。

BOOL CSetIPDialog::OnInitDialog() 
{
    CDialog::OnInitDialog();

    m_IPAddressCtrl2.p_IPAddressCtrl = &m_IPAddressCtrl1;

    return TRUE;
}

在这里把输入IP地址的控件地址传送给子网掩码控件。

经过以上设置,该对话框就可以用来输入IP地址、子网掩码、默认网关等参数了,至于输入值的保存,可根据实际应用另行设置。

在实例中,我定义了一个名为CPC的类存放由对话框设置的IP地址。具体情况可以参考示例程序。


usidc5 2010-12-07 23:11
addpage前加一步操作
mypage.m_psp.dwFlags |= PSP_PREMATURE; 

usidc5 2010-12-13 23:02
主要介绍了VC++/MFC中如下内容的剪贴板操作:

1、文本内容的操作

2、WMF数据的操作

3、位图的操作

4、设置使用自定义格式

5、感知剪贴板内容的改变

6、自动将数据粘贴到另一应用程序窗口
一、文本内容的操作

下面的代码示范了如何将文本内容复制到剪贴板(Unicode编码的先转化为ASCII):

CString source;

//文本内容保存在source变量中

if( OpenClipboard() )

{

HGLOBAL clipbuffer;

char * buffer;

EmptyClipboard();

clipbuffer = GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);

buffer = (char*)GlobalLock(clipbuffer);

strcpy(buffer, LPCSTR(source));

GlobalUnlock(clipbuffer);

SetClipboardData(CF_TEXT,clipbuffer);

CloseClipboard();

}

下面的代码显示了如何从剪贴板上获得文本内容:

char * buffer = NULL;

//打开剪贴板

CString fromClipboard;

if ( OpenClipboard() )

{

HANDLE hData = GetClipboardData(CF_TEXT);

char * buffer = (char*)GlobalLock(hData);

fromClipboard = buffer;

GlobalUnlock(hData);

CloseClipboard();

}

二、WMF数据的操作

  在剪贴板上读写图象数据是非常有用的功能,并且实现起来也很简单。下面的代码显示了如何将扩展图元文件复制到剪贴板:

 if(OpenClipboard());

{

EmptyClipboard();

//创建图元文件DC

CMetaFileDC * cDC = new CMetaFileDC();

cDC->CreateEnhanced(GetDC(),NULL,NULL,"the_name");

//调用绘图例程

//关闭CMetafileDC并获得它的句柄

HENHMETAFILE handle = cDC->CloseEnhanced();

//复制到剪贴板

SetClipBoardData(CF_ENHMETAFILE,handle);

CloseClipboard();

//删除dc

delete cDC;

}

下面的代码演示了从剪贴板获得图元文件并将其绘制到client DC上:

if(OpenClipboard())

{

//获得剪贴板数据

HENMETAFILE handle = (HENMETAFILE)GetClipboardData(CF_ENHMETAFILE);

//显示

CClientDC dc(this);

CRect client(0,0,200,200);

dc.PlayMetaFile(handle,client);

//关闭剪贴板

CloseClipboard();

}

三、位图的操作



位图的操作稍微复杂一点,下面这个例子显示了如何在剪贴板保存位图:

if(OpenClipboard())

{

EmptyClipboard();

CBitmap * junk = new CBitmap();

CClientDC cdc(this);

CDC dc;

dc.CreateCompatibleDC(&cdc);

CRect client(0,0,200,200);

junk->CreateCompatibleBitmap(&cdc,client.Width(),client.Height());

dc.SelectObject(junk);

DrawImage(&dc,CString("Bitmap"));

//复制数据到剪贴板

SetClipboardData(CF_BITMAP,junk->m_hObject);

CloseClipboard();

delete junk;

}

下面的代码显示了如何从剪贴板上获得位图数据:

if(OpenClipboard())

{

//获得剪贴板数据

HBITMAP handle = (HBITMAP)GetClipboardData(CF_BITMAP);

CBitmap * bm = CBitmap::FromHandle(handle);

CClientDC cdc(this);

CDC dc;

dc.CreateCompatibleDC(&cdc);

dc.SelectObject(bm);

cdc.BitBlt(0,0,200,200,&dc,0,0,SRCCOPY);

CloseClipboard();

}

四、设置并使用自定义格式

使用RegisterClipboardFormat()函数,可以复制和粘贴任何你需要的数据类型。比如我们有以下一个数据类型:

struct MyFormatData

{

long val1;

int val2;

};

我们要把它复制到剪贴板,可以使用如下的代码:

UINT format = RegisterClipBoardFormat("MY_CUSTOM_FORMAT");

if(OpenClipboard())

{

MyFormatData data;

data.val1 = 100;

data.val2 = 200;

HGLOBAL clipbuffer;

EmptyClipboard();

clipbuffer = GlobalAlloc(GMEM_DDESHARE, sizeof(MyFormatData));

MyFormatData * buffer = (MyFormatData*)GlobalLock(clipbuffer);

//保存到内存

*buffer = data;

//保存到剪贴板

GlobalUnlock(clipbuffer);

SetClipboardData(format,clipbuffer);

CloseClipboard();

}

读取数据使用以下代码:

UINT format = RegisterClipboardFormat("MY_CUSTOM_FORMAT");

MyFormatData data;

if(Openclipboard())

{

HANDLE hData =GetClipboardData(format);

MyFormatData * buffer = (MyFormatData*)GlobalLock(hData);

data = *buffer;

GlobalUnlock(hData);

CloseClipboard();

}

五、感知剪贴板内容的改变

通过Windows消息可以感知剪贴板内容是否发生改变,代码如下:

//In your initialization code call:

SetClipboardViewer(); //将我们的程序添加到剪贴板观察链

//In your message map add:

ON_MESSAGE(WM_DRAWCLIPBOARD, OnClipChange) //添加Message handle

//Which is declared as:

afx_msg void OnClipChange();

Finally implement:

void CDetectClipboardChangeDlg::OnClipChange()

{

CTime time = CTime::GetCurrentTime();

SetDlgItemText(IDC_CHANGED_DATE,time.Format("%a, %b %d, %Y -- %H:%M:%S"));

DisplayClipboardText();

}

六、自动将数据粘贴到另一应用程序窗口

只需获得相应窗口的句柄,并发送一个消息就可以了:

usidc5 2010-12-13 23:03
SendMessage(m_hTextWnd, WM_PASTE, 0, 0);


简单的说:

一、打开剪切板
        OpenClipboard();
二、获得指向剪切板的全局指针变量
        HANDLE hClipMemory=::GetClipboardData(CF_TEXT);
三、锁定剪切板
        LPBYTE lpClipMemory = (LPBYTE)GlobalLock(hClipMemory);
四、获取剪切板内容
        CString m_sMessage = CString(lpClipMemory);
五、解锁剪切板
        GlobalUnlock(hClipMemory);
六、关闭剪切板
        ::CloseClipboard();
         读取剪切板内容必须先锁定剪切板,否则会发生异常。以上只能读取文本格式的内容,无法读取位图,具体请查阅MSDN帮助文档。

usidc5 2010-12-18 22:04
方式一:VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时间隔, 如SetTimer(0,200,NULL)即为设置200ms的时间间隔。然后在应用程序中增加定时响应函数 OnTimer(),并在该函数中添加响应的处理语句,用来完成到达定时时间的操作。这种定时方法非常简单,可以实现一定的定时功能,但其定时功能如同 Sleep()函数的延时功能一样,精度非常低,最小计时精度仅为30ms,CPU占用低,且定时器消息在多任务操作系统中的优先级很低,不能得到及时响 应,往往不能满足实时控制环境下的应用。只可以用来实现诸如位图的动态显示等对定时精度要求不高的情况。如示例工程中的Timer1。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
方式二:VC中使用sleep()函数实现延时,它的单位是ms,如延时2秒,用sleep(2000)。精度非常 低,最小计时精度仅为30ms,用sleep函数的不利处在于延时期间不能处理其他的消息,如果时间太长,就好象死机一样,CPU占用率非常高,只能用于 要求不高的延时程序中。如示例工程中的Timer2。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
方式三:利用COleDateTime类和COleDateTimeSpan类结合WINDOWS的消息处理过程来实现秒级延时。如示例工程中的Timer3和Timer3_1。以下是实现2秒的延时代码:


COleDateTime start_time = COleDateTime::GetCurrentTime();
COleDateTimeSpan end_time= COleDateTime::GetCurrentTime()-start_time;
while(end_time.GetTotalSeconds()< end_time =" COleDateTime::GetCurrentTime()-start_time;">
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
方式四:在精度要求较高的情况下,VC中可以利用GetTickCount()函数,该函数的返回值是 DWORD型,表示以ms为单位的计算机启动后经历的时间间隔。精度比WM_TIMER消息映射高,在较短的定时中其计时误差为15ms,在较长的定时中 其计时误差较低,如果定时时间太长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。如示例工程中的Timer4和 Timer4_1。下列代码可以实现50ms的精确定时:
DWORD dwStart = GetTickCount();
DWORD dwEnd = dwStart;
do
{
 dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
为使GetTickCount()函数在延时或定时期间能处理其他的消息,可以把代码改为:
DWORD dwStart = GetTickCount();
DWORD dwEnd = dwStart;
do
{
 MSG msg;
 GetMessage(&msg,NULL,0,0);
 TranslateMessage(&msg);
 DispatchMessage(&msg);
 dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
虽然这样可以降低CPU的占有率,并在延时或定时期间也能处理其他的消息,但降低了延时或定时精度。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
方式五:与 GetTickCount()函数类似的多媒体定时器函数DWORD timeGetTime(void),该函数定时精度为ms级,返回从Windows启动开始经过的毫秒数。微软公司在其多媒体Windows中提供了精 确定时器的底层API持,利用多媒体定时器可以很精确地读出系统的当前时间,并且能在非常精确的时间间隔内完成一个事件、函数或过程的调用。不同之处在于 调用DWORD timeGetTime(void) 函数之前必须将 Winmm.lib 和 Mmsystem.h 添加到工程中,否则在编译时提示DWORD timeGetTime(void)函数未定义。由于使用该函数是通过查询的方式进行定时控制的,所以,应该建立定时循环来进行定时事件的控制。如示例工 程中的Timer5和Timer5_1。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
方式六:使用多媒体定时器timeSetEvent()函数,该函数定时精度为ms级。利用该函数可以实现周期性的函数调用。如示例工程中的Timer6和Timer6_1。函数的原型如下:
MMRESULT timeSetEvent( UINT uDelay,
                                           UINT uResolution,
                                           LPTIMECALLBACK lpTimeProc,
                                           WORD dwUser,
                                           UINT fuEvent )
该函数设置一个定时回调事件,此事件可以是一个一次性事件或周期性事件。事件一旦被激活,便调用指定的回调函数, 成功后返回事件的标识符代码,否则返回NULL。函数的参数说明如下:


  uDelay:以毫秒指定事件的周期。
  Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
  LpTimeProc:指向一个回调函数。
  DwUser:存放用户提供的回调数据。
  FuEvent:指定定时器事件类型:
  TIME_ONESHOT:uDelay毫秒后只产生一次事件
  TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。


  具体应用时,可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在LpTimeProc回调函数中(如:定时采样、控制 等),从而完成所需处理的事件。需要注意的是,任务处理的时间不能大于周期间隔时间。另外,在定时器使用完毕后,应及时调用 timeKillEvent()将之释放。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
方式七:对于精确度要求更高的定时操作,则应该使用 QueryPerformanceFrequency()和 QueryPerformanceCounter()函数。这两个函数是VC提供的仅供Windows 95及其后续版本使用的精确时间函数,并要求计算机从硬件上支持精确定时器。如示例工程中的Timer7、Timer7_1、Timer7_2、 Timer7_3。


QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下:
BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);
数据类型ARGE_INTEGER既可以是一个8字节长的整型数,也可以是两个4字节长的整型数的联合结构, 其具体用法根据编译器是否支持64位而定。该类型的定义如下:
typedef union _LARGE_INTEGER
{
 struct
 {
  DWORD LowPart ;// 4字节整型数
  LONG HighPart;// 4字节整型数
 };
 LONGLONG QuadPart ;// 8字节整型数


}LARGE_INTEGER ;
在进行定时之前,先调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频 率,然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差及时钟频率, 计算出事件经 历的精确时间。下列代码实现1ms的精确定时:其定时误差不超过1微秒,精度与CPU等机器配置有关。
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 获得初始值
do
{
 QueryPerformanceCounter(&litmp);
 QPart2 = litmp.QuadPart;//获得中止值
 dfMinus = (double)(QPart2-QPart1);
 dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
}while(dfTim<0.001);
下面的程序用来测试函数Sleep(100)的精确持续时间:
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;// 获得初始值
Sleep(100);
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//获得中止值
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒

usidc5 2010-12-18 22:05
  VC函数中的延时操作 收藏
VC函数中的延时操作 
我在这里把论坛里能见到的几种延时方式总结一下。


COleDateTime  start_time = COleDateTime::GetCurrentTime();
 COleDateTimeSpan  end_time = COleDateTime::GetCurrentTime() - start_time; 
while(end_time.GetTotalSeconds()  <=  2)
 {    MSG  msg;    GetMessage(&msg,NULL,0,0);  
 TranslateMessage(&msg);
   DispatchMessage(&msg);   
end_time = COleDateTime::GetCurrentTime() - start_time;  }
还有一点说明,因为COleDateTimeSpan类的成员函数还有:GetTotalMinutes、GetTotalHours、GetTotalDays,能够实现更大时间段的延时。
    往更小的时间跨度上说,执行毫秒级的延时用GetTickCount就行:DWORD dwStart = GetTickCount();DWORD dwEnd = dwStart;do{    MSG  msg;    GetMessage(&msg,NULL,0,0);    TranslateMessage(&msg);    DispatchMessage(&msg);    dwEnd = GetTickCount();  } while((dwEnd - dwStart)  <=  2000); 
    然后是微秒级延时:LARGE_INTEGER  litmp ;LONGLONG  QPart1,QPart2 ;double d=0;QueryPerformanceCounter(&litmp) ; // 获得初始值QPart1 = litmp.QuadPart ;while (d<40)//你想要的时间{    QueryPerformanceCounter(&litmp) ;    QPart2 = litmp.QuadPart ;    d=(double)(QPart2 - QPart1);}。
最后,如果还不能满足,那就去做时钟周期的延时吧:
#define NOP_COUNT 3//需要自己根据NOP及LOOP的指令周期计算.__asm {  MOV ECX, NOP_COUNTDELAY: NOP  LOOP DELAY }不过,用VC做这个工作是不是有点……

usidc5 2010-12-20 00:02

用VC2003一直很爽,到新公司要用2005,早听说其检查严格,没想到,完全是没事找事型的编译器.
且不说满屏幕的:
"警告 4 warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 "
运行个MFC的东西就报"由于应用程序的配置不正确,应用程序未能启动"
一查才知道,这是05的特权,缺少动态库也不说,直接报个配置不正确,Fuck.不如直接弹出个警告框"程序无法运行"适用性更大呢.
这里是重置2005配置的方法:
命令行输入:devenv /resetuserdata
[[
开始执行程序时出现错误提示:
没有找到MSVCP80D.dll,因此这个应用程序未能启动。重新安装应用程序可能会修复此问题。
解决方法:
项目->属性->配置属性->清单工具->常规->使用FAT32解决办法
改为“是”即可
此时,应该将上次产生的debug文件夹删除才能正确运行。
]]
原因大致是由于2005编译时,FAT32格式硬盘会给程序加以时间戳,换机器运行时由于时间戳不符的原因导致无法运行,如果编译机器格式为NTFS格式则也不会出现该问题。
这两天有点焦头烂额, 我们这边运行的好好的程序, 到了测试的机器上就不能启动(是根本运行不了, 而不是运行出错), 弄得我异常郁闷。 经过了一番摸索, 发现和 winxp、win2003中为解决dll hell而引入的manifest机制有关系。而以前我们用vs2003开发, 它并没有强制程序使用manifest, 但到了vs2005中, 这已经改成必需的了, 而我们并没有按照需要进行相关的配置, 所以程序启动不了了。 根据目前的经验, vs2005编译的程序不能启动大致有两个原因, 下面简单介绍解决办法。
1、在开发组的机器上(安装有vs2005)有时都不能启动
这一般是项目的文件被放在了fat/fat32分区上导致的, 解决方法是把它们都移动到ntfs分区上, 或者把“项目属性|Manifest Tool|General|Use FAT32 Work-around”设为yes。
2、开发组运行正常, 换到其它机器上就不行了
这一般就是系统dll(包括crt,mfc,atl等)没有正确配置导致的。 如果程序是release版, 那么很简单, 只要把“\SDK\v2。0\BootStrapper\Packages\vcredist_x86”下的"vcredist_x86。exe"拷贝到目标机器上运行即可, 这是以x86平台为例的, 如果你用的是别的cpu平台(amd64或ia64)把x86替换成相应的内容就可以了。
注:[[ ]] 内信息转自http://hi.baidu.com/crafter_xmu/blog/item/104040b44b4876768bd4b281.html
这里是"general error c101008a: Failed to save the updated manifest to the file "网上普遍的解决方法:
在用vs2005时,默认编码为unicode,如果在项目中途改变,则编译时会碰到错误general error c101008a: Failed to save the updated manifest to the file ".\Debug\Menu.exe.embed.manifest". Bpcn mt.exe .这时清理解决方案,再重新生成项目便可解决.
这里是"warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 "网上普遍的解决方法:
一般来说,这个警告没有什么影响。要想去掉这个警告的方法有:
(1)转换Code文件为Unicode格式;
(2)在Project -> Properties -> Configuration Properties -> C/C++ -> Advance 的 Disable Specific Warnings 中添加相应的警告编号:4819;
(3)或找出不符合Unicode格式的文件,然后在该文件的开始处加入下面的语句:
        # pragma warning (disable:4819)
2005断点无法命中的解决方案
1:重新编译该工程.
2:文件的编码格式不匹配造成的;修改步骤: 菜单->文件->XXX文件另存为->编码保存(保存按钮旁边的下拉菜单)->Unicode (UTF-8 带签名)->确定
这里是"由于应用程序的配置不正确,应用程序未能启动"网上普遍的解决方法:
方法一:在类似C:\Program Files\Microsoft Visual Studio 8\VC\redi st\Debug_NonRedist\x86\Microsoft.VC80.DebugCRT 下找到了下列文件: msvcm80d.dll msvcp80d.dll msvcr80d.dll Microsoft.VC80.DebugCRT.manifest 把这几个文件拷贝到目标机器上,与运行程序同一文件夹或放到system32下,就可以正确运行了。 其他release版、MFC程序什么的都是拷redist下相应文件夹下的文件就可以了,文件夹后都有标识!
结果: 把这几个Dll Copy过去,无效....
方法二:工程-》属性-》配置属性-》c/c++ -〉代码生成里,修改运行时库选项,将/MD或/MDd 改为 /MT或/MTd,这样就实现了对VC运行时库的静态链接,在运行时就不再需要VC的dll了。
结果:由于我的程序使用了DLL输出,使用多线程调试是不行的,必须多线程调试DLL,这招也无效....
方法三: 工程-》属性-》配置属性-》常规-》MFC的使用,选择“在静态库中使用mfc” 这样生成的exe文件应该就可以在其他机器上跑了。
结果: 告诉我缺少Boost库,编译一次Boost够我吃顿饭回来了- -
方法四: 你的vc8安装盘上找到再分发包vcredist_xxx.exe和你的程序捆绑安装

usidc5 2011-01-02 13:31
没想到 VC6 不支持 __FUNCTION__

所以我写了如下的奇怪代码


//用来记录当前行和当前函数//也可说是记录 堆栈
void log_stack(const char *file, int line, const char * function);


//当然还要对 __FUNCTION__ 宏作点修饰,因为这个宏只是在函数里面才起作用
//据说 VC6 也是不支持 __FUNCTION__ 的
#ifndef __FUNCTION__
    #define __FUNCTION__ "Global"
#endif

#define DEBUG_NEW_HOOK

#ifdef DEBUG_NEW_HOOK
  //就是先写跟踪信息再实际调用函数
  #define  debug_new_check_point(a)  log_stack(__FILE__, __LINE__, __FUNCTION__); debug_new_check(a, false)
  #define  debug_new_check_free(a)  log_stack(__FILE__, __LINE__, __FUNCTION__); debug_new_check(a, true)
  //#define  printfd2(a,b)  printf(a,b)
  //#define  printfd3(a,b,c)  printf(a,b,c)
  //#define  printfd4(a,b,c,d)  printf(a,b,c,d)
#else

#endif

usidc5 2011-01-02 13:32
仅仅为了获取函数名,就在函数体中嵌入硬编码的字符串,这种方法单调乏味还易导致错误,不如看一下怎样使用新的C99特性,在程序运行时获取函数名吧。 

  对象反射库、调试工具及代码分析器,经常会需要在运行时访问函数的名称,直到不久前,唯一能完成此项任务并且可移植的方法,是手工在函数体内嵌入一个带有该函数名的硬编码字符串,不必说,这种方法非常单调无奇,并且容易导致错误。本文将要演示怎样使用新的C99特性,在运行时获取函数名。

  那么怎样以编程的方式从当前运行的函数中得到函数名呢?

  答案是:使用__FUNCTION__ 及相关宏。

  引出问题

  通常,在调试中最让人心烦的阶段,是不断地检查是否已调用了特定的函数。对此问题的解决方法,一般是添加一个cout或printf()——如果你使用C语言,如下所示:
void myfunc()
{
cout<<"myfunc()"< //其他代码
}


  通常在一个典型的工程中,会包含有数千个函数,要在每个函数中都加入一条这样的输出语句,无疑难过上“蜀山”啊,因此,需要有一种机制,可以自动地完成这项操作。

  获取函数名

  作为一个C++程序员,可能经常遇到 __TIME__、__FILE__、__DATE__ 这样的宏,它们会在编译时,分别转换为包含编译时间、处理的转换单元名称及当前时间的字符串。

  在最新的ISO C标准中,如大家所知的C99,加入了另一个有用的、类似宏的表达式__func__,其会报告未修饰过的(也就是未裁剪过的)、正在被访问的函数名。请注意,__func__不是一个宏,因为预处理器对此函数一无所知;相反,它是作为一个隐式声明的常量字符数组实现的:
static const char __func__[] = "function-name";


  在function-name处,为实际的函数名。为激活此特性,某些编译器需要使用特定的编译标志,请查看相应的编译器文档,以获取具体的资料。

  有了它,我们可免去大多数通过手工修改,来显示函数名的苦差事,以上的例子可如下所示进行重写:
void myfunc()
{
cout<<"__FUNCTION__"< }


  官方C99标准为此目的定义的__func__标识符,确实值得大家关注,然而,ISO C++却不完全支持所有的C99扩展,因此,大多数的编译器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一个定义为 __func__ 的宏,之所以使用这个名字,是因为它已受到了大多数的广泛支持。

  在Visual Studio 2005中,默认情况下,此特性是激活的,但不能与/EP和/P编译选项同时使用。请注意在IDE环境中,不能识别__func__ ,而要用__FUNCTION__ 代替。

  Comeau的用户也应使用 __FUNCTION__ ,而不是 __func__ 。

  C++ BuilderX的用户则应使用稍稍不同的名字:__FUNC__ 。

  GCC 3.0及更高的版本同时支持 __func__ 和__FUNCTION__ 。

  一旦可自动获取当前函数名,你可以定义一个如下所示显示任何函数名的函数:
void show_name(const char * name)
{
cout< }

void myfunc()
{
show_name(__FUNCTION__); //输出:myfunc
}

void foo()
{
show_name(__FUNCTION__); //输出:foo
}


  因为 __FUNCTION__ 会在函数大括号开始之后就立即初始化,所以,foo()及myfunc()函数可在参数列表中安全地使用它,而不用担心重载。

  签名与修饰名

  __FUNCTION__ 特性最初是为C语言设计的,然而,C++程序员也会经常需要有关他们函数的额外信息,在Visual Studio 2005中,还支持另外两种非标准的扩展特性:__FUNCDNAME__ 与 __FUNCSIG__ ,其分别转译为一个函数的修饰名与签名。函数的修饰名非常有用,例如,在你想要检查两个编译器是否共享同样的ABI时,就可派得上用场,另外,它还能帮助你破解那些含义模糊的链接错误,甚至还可用它从一个DLL中调用另一个用C++链接的函数。在下例中,show_name()报告了函数的修饰名:
void myfunc()
{
show_name(__FUNCDNAME__); //输出:?myfunc@@YAXXZ
}


  一个函数的签名由函数名、参数列表、返回类型、内含的命名空间组成。如果它是一个成员函数,它的类名和const/volatile限定符也将是签名的一部分。以下的代码演示了一个独立的函数与一个const成员函数签名间的不同之处,两个函数的名称、返回类型、参数完全相同:
void myfunc()
{
show_name(__FUNCSIG__); // void __cdecl myfunc(void)
}

struct S
{
void myfunc() const 
{
show_name(__FUNCSIG__); //void __thiscall S::myfunc(void) const
}
};

usidc5 2011-01-02 14:04


    Invalidate 在消息队列中加入一条 WM_PAINT 消息,其无效区为整个客户区。
    UpdateWindow 直接发送一个 WM_PAINT 消息,其无效区范围就是消息队列中 WM_PAINT 消息(最多只有一条)的无效区。
    效果很明显,当调用 Invalidate 之后,屏幕不一定马上更新,因为 WM_PAINT 消息不一定在队列头部,而调用 UpdateWindow 会使 WM_PAINT 消息马上执行的,绕过了消息队列。
    如果调用 Invalidate 之后想马上更新屏幕,那就加上 UpdateWindow() 这条语句。 

MSDN的解释 
UpdateWindow 
The UpdateWindow function updates the client area of the specified window by sending a WM_PAINT 
message to the window if the window's update region is not empty. The function sends a WM_PAINT
 message directly to the window procedure of the specified window, bypassing the application queue. 
If the update region is empty, no message is sent.  

InvalidateRect 
The system sends a WM_PAINT message to a window whenever its update region is not empty and
 there are no other messages in the application queue for that window.  

翻译成中文大概的解释如下: 
  UpdateWindow: 如果有无效区,则马上sending a WM_PAINT message到窗口处理过程,不进消息队列进行排队等待,立即刷新窗口,否则,什么都不做。 
 InvalidateRect:设置无效区,如果为NULL参数,则设置整个窗口为无效区。当应用程序的那个窗口的消息队列为空时,则sending a WM_PAINT message(即使更新区域为空).在sending a WM_PAINT message的所有InvalidateRect的更新区域会累加。 
 1:设置无效区 
 InvalidateRect 

 2:立即刷新 
 UpdateWindow(); 

如果不调用 InvalidateRect就调用 UpdateWindow,那么UpdateWindow什么都不做。 ??????
如果调用 InvalidateRect 后不调用UpdateWindow,则系统会自动在窗口消息队列为空的时候,系统自动发送一WM_PAINT消息。 
调用UpdateWindow()时将会发送一个WM_PAINT消息,而应用程序在接收到WM_PAINT消息后,将自动地调用Invalidate(),所以,在程序代码中,不一定要出现Invalidate()!
UpdateWindow()就是立即发送WM_PAINT消息,只对声明无效的区域起作用,   
  Invalidate()则是声明无效的方式之一。
Invalidate()表示客户区域无效,在下次WM_PAINT发生时重绘。 而WM_PAINT是由系统进行维护的,每当CWnd的更新区域不为空,并且在应用程序的窗口消息队列中没有其它消息时,Windows就发送一条WM_PAINT消息。   
  Invalidate里面有个bool型的参数,用来标识重绘的时候是否用背景色填充。是不是用SetBkcolor函数?下去继续研究。
 updateWindow则是要求系统对区域进行立即重绘。
 看到有人在网上提出问题,他在Invalidate后面又写了绘图的函数但是没有执行,因为invalidate执行过以后转到PAINT命令了。所以后面的都没有显示。
 也终于想通我绘的图一直在闪啊闪,因为我在PAINT里面用到Invalidate()函数,所以他不停的自嵌套,倒是绘的图不停的闪。
Invalidate让客户区处于可以重画的状态,而UpdateWindow开始重画,但是它先判断客户区是否为空,不空UpdateWindow不执行,为空才执行重画。
Invalidat最后也是调用InvalidatRect,在windows API里只有InvalidatRect的

usidc5 2011-01-10 20:52
《windows环境多线程编程原理与应用》中解释: 如果将类的封装比喻成一堵墙的话,那么友元机制就像墙上了开了一个门,那些得 到允许的类或函数允许通过这个门访问一般的类或者函数无法访问的私有属性和方法。友元机制使类的封装性得到消弱,所以使用时一定要慎重。友元类的说明将外界的某个类在本类别的定义中说明为友元,那么外界的类就成为本类的“朋 友”,那个类就可以访问本类的私有数据了。


class Merchant
{
private :
    int m_MyMoney;
    int m_MyRoom;
    … …
public:
    Friend class Lawyer;
    Int getmoney();
    … …
};


class Lawyer
{
private:
    … …
public:
    … …
};


只有你赋予某个类为你的友元时,那个类才有访问你的私有数据的权利。
说明一个函数为一个类的友元函数则该函数可以访问此类的私有数据和方法。定义方法是在类的定义中,在函数名前加上关键字friend.


《挑战30天C/C++》这样解释:
在说明什么是友元之前,我们先说明一下为什么需要友元与友元的缺点:
通常对于普通函数来说,要访问类的保护成员是不可能的,如果想这么做那么必须把类的成员都生命成为public(共用的),然而这做带来的问题遍是任何外部函数都可以毫无约束的访问它操作它,c++利用friend修饰符,可以让一些你设定的函数能够对这些保护数据进行操作,避免把类成员全部设置成public,最大限度的保护数据成员的安全。友元能够使得普通函数直接访问类的保护数据,避免了类成员函数的频繁调用,可以节约处理器开销,提高程序的效率,但所矛盾的是,即使是最大限度大保护,同样也破坏了类的封装特性,这即是友元的缺点,在现在cpu速度越来越快的今天我们并不推荐使用它,但它作为c++一个必要的知识点,一个完整的组成部分,我们还是需要讨论一下的。在类里声明一个普通数学,在前面加上friend修饰,那么这个函数就成了该类的友元,可以访问该类的一切成员。
下面我们来看一段代码,看看我们是如何利用友元来访问类的一切成员的






#include
using namespace std;
class Internet
{
public:
    Internet(char *name,char *address)   // 改为:internet(const char *name , const char *address)
    {
        strcpy(Internet::name,name);
        strcpy(Internet::address,address);
    }
    friend void ShowN(Internet &obj);   //友元函数的声明
public:              // 改为:private
    char name[20];
    char address[20];
};
void ShowN(Internet &obj)        //函数定义,不能写成,void Internet::ShowN(Internet &obj)
{
    cout<
}
void main()
{
    Internet a("美国主机论坛","www.usidcbbs.com");
    ShowN(a);
    cin.get();
}


上面的代码通过友元函数的定义,我们成功的访问到了a对象的保护成员name,友元函数并不能看做是类的成员函数,它只是个被声明为类友元的普通函数,所以在类外部函数的定义部分不能够写成void Internet::ShowN(Internet &obj),这一点要注意。


一个普通函数可以是多个类的友元函数,对上面的代码我们进行修改,注意观察变化:






#include
using namespace std;
class Country;
class Internet
{
public:
    Internet(char *name,char *address)        // 改为:internet(const char *name , const char *address)
    {
        strcpy(Internet::name,name);
        strcpy(Internet::address,address);
    }
    friend void ShowN(Internet &obj,Country &cn);//注意这里
public:
    char name[20];
    char address[20];
};
class Country
{
public:
    Country()
    {
        strcpy(cname,"中国");
    }
    friend void ShowN(Internet &obj,Country &cn);//注意这里
protected:
    char cname[30];
};


void ShowN(Internet &obj,Country &cn)
{
    cout<
}
void main()
{
    Internet a("美国主机论坛","www.usidcbbs.com");
    Country b;
    ShowN(a,b);
    cin.get();
}


一个类的成员函数函数也可以是另一个类的友元,从而可以使得一个类的成员函数可以操作另一个类的数据成员,我们在下面的代码中增加一类Country,注意观察






#include
using namespace std;
class Internet;
class Country
{
public:
    Country()
    {
        strcpy(cname,"中国");
    }
    void Editurl(Internet &temp)               ;//成员函数的声明
protected:
    char cname[30];
};
class Internet
{
public:
    Internet(char *name,char *address)
    {
        strcpy(Internet::name,name);
        strcpy(Internet::address,address);
    }
    friend void Country::Editurl(Internet &temp); //友元函数的声明
protected:
    char name[20];
    char address[20];
};
void Country::Editurl(Internet &temp)        //成员函数的外部定义
{
    strcpy(temp.address,"edu.usidcbbs.com");
    cout<
}
void main()
{
    Internet a("美国主机论坛","www.usidcbbs.com");
    Country b;
    b.Editurl(a);
    cin.get();
}


整个类也可以是另一个类的友元,该友元也可以称做为友类。友类的每个成员函数都可以访问另一个类的所有成员








#include
using namespace std;
class Internet;
class Country
{
public:
    Country()
    {
        strcpy(cname,"中国");
    }
    friend class Internet;             //友类的声明
protected:
    char cname[30];
};
class Internet
{
public:
    Internet(char *name,char *address)
    {
        strcpy(Internet::name,name);
        strcpy(Internet::address,address);
    }
    void Editcname(Country &temp);
protected:
    char name[20];
    char address[20];
};
void Internet::Editcname(Country &temp)
{
    strcpy(temp.cname,"中华人民共和国");
}
void main()
{
    Internet a("美国主机论坛","www.usidcbbs.com");
    Country b;
    a.Editcname(b);
    cin.get();
}

usidc5 2011-01-10 21:26
CFont class encapsulates the functionalities needed to manipulate the Fonts in Windows programming. A font can be created in 4 ways with a CFont class using CreateFont, CreateFontIndirect, CreatePointFont, or CreatePointFontIndirect functions. This CFont Samples page tries to give a piece of sample code for all of the above functions.

CFont Sample for using CFont :: CreateFont
:
   The following CFont sample illustrates how to create a font using CreateFont function of the CFont class.
 


   CClientDC dc(this);
   CFont l_font;
   l_font.CreateFont(14, 0, 0, 0, FW_NORMAL,
   FALSE, FALSE, FALSE, 0, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,   DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN, "Times New Roman");

   CFont* l_old_font = dc.SelectObject(&l_font); dc.TextOut(50, 50, "Hello World");    
   dc.SelectObject(l_old_font);
   // Delete the font object. 
   l_font.DeleteObject(); 




   In the above CFont Sample, the CreateFont function uses all default parameters (either 0 or Default constants), except for the height parameter. If CreateFont is called as above, the MFC framework will select the best fit parameters by itself and create a font accordingly. 

CFont Sample for using CFont :: CreateFontIndirect: 
   This part of CFont sample illustrates the usage of CreateFontIndirect. 


   CClientDC dc(this);
   CFont l_font;
   LOGFONT lf;
   lf.lfHeight = 12;
   strcpy(lf.lfFaceName, "Arial"); // Need a face name "Arial". 
   l_font.CreateFontIndirect(&lf); 
   CFont* l_old_font = dc.SelectObject(&l_font); 
   dc.TextOut(50, 50, "Hello World"); 
   dc.SelectObject(l_old_font); 
   // Delete the font object.
   l_font.DeleteObject(); 


The LOGFONT is a structure with all members required for the Font object. 

CFont Sample for using CFont :: CreatePointFont:
 This part of the sample illustrates the use of CreatePointFont for creating a font.


   CClientDC dc(this); 
   CFont l_font; 
   l_font.CreatePointFont(140,"Times New Roman"); 
   CFont* l_old_font = dc.SelectObject(&l_font);
   dc.TextOut(50, 50, "Hello World"); 
   dc.SelectObject(l_old_font); 
   // Delete the font object. 
   l_font.DeleteObject(); 


The first parameter of the CreatePointFont function is the height of the Font in tenths of a point. The return value of this function is non-zero value if successful. 

CFont Sample for using CFont :: CreatePointFontIndirect:
 


   CClientDC dc(this); 
   CFont l_font; 
   LOGFONT lf;
   lf.lfHeight = 120; 
   strcpy(lf.lfFaceName, "Arial"); // Need a face name "Arial". 
   l_font.CreatePointFontIndirect(&lf); 
   CFont* l_old_font = dc.SelectObject(&l_font); 
   dc.TextOut(50, 60, "Hello World"); 
   dc.SelectObject(l_old_font); 
   //  Delete the font object. 
   l_font.DeleteObject(); 


   Usually it is better to use either CreatePointFont or CreatePointFontIndirect functions as they reduce the head-ache of thinking about the width, weight and such parameters.
   Also, in the above CFont sample the l_font is deleted in the end. It is a good programming practice as advised by Windows Programming manuals to delete the Objects after removing them from the current device context. 

usidc5 2011-01-12 00:16
下表是一些常用颜色的值     
    
   Colour      Red      Green      Blue      值     
   黑色   Black    0   0    0    0     
   白色   White    255    255    255    16777215     
   灰色   Gray    192    192    192    12632256     
   深灰色    Dark    Grey    128    128    128    8421504     
   红色    Red    255    0    0    255     
   深红色    Dark    Red    128    0    0    128     
   绿色    Green    0    255    0    65280     
   深绿色    Dark    Green    0    128    0    32768     
   蓝色    Blue    0    0    255    16711680     
   深蓝色    Dark    Blue    0    0    128    8388608     
   紫红色    Magenta    255    0    255    16711935     
   深紫红    Dark    Magenta    128    0    128    8388736     
   紫色    Cyan    0    255    255    16776960     
   深紫    Dark    Cyan    0    128    128    8421376     
   黄色    Yellow    255    255    0    65535     
   棕色    Brown    128    128    0    32896     
   特殊颜色     
   Button    Face                79741120     
   Text                33554432     
   Background                1090519039     
   App.    Workspace                276856960     
    
   RGB函数计算公式:    颜色值    =    (65536    *    Blue)    +    (256    *    Green)    +    (Red) 

==============================================

rgb(r,g,b)=r+g*256+b*256*256

=================================

Dim color As Double
Dim r, g, b As Integer
color = RGB(0, 0, 0)
r = color Mod 256
g = (color \ 256) Mod 256
b = (color \ 256 \ 256) Mod 256
Print r
Print g
Print b

usidc5 2011-01-12 00:17
常用颜色的RGB值及中英文名称


颜  色   

RGB值 英文名 中文名
  #FFB6C1 LightPink 浅粉红
  #FFC0CB Pink 粉红
  #DC143C Crimson 深红/猩红
  #FFF0F5 LavenderBlush 淡紫红
  #DB7093 PaleVioletRed 弱紫罗兰红
  #FF69B4 HotPink 热情的粉红
  #FF1493 DeepPink 深粉红
  #C71585 MediumVioletRed 中紫罗兰红
  #DA70D6 Orchid 暗紫色/兰花紫
  #D8BFD8 Thistle 蓟色
  #DDA0DD Plum 洋李色/李子紫
  #EE82EE Violet 紫罗兰
  #FF00FF Magenta 洋红/玫瑰红
  #FF00FF Fuchsia 紫红/灯笼海棠
  #8B008B DarkMagenta 深洋红
  #800080 Purple 紫色
  #BA55D3 MediumOrchid 中兰花紫
  #9400D3 DarkViolet 暗紫罗兰
  #9932CC DarkOrchid 暗兰花紫
  #4B0082 Indigo 靛青/紫兰色
  #8A2BE2 BlueViolet 蓝紫罗兰
  #9370DB MediumPurple 中紫色
  #7B68EE MediumSlateBlue 中暗蓝色/中板岩蓝
  #6A5ACD SlateBlue 石蓝色/板岩蓝
  #483D8B DarkSlateBlue 暗灰蓝色/暗板岩蓝
  #E6E6FA Lavender 淡紫色/熏衣草淡紫
  #F8F8FF GhostWhite 幽灵白
  #0000FF Blue 纯蓝
  #0000CD MediumBlue 中蓝色
  #191970 MidnightBlue 午夜蓝
  #00008B DarkBlue 暗蓝色
  #000080 Navy 海军蓝
  #4169E1 RoyalBlue 皇家蓝/宝蓝
  #6495ED CornflowerBlue 矢车菊蓝
  #B0C4DE LightSteelBlue 亮钢蓝
  #778899 LightSlateGray 亮蓝灰/亮石板灰
  #708090 SlateGray 灰石色/石板灰
  #1E90FF DodgerBlue 闪兰色/道奇蓝
  #F0F8FF AliceBlue 爱丽丝蓝
  #4682B4 SteelBlue 钢蓝/铁青
  #87CEFA LightSkyBlue 亮天蓝色
  #87CEEB SkyBlue 天蓝色
  #00BFFF DeepSkyBlue 深天蓝
  #ADD8E6 LightBlue 亮蓝
  #B0E0E6 PowderBlue 粉蓝色/火药青
  #5F9EA0 CadetBlue 军兰色/军服蓝
  #F0FFFF Azure 蔚蓝色
  #E0FFFF LightCyan 淡青色
  #AFEEEE PaleTurquoise 弱绿宝石
  #00FFFF Cyan 青色
  #00FFFF Aqua 浅绿色/水色
  #00CED1 DarkTurquoise 暗绿宝石
  #2F4F4F DarkSlateGray 暗瓦灰色/暗石板灰
  #008B8B DarkCyan 暗青色
  #008080 Teal 水鸭色
  #48D1CC MediumTurquoise 中绿宝石
  #20B2AA LightSeaGreen 浅海洋绿
  #40E0D0 Turquoise 绿宝石
  #7FFFD4 Aquamarine 宝石碧绿
  #66CDAA MediumAquamarine 中宝石碧绿
  #00FA9A MediumSpringGreen 中春绿色
  #F5FFFA MintCream 薄荷奶油
  #00FF7F SpringGreen 春绿色
  #3CB371 MediumSeaGreen 中海洋绿
  #2E8B57 SeaGreen 海洋绿
  #F0FFF0 Honeydew 蜜色/蜜瓜色
  #90EE90 LightGreen 淡绿色
  #98FB98 PaleGreen 弱绿色
  #8FBC8F DarkSeaGreen 暗海洋绿
  #32CD32 LimeGreen 闪光深绿
  #00FF00 Lime 闪光绿
  #228B22 ForestGreen 森林绿
  #008000 Green 纯绿
  #006400 DarkGreen 暗绿色
  #7FFF00 Chartreuse 黄绿色/查特酒绿
  #7CFC00 LawnGreen 草绿色/草坪绿
  #ADFF2F GreenYellow 绿黄色
  #556B2F DarkOliveGreen 暗橄榄绿
  #9ACD32 YellowGreen 黄绿色
  #6B8E23 OliveDrab 橄榄褐色
  #F5F5DC Beige 米色/灰棕色
  #FAFAD2 LightGoldenrodYellow 亮菊黄
  #FFFFF0 Ivory 象牙色
  #FFFFE0 LightYellow 浅黄色
  #FFFF00 Yellow 纯黄
  #808000 Olive 橄榄
  #BDB76B DarkKhaki 暗黄褐色/深卡叽布
  #FFFACD LemonChiffon 柠檬绸
  #EEE8AA PaleGoldenrod 灰菊黄/苍麒麟色
  #F0E68C Khaki 黄褐色/卡叽布
  #FFD700 Gold 金色
  #FFF8DC Cornsilk 玉米丝色
  #DAA520 Goldenrod 金菊黄
  #B8860B DarkGoldenrod 暗金菊黄
  #FFFAF0 FloralWhite 花的白色
  #FDF5E6 OldLace 老花色/旧蕾丝
  #F5DEB3 Wheat 浅黄色/小麦色
  #FFE4B5 Moccasin 鹿皮色/鹿皮靴
  #FFA500 Orange 橙色
  #FFEFD5 PapayaWhip 番木色/番木瓜
  #FFEBCD BlanchedAlmond 白杏色
  #FFDEAD NavajoWhite 纳瓦白/土著白
  #FAEBD7 AntiqueWhite 古董白
  #D2B48C Tan 茶色
  #DEB887 BurlyWood 硬木色
  #FFE4C4 Bisque 陶坯黄
  #FF8C00 DarkOrange 深橙色
  #FAF0E6 Linen 亚麻布
  #CD853F Peru 秘鲁色
  #FFDAB9 PeachPuff 桃肉色
  #F4A460 SandyBrown 沙棕色
  #D2691E Chocolate 巧克力色
  #8B4513 SaddleBrown 重褐色/马鞍棕色
  #FFF5EE Seashell 海贝壳
  #A0522D Sienna 黄土赭色
  #FFA07A LightSalmon 浅鲑鱼肉色
  #FF7F50 Coral 珊瑚
  #FF4500 OrangeRed 橙红色
  #E9967A DarkSalmon 深鲜肉/鲑鱼色
  #FF6347 Tomato 番茄红
  #FFE4E1 MistyRose 浅玫瑰色/薄雾玫瑰
  #FA8072 Salmon 鲜肉/鲑鱼色
  #FFFAFA Snow 雪白色
  #F08080 LightCoral 淡珊瑚色
  #BC8F8F RosyBrown 玫瑰棕色
  #CD5C5C IndianRed 印度红
  #FF0000 Red 纯红
  #A52A2A Brown 棕色
  #B22222 FireBrick 火砖色/耐火砖
  #8B0000 DarkRed 深红色
  #800000 Maroon 栗色
  #FFFFFF White 纯白
  #F5F5F5 WhiteSmoke 白烟
  #DCDCDC Gainsboro 淡灰色
  #D3D3D3 LightGrey 浅灰色
  #C0C0C0 Silver 银灰色
  #A9A9A9 DarkGray 深灰色
  #808080 Gray 灰色
  #696969 DimGray 暗淡灰
  #000000 Black 纯黑

shirley0117 2011-04-14 15:24
真的是很用心的整理啊~谢谢~非常有用~

usidc5 2011-05-02 22:33
我曾经使用过QT和MFC来开发过软件,我想和大家分享我使用他们时所体会的不同之处。

我并非一个职业作家,这篇文章可能看起来不如专业的杂志和网站上的那么条理清晰。但是,我在这里是用我自己的语言来表达我自己的经验,希望能和你分享。英语比不是我的母语,所以可能会有一些用词古怪,词句错误之处,请发信给我,我可以改正他们。

本文不想假装客观公正,我只想表述我使用的经验。文中不会逐条的列举Qt和MFC各自的优缺点。我在使用MFC之前就已经使用Qt这个事实可能影响了我的客观性。

文章从实用主义的观点出发:我的老板给我一份软件的规划说明,并且让我来开发。其中一些我用Qt来开发,而另外一些我使用MFC来开发。

MFC(微软基础类库)是专门为windows设计的一个用于开发图形用户界面的类库。MFC或多或少使用了面向对象的方法包装了Win32的API,正因如此,这些API有时是C++,有时是C,甚至是C和C++的混合体。

Qt这个C++的图形库由Trolltech在1994年左右开发。它可以运行在Windows,Mac OS X, Unix,还有像Sharp Zaurus这类嵌入式系统中。Qt是完全面向对象的。

Document/View model
MFC编程需要使用Document/View模式以及模板(template),如果不使用的话,编程将变得异常困难。而且,模板(template)设定了固定的结构,若所需结构乃模板未定义之结构,则编程难已。例如,划分一区域使显示两个视图(view)于两个文档(document)。还有一个经常的问题是:模板(template)创建了视图(view)却无法访问(access)它,文档(document)要做完所有事情,但是这经常会出现问题。

Qt不强制使用任何设计模式。如果你认为恰当,使用Document/view没有任何问题。不使用也没有任何问题。

伪对象 vs 真对象
归根结底,Qt和MFC的差异在于其设计的差异。

MFC的根本目的是访问包装起来的用C语言写的windows的API。 这绝非好的面向对象的设计模式,在很多地方,你必须提供一个包含15个成员的C语言的struct,但是其中只有一个与你所期望的相关,或者必须用旧式的参数来调用你的函数。

MFC还有许多让人摸不着头脑的地方,函数名没有任何的连续性。比如,如果你创建了一个graphical类,直到调用了creat()以后该类才会被创建。然而对dialogs,必须要等到OnInitDialog()才能创建这个对象。奇怪的是到了views,创建该类的函数名竟然成了OnInitUpdate(),......你自己创建一个类用他们的方式调用它,你的程序崩溃了。

比如说有一个dialog包含CEdit控件,如果没有调用DoModal()你就不能使用GetWindowText()。否则将会莫名其妙的失败。总之,MFC充满了丈二和尚摸不着头脑的事情,并且,这种错误很难调试。

Qt恰恰相反,它的架构明显是经过精心设计的面向对象的。Qt因此在命名,继承,类的组织等方面保持了优秀的一致性。你只需要提供唯一一个方法的参数,仅此一个。在不同的类中调用方式也是有很强的连贯性。返回值也很有逻辑性。所有一切达到了简单和强大的和谐统一。一旦你使用了其中一个类,其他的类也就触类旁通,因为他们是一致的。

在Qt中可以利用Edit控件,用C++创建类的方法来创建自己的QLineEdit。永远可以马上访问任何的方法,不管它是显示还是隐藏。在这里没有迷局,一切都按照你认为的简单的方式来运作。

消息循环
MFC是事件驱动的架构。要执行任何操作,都必须是对特定的消息作出响应。Windows对应用程序发送的
信息数以千计,遗憾的是,要分清楚这些分繁芜杂的消息是很困难的,并且关于这方面的文档并不能很好的解决这些问题。

Qt的消息机制是建立在SIGNAL()发送和SLOT()接受的基础上的。这个机制是对象间建立联系的核心机制。利用SIGNAL()可以传递任何的参数。他的功能非常的强大。可以直接大传递信号给SLOT(),因此可以清楚的理解要发生的事情。一个类所发送的信号的数量通常非常的小(4或者5),并且文档也非常的齐全。这让你感觉到一切尽在掌握之中。SIGNAL/SLOT机制类似于Java中listener机制,不过这种机制更加轻量级,功能更齐全。

创建界面
MFC无法创建大小动态可变的子窗口 ,必须重新手动修改代码来改变窗口的位置(这恰好解释了为什么windows里的dialog是不可以改变的)这个问题在软件进行国际化翻译的时候更加严重,因为许多国家表达相同意思需要更长的词汇和句子,必须要对每个语言的版本重新修改自己的软件。

在Qt中,任何东西都可以手动的敲出来,因为它很简单:为了得到一个button,可以这样些

button = new PushButton( "buttonName", MyParentName );

如果想在按下某个按钮以后想调用某断代码的执行,可以这样写:

connect( button, SIGNAL( clicked() ), qApp, SLOT( action() ) );

Qt拥有非常简单而又不失强大的layout机制,以至于不使用它就是在浪费时间了。

Qt还提供了一个图形用户工具,Qt Designer,可以用来帮助建立用户界面。可以修改所使用的任何控件的属性。不用将他们放在严格的位置,可以通过layout完美的组织他们。这个工具所产生的代码我们是可以实际上阅读并且可以理解的。生成的代码单独放在一个文件里,在编程的同时,你可以随心所欲的多次重新生成用户界面。

Qt Designer可以让你完成许多在MFC中不可能完成的任务,比如用预先填好的生成listview,在每个tab上用不同的view来使用tab 控制。

帮助文档
用户选择图形开发环境的时候,帮助文档是否周全是左右其选择的重要因素。Visual的开发环境的帮助文档MSDN(这个还要单独掏钱购买)非常的庞大,有10个CDROM光盘。他包罗万象,涵盖广泛。但是难免有泥沙俱下,主题模糊,关键信息不突出的遗憾。其链接设计的也很糟糕,通过链接很难从一个类跳转到其父类或者子类以及相关的类。如果你搜索一个关键字,不管是Visual C++, Visual J++, Visual Basic,只要包含这些关键字的信息统统的返回来。

Qt的文档设计的相当优秀。你可以到doc.tolltech.com上面一睹芳容。

Qt的文档完备且详细的覆盖了Qt的方方面面,竟然仅有18M。每一个类和方法都被详尽描述,巨细靡遗,举例充实。通过Trolltech公司提供的链接或者是Qt Assistant工具,可以方便的从一个类或者方法跳转到其他的类。文档还包含了一个初学者教程和一些典型应用的例子。同时还提供了FAQ和邮件列表,方便通过Internet或者用户群来查阅。如果你购买了授权,在一天之内你将会得到Trolltech公司的技术支持。

实际上,Qt优秀的帮助文档使得寻求外部帮助的机会大大减少。Tolltech公司的一个宗旨是:有如此优秀的Qt产品以及其帮助文档,技术支持是多余的。

Unicode
使用MFC,如果要显示unicode,在编译链接的时候必须用到特殊的参数(和改变可执行文件执行的入口),必须在每个string前面加上T,将char修改成TCHAR,每个字符串处理函数(strcpy(), strdup(), strcat()...... )都要改变成另外的函数名。更令人恼火的是支持Unicode的软件竟然不能和不支持Unicode的DLL一起工作。当使用外部DLL来开发的时候这是个很严重的问题,但是你毫无选择。

使用Qt,字符串用QString来处理,其本身是与生俱来的Unicode.不需要改变什么东西。不要在编译/链接时候增添参数,不要修改代码,只需要使用QString就可以了。

QSting类功能强大,你可以广泛的使用它,并且不要担心Unicode问题。这使得转换为Unicode非常的方便。QSting提供了转换为char * 和UTF8的函数。

显然,MFC的CString的设计相比于Qt的QString设计有着巨大的不同。CString以char *为基础提供了很少的功能。它的优点是当需要char *类型的时候,可以直接使用CString类型。乍看起来这个好像是个优点,其实实质上还是有很大的缺陷的,特别是可以直接修改char * 而不要更新类。在转变为Unicode的时候这个也碰到很大的麻烦。

相反,QString在内部以unicode存储string,需要时提供char *功能。实际上很少用到char *,因为整个Qt的API用文本的方式响应QString参数。QString还附带许多其他的功能,比如自动分享QString的内容。这是一个非常强大的类,你会喜欢在很多地方用它的。

国际化
使用MFC是可以国际化的,但是需要将每一个字符串放在一个字符串表中,在代码中到处使用LoadString(IDENTIFIET)。然后转化这些资源到DLL中,翻译字符串到所需要的语言,改变图形界面,然后调用程序使用这个DLL。整个过程是如此的繁琐,可谓牵一发而动全身。考虑的事情要面面俱到。

使用Qt的时候,只需要将字符串置于函数tr()中,在程序开发中这算是举手之劳。可以直接在代码中改变字符串的参考。Qt Linguist,Qt的一个工具,能够提取所有待翻译的string并按照友好的界面显示出来。这个用户界面非常适合翻译,使用字典,显示字符串内容,恰当的unicode显示,快捷方式冲突检测,检测未翻译的字符串,检测字符串修改情况,功能齐全。这个软件可以供没有任何编程经验的翻译者使用。同时该软件在GPL的版权下发布,可以按照你的需求来修改它。
翻译以后的文档保存在XML中,适合软件复用的原则。为软件增加一种新的语言版本仅仅是用Qt Linguist产生一个新的文件而已。

resources问题
使用MFC,一部分开发过程要依靠“resources”,在很多的案例中开发者必须使用他们。这样会导致如下的后果:

出了Visual Studio,你很难使用其他的工具来完成开发。  
资源编辑器仅有有限的功能,比如:通过Dialog编辑器不可能改变所有的属性,一些属性可以改变,另一些属性则不可能改变。(译者注:下面还有两条陈述MFC缺点的实例,但我感觉这些已经够说明问题了,暂时删节不译)  
然而Qt并没有资源的概念,这就解决了以上所提到的问题。Qt提供了一个脚本使得能将编入你的代码。对于界面设计,Qt Designer则创建了可读的代码。

价格
一旦你购买了Visual Studio,你将免费的获得MFC SDK。

Qt在Unix上是可以免费获得其遵守GPL版权的版本(译者注:现在在windows 上也可以免费获得其GPL版本)。如果要开发不公开源代码的软件,必须购买Qt的授权。在特定平台下,每个开发者购买一个永久性授权,并获得一年的技术支持。(译者注:后面关于购买价格等问题删去,因为价格不固定,如果有疑问请到官方网站查询价格)

发布
在发布基于MFC的软件时,必须依靠存在于客户电脑上的MFC。但是这是不安全的,同样是MFC42.dll,可以基于相同的库得到3个不同的版本。通常,需要检查是否拥有正确的MFC42.dll版本,如果不是,就升级它。但是升级MFC42.dll会改变很多软件的行为。这让我感到很不舒服,如果用户在安装我的软件以后导致其机器死机该怎么办?

Qt则没有这个风险,因为Qt压根就没有“升级整个系统”这个概念。

usidc5 2011-06-15 19:46
关键字:Microsoft Visual C++ 6.0、编辑框、回车键、消息、资源

  一、引言

  在通常的以CEditView为基类的单文档/多文档视图程序中,可以很好的响应键盘输入的回车键,只需比较最近两次的输入的字符,看看最新输入的字符是否内码是13(0x0d,回车键的内码)即可识别出来,而要单独把一个编辑框放入对话框中却根本不响应,这个看似简单的问题在实际应用中还是解决起来比较困难的。尤其是当一个充当表单录入的对话框上有若干个编辑框,这就要求在一个编辑框添完一项表单后用习惯的回车键将该编辑框上的数据读取到内存中去,并自动将光标移动到下一个编辑框中准备填写下一栏表单。无疑这种界面是十分人机友好的,使录入人员不必去执行每填一下表单就去按一下执行读入到缓存功能的按钮的烦琐操作。但上述功能的实现却并不象其演示的功能那样简单,下面本文就对这项技术的实现及附带的其他技术作简要的介绍。

  二、不能响应回车键的原因分析

  之所以在以CEditView作为基类的程序中可以响应回车键,是由于该程序的视类本身就是一个Edit控件,这就是问题的关键所在。CEditView作为CView的派生类能响应从键盘输入的各种消息,其中有和键盘输入相关的WM_CHAR、WM_KEYDOWN、WM_KEYUP等消息。我们就可以在这些消息的响应函数中灵活地设计程序去捕捉到回车键的输入,并执行响应的操作。

  当我们将编辑框作为一个普通的控件放到对话框上时情况就发生了变化。在此我们以CFormView为例,它也是CView的一个派生类,视是一个Form窗体(即对话框),当放有编辑框的窗体有回车键输入时,由于只有编辑框可以接受从键盘输入的字符,所以当键盘按下时统统把消息都发给了编辑框(在Windows下每个窗口、按钮、编辑框都看作一个窗口,都可以接受消息),可以通过ClassWizard在"Object IDs"选中编辑框所对应的ID号,在右边的消息框中可以看出该编辑框并不能响应WM_CHAR等消息,只能用EN_CHANGE事件来做类似的响应。可当我们加入了对该事件的处理函数时,却又将回车键当作控制字符,当输入回车键并不会激发EN_CHANGE事件,也就是说用这种方法仍旧无法捕获回车键的输入。

  三、拦截回车键的思路与方法

  Windows操作系统下各个窗口、控件归根结底都是通过系统的各种各样的消息来相互协调、相互联系的,而我们所遇到的这个问题换到消息的角度说就是"如何使程序能响应在编辑框上输入的回车键所发出的消息",只要能响应到这个消息,剩下的工作都可以在消息处理函数中完成。所以有必要对Windows系统的消息机制做些了解。

  每个Windows应用程序开始执行后,Windows都为该程序创建一个"消息队列(message queue)",用来存放邮寄给该程序可能创建的各种不同窗口的消息。消息队列中消息的结构(MSG)为:

typedef struct tagMSG{
 /*msg*/
 HWND hwnd;//窗口句柄,标识接收消息的窗口。
 UINT message;//消息标识号,如WM_TIMER等。
 WPARAM wParam;//消息参数,当为键盘消息时,表示虚拟键码如VK_RETURN等。
 LPARAM lParam;//消息参数。
 DWORD time;//邮寄消息的时间。
 POINT pt;//邮寄消息时的光标位置,用屏幕坐标表示。
}MSG;

  在系统下最常用的消息循环是调用GetMessage()函数从消息队列中取出消息,然后调用DespatchMessage() 函数让系统把消息发送给窗口函数,一般情况下其结果是把窗口的所有消息都传送给窗口函数。但特殊情况下可以在GetMessage()函数获得消息而又没发送出去之前,通过TranslateMessage()函数可以中途对消息进行解析,可以对指定的消息进行拦截,拦截后即可以照样发送出去,也可以不继续发送,完成对该消息的拦截,下面代码是该过程的示例:

MSG msg;
while(GetMessage(&msg,NULL,NULL,NULL,NULL){
 TranslateMessage(&msg);
 …… //对拦截的消息进行处理
 DispathchMessage(&msg);
}

  由于按下回车键时把产生的消息加入到消息队列中了,也传给了编辑框,但仅仅是由于编辑框没有能力处理该消息而造成了无法对回车键的响应,所以可以在消息循环里在把消息发送到编辑框之前就对消息进行拦截,并对其进行处理。其效果同编辑框响应回车键是一样的,仅在时序上有所提前而已。上述代码是在SDK(Software Develope Kits)下使用的,在MFC(Microsoft Foundation Class)下早已对其进行了封装,可以通过重载虚函数PreTranslateMessage()对所关心的消息进行解析:

BOOL CTestView::PreTranslateMessage(MSG* pMsg)
{
 if (WM_KEYFIRST <= pMsg->message && pMsg->message <= WM_KEYLAST)
 {
  if(pMsg->wParam==VK_RETURN )
  
 }
 return CFormView::PreTranslateMessage(pMsg);
}



  在上面的代码中,首先将pMsg->message所表示的消息同WM_KEYFIRST 和WM_KEYLAST比较,确定是键盘消息,然后通过消息参数pMsg->wParam的值来判断是否是回车键(VK_RETURN,虚拟键码可以从SDK相关资料查到)。如是,则可以将已输入到编辑框中的字符读取到m_Text中,并将其显示出来。



四、对编辑框的识别

  前面已经可以对回车键响应了,可一个表单窗体有若干个编辑框,其各自的处理方式不尽相同,这就有必要对编辑框进行识别、对不同的编辑框做不同的处理。而且当按下回车键时必须保证只有当前有焦点的编辑框能完成对回车键的响应动作,否则也就失去了实际意义。

  在Windows下的程序中,所有的资源都是有唯一标号的,使每个资源对象能唯一的区别于其他资源,所以我们可以通过资源ID来对编辑框做出区别,使之完成各自的响应处理。在Microsoft Visual C++ 6.0下可以通过"View"菜单的"ID= Resource Symboles…"查到指定ID的资源标识号的实际数值,如在本例中的两个编辑框IDC_EDIT1和IDC_EDIT2所对应的数值分别为1000和1001,对前面的解析消息的代码做些改动,主要如下所示:

……
if(pMsg->wParam==VK_RETURN )
{
 HWND hWnd=::GetFocus();
 int iID=::GetDlgCtrlID(hWnd);
 if(iID==1000)//第一个编辑框的标识为1000
 
 if(iID==1001) //第二个编辑框的标识为1001
 
}
……

  在此通过API函数::GetFocus()(注意前面的"::",标识是全局API函数,而非某个类中的成员函数)取得当前光标所处的(即有焦点的)编辑框的句柄,然后通过API函数::GetDlgCtrlID()根据这个句柄返回此窗口资源的ID 号,该ID号是动态获取的,使之同预先查看好的编辑框的ID作下比较即可区分出是需要哪个编辑框对回车键作出响应。

  小结:

  本文通过对消息的解析实现了对特定编辑框的回车键的响应,在对消息机制有了基本的了解之后,可以用与本文类似的方法,对代码稍作改动,就可以使其他一些不能响应特殊消息的控件能接收、处理特定的消息。

usidc5 2011-06-16 13:44
在程序中指定颜色一般试用RGB函数,分别输入R、G、B值。RGB函数返回颜色值(Long)。如果在程序中直接指定颜色对应的值,可以稍微加快程序的运行速度。特别在使单双行显示不同颜色等频繁调用RGB函数时,效果要好一些。    
    
   下表是一些常用颜色的值    
    
   Colour      Red      Green      Blue      值    
   黑色   Black    0   0    0    0    
   白色   White    255    255    255    16777215    
   灰色   Gray    192    192    192    12632256    
   深灰色    Dark    Grey    128    128    128    8421504    
   红色    Red    255    0    0    255    
   深红色    Dark    Red    128    0    0    128    
   绿色    Green    0    255    0    65280    
   深绿色    Dark    Green    0    128    0    32768    
   蓝色    Blue    0    0    255    16711680    
   深蓝色    Dark    Blue    0    0    128    8388608    
   紫红色    Magenta    255    0    255    16711935    
   深紫红    Dark    Magenta    128    0    128    8388736    
   紫色    Cyan    0    255    255    16776960    
   深紫    Dark    Cyan    0    128    128    8421376    
   黄色    Yellow    255    255    0    65535    
   棕色    Brown    128    128    0    32896    
   特殊颜色    
   Button    Face                79741120    
   Text                33554432    
   Background                1090519039    
   App.    Workspace                276856960    
    
   RGB函数计算公式:    颜色值    =    (65536    *    Blue)    +    (256    *    Green)    +    (Red)

==============================================

rgb(r,g,b)=r+g*256+b*256*256

=================================

Dim color As Double
Dim r, g, b As Integer
color = RGB(0, 0, 0)
r = color Mod 256
g = (color \ 256) Mod 256
b = (color \ 256 \ 256) Mod 256
Print r
Print g
Print b

usidc5 2011-06-16 16:00

1,  如果该Dialog中,某个button拥有focus,则敲回车键时会响应该按钮;
注:button拥有focus的方法:
(a)使用Tab键切换到某按钮;
(b)在建立Dialog时设定Tab Order,把某一button的tab顺序设为1,则该Dialog初始时,这个button拥有focus;(打开资源(.rc)文件,可以发现得到焦点的按钮总是在最前面)
(c)但是,如果在程序代码中 用SetFocus()设定某一按钮拥有focus,再敲回车键,该按钮不能被响应,不知为何?!

2,  如果该Dialog中,没有任何button拥有focus,但是该Dialog有default button,则敲回车键时会响应该default button;
例如当前的focus在edit控件上,则此时回车 会响应default button。
一般来讲,每个dialog都应该设有一个default button,且该button最好没有伤害性(比如删除、保存操作);
注:所谓Default Button,其表现为,在Dialog中如果focus不在任何按钮上,则Default button 为黑边。

3,  如果该Dialog中,没有任何button拥有focus,也没有default button,则敲回车键时:
windows为对话框提供了一个专用的键盘接口,专门用于对几个键进行处理,如:
ENTER键,给对话框发送一条WM_COMMAND消息,参数wParam被设置成IDOK;而ESC键,给对话框发送一条WM_COMMAND的消息,参数wParam被设置成IDCANCEL。所以敲回车时,会响应IDOK的处理函数OnOK(),如果在你的Dialog中没有重载OnOK(),则会调用CDialog::OnOK(),在这个函数中会调用EndDialog函数关闭对话框。
因此如果想使回车键和ESC键都不关闭对话框,则应在你的Dialog中重载OnOK()和OnCancel()函数。如下:(方法一)
void CMyDlg::OnOK()
{
       //CDialog::OnOK();
       return;
}

void CMyDlg::OnCancel()
{
       //CDialog::OnCancel();
       return;
}
但是上述方法的缺点时无法用X关闭对话框了(可以不重载OnCancel()),优点是,当focus在某个button上时,按回车键可以响应该button!

随着C++和MFC的日臻成熟,现在几乎整个消息循环(但不是全部)都被隐藏到了MFC中,为了能让任何窗口都有机会获得一点消息泵的行为,MFC提供了一个专门的虚函数PreTranslateMessage,如果你有足够的勇气去探究CWinThread中的消息处理机制的话,你会遇到类似如下的代码:
// 简化后的 CWinThread
while (GetMessage(...)) {
    if (PreTranslateMessage(...)) {
        // continue looping
    } else {
        TranslateMessage(...);
        DispatchMessage(...);
    }
}
CWinThread::PreTranslateMessage是个虚函数,在应用中,其缺省的实现以相同的名字调用另一个虚函数,CWnd::PreTranslateMessage。
所以如果想使回车键和ESC键都不关闭对话框,则可以在我们的对话框总重载PreTranslateMessage,如下:(方法二)
BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)
{
       if (pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST)
       {
              if (pMsg->wParam == VK_RETURN)
              {
                     return TRUE;
              }
              else if (pMsg->wParam == VK_ESCAPE)
              {    
                     return TRUE;
              }
       }
       return CDialog::PreTranslateMessage(pMsg);
}
上述做法的缺点是:回车键彻底无效了,即使某个button拥有focus,按回车也不行去响应他!优点是:可以用X关闭对话框!

4,如果想在对话框中使用回车键,比如使得多个Edit控件响应回车键:
首先看一下:
typedef struct tagMSG{
 /*msg*/
 HWND hwnd;//窗口句柄,标识接收消息的窗口。
 UINT message;//消息标识号,如WM_TIMER等。
 WPARAM wParam;//消息参数,当为键盘消息时,表示虚拟键码如VK_RETURN等。
 LPARAM lParam;//消息参数。
 DWORD time;//邮寄消息的时间。
 POINT pt;//邮寄消息时的光标位置,用屏幕坐标表示。
}MSG;

  在Windows下的程序中,所有的资源都是有唯一标号的,使每个资源对象能唯一的区别于其他资源,所以我们可以通过资源ID来对编辑框做出区别,使之完成各自的响应处理。在Microsoft Visual C++ 6.0下可以通过"View"菜单的"ID= Resource Symboles…"查到指定ID的资源标识号的实际数值,如在本例中的两个编辑框IDC_EDIT1和IDC_EDIT2所对应的数值分别为1000和1001,对前面的解析消息的代码做些改动,主要如下所示:
BOOL CMyDialog::PreTranslateMessage(MSG* pMsg)
{
 if (WM_KEYFIRST <= pMsg->message && pMsg->message <= WM_KEYLAST)
 {

if(pMsg->wParam==VK_RETURN )
{
     HWND hWnd=::GetFocus();
     int iID=::GetDlgCtrlID(hWnd);
     if(iID==1000)//第一个编辑框的标识为1000
     {
         UpdateData(TRUE);
         AfxMessageBox(m_Text1);//显示第一个编辑框的内容
     }
     if(iID==1001) //第二个编辑框的标识为1001
     {
         UpdateData(TRUE);
         AfxMessageBox(m_Text2);//显示第二个编辑框的内容
     }
}
}
  在此通过API函数::GetFocus()取得当前光标所处的(即有焦点的)编辑框的句柄,然后通过API函数::GetDlgCtrlID()根据这个句柄返回此窗口资源的ID 号,该ID号是动态获取的,使之同预先查看好的编辑框的ID作下比较即可区分出是需要哪个编辑框对回车键作出响应。

4,  如何解决 某个对话框中的Child Dialog的按钮拥有focus,但此时回车键不能响应它?

问题解释:此时回车,会产生WM_COMMAND消息,参数是当前拥有focus的按钮的ID,但是由于child dialog没有消息循环,所以这个消息被 父dialog截获。可是,父dialog中并没有这个button ID,所以它也无法响应。(如果父dialog中有这个button id, 则在child dialog中回车会响应父dialog中相同id的button!!)因此,我们要做的是在父dialog无法处理这个消息的时候,将其返回给child dialog 处理。
示例:
BOOL CParentDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
       /*When the focus is on a button of child dialog, click [enter] key, and then the message will go here(Parent dialog)!But the parent dialog doesn't have this nID, so OnCmdMsg will return false.In this condition, we just return it to the child dialog to deal with it...*/
       if (!CParentParentDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
       {
              if (m_dlgchild1.IsWindowEnabled())
              {
                     return m_ dlgchild1.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);      
              }
              else if (m_ dlgchild2.IsWindowEnabled())
              {
                     return m_ dlgchild2.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
              }
              else if (m_ dlgchild2.IsWindowEnabled())
              {
                     return m_ dlgchild2.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
              }
              else
               return FALSE;
       }
       return TRUE;
}

usidc5 2011-06-17 18:34

串行化数据


——例子程序:Memo


  创建一个新的单文档 SDI 应用,视图类选择 CFormView,以便用户可以在窗口中输入。 在界面中创建三个编辑框,然后再添加三个相应的编辑框变量。这三个变量是视图类的成员变量,为了交互数据,文档类中也要创建三个对应的变量。然后,文档类和视图类都要对数据成员进行初始化操作,在文档类中这个工作通常都在 OnNewDocument() 函数中进行。因为下面任何一个操作发生时都触发文档类 OnNewDocument()函数执行:


当用户启动应用程序;
当用户在“File”菜单中选择“New”选项;
视图类的初始化通常由 OnInitialUpdate() 负责,下面的任何一个操作发生时,代码都会触发视图类 OnInitialUpdate()函数执行 :


当用户启动应用程序;
当用户在“File”菜单中选择“New”选项;
当用户从“File”菜单中选择 “Open”选项;
在视图类中获得文档类指针的方法是:CFooDoc* pDoc = GerDocument();
用此文档指针便可以操作文档类数据:m_ViewData = pDoc->m_DocData;


串行化的代码很简单,ar 是一个与用户选择的文件相对应的文档对象(CArchive 对象):


// CFooDoc 序列化
void CFooDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        // 将数据写入文件
         ar << m_DocData;
    }
    else
    {
        // 从文件中读取数据
         ar >> m_DocData;
    }
}
  这样就将数据写入了文件,选择“File”菜单中的“Save”或者“Save as”即可完成数据的串行化。 如果没有保存数据,退出程序是会提示用户是否保存修改过的数据。具体细节请参考源代码。


串行化C++对象


——例子程序:PHN


创建一个新的单文档 SDI 应用,视图类选择 CFormView,以便可以有窗口中用户可以输入。


声明一个要串行化的 C++ 类。如 CPhone;


文档类的处理:
  在文档类中声明一个 MFC CObList 类对象,这个类很有用,功能也很强,用它可以很轻松地维护 C++ 对象列表,例如 添加、删除列表元素等。在文档类的头文件中作如下声明:


CObList m_PhoneList;
  上面的声明可以是 public 类型,这样其它类可以直接访问它。也可以是 private 类型,这样就必须声明一个公共的访问函数,比如:GetPhoneList(),这个函数能返回 m_PhoneList 的地址。


通常可以在文档类的 OnNewDocument()函数中进行数据初始化;


    // Create a CPhone Object
    CPhone* pPhone = new CPhone();
    pPhone->m_Name = "";
    pPhone->m_Phone = "";


    // Add new object to the m_PhoneList list
    m_PhoneList.AddHead(pPhone);        
  在此 CPhone 类的成员变量的初始化不是必须的,因为 CPhone 的构造函数已经完成了这个工作。AddHead()函数向 m_PhoneList 列表添加刚创建的 CPhone 对象。所以,无论什么时候创建新文档(如启动应用程序)都会向 m_PhoneList 列表中添加一个空的 CPhone 对象。注意类 CObList 的成员函数 AddHead() 是向列表的“头部”添加对象(列表的开始),所以参数是想要添加的对象的地址。
删除 m_PhoneList 列表中的内容


  因为 m_PhoneList 是在内存中维护的,所以要随时维护,只要下面三个事件中的任何一个事件发生,都需要从内存中删除 m_PhoneList 列表中的对象:


用户退出应用程序;
用户开始一个新的文档,如从“File”菜单中选择“New”选项;
用户打开一个已存在的文档,如从“File”菜单中选择“Open”选项;
在文档类的头文件中声明删除操作的函数:


virtual void DeleteContents();
其实现如下:


// 删除列表中的所有项目并释放列表对象占用的内存
while ( ! m_PhoneList.IsEmpty() )
{
    delete m_PhoneList.RemoveHead();
}


视图类处理:


声明视图类的数据成员:


POSITION m_position; // 在文档类列表中的当前位置
CObList* m_pList; // 指向文档类的列表
在 OnInitialUpdate()函数中初始化视图类的数据成员


    POSITION m_position;  
    CObList* m_pList;    




    // 获取文档类指针
    CFooDoc* pDoc = (CFooDoc*) GetDocument();


    // 获得文档类 m_PhoneList 的地址
    m_pList = &(pDoc->m_PhoneList);


    // 获得列表头位置
    m_position = m_pList->GetHeadPosition();


    // 用文档类数据更新视图类数据成员
    CPhone* pPhone = (CPhone*)m_pList->GetAt(m_position);
    m_Name = pPhone->m_Name;
    m_Phone = pPhone->m_Phone;


    // 用新的数据成员变量值更新屏幕显示
    UpdateData(FALSE);


    // 控制输入焦点
    ((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));


更新文档数据


当用户修改了视图类的数据成员,即修改了窗体编辑框中的内容时,执行这些代码后也会修改文档类的数据成员。


void CFooView::OnEnChangeName()
{
    // 用屏幕输入更新控件变量
    UpdateData(TRUE);


    // 获得文档指针
    CFooDoc* pDoc =(CFooDoc*)GetDocument();


    // 更新文档
    CPhone* pPhone = (CPhone*)m_pList->GetAt(m_position);
    pPhone->m_Name = m_Name;


    // 置修改标志为 TRUE
    pDoc->SetModifiedFlag();
}


在列表中移动记录,修改视图类中相应的函数。


    // 声明一个临时的位置变量
    POSITION temp_pos;


    // 用当前的列表位置更新 temp_pos
    temp_pos = m_position;


    // 用前一个/或后一个位置更新 temp_pos
    m_pList->GetPrev(temp_pos);


    if ( temp_pos == NULL)
    {
        // no previous element
        MessageBox(_T("Bottom of file encountered!"),_T("Phone for Windows"));


    }else
    {
        // 用列表前一个记录内容更新视图成员数据
        m_position = temp_pos;
        CPhone* pPhone = (CPhone*)m_pList->GetAt(m_position);
        m_Name = pPhone->m_Name;
        m_Phone = pPhone->m_Phone;
        UpdateData(FALSE);
    }
    // 控制输入焦点
    ((CDialog*) this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));


添加和删除列表记录:


//添加记录
    // 清空屏幕输入控制
    m_Name = "";
    m_Phone = "";
    UpdateData(FALSE);


    // 创建一个新的  CPhone 对象
    CPhone* pPhone = new CPhone();
    pPhone->m_Name = m_Name;
    pPhone->m_Phone = m_Phone;


    // 添加新的对象到列表尾部,并用新的位置更新 m_position
    m_position = m_pList->AddTail(pPhone);


    // 获得文档指针
    CFooDoc* pDoc = (CFooDoc*) GetDocument();


    // 置修改标志为 TRUE
    pDoc->SetModifiedFlag();


    // 控制输入焦点
    ((CDialog*) this)->GotoDlgCtrl(this->GetDlgItem(IDC_NAME));


//删除记录
    // 删除前先保存旧的指针
    CObject* pOld;
    pOld = m_pList->GetAt(m_position);


    // 从列表中删除元素
    m_pList->RemoveAt(m_position);


    // 从内存中删除对象
    delete pOld;


    // 如果列表已经清空则添加一个空记录
    if ( m_pList->IsEmpty())
    {
        OnBnClickedAddButton();
    }


    // 获取文档指针
    CPHNDoc* pDoc = (CPHNDoc*) GetDocument();


    // 置修改标志为 TRUE
    pDoc->SetModifiedFlag();


    // 显示列表的第一条记录
    OnInitialUpdate();




串行化处理


  我们要串行化 CPhone 对象,把C++对象写入文件,所以需要在 CPhone 类的定义和实现文件中加入相应的串行化代码,首先要在 CPhone 头文件中加入一个 MFC 宏,这是串行化需要的宏,必须为它提供一个参数,也就是类的名字。


// 串行化宏定义
DECLARE_SERIAL(CPhone)
  其次是声明串行化函数,这个原型是必须的,因为要串行化类 CPhone 对象列表,所以 CPhone 类必须有一个属于自己的 Serialize()函数:// 串行化函数 Serialize()
virtual void Serialize(CArchive& ar);


  在 CPhone 实现文件中也要加入对应的代码,这个宏也是串行化需要的另一个宏,它有三个参数,第一个是类名,第二个是基类名,第三个是应用程序的版本号,可以将版本号定义为任何值,当串行化数据到文件时,此版本号也要写入文件。


// 串行化宏实现
IMPLEMENT_SERIAL(CPhone,CObject,0);
串行化函数 Serialize() 实现 if (ar.IsStoring())
{
     ar << m_Name << m_Phone;
}
else
{
     ar >> m_Name >> m_Phone;
}


这里要注意的是为了使用 CObList 类的成员函数 Serialize(),有几个前提条件需要满足:


列表类对象必须是 MFC CObject 类的派生类对象,也就是说 CPhone 类必须是 CObject 的派生类;
在列表中的对象类必须具备一个不带参数的构造函数。如果需要,也可以有其它带参数的构造函数;
必须声明和实现列表类的串行化函数 Serialize(),即 CPhone::Serialize();
实现列表对象的串行化必须使用 DECLARE_SERIAL/IMPLEMENT_SERIAL 宏;
调用列表 Serialize()函数


  这一步是串行化列表 m_PhoneList,也就是调用 m_PhoneList 的成员函数 Serialize()。在什么地方调用呢?记住,无论用户什么时候从“File”菜单中选择“Save”或者“Save as”或“Open”选项,都将执行文档类的 Serialize()函数,所以必须在文档类的 Serialize()函数中调用 m_PhoneList 的 Serialize()函数。
  这样一来,无论用户什么时候从 File 菜单中选择 Save/Save as 时,都将把 m_PhoneList 保存在用户选择的文件中,同样地,无论用户什么时候从选择 Open 时,都将把文件中保存的列表信息加载到 m_PhoneList 中来。m_PhoneList 的串行化调用如下:


m_PhoneList.Serialize(ar);
  只要在文档类的 Serialize() 函数中调用上面这条语句时,必须把 ar 作为参数传入,它将完成需要串行化 m_PhoneList 列表数据的所有工作。不必在if语句中再做其它处理。


定制串行化




——例子程序:ARCH


  串行化处理有时并不需要用户选择文件,此时仍要从或向一个特定文件串行化数据,本部分将描述怎样创建并定制一个 CArchive 对象。创建一个新的单文档 SDI 应用, 工程名为 ARCH。视图类仍然选择 CFormView。视图中两个编辑框和两个按钮,编辑框用于输入数据,“Save to File”按钮用于将输入的数据串行化到文件,“Load from File”按钮用于从文件中抽取数据。为简单起见,文件使用的硬编码。
下面是 “Save to File”的操作代码:


    // 用屏幕输入内容更新 m_Var1 和 m_Var2
    UpdateData(TRUE);


    // 创建文件 C:\ARC.ARC
    CFile f;
    f.Open("c:\\arc.arc",CFile::modeCreate|CFile::modeWrite);


    // 创建一个 CArchive 对象,并将文件与对象关联
    CArchive ar(&f,CArchive::store);


    // 串行化 m_Var1 和 m_Var2 到文档
    ar<

    // 关闭文档
    ar.Close();


    // 关闭文件
    f.Close();


下面是 “Load from File”的操作代码:


    // 打开文件 C:\ARC.ARC
    CFile f;
    if ( f.Open("c:\\arc.arc",CFile::modeRead == FALSE )
        return;


    // 创建一个 CArchive 对象,并将文件与对象关联
    CArchive ar(&f,CArchive::load);


    // 从对象中抽取数据并赋值给成员变量
    ar>>m_Var1>>m_Var2;


    // 关闭文档
    ar.Close();


    // 关闭文件
    f.Close();


    // 更新屏幕显示
    UpdateData(FALSE);


  以上是三个 MFC 串行化数据的例子,Memo 程序的功能是串行化数据到文件,Phn 程序是串行化 C++ 对象列表到文件,而 ARCH 则是定制串行化。详细实现细节请下载源代码。


usidc5 2011-06-17 18:38
在微软公司举行的Microsoft Windows Security Push 活动期间,一批测试者、程序管理经理和普通程序员共同决定要为 C 语言量身定制一套具有较高安全性的字符串处理函数,并且希望这些函数能被 Windows 程序员和微软公司内部的程序员所采用。
简单说来,现有的 C 语言运行时函数实在难以在当今充斥着恶意攻击企图的大环境下立足。这些函数要么在返回值和参数上缺乏一致性,要么隐含着所谓的“截断误差”(truncation errors) 错误,要么无法提供足够强大的功能。坦言之,调用这些函数的代码太容易产生“内存溢出”问题了。
  我们发现,面向 C++ 程序员的类足以应付各种安全处理字符串的编程需要;他们能够选择 MFC 的Cstring 类、ATL 的CComBSTR 类 或者STL 的string 类,等等。然而,经典的 C 语言程序仍然普遍地存在,何况许多人正在把 C++ 当作 “改良的 C 语言” 来用,却把丰富的 C++ 类束之高阁。
  其实只需要添加一行代码,你就能在 C 语言代码中调用安全性良好的 strsafe 系列函数了,详细请参阅:
《Using the Strsafe.h Functions》
这些新函数包含在一个头文件和一个函数库(可选)中,而后两者能在新版的 Platform SDK 中找到。对,就这么简单:


#include "strsafe.h"
还等什么呢!
再强调一次,对 strsafe 函数库的引用是可选的。
为了实现 strsafe 系列函数的目标,你的代码必须满足下列条件:


始终以 NULL 字符结束字符串。
始终检测目标缓冲区的长度。
始终用 HRESULT 语句产生统一的返回值。
兼顾 32 位与 64 位两种运行环境。
具有灵活性。
  我们觉得,缺乏统一性是导致现有许多 C 语言字符串处理函数容易产生安全漏洞的根本原因,而 strsafe 系列函数所带来的高度统一性恰恰是解决此问题的一剂良药。然而,strsafe 也不是万能药。单纯依靠 strsafe 系列函数并不能保证代码的安全性和坚固性——你还必须开动你的大脑才行——然而这样对解决问题还是大有帮助的!
下面给出一段采用经典 C 语言运行时间函数的代码:


void UnsafeFunc(LPTSTR szPath,DWORD cchPath) {
    TCHAR szCWD[MAX_PATH];


    GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD);
    strncpy(szPath, szCWD, cchPath);
    strncat(szPath, TEXT("\\"), cchPath);
    strncat(szPath, TEXT("desktop.ini"),cchPath);
}
  以上代码中的 bug 随处可见 —— 它没有检查任何一个返回值,而且在对 strncat 函数的调用中也没有正确地使用 cchPath (因为MAX_PATH 中保存的是目标缓冲区内剩余空间的长度,而不是目标缓冲区的总长度)。于是,“内存溢出” 问题将会快找上门来。然而,象这样的代码片段早已泛滥成灾了。如果改用 strsafe 系列函数,那么以上代码应该变成:


bool SaferFunc(LPTSTR szPath,DWORD cchPath) {
    TCHAR szCWD[MAX_PATH];


    if (GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD) &&
            SUCCEEDED(StringCchCopy(szPath, cchPath, szCWD)) &&
            SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("\\"))) &&
            SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("desktop.ini")))) {


            return true;
    }


    return false;
}                        
  这段代码不但检查了每一个返回值,还保证了适时传入同一目标缓冲区的总长度。你还可以采用 Ex 版本的 strsafe 系列函数来实现更加高级的功能,比如:
获取目标缓冲区的当前指针。
获取目标缓冲区的剩余空间长度。
以某个特定字符填充空闲缓冲区。
一旦字符串处理函数失败,就把用特定值填充字符串。
一旦字符串处理函数失败,就把目标缓冲区设成 NULL 。
  如此改进后的代码性能又如何呢?告诉你一个好消息:它与原先的代码在性能上几乎没有差别。我曾在自己的 1.8 GHz 电脑上测试过混用经典 C 语言中各种字符串连接函数的代码、混用 strsafe 系列中各种字符串连接函数的代码和混用 Ex 版本 strsafe 系列中各种字符串连接函数的代码。它们各自独立运行一百万次(没错,就是 10,000,000 次)所消耗的时间分别为:


经典 C 语言 —— 7.3 秒
Strsafe 系列—— 8.3 秒
Strsafe 系列 (Ex 版) —— 11.1 秒
在测试中,调用 Ex 版本的 strsafe 系列函数的程序会在调用失败时把缓冲区设为 NULL ,并以 0xFE 作为填充字节,代码如下:


DWORD dwFlags = STRSAFE_NULL_ON_FAILURE | STRSAFE_FILL_BYTE(0xFE);
  其中设置填充字节的代码耗时较多。事实上,如果这里仅仅把缓冲区设置为 NULL 的话,则采用 Ex 版本的 strsafe 系列函数的代码将会与采用普通的 strsafe 系列函数的代码耗时相同。
  由此可见,以上三种方案的性能差异极小。我相信你也不会经常在一个程序中数百万次地反复执行包含大量字符串处理函数的代码吧!
还有一点值得引起注意:当你引用 strsafe 系列函数时,原有的 C 语言字符串处理函数都将被自动进行 #undef 处理。这也没问题,因为调试过程中的出错信息将会告诉你哪些函数已经被相应的 strsafe 系列函数取代了。好了,请放心地使用 strsafe.h 吧!更多相关信息请参阅 《Using the Strsafe.h Functions》 http://msdn.microsoft.com/en-us/library/ms647466.aspx 。


usidc5 2011-06-17 18:40
摘要


随着计算机的发展和普及,人们对于软件的界面美观性要求越来越高。MFC提供了很多标准控件,比如按钮控件,按钮在MFC编程中有着较高的使用频率。本文将介绍如何实现在有背景图片的情况下,任意形状按钮的自绘方法。本文使用基于对话框工程程序进行演示。


关键字: VC++ 按钮 自绘 任意形状 图形


一、实现原理


我们知道windows窗口默认都是矩形,要实现任意形状的窗口就需要自绘。为此从CBUTTON派生一个按钮类CControlButton,重载DrawItem消息处理进行自绘。图片的背景是矩形的,假如我们的按钮图片是圆形的,当把图片绘制上去之后,我们发现多出了背景部分。如何消除背景呢?


为了解决这个问题,我们可以用BitBlt 中的MERGEPAINT和SRCAND的方式进行绘制。 MERGEPAINT是把图形反色后再同贴图目的地进行OR操作,而SRCAND是把图形和贴图目的地进行AND操作。在计算机中,使用的是数字图像处理,每一种颜色都是由RGB表示的,RGB是指红、绿、蓝三原色,只要有这3种颜色和对应的颜色强度就可以合成各种颜色了。比如,黑色的RGB值为(0,0,0),白色的RGB值为(255,255,255),括号内对应的是红绿蓝3种颜色的强度。在数字图像处理中可以实现OR、AND等逻辑运算。任何颜色同白色进行OR运算结果都为白色,进行AND运算结果都是该颜色本身;任何颜色跟黑色进行OR运算结果都为该颜色本身;进行AND运算结果都是黑色。为此,我们准备两张图片,如下图所示:







图1        图2


图1的背景为白色,图2是将图1中需要显示部分填充黑色而得。实现去除背景贴图关键代码如下:


if (IsMask==TRUE) //值为真则去除图片背景
{
       CDC MaskDC;
       MaskDC.CreateCompatibleDC(pDC);
       if (IsBackBmp==TRUE)//使用和主窗口相同的背景图片
       {
              CBitmap *pOldBmp;
              CDC BackDC;
              BackDC.CreateCompatibleDC(pDC);
              pOldBmp = MaskDC.SelectObject(&m_MaskBitmap);
              BackDC.SelectObject(&m_BackBitmap);
              pDC->BitBlt(0,0,rect.Width(),rect.Height(),&BackDC,BackRect.left,BackRect.top,SRCCOPY);
       }


       pDC->BitBlt(0,0,rect.Width(),rect.Height(),&MaskDC,0,0,MERGEPAINT);
       pDC->BitBlt(0,0,rect.Width(),rect.Height(),&MemDC,0,0,SRCAND);
       ReleaseDC(&MaskDC);
       }


       else
       {
              pDC->BitBlt(0,0,rect.Width(),rect.Height(),&MemDC,0,0,SRCCOPY);
       }




MaskDC是图2的DC,MemDC为图1的DC。
效果如下图所示:






可能这时你就纳闷了,为什么背景色还是白色呢,是不是代码没有去掉图片的背景色呢?答案是贴图的时候已经去掉了背景色。请看分析


按钮是一个子窗口,默认情况下主窗口和按钮子窗口背景都是白色,但是往往我们需要在主窗口上绘制一张图片,这样窗口看起来就比较美观。这样子做之后,按钮子窗口和主窗口的背景就不一样了。在进行按钮自绘的时候,那就是把按钮背景作为目的地进行OR、AND运算,因为按钮背景就是白色的,所以效果看起来也就是白色的。


要解决这个问题也很简单,我们获取按钮所在主窗口中的矩形区域,把这个区域的主窗口背景绘制到按钮中,再进行绘制按钮图片的操作就可以了。


通过这样做之后,效果如下图:








为此,我们已经得到一个图片按钮了。但仅仅这样还不行,这按钮的响应区域还是矩形区域,也就是说除了按钮图片之外的区域也响应鼠标点击。那我们就需要构造一个按钮图片区域,使用库函数SetWindowRgn就可以确定响应区域了。SetWindowRgn有个参数为HRGN类型,因此我们需要获得一个HRGN。


Jean-Edouard Lachand-Robert 写了一个 BitmapToRegion 函数,函数的功能为把一张位图根据一种颜色转化为一个区域,这个我们就可以得到一个HRGN。有关BitmapToRegion详情请看代码说明。我们用图2中的黑色区域去转化成区域,为此我们就得了一个图片按钮的响应区域了。


另外,CControlButton类还提供了通常的四态按钮的支持,即鼠标划过、点击、正常、获得焦点四种情况对应加载四张不同的位图。


二、成员函数介绍


① void CControlButton::SetMaskBitmapId(int mask, bool action)


功能:设置图2资源图片


返回值:无


参数:mask ,图2的资源ID


action,值为TRUE则有效,FALSE为无效


② void CControlButton::SetBackBmp(int nBgdBmpId,CRect rect);


功能:设置按钮背景图片


返回值:无


参数:nBgdBmpId ,主窗口背景图片资源ID


rect , 按钮在主窗口中的客户区矩形, 使用GetWindowRect, ScreenToClient这两个函数即可以轻松获得。


③ void CControlButton::SetRgnMask(int nMaskBmpId, bool nAction)


功能:设置有效区域函数:


返回值:无


参数:nMaskBmpId ,图2的资源ID


nAction ,值为TRUE则设置有效,FALSE则无效,通过这样可以使用或禁止构造响应区域


④void CControlButton::SetBitmapId(int nOver,int nNormal,int nPressed,int nFocus)


功能:设置按钮动态加载的四幅图片 :


返回值:无


参数:nOver,鼠标划过对应按钮图片资源ID。
nNormal ,正常状态下 对应按钮图片资源ID
nPressed ,按下按钮对应图片资源ID
nFocus ,获得焦点情况下图片资源ID


三、使用说明


CControlButton类从CButton类派生,使用时,只需在界面上放置一个按钮控件,添加CControlButton类,关联一个CControlButton的控件变量,然后进行初始化即可:


CRect btnRect; //定义按钮矩形变量
m_demoBtn.GetWindowRect(btnRect); //获取按钮窗口矩形区域
ScreenToClient(btnRect); //转换成客户区域


//设置按钮的背景图片,跟主窗口的背景图片一样
m_demoBtn.SetBackBmp(IDB_BACKGROUND,btnRect);
m_demoBtn.SetRgnMask(IDB_OKmask,TRUE);//设置响应区域,TRUE设置构造区域有效
m_demoBtn.SetMaskBitmapId(IDB_OKmask,TRUE);   //设置掩码图片


//设置按钮的四种状态图
m_demoBtn.SetBitmapId(IDB_btn_ok_b,IDB_btn_ok_a,IDB_btn_ok_c,IDB_btn_ok_a);


四、结束语


本类是在我朋友hurryboylqs四态图片按钮类的基础上完成,衷心感谢hurryboylqs的帮助,希望本文对大家有一点点帮助。


usidc5 2011-06-23 19:25

要找到某个CWnd对象的HWND,用GetSafeHwnd()。

在窗口类中,有句柄的成员变量,可以直接访问:   m_hWnd  
  在窗口类外,可以用AfxGetMainWnd()->m_hWnd获得。

在MainFrame里直接用this;  
  其它地方用  
  CMainFrame*   pMainFrame   =   (CMainFrame*)theApp.m_pMainWnd;  

想得到一个控件的的句柄
GetDlgItem(ID…)->m_hWnd
得到视图的句柄
AfxGetMainWnd()->GetActiveView();
SDI:  
  ((CFrameWnd*)(AfxGetApp()->m_pMainWnd))->GetActiveView();  
    
  MDI:  
  ((CFrameWnd*)(AfxGetApp()->m_pMainWnd))->GetActiveFrame()->GetActiveView();  

usidc5 2011-06-30 22:27
SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS));


或是:


HCURSOR   hc;


hc=LoadCursor(NULL,IDC_CROSS);


             SetCursor(hc);


IDC_APPSTARTING     带小沙漏的标准箭头  
IDC_ARROW           标准箭头  
IDC_CROSS           十字光标  
IDC_HAND            windows   2000:手型  
IDC_HELP            带问号的箭头  
IDC_IBEAM           i型标  
IDC_ICON            obsolete   for   applications   marked   version   4.0   or   later.    
IDC_NO              禁止符号  
IDC_SIZE            obsolete   for   applications   marked   version   4.0   or   later.   use   idc_sizeall.    
IDC_SIZEALL         十字箭头  
IDC_SIZENESW        指向东北和西南的双向箭头  
IDC_SIZENS          指向南和北的双向箭头  
IDC_SIZENWSE        指向西北和东南的双向箭头  
IDC_SIZEWE          指向东西的双向箭头  
IDC_UPARROW         上箭头  
IDC_WAIT            沙漏


当我们要自己选择一个图标的时候按照以下几个步骤:


1、定义一个成员变量: HCURSOR m_cursor;


          m_cursor = AfxGetApp()->LoadCursor(IDC_CURSOR1);


也可以


        m_cursor = LoadCursorFromFile(".\\res\\123.cur");


2、重载 OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)函数,


3、在函数里面写入:


     ::SetCursor(m_cursor);


OK,我们自定义的鼠标完成;


当然我们也可以用ani格式的动态鼠标形状,可以使程序更帅;


   和载入.cur一样:例如;


m_cursor = LoadCursorFromFile(".\\res\\123.ani");


-----------------------------------------------------------------------------------------------------------


当然也可以先把文件加载进来直接导入:按照以下步骤:


1、先把ani文件导入;


2、随便保存成什么名字;然后在程序的.rc文件下,把对应的代码 修改成以下代码:


IDR_FLY                 ANICURSOR DISCARDABLE   "fly.ani"


或者


IDR_FLY                 CURSOR DISCARDABLE   "fly.ani"


不然有的时候可能出现错误,我现在也没有搞清楚是什么问题;


或者直接在.rc文件下添加代码:


IDR_FLY                 ANICURSOR DISCARDABLE   "fly.ani"


或者


IDR_FLY                 CURSOR DISCARDABLE   "fly.ani"


就不用第一步骤了;然后


m_cursor = AfxGetApp()->LoadCursor("IDR_FLY");


这样就可以实现了;


ani格式我们可以通过一些动画或gif格式转换过来;


下面一些理论的东西可能对我们也有用,是转自其他人的博客的内容:






windows编程中有两种方法改变指针:一种是当应用的主窗口类注册时,为wndclass结构提供一个全程光标指针,另外一种方法是在程序中处理wm_setcursor消息来设置鼠标光标。标准的mfc应用程序使用第一种方法自动在主窗口注册时将光标指针设置为一个箭头。如果要改变光标指针,则可以通过在主窗口或子窗口中重载消息wm_setcursor的处理函数来重新设置鼠标指针。


// handle wm_setcursor in button class
bool cmybutton::onsetcursor(cwnd* pwnd, uint nhittest, uint msg)
{
::setcursor(m_hmycursor);
return true;
}




当用户将鼠标指针移到按钮上时,鼠标不被捕获,windows发送一个wm_setcursor消息到按钮。从上面onsetcursor的代码中可以看到,它传递一个参数是窗口句柄- 即鼠标指针所指的窗口,这里指的是按钮本身;onsetcursor传递的第二个参数是nhittest,这是一个鼠标点击测试代码,它以htxxx开头,用于wm_nchittest消息;onsetcursor传递的第三个参数是触发事件的鼠标消息的消息id-例如,wm_mousemove。wm_setcursor是专门用来设置鼠标指针的消息,当设置了鼠标指针以后,应该让它返回true以防止windows再作缺省处理。


wm_setcursor的处理机制是这样的,如果有父窗口的话,缺省的窗口过程首先发送wm_setcursor消息到父窗口,如果父窗口处理wm_setcursor消息,则windows不再作任何多余的事情,处理完消息便结束。如果父窗口不处理wm_setcursor消息,windows让子窗口来处理wm_setcursor,如果子窗口也不做任何处理,windows使用全程光标指针,如果没有全程光标指针,则使用箭头指针。




如果你在程序中要是使用动态光标指针,你必须决定是在子窗口处理wm_setcursor消息还是在父窗口中处理wm_setcursor消息。两种方法各有利弊,根据具体情况而定。一般总是让对象决定它们自己的行为属性-也就是说最好在子窗口中做处理。本文中的子窗口即按钮。这就要派生一个新的按钮类,新的按钮类有自己的消息映射及其消息处理过程。可以使用类向导来产生新的按钮类,但那样要做的事情太多。如果你已经有自己的按钮类,那当然是在自己的按钮类中处理wm_setcursor消息。如果没有自己的按钮类而又想偷懒的话,那就在对话框中处理wm_setcursor消息得啦,只是不要向面向对象专家说是我告诉你这么做的。




本文附带一个简单的基于对话框的程序,mycursor,这个例子程序示范了两种方法来改变光标指针。如果将鼠标移到按钮上,光标指针变为蓝色手指,它是通过在对话框中处理onsetcursor实现的,另外,如果将鼠标指针移到带下划线的超链接上,光标指针又变为另外一个windows中常常使用的手指。它是通过在窗口子类中实现的。cstaticlink是一个通用的可重用类,在vc知识库的范例程序中经常用到,其中cstaticlink::onsetcursor的大多数代码处理是从win32hlp.exe可执行程序中吸取手型指针,它们与本文有关光标设置的内容关系不大.

你可能感兴趣的:(vc/MFC编程)