|            
作者: 徐毅 许润涛 王宇 
  摘 要:本文通过详细讨论如何用VC实现对属性表按钮区的操作以改变属性页的外观,从而提供一种对Windows应用程序的非窗口客户区进行绘制的方法,并给出了一个简单的示例程序。 
 
 
  关键字:属性表类、非窗口区、位图 
 
 
  属性表类(CPropertySheet Class)在编写Windows应用程序时使用非常广泛,如编写安装向导程序、应用程序配置等很多应用程序都必须使用属性表类,但是如何实现用VC对属性表类的按钮区进行绘制却是一个比较困难的问题。因为VC的MFC类库封装了属性表类,使得其外观表现一般不容易改变。而在编写应用程序的过程中却常常遇到要在属性表的按钮区域进行绘制的问题,如在属性表按钮区加入公司的标识等等。属性表按钮区是非窗口客户区,因此要对其直接进行绘制需要采用一些特殊的处理。我们在实际编程开发过程中,对此问题进行了一些探索。下面我们通过示例说明在VC5.0环境下实现对属性页按钮区域位图绘制的方法。 
 
 
  1、 实现非窗口区域绘制的基本思想: 
  要完成对属性表按钮区域(即非窗口客户区)的操作,必须得到相关的绘图设备环境(CPaint DC),找出按钮区域的具体位置,才能够对其进行操作。为此,需要对MFC的CPropertySheet类进行继承,对其继承类的OnPaint消息处理函数进行重载,在OnPaint消息处理函数中,直接以当前指针为变量定义一个设备环境对象,这就是我们所需的绘图设备环境,再找出属性表类的制表控件(table control)客户区位置和属性表类的缺省按钮位置,就能够计算出按钮区域的具体位置。只要完成上述两步,对属性表按钮区的操作也就不难实现了。 
 
 
  2.示例程序具体实现 
  首先,用VC的Wizard代码生成器生成一个MFC应用程序框架,在自动生成的过程中,选择应用程序是基于对话框的程序。当生成完毕后,在将自动生成的对话框类全部删除。再手动添加一个从CPropertySheet类继承的子类CPropertySheetWithLogoDlg类和一个基于CDialog类的CFirstPropertyPage类,同时在程序App类的InitInstance方法中删除关于自动生成的对话框类的代码。并加入如下代码: 
  CPropertySheetWithLogoDlg dlg("属性表按钮区绘制"); 
  CFirstPropertyPage FirstPage; //进行类的实例化 
  dlg.SetLogoText("Example Vision"); //对要在按钮区域绘制的字符串进行赋值 
  dlg.AddPage(&FirstPage); //向属性表中添加属性页 
  int nResponse = dlg.DoModal(); 
  if (nResponse == IDOK){} 
  else if (nResponse == IDCANCEL){} 
  return FALSE; 
  这段代码使由Wizard代码生成器生成的应用程序的主框架(mainframe)成为一个属性表。其中SetLogoText是CPropertySheetWithLogoDlg类的用户自定义方法,它是给写在属性表按钮区的字符串赋值。 
  下面就是如何对按钮区域进行操作。属性表按钮区是非窗口客户区,因此我们不能通过重载CPropertySheetWithLogoDlg类的OnDraw方法来直接对属性表按钮区进行操作。而必须重载CPropertySheetWithLogoDlg类的OnPaint方法。其具体实现代码如下: 
  void CPropertySheetWithLogoDlg::OnPaint() 
  { 
  CPaintDC dc(this); //获得绘制的设备环境。 
  if(m_LogoText.IsEmpty())//判断字符串是否为空。 
  return; 
  CRect rectTabCtrl; 
  GetTabControl()->GetWindowRect(rectTabCtrl);//获得属性表的制表控件的客户区屏幕坐标。 
  ScreenToClient(rectTabCtrl);//屏幕坐标转换为窗口逻辑坐标。 
  CRect rectOk; 
  GetDlgItem(IDOK)->GetWindowRect(rectOk);//获得客户区最左按钮屏幕坐标。 
  ScreenToClient(rectOk); //屏幕坐标转换为窗口逻辑坐标。 
  dc.SetBkMode(TRANSPARENT);//背景模式设为透明。 
  CRect rectText; 
  rectText.left = rectTabCtrl.left; 
  rectText.top = rectOk.top; 
  rectText.bottom = rectOk.bottom; 
  rectText.right = rectOk.left;//获得所需绘制按钮区窗口逻辑坐标。 
  CFont * OldFont = dc.SelectObject(&m_fontLogo);//选择所需字体。 
  COLORREF OldColor = dc.SetTextColor( ::GetSysColor( COLOR_3DHILIGHT));//设置文本颜色。 
  dc.DrawText( m_LogoText, rectText + CPoint(1,1), DT_SINGLELINE | DT_LEFT | DT_VCENTER);//显示字符串。 
  dc.SetTextColor( ::GetSysColor( COLOR_3DSHADOW)); 
  dc.DrawText( m_LogoText, rectText, DT_SINGLELINE | DT_LEFT | DT_VCENTER);//显示字符串3D阴影。 
  dc.SetTextColor( OldColor);//恢复原文本颜色。 
  dc.SelectObject(OldFont);//恢复原字体。 
  } 
  在这段代码中,首先通过定义一个以thhis指针为变量的CPaintDC变量dc得到当前绘图设备环境。然后是要找出按钮区的具体位置。按钮区操作的位置实际就是属性表的制表控件客户区的最左端直到第一个按钮最左端为此的区域。也就是说,先需要得到属性表制表控件的指针,这可用CPropertySheet类的方法GetTabCtrl()得到。再通过WIN32API函数GetWindowRect()得到控件客户区的屏幕坐标。然后得到用GetDlgItem(IDOK)->GetWindowRect()得到客户区最左OK按钮的屏幕坐标。将它们都转换为窗口逻辑坐标,以控件客户区的左坐标作为操作按钮区的左坐标,以最左OK按钮的上、下坐标作为操作按钮区的上、下坐标,以最左OK按钮的左坐标作为操作按钮区的右坐标。就得到了所需按钮区的具体位置。最后只需再选择字体和文本颜色,用DrawText() 进行显示即可在属性表按钮区绘出字符串。 
  如果我们要在按钮区显示一幅位图,只须对以上代码作出很少修改,具体代码如下: 
  CBitmap bmp, *poldbmp; 
  CDC memdc; 
  CRect rect; 
  bmp.LoadBitmap(IDB_BITMAPLOGO); //载入位图资源。 
  memdc.CreateCompatibleDC(&dc);//生成一个与当前设备环境兼容的内存设备环境。 
  poldbmp = memdc.SelectObject(&bmp);//将位图写入内存设备环境。 
  GetClientRect(&rect);//获得属性表客户区的大小 
 
 
  //从内存设备环境向屏幕挎贝位图。 
  dc.BitBlt(left, rect.bottom - lower, w, h, &memdc, 0, 0, SRCCOPY); 
  //w,h为位图的宽度和高度。Left为位图距属性页左边框的距离,lower 为位图距下边框的距离。 
  memdc.SelectObject(poldbmp); 
  通过引入位图资源,再将其选入内存设备环境,最后用BitBlt函数显示到实际设备环境,我们就在按钮区绘出了所选位图。这一步骤与其它在正常窗口显示位图的方法是基本一致的。 
  3.实现所需注意问题 
  需要注意的是,属性表有两种模式,一种是正常属性表模式,另一种是向导模式(Wizard)。我们可以通过加入如下代码检测属性表的模式: 
  BOOL bWizMode; 
  //从PROPSHEETHEADER 结构中得到当前属性表的模式。 
  if( m_psh.dwFlags & PSH_WIZARD ) 
  bWizMode = TRUE; //是向导模式 
  else 
  bWizMode = FALSE; // 是正常属性表模式 
  其中,m_psh是CPropertySheet类的公有成员变量,它是PROPSHEETHEADER结构,可以通过访问该成员变量获取属性表的基本属性。 
  在正常属性表模式下,上述获取属性表按钮区位置的方法可以正确通过。而在向导模式下,属性表没有制表控件,无法得到制表控件客户区位置。在这时,需要用属性表对话框窖口代替控件客户区。可在原代码中加入如下代码: 
  if( bWizMode ) { 
  GetWindowRect(rectTabCtrl); //得到对话框窗口屏幕坐标。 
  rectTabCtrl.OffsetRect(14,0); // 对窗口位置校正 
  } 
  else{ 
  GetTabControl()->GetWindowRect(rectTabCtrl); 
  } 
  通过以上代码,我们就能够正确得到属性表按钮区域位置。 
  4.结论 
  由上述讨论可知,对于属性表按钮区进行操作,关键是要了解对非窗口客户区进行绘制是不同于在一般窗口绘制的过程时,在一般窗口绘制位图或写入字符时,只需重载窗口类的OnDraw方法,而对非窗口客户区进行绘制时,必须重载OnPaint函数。这一法则适用于对任何非窗口客户区的操作。 
 
 
  参考文献 
  1.Microsoft Widnows 95 开发者必读 翟炯 石秋云译 电子工业出版社 1997年2月。 
  2.Visual C++开发指南 屠强等译 电子工业出版社 1998年1月  
 |