科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件MFC程序员的WTL指南之属性页与向导

MFC程序员的WTL指南之属性页与向导

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

向导模式的属性表通常用来引导用户安装软件或完成其他复杂的工作。

作者:LITHE 来源:blog 2007年10月19日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
使用DDV添加更多的属性页

  为了使这个向导能够有点用处,我们要为其添加一个设置视图背景颜色的页面。这个页面还将有一个checkbox演示如何处理DDV验证失败并阻止向导进行到下一页。下面就是新的页面,ID是IDD_WIZARD_BKCOLOR:

           

  这个类的实现代码在CWizBkColorPage类中,下面是相关的部分代码

class CWizBkColorPage :
public CPropertyPageImpl<CWizBkColorPage>,
public CWinDataExchange<CWizBkColorPage>
{
 public:
  // some stuff removed for brevity...

  BEGIN_DDX_MAP(CWizBkColorPage)
   DDX_RADIO(IDC_BLUE, m_nColor)
   DDX_CHECK(IDC_FAIL_DDV, m_bFailDDV)
  END_DDX_MAP()

  // Notification handlers
  int OnSetActive();
  BOOL OnKillActive();

  // DDX vars
  int m_nColor;

 protected:
  int m_bFailDDV;
};

  OnSetActive()的工作和前面的介绍页面相同,它使“上一步”和“下一步”按钮可用。OnKillActive()是个新的处理函数,它触发DDV,然后检查m_bFailDDV的值,如果是TRUE就表示checkbox处于选中状态,OnKillActive()将阻止向导进行到下一页。

int CWizBkColorPage::OnSetActive()
{
 SetWizardButtons ( PSWIZB_BACK | PSWIZB_NEXT );
 return 0;
}

int CWizBkColorPage::OnKillActive()
{
 if ( !DoDataExchange(true) )
  return TRUE; // prevent deactivation

 if ( m_bFailDDV )
 {
  MessageBox (_T("Error box checked, wizard will stay on this page."),_T("PSheets"), MB_ICONERROR );
  return TRUE; // prevent deactivation
 }

 return FALSE; // allow deactivation
}

  需要注意的是OnKillActive()中做的事情也可以在OnWizardNext()中完成,因为这两个处理函数都可以使向导维持在当前页面。它们的不同之处在于OnKillActive()在用户单击“上一步”和“下一步”按钮时被调用,而OnWizardNext()只是在用户单击“下一步”按钮时被调用。OnWizardNext()还被用来完成其它目的,比如,它可以直接将向导引导到指定的页面而不是按顺序的下一页。

  例子工程的向导还有另外两个页面,CWizBkPicturePage 和 CWizFinishPage,由于它们和前面的两个页面相似,我就不再详细介绍它们,想了解它们的细节可以查看源代码。

  其他的界面考虑

  置中一个属性表


  属性页和向导的默认位置是出现在父窗口的左上角:


  这看起来有点不爽,还好有方法可以补救。第一种方法是重载CPropertySheetImpl::PropSheetCallback()函数,在这个函数中将属性表置中。PropSheetCallback()是MSDN中介绍的PropSheetProc()的回调函数,操作系统在属性表创建时调用这个函数,WTL也是利用这个时间子类化属性表窗口的。所以我们的第一种尝试是:

class CAppPropertySheet : public CPropertySheetImpl<CAppPropertySheet>
{
 //...
 static int CALLBACK PropSheetCallback(HWND hWnd, UINT uMsg, LPARAM lParam)
 {
  int nRet = CPropertySheetImpl<CAppPropertySheet>::PropSheetCallback (hWnd, uMsg, lParam );

  if ( PSCB_INITIALIZED == uMsg )
  {
   // center sheet... somehow?
  }

  return nRet;
 }
};

  正如你看到的,我们遇到了棘手的问题。PropSheetCallback()是一个静态方法,不能使用this指针访问属性表窗口。那将这些代码从CPropertySheetImpl::PropSheetCallback()中拷贝出来,然后添加我们自己的方法行不行呢?撇开刚才将代码和特定版本的WTL联系在一起的方法(这已经被证明不是各好方法),现在代码应该是这样的:

class CAppPropertySheet : public CPropertySheetImpl<CAppPropertySheet>
{
 //...
 static int CALLBACK PropSheetCallback(HWND hWnd, UINT uMsg, LPARAM)
 {
  if(uMsg == PSCB_INITIALIZED)
  {
   // Code copied from WTL and tweaked to use CAppPropertySheet
   // instead of T:
   ATLASSERT(hWnd != NULL);
   CAppPropertySheet* pT = (CAppPropertySheet*)
   _Module.ExtractCreateWndData();
   // subclass the sheet window
   pT->SubclassWindow(hWnd);
   // remove page handles array
   pT->_CleanUpPages();

   // Our own code follows:
   pT->CenterWindow ( pT->m_psh.hwndParent );
  }

  return 0;
 }
};

  这从理论上讲很完美,但是我试过,属性表的位置并未改变。显然,通用控件的代码在我们调用CenterWindow()之后又改变了属性表窗口的位置。

  必须放弃这个将代码封装到属性表类的方法,尽管它是个好的解决方案。我又回到原来的方案,即使用属性页窗口和属性表窗口相互协作是属性表窗口置中。我添加了一个用户定义消息UWM_CENTER_SHEET:

#define UWM_CENTER_SHEET WM_APP
CAppPropertySheet 在它的消息映射链中处理这个消息:

class CAppPropertySheet : public CPropertySheetImpl<CAppPropertySheet>
{
 //...
 BEGIN_MSG_MAP(CAppPropertySheet)
  MESSAGE_HANDLER_EX(UWM_CENTER_SHEET, OnPageInit)
  CHAIN_MSG_MAP(CPropertySheetImpl<CAppPropertySheet>)
 END_MSG_MAP()

 // Message handlers
 LRESULT OnPageInit ( UINT, WPARAM, LPARAM );

 protected:
  bool m_bCentered; // set to false in the ctor
};

LRESULT CAppPropertySheet::OnPageInit ( UINT, WPARAM, LPARAM )
{
 if ( !m_bCentered )
 {
  m_bCentered = true;
  CenterWindow ( m_psh.hwndParent );
 }

 return 0;
}

  然后,每个属性页的OnInitDialog() 方法发送这个消息到属性表窗口:

BOOL CBackgroundOptsPage::OnInitDialog ( HWND hwndFocus, LPARAM lParam )
{
 GetPropertySheet().SendMessage ( UWM_CENTER_SHEET );

 DoDataExchange(false);
 return TRUE;
}

  添加m_bCentered标志确保属性表窗口只响应收到的第一个UWM_CENTER_SHEET消息。

  在属性页中添加图标

  如果要使用属性表和属性页的未被成员函数封装的特性,就需要直接访问相关的数据结构:CPropertySheetImpl类中的PROPSHEETHEADER类型(结构)成员m_psh和CPropertyPageImpl类中的PROPSHEETPAGE类型(结构)成员m_psp。

  例如:为例子中Option属性表中的Background页面添加一个图标,就需要添加一个标志并设置属性页的PROPSHEETPAGE结构中的几个成员:

CBackgroundOptsPage::CBackgroundOptsPage()
{
 m_psp.dwFlags |= PSP_USEICONID;
 m_psp.pszIcon = MAKEINTRESOURCE(IDI_TABICON);
 m_psp.hInstance = _Module.GetResourceInstance();
}

  下面是这些代码的效果:

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章