MFC编程2

深入掌握MFC编程:《Programming Windows with MFC 2E》精华解读 Microsoft Foundation Classes (MFC) 是一个封装好的C++类库,它为开发Windows应用程序提供了一个框架。MFC封装了大量的Windows API,使得开发者可以更加快速和方便地开发复杂的图形用户界面(GUI)程序。MFC作为一个中间层,隐藏了Windows API的许多复杂性,同时提供了一个面向对象的编程接口,使得代码更加模块化和易于理解。在MFC应用程序中,按钮是一种最常见的控件,它用于响应用户的点击操作,触发相应的事件。 阅读详情
二、工具条的编程技术
  1、命令处理
  要使菜单和工具条执行命令,光为它们指定命令ID是不行的,必须为每个命令ID定义命令处理函数。如果不为命令定义命令处理函数或下面将要提到的命令更新处理函数,则框架将自动使该命令对应的菜单项和按钮禁止(灰化)。
  利用ClassWizard在CmainFrame中,加入OnRecordStart和OnRecordStop消息处理函数。
  现在要在这两个命令处理函数中插入相应的源代码以实现其功能。我们只是让这两个函数发出一个声音,象征性地表示功能的执行,具体代码如清单4.2所示。
  清单4.2 OnRecordStart和OnRecordStop函数
  void CMainFrame::OnRecordStart()
  {
  // TODO: Add your command handler code here
  MessageBeep((UINT)(-1));
  }
  void CMainFrame::OnRecordStop()
  {
  // TODO: Add your command handler code here
      MessageBeep((UINT)(-1));
  }
  2、命令更新
  虽然Start和Stop命令可以执行了,但是还有一个不足之处。在没有开始录音之前,Stop命令应该是禁止的,也即对应的菜单项和按钮应是禁止的,这是因为此时没有必要执行该命令。录音开始后,Stop命令应该允许,而Start命令则应变为禁止。我们可以利用MFC的命令更新机制实现此逻辑功能。
  在菜单下拉之前,或在工具条按钮处在空闲循环期间,MFC会发一个更新命令,这将导致命令更新处理函数的调用。命令更新处理函数可以根据情况,使用户接口对象(主要指菜单项和工具条按钮)允许或禁止。定义命令更新处理函数的方法如下:
  按Ctrl+W键进入ClassWizard。
  在Class name栏中选择CMainFrame,在Object IDs栏中选择ID_RECORD_START,在Messages栏中双击UPDATE_COMMAND_UI项,则ClassWizard会弹出一个对话框询问命令更新处理函数的名字,使用其提供的函数名即可。按OK按钮后,函数OnUpdateRecordStart就被加入到了Member
functions栏中。
  仿照步2,为ID_RECORD_STOP定义一个命令更新处理函数。
  按OK按钮关闭ClassWizard对话框。这时读者会发现CMainFrame类多了两个成员函数,OnUpdateRecordStart和OnUpdateRecordStop。
 命令更新处理函数有一个参数是CCmdUI类的指针,通过调用CCmdUI类的成员函数Enable(TRUE)或Enable(FALSE)可以使用户接口对象允许或禁止。需要给CMainFrame加一个布尔型成员变量以表明是否正在录音,这样命令更新处理函数可根据这个变量来决定用户接口对象的状态。请读者在CMainFrame类内加入下面一行代码:
  BOOL m_bWorking;
  接下来请读者按清单4.3进行修改。
  清单4.3 命令更新处理
  CMainFrame::CMainFrame()
  {
  // TODO: add member initialization code here
  m_bWorking=FALSE;
  }
  void CMainFrame::OnRecordStart()
  {
  // TODO: Add your command handler code here
  MessageBeep((UINT)(-1));
  m_bWorking=TRUE;
  }
  void CMainFrame::OnRecordStop()
  {
  // TODO: Add your command handler code here
  MessageBeep((UINT)(-1));
  m_bWorking=FALSE;
  }
  void CMainFrame::OnUpdateRecordStart(CCmdUI* pCmdUI)
  {
  // TODO: Add your command update UI handler code here
  pCmdUI->Enable(!m_bWorking);
  }
  void CMainFrame::OnUpdateRecordStop(CCmdUI* pCmdUI)
  {
  // TODO: Add your command update UI handler code here
  pCmdUI->Enable(m_bWorking) ;
  }
  m_bWorking的初值应是FALSE,对它的初始化工作在CMainFrame的构造函数中完成。m_bWorking的值在处理Start和Stop命令时会被更新以反映当前的状态。两个命令更新处理函数都调用了CCmdUI::Enable,该函数根据m_bWorking的值来更新命令接口对象。
  编译并运行Record,现在Start和Stop命令的逻辑功能已经实现了。
  3、按钮风格
  在Record程序中,用户可以选择两种采样频率来录音。用户接口对象应该能反映出当前的采样频率。普通的工具条按钮在按下后会立刻弹起来,我们希望Record程序的频率选择按钮具有单选按钮的风格,即当用户选择了一个采样频率时,该采样频率对应的按钮一直处于按下的状态,而另一个频率选择按钮应处于弹起状态。
  我们可以利用CCmdUI::SetCheck函数来实现这一功能,在命令更新函数中调用CCmdUI::SetCheck(TRUE)或CCmdUI::SetCheck(FALSE)可将用户接口对象设定为选中或不选中状态,当一个用户接口对象被选中时,相应的工具按钮会处于按下的状态,并且相应的菜单项的前面会加上一个选中标记。这里需要给CMainFrame类加一个布尔型成员变量以表明当前的采样频率。请读者在CMainFrame类内加入下面一行代码:BOOL
m_bHighQuality;
  接下来请读者按清单4.4进行修改。
  清单4.4
  CMainFrame::CMainFrame()
  {
  m_bWorking=FALSE;
  m_bHighQuality=TRUE;
  }
  void CMainFrame::OnHighQuality()
  {
  m_bHighQuality=TRUE;
  }
  void CMainFrame::OnLowQuality()
  {
  m_bHighQuality=FALSE;
  }
  void CMainFrame::OnUpdateHighQuality(CCmdUI* pCmdUI)
  {
  pCmdUI->SetCheck(m_bHighQuality);
  }
  void CMainFrame::OnUpdateLowQuality(CCmdUI* pCmdUI)
  {
  pCmdUI->SetCheck(!m_bHighQuality);
  }
  m_bHighQuality的初值是TRUE,即缺省时是高频采样,对它的初始化工作在CMainFrame的构造函数中完成。m_bHighQuality的值在处理High
quality和Low quality命令时会被更新以反映当前的状态。两个命令更新处理函数都调用了CCmdUI::SetCheck,该函数根据m_bHighQuality的值来更新命令接口对象,从而使工具条按钮具有了单选按钮的风格。
  4、工具条的隐藏/显示
  读者可能已经试过了Record程序的View菜单的功能。通过该菜单用户可以隐藏/显示工具条和状态栏,这个功能是由AppWizard自动实现的。由于第二个工具条是手工建立的,因此它不会自动具备隐藏/显示功能。但我们可以通过编程来实现第二个工具条的隐藏/显示:
  打开IDR_MAINFRAME菜单资源
  在View菜单中加入一个名为Toolbar1的菜单项,指定其ID为ID_VIEW_TOOLBAR1,并在Prompt栏中输入Show or hide the toolbar1/nToggle ToolBar1。
  按Ctrl+W键进入ClassWizard。在Class name栏中选择CMainFrame,在Object IDs栏中选择ID_VIEW_TOOLBAR1,并为该命令ID定义命令处理函数OnViewToolbar1和命令更新处理函数OnUpdateViewToolbar1。
  按清单4.5修改程序。
  清单4.5 显示/隐藏工具条
  void CMainFrame::OnViewToolbar1()
  {
  m_wndToolBar1.ShowWindow(m_wndToolBar1.IsWindowVisible()?SW_HIDE:SW_SHOW);
  RecalcLayout();
  }
  void CMainFrame::OnUpdateViewToolbar1(CCmdUI* pCmdUI)
  {
  pCmdUI->SetCheck(m_wndToolBar1.IsWindowVisible());
  }
  调用CWnd::ShowWindow(SW_SHOW)或CWnd::ShowWindow(SW_HIDE)可以显示或隐藏窗口。由于工具条也是窗口,CToolBar是CWnd类的继承类,故该函数也是CToolBar的成员。在命令处理函数OnViewToolbar1中,我们调用CToolBar::ShowWindow来显示/隐藏工具条,在调用时会利用CWnd::IsWindowVisible函数作出判断,如果工具条是可见的,就传给ShowWindow函数SW_HIDE参数以隐藏工具条,否则,就传SW_SHOW参数显示工具条。接着要调用CMainFrame::RecalcLayout以重新调整主框架窗口的布局。
  命令更新处理函数OnUpdateViewToolbar1会根据工具条是否可见使View->Toolbar1菜单项选中或不选中。
  三、状态栏的设计与实现
  状态栏实际上是个窗口,一般分为几个窗格,每个窗格显示不同的信息。AppWizard会为应用程序自动创建一个状态栏,该状态栏包括几个窗格,分别用来显示状态栏提示和CAPS
LOCK、NUM LOCK 、SCROLL LOCK键的状态。在MFC中,状态栏的功能由CStatusBar类实现。创建一个状态栏需要以下几个步骤:
  构建一个CStatusBar对象。
  调用CStatusBar::Create创建状态栏窗口。
  调用CStatusBar::SetIndicators函数分配窗格,并将状态栏的每一个窗格与一个字符串ID相联系。
  相应的代码读者可以在Record工程的CMainFrame::OnCreate成员函数中找到。
  清单4.6 创建状态栏
  …
  if (!m_wndStatusBar.Create(this) ||
  !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))
  {
  TRACE0("Failed to create status bar/n");
  return -1; // fail to create
  }
  …
  SetIndicators函数的第一个参数indicators是一个ID数组,在CMainFrame类所在的CPP文件的开头部分可以找到该数组,如清单4.7所示。
  清单4.7 ID数组
  static UINT indicators[] =
  {
  ID_SEPARATOR, // status line indicator
  ID_INDICATOR_CAPS,
  ID_INDICATOR_NUM,
  ID_INDICATOR_SCRL,
  };
  indicator数组提供了状态栏窗格的分配信息,它的第一项一般为ID_SEPARATOR,该ID对应的窗格用来显示命令提示信息,后三项都是字符串ID,读者可以在String Table字符串资源中找到这三个字符串分别是CAP、NUM和SCRL。它们对应的三个窗格用来显示键盘的状态。
  现在让我们来给状态栏再加一个时间窗格,它将用来显示系统时间。显示的格式是hh:mm:ss,即时:分:秒。
  首先在indicators数组的ID_SEPARATOR项之后插入一个名为ID_INDICATOR_CLOCK的ID。然后找到并双击名为String Table的字符串资源,打开字符串资源编辑窗口。接着在编辑窗口内按Insert键以插入一个新的字符串,请指定字符串的ID为ID_INDICATOR_CLOCK,内容为00:00:00。状态栏将根据字符串的长度来确定相应窗格的缺省宽度,所以指定为00:00:00就为时间的显示预留了空间。
  提示:上述方法不能动态改变窗格宽度,并且有时是不精确的,当系统字体改变时,这种做法可能会导致一些误差。考虑到该方法简单直观,且一般情况下问题不大,故本文用它来举例。
  时间窗格显示的时间必须每隔一秒钟更新一次。更新时间窗格的正文可调用CStatusBar::SetPaneText函数,要定时更新,则应利用WM_TIMER消息。在Windows中用户可以安装一个或多个计时器,计时器每隔一定的时间间隔就会自动发出一个WM_TIMER消息,而这个时间间隔可由用户指定。MFC的Window类提供了WM_TIMER消息处理函数OnTimer,我们应在该函数内进行更新时间窗格的工作。
  请读者利用ClassWizard给CMainFrame类加入WM_TIMER的消息处理函数OnTimer和WM_CLOSE消息的处理函数OnClose。CMainFrame::OnClose函数是在关闭主框架窗口是被调用的,程序可以在该函数中做一些清除工作。接下来请按清单4.8修改程序。
  清单4.8 CMainFrame类的部分代码
  int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
  {
  …
  SetTimer(1,1000,NULL);
  return 0;
  }
  void CMainFrame::OnTimer(UINT nIDEvent)
  {
    CTime time;
    CFrameWnd::OnTimer(nIDEvent);
    time=CTime::GetCurrentTime();
    CString s=time.Format("%H:%M:%S");
    m_wndStatusBar.SetPaneText(m_wndStatusBar.CommandToIndex(ID_INDICATOR_CLOCK),s);
    CFrameWnd::OnTimer(nIDEvent);
  }
  void CMainFrame::OnClose()
  {
  KillTimer(1);
  CFrameWnd::OnClose();
  }
  在CMainFrame::OnCreate函数内调用了CWnd::SetTimer以安装一个计时器,SetTimer的第一个参数指定计时器ID为1,第二个参数则规定了计时器的时间间隔为1000毫秒即1秒。这样,每隔1秒OnTimer函数就会被调用一次。
  在OnTimer函数中,首先构建了一个CTime对象,接着调用CTime的静态成员函数GetCurrentTime以获得当前的系统时间,然后利用CTime::Format函数返回一个按时:分:秒的格式表示的字符串,最后调用CStatusBar::SetPaneText来更新时间窗格显示的正文。SetPaneText的第一个参数是窗格的索引,对于某一个窗格ID,可调用CStatusBar::CommandToIndex来获得索引。
  在撤销主框架窗口时应关闭计时器,因此在CMainFrame::OnClose函数内调用了KillTimer函数。
  现在让我们来看一下CMainFrame的消息映射,在CMainFrame类所在CPP文件的开始部分可以找到该类的消息映射,如清单4.9所示。
  清单4.9
  BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
  //{{AFX_MSG_MAP(CMainFrame)
  ON_WM_CREATE()
  ON_COMMAND(ID_RECORD_STOP, OnRecordStop)
  ON_COMMAND(ID_RECORD_START, OnRecordStart)
  ON_UPDATE_COMMAND_UI(ID_RECORD_START, OnUpdateRecordStart)
  ON_UPDATE_COMMAND_UI(ID_RECORD_STOP, OnUpdateRecordStop)
  ON_COMMAND(ID_HIGH_QUALITY, OnHighQuality)
  ON_COMMAND(ID_LOW_QUALITY, OnLowQuality)
  ON_UPDATE_COMMAND_UI(ID_HIGH_QUALITY, OnUpdateHighQuality)
  ON_UPDATE_COMMAND_UI(ID_LOW_QUALITY, OnUpdateLowQuality)
  ON_COMMAND(ID_VIEW_TOOLBAR1, OnViewToolbar1)
  ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR1, OnUpdateViewToolbar1)
  ON_WM_TIMER()
  ON_WM_CLOSE()
  //}}AFX_MSG_MAP
  END_MESSAGE_MAP()
  读者可以看到,在消息映射表中,ClassWizard为消息处理函数和命令处理函数自动加入了消息映射。自动加入的部分呈灰色显示,位于注释行//{{AFX_MSG_MAP和//}}AFX_MSG_MAP之间。命令处理函数由ON_COMMAND宏来映射,命令更新处理函数由ON_UPDATE_COMMAND_UI,而WM_消息的处理函数由ON_WM_消息宏来映射。
  提示:今后只要看到//{{AFX_...的注释对,则说明它们之间的部分是ClassWizard自动加入的,这部分呈灰色显示。请不要随便修改它们,更不能把手工加入的部分放在//{{AFX_...注释对内,否则有可能导致ClassWizard出错。
  四、小 结
  本章主要向读者介绍了工具条和状态栏的一些实用技术。要点如下:
  在MFC中,创建一个窗口一般分两步:
  1.构建一个窗口对象。构建的方法是定义一个对象或用new操作符动态创建之。
  2.调用窗口类的Create成员函数。该函数把实际的窗口作出来,并将其HWND保存在窗口的公共数据成员m_hWnd中。
  创建工具条和状态栏的工作是在CMainFrame::OnCreate函数中完成的,OnCreate函数是在创建窗口时被调用的,这时窗口的创建已部分完成,窗口对象的HWND句柄也已有效,但窗口还是不可见的。因此一般在OnCreate函数中作一些诸如创建子窗口的初始化工作。
  afx_msg前缀保证了正确版本的消息处理函数被调用。
  工具条有两个要素:工具条资源和工具条类CToolBar。若用户只需要一个工具条,可利用AppWizard自动生成,然后再修改之。若需要多个工具条,则必须手工创建。
  如果不为命令定义命令处理函数或命令更新处理函数,则框架将自动使该命令对应的用户接口对象(主要指菜单项和按钮)禁止。利用ClassWizard可以十分方便的加入命令处理函数和命令更新处理函数。
  在菜单下拉之前,或在工具条按钮处在空闲循环期间,MFC会发一个更新命令,这将导致命令更新处理函数的调用。命令更新处理函数利用CCmdUI类来更新用户接口对象。调用CCmdUI::Enable可使用户接口对象允许或禁止,调用CCmdUI::SetCheck可使用户接口对象选中或不选中。调用CWnd::ShowWindow可以隐藏/显示一个窗口。
  要在状态栏中插入新的窗格,需要在indicator数组中插入新的字符串ID。而状态栏将根据这个字符串的长度来确定新窗格的缺省宽度。
  调用CStatusBar:: SetPaneText可更新状态栏窗格显示的正文。
  对话框
  对话框是一种用户界面,它的主要功能是输出信息和接收用户的输入。对话框与控件是密不可分的,在每个对话框内一般都有一些控件,对话框依靠这些控件与用户进行交互.一个典型的对话框例子是选择了File-Open命令后弹出的文件对话框.
  一、对话框和控件的基本概念
  1、对话框的基本概念
  对话框(Dialog)实际上是一个窗口.在MFC中,对话框的功能被封装在了CDialog类中,CDialog类是CWnd类的派生类.
  对话框分为模态对话框和非模态对话框两种。模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互,而其它用户界面对象收不到输入信息。非模态对话框的典型例子是Windows95提供的写字板程序中的搜索对话框,搜索对话框不垄断用户的输入,打开搜索对话框后,仍可与其它用户界面对象进行交互,用户可以一边搜索,一边修改文章,这样就大大方便了使用。
  从MFC编程的角度来看,一个对话框由两部分组成:
  对话框模板资源:对话框模板用于指定对话框的控件及其分布,Windows根据对话框模板来创建并显示对话框.
  对话框类:对话框类用来实现对话框的功能,由于对话框行使的功能各不相同,因此一般需要从CDialog类派生一个新类,以完成特定的功能.
  相应地,对话框的设计包括对话框模板的设计和对话框类的设计两个主要方面.
  与对话框有关的消息主要包括WM_INITDIALOG消息和控件通知消息。在对话框创建时,会收到WM_INITDIALOG消息,对话框对该消息的处理函数是OnInitDialog。
  OnInitDialog的主要用处是初始化对话框。对话框的控件会向对话框发送控件通知消息,以表明控件的状态发生了变化。
  2、控件的基本概念
  图5.1对话框中的控件
  控件(Control)是独立的小部件,在对话框与用户的交互过程中,控件担任着主要角色.控件的种类较多,图5.1显示了对话框中的一些基本的控件.MFC的控件类封装了控件的功能,下表介绍了一些常用的控件及其对应的控件类。
  控件功能对应控件类
  静态正文(Static Text)显示正文,一般不能接受输入信息。CStatic
  图片(Picture)显式位图、图标、方框和图元文件,一般不能接受输入信息.CStatic
  编辑框(Edit Box)输入并编辑正文,支持单行和多行编辑.CEdit
  命令按钮(Pushbutton)响应用户的输入,触发相应的事件.CButton
  检查框(Check Box)用作选择标记,可以有选中、不选中和不确定三种状态。CButton
  单选按钮(Radio Button)用来从两个或多个选项中选中一项.CButton
  组框(Group Box)显示正文和方框,主要用来将相关的一些控件聚成一组.CButton
  列表框(List Box)显示一个列表,用户可以从该列表中选择一项或多项.CListBox
  组合框(Combo Box)是一个编辑框和一个列表框的组合.分为简易式、下拉式和下拉列表式.CComboBox
  滚动条(Scroll Bar)主要用来从一个预定义范围值中迅速而有效地选取一个整数值.CScrollBar
  控件实际上都是窗口,所有的控件类都是CWnd类的派生类。控件通常是作为对话框的子窗口而创建的,控件也可以出现在视窗口,工具条和状态条中。
  二、对话框模板的设计
  利用Developer Studio提供的可视化设计工具,用户可以方便地设计对话框模板.
  请读者按前面章节介绍的方法利用AppWizard建立一个名为Register的MFC应用程序,并在进入MFC AppWizard对话框后按下面几步操作:
  1.在第1步中选中Single document以建立一个单文档应用程序.
  2.在第4步中使Docking toolbar项不选中,这样AppWizard就不会创建工具条.
  3.在第6步中先选择CRegisterView,然后在Base class栏中选择CEditView,这样CRegisterView将是CEditView的继承类,从而使视图具有了编辑功能.
  编译并运行Register,读者会发现Register居然是个编辑器,它可以打开、编辑和保存文本文件。当然,Register的目的不仅仅是个编辑器。假设要对某一地区的就业情况进行调查,我们希望Register程序能够登录就业情况数据并将数据存储起来。
  要登录数据,用对话框是再合适不过了。一个典型的就业情况登录对话框如图5.1所示,本节的任务就是设计如图5.1的中文对话框模板。
  切换至资源视图,选择Insert-Resource命令,并在Insert Resource对话框中双击Dialog项。完成后在资源视图中会出现一个名为IDD_DIALOG1的新的对话框模板资源。双击IDD_DIALOG1,则会打开该对话框模板的编辑窗口。缺省的对话框模板有OK和Cancel两个按钮,在窗口的旁边有一个控件面板,在控件面板上用鼠标选择一个控件,然后在对话框中点击,则相应的控件就被放置到了对话框模板中。
  选中控件或对话框后按回车键,则会弹出一个属性对话框,属性对话框用来设置控件或对话框的各种属性。属性对话框是标签式对话框,第一页是常规属性(General)。
  在控件属性对话框的常规属性中,有一些控件共同的属性:
  ID属性。用于指定控件的标识符,Windows依靠ID来区分不同的控件。
  Caption(标题)属性。静态正文、组框、按钮、检查框、单选按钮等控件可以显示标题,用来对控件进行文字说明。控件标题中的字符&使紧跟其后的字符有下划线,按Alt+下划线将启动该控件。若控件是一个单选按钮,则Alt+下划线字符将选择该按钮;若是检查框,则相当于对该检查框按空格键;若是按钮,则将激活按钮命令;若控件是一个静态正文,则将激活按tab顺序紧随其后的下一个控件。
  Visible属性。用来指定控件是否是可见的。
  Disable属性。使控件允许或禁止,一个禁止的控件呈灰色显示,不能接收任何输入。
  Tabstop属性。用户可以按Tab键移动到具有Tabstop属性的控件上。Tab移动的顺序可以由用户指定。按Ctrl+D则Tab顺序会显示出来,用户可以用鼠标来重新指定Tab顺序。缺省的Tab顺序是控件的创建次序。
  Group属性。用来指定一组控件,用户可以用箭头键在该组控件内移动。在同一组内的单选按钮具有互斥的特性,即在这些单选按钮中只能有一个是选中的。如果一个控件具有Group属性,则这个控件以及按Tab顺序紧随其后的所有控件都属于一组的,直到遇到另一个有Group属性的控件为止。
  现在就开始进行对话框模板的设计。首先,用鼠标选中对话框,按回车键,在弹出的属性对话框中将ID改为IDD_REGISTER并指定对话框的标题为“登录数据”。需要注意的是,由于要在对话框中显示汉字,因此必须设定正确的语种和字体。请读者在工作区资源视图的Dialog类型中单击鼠标选中IDD_REGISTER项,然后按Alt+Enter键,并在弹出的属性对话框中的Language栏中选择Chinese(P.R.C.)。接着,打开模板的属性对话框,单击Font...按钮,并选择“宋体”。
  接着,请将对话框模板上的所有控件删除,删除的办法是选择控件后按Del键。为了容纳所有需要的控件,需将对话框的尺寸扩大到280×180。然后,请读者按图5.1和表5.2来设计对话框模板。
  提示:对话框的尺寸单位不是象素,而是与字体的大小有关。X方向上一个单位等于字符平均宽度的1/4,Y方向上一个单位等于字符平均高度的1/8。这样,随着字体的改变,对话框单位也会改变,对话框本身的总体比例保持不变。
  表5.2
  控件类型 ID 标题(Caption) 其它属性
  组框(个人情况) 缺省 个人情况 缺省
  组框(单位情况) 缺省 单位情况 缺省
  静态正文(姓名) 缺省 姓名 缺省
  编辑框(姓名) IDC_NAME 缺省
  检查框(婚否) IDC_MARRIED 婚否 缺省
  静态正文(年龄) 缺省 年龄 缺省
  编辑框(年龄) IDC_AGE 缺省
  组框(性别) 缺省 性别 缺省
  单选按钮(男) IDC_SEX 男 Group、Tabstop
  单选按钮(女) 缺省 女 缺省
  组框(就业状况) 缺省 就业状况 缺省
  单选按钮(在职) IDC_WORK 在职 Group、Tabstop
  单选按钮(下岗) IDC_WORK1 下岗 缺省
  静态正文(工作单位) 缺省 工作单位 缺省
  编辑框(工作单位) IDC_UNIT   缺省
  静态正文(单位性质) 缺省 单位性质 缺省
  组合框(单位性质) IDC_KIND Drop List、 不排序(不选中Sort风格)、初始化列表项(见下文说明)
  静态正文(工资收入) 缺省 工资收入 缺省
  列表框(工资收入) IDC_INCOME  不排序(不选中Sort)
  按钮(确定) IDOK 确定(&Y) 缺省
  按钮(取消) IDCANCEL 取消(&C) 缺省

  请注意:
  组合框有简易式(Simple)、下拉式(Dropdown)和下拉列表式(Drop List)三种。简易式组合框包含一个编辑框和一个总是显示的列表框。下拉式组合框同简易式组合框的区别在于仅当单击下滚箭头时才出现列表框。下拉列表式组合框也有一个下拉的列表框,但它的编辑框是只读的,不能输入字符。
  组合框的列表项可以在设计模板时初始化,而列表框的初始化只能在程序中进行。请读者在组合框IDC_KIND的属性对话框的Data页中输入以下几个列表项,以作为单位性质的选项。输入时要注意,换行时不要按回车键,而应按Ctrl+回车键。国有企事业、集体企业、私有企业、中外合资、外商独资
  组合框控件的一个与众不同之处是它有两个尺寸,一个是下拉前的尺寸,一个是下拉后的尺寸。当用鼠标点击组合框上的箭头后,可设定下拉后的尺寸。
  控件最好都放在对话框模板的蓝色虚框内,控件之间的距离不要太近,否则有可能造成不正确的显示。
  安置好控件之后,下一步的任务是指定Tab顺序。按Ctrl+D键后,会显示当前的Tab顺序,通过用鼠标点击控件可以设定新的Tab顺序,如果想放弃本次修改,在对话框的空白处点击一下即可。
  最后,需要测试一下对话框。按Ctrl+T,则会弹出一个当前模板的测试对话框,这个对话框的外观和基本行为与程序中将要弹出的对话框一样。这样,读者不用编译运行程序,通过测试对话框就可以评估对话框是否合乎要求。如果发现了错误或不满意的地方,可按ESC键退出测试对话框并重新修改对话框模板。
  至此,对话框模板的设计就完成了。
  三、对话框类的设计
  完成对话框模板的设计后,就需要设计一个对话框类以实现对话框的功能。设计对话框类主要包括下面几步:
  创建对话框类。该类应从CDialog类派生。
  为对话框类加入与控件相对应的成员变量。
  为对话框进行初始化工作。
  增加对控件通知消息的处理
  1、对话框类的创建
  利用ClassWizard,程序员可以十分方便的创建MFC窗口类的派生类,对话框类也不例外。请读者按以下几步操作:
  打开IDD_REGISTER对话框模板,然后按Ctrl+W进入ClassWizard。
  进入ClassWizard后,ClassWizard发现IDD_REGISTER是一个新的对话框模板,于是它会询问是否要为IDD_REGISTER创建一个对话框类。按OK键确认。
  Create New Class对话框中,在Name栏中输入CRegisterDialog,在Base class栏中选择CDialog,在Dialog ID栏中选择IDD_REGISTER。按Create按钮后,对话框类CRegisterDialog即被创建。
  ClassWizard自动使类CRegesterDialog与IDD_REGISTER模板联系起来。
  提示:只要想创建的类是某一MFC窗口类的派生类,一般都可以利用ClassWizard来自动完成创建。创建的一般方法是:打开ClassWizard,选择Add Class->New,然后在Create New Class对话框中输入新类的类名,选择其MFC基类,如果是对话框类,则还要选择对话框的ID。
  2、为对话框类加入成员变量
  对话框的主要功能是输出和输入数据。对话框需要有一组成员变量来存储数据。在对话框中,控件用来表示或输入数据,因此,存储数据的成员变量应该与控件相对应。
  与控件对应的成员变量即可以是一个数据,也可以是一个控件对象,这将由具体需要来确定。例如,可以为一个编辑框控件指定一个数据变量,这样就可以很方便地取得或设置编辑框控件所代表的数据,如果想对编辑框控件进行控制,则应该为编辑框指定一个CEdit对象,通过CEdit对象,程序员可以控制控件的行为。需要指出的是,不同类的控件对应的数据变量的类型往往是不一样的,而且一个控件对应的数据变量的类型也可能有多种。表5.3说明了控件的数据变量的类型。
      控    件 数据 变量的类型
      编辑框CString,  int, UINT, long, DWORD, float, double, short, BOOL, COleDateTime, COleCurrency
      普通检查框 BOOL(真表示被选中,假表示未选中)
      三态检查框 int(0表示未选中,1表示选中,2表示不确定状态)
      单选按钮(组中的第一个按钮) int(0表示选择了组中第一个单选按钮,1表示选择了第二个...,-1表示没有一个被选中)
      不排序的列表框CString(为空则表示没有一个列表项被选中),
int(0表示选择了第一项,1表示选了第二项,-1表示没有一项被选中)
      下拉式组合框Cstring,  int(含义同上)
      其它列表框和组合框Cstring(含义同上)
  利用ClassWizard可以很方便地为对话框类CRegisterDialog加入成员变量。请读者按下列步骤操作。
  按Ctrl+W进入ClassWizard。
  选择ClassWizard上部的Member Variables标签,然后在Class name栏中选择CRegisterDialog。这时,在下面的变量列表中会出现对话框控件的ID。
  双击列表中的ID_AGE会弹出Add Member Variable对话框。在Member variable name栏中输入m_nAge,在Category栏中选择Value,在Variable type栏中选择UINT。按OK按钮后,数据变量m_nAge就会被加入到变量列表中。
  仿照第3步和表5.4,为各个控件加入相应的成员变量。
  将m_nAge的值限制在16到65之间。方法是先选择m_nAge,然后在ClassWizard对话框的左下角输入最大和最小值。m_nAge代表年龄,这里规定被调查的人的年龄应在16岁以上,64岁以下。有了这个限制后,对话框会对输入的年龄值进行有效性检查,若输入的值不在限制范围内,则对话框会提示用户输入有效的值。
  表5.4
      控件ID 变量类型 变量名
      IDC_AGE UINT m_nAge
      IDC_INCOME CString m_strIncome
      IDC_INCOME CListBox m_ctrlIncome
      IDC_KIND CString m_strKind
      IDC_MARRIED BOOL m_bMarried
      IDC_NAME CString m_strName
      IDC_SEX Int m_nSex
      IDC_UNIT CString m_strUnit
      IDC_WORK Int m_nWork
  读者会注意到控件IDC_INCOME居然有两个变量,一个是CString型的,一个是CListBox型的,这是完全合法的,不会引起任何冲突。之所以要加入CListBox型的变量,是因为列表框的初始化要通过CListBox对象进行。
  提示:在ClassWizard中可分别为一个控件指定一个数据变量和一个控件对象,这样做的好处是即能方便地获得数据,又能方便地控制控件。
  3、对话框的初始化
  对话框的初始化工作一般在构造函数和OnInitDialog函数中完成。在构造函数中的初始化主要是针对对话框的数据成员。读者可以找到CRegisterDialog的构造函数,如清单5.1所示。
  清单5.1 CRegisterDialog的构造函数
  CRegisterDialog::CRegisterDialog(CWnd* pParent /*=NULL*/)
  : CDialog(CRegisterDialog::IDD, pParent)
  {
  //{{AFX_DATA_INIT(CRegisterDialog)
  m_nAge = 0;
  m_strIncome = _T("");
  m_strKind = _T("");
  m_bMarried = FALSE;
  m_strName = _T("");
  m_nSex = -1;
  m_strUnit = _T("");
  m_nWork = -1;
  //}}AFX_DATA_INIT
  }
  可以看出,对数据成员的初始化是由ClassWizard自动完成的。
  在对话框创建时,会收到WM_INITDIALOG消息,对话框对该消息的处理函数是OnInitDialog。调用OnInitDialog时,对话框已初步创建,对话框的窗口句柄也已有效,但对话框还未被显示出来。因此,可以在OnInitDialog中做一些影响对话框外观的初始化工作。OnInitDialog对对话框的作用与OnCreate对CMainFrame的作用类似。
  提示:MFC窗口的初始化工作一般在OnCreate成员函数中进行,但对话框的初始化工作最好在OnInitDialog中进行。
  OnInitDialog是WM_INITDIALOG消息的处理函数,所以要用ClassWizard为RegisteritDialog类增加一个WM_INITDIALOG消息的处理函数,增加的方法是进入ClassWizard后,先选中MessageMaps标签,然后在Class name中选择CRegisterDialog,在Object IDs栏中选择CRegisterDialog,在Messages栏中找到WM_INITDIALOG并双击之,最后按OK按钮退出ClassWizard。请读者按清单5.2修改OnInitDialog函数。
  清单5.2 OnInitDialog函数
  BOOL CRegisterDialog::OnInitDialog()
  {
  CDialog::OnInitDialog();
  m_ctrlIncome.AddString("500元以下");
  m_ctrlIncome.AddString("500-1000元");
  m_ctrlIncome.AddString("1000-2000元");
  m_ctrlIncome.AddString("2000元以上");
  return TRUE;
  }
  CRegisterDialog::OnInitDialog()的主要任务是对工资收入列表框的列表项进行初始化。调用CListBox::AddString可将指定的字符串加入到列表框中。由于该列表是不自动排序的,因此AddString将表项加在列表框的末尾。
  4、对话框的数据交换机制
  对话框的数据成员变量存储了与控件相对应的数据。数据变量需要和控件交换数据,以完成输入或输出功能。例如,一个编辑框即可以用来输入,也可以用来输出:用作输入时,用户在其中输入了字符后,对应的数据成员应该更新;用作输出时,应及时刷新编辑框的内容以反映相应数据成员的变化。对话框需要一种机制来实现这种数据交换功能,这对对话框来说是至关重要的。
  MFC提供了类CDataExchange来实现对话框类与控件之间的数据交换(DDX),该类还提供了数据有效机制(DDV)。数据交换和数据有效机制适用于编辑框、检查框、单选按钮、列表框和组合框。
  数据交换的工作由CDialog::DoDataExchange来完成。读者可以找到CRegisterDialog::DoDataExchange函数,如清单5.3所示。0
  清单5.3 DoDataExchange函数
  void CRegisterDialog::DoDataExchange(CDataExchange* pDX)
  {
  CDialog::DoDataExchange(pDX);
  //{{AFX_DATA_MAP(CRegisterDialog)
  DDX_Control(pDX, IDC_INCOME, m_ctrlIncome);
  DDX_LBString(pDX, IDC_INCOME, m_strIncome);
  DDX_CBString(pDX, IDC_KIND, m_strKind);
  DDX_Check(pDX, IDC_MARRIED, m_bMarried);
  DDX_Text(pDX, IDC_NAME, m_strName);
  DDX_Radio(pDX, IDC_SEX, m_nSex);
  DDX_Text(pDX, IDC_UNIT, m_strUnit);
  DDX_Radio(pDX, IDC_WORK, m_nWork);
  DDX_Text(pDX, IDC_AGE, m_nAge);
  DDV_MinMaxUInt(pDX, m_nAge, 16, 65);
  //}}AFX_DATA_MAP
  }
  读者可以看出,该函数中的代码是由ClassWizard自动加入的。DoDataExchange只有一个参数,即一个CDataExchange对象的指针pDX。在该函数中调用了DDX函数来完成数据交换,调用DDV函数来进行数据有效检查。
  当程序需要交换数据时,不要直接调用DoDataExchange函数,而应该调用CWnd::UpdateData。UpdataData函数内部调用了DoDataExchange。该函数只有一个布尔型参数,它决定了数据传送的方向。调用UpdateData(TRUE)将数据从对话框的控件中传送到对应的数据成员中,调用UpdateData(FALSE)则将数据从数据成员中传送给对应的控件。
  在缺省的CDialog::OnInitDialog中调用了UpdateData(FALSE),这样,在对话框创建时,数据成员的初值就会反映到相应的控件上。若用户是按了OK(确定)按钮退出对话框,则对话框认为输入有效,就会调用UpdataData(TRUE)将控件中的数据传给数据成员。图5.9描绘了对话框的这种数据交换机制。
  图5.9 对话框的数据交换
  5、对话框的运行机制
  在程序中运行模态对话框有两个步骤:
  在堆栈上以变量的形式构建一个对话框对象。
  调用CDialog::DoModal ( )。
  DoModal负责对模态话框的创建和撤销。在创建对话框时,DoModal的任务包括载入对话框模板资源、调用OnInitDialog初始化对话框和将对话框显示在屏幕上。完成对话框的创建后,DoModal启动一个消息循环,以响应用户的输入。由于该消息循环截获了几乎所有的输入消息,使主消息循环收不到对对话框的输入,致使用户只能与模态对话框进行交互,而其它用户界面对象收不到输入信息。
  若用户在对话框内点击了ID为IDOK的按钮(通常该按钮的标题是“确定”或“OK”),或按了回车键,则CDialog::OnOK将被调用。OnOK首先调用UpdateData(TRUE)将数据从控件传给对话框成员变量,然后调用CDialog::EndDialog关闭对话框。关闭对话框后,DoModal会返回值IDOK。
  若用户点击了ID为IDCANCEL的按钮(通常其标题为“取消”或“Cancel”),或按了ESC键,则会导致CDialog::OnCancel的调用。该函数只调用CDialog::EndDialog关闭对话框。关闭对话框后,DoModal会返回值IDCANCEL。
  程序根据DoModal的返回值是IDOK还是IDCANCEL就可以判断出用户是确定还是取消了对对话框的操作。
  在弄清了对话框的运行机制后,下面让我们来就可以实现Register程序登录数据的功能。
  首先,将Register工程的工作区切换至资源视图。打开IDR_MAINFRAME菜单资源,在Edit菜单的底端加入一个名为“登录数据”的新菜单项,并令其ID为ID_EDIT_REGISTER(最好在该项之前加一条分隔线,以便和前面的菜单项分开)。
  接着,用ClassWizard为该菜单命令创建命令处理函数CRegisterView::OnEditRegister。注意,OnEditRegister是类CRegisterView的成员函数,这是因为CRegisterView要负责打开和关闭登录数据对话框,并将从对话框中输入的数据在视图中输出。
  然后,请读者在RegisterView.cpp文件的开头加入一行  #include "RegisterDialog.h"
  最后,按清单5.4修改程序。
  清单5.4 OnEditRegister函数
  void CRegisterView::OnEditRegister()
  {
  CRegisterDialog dlg;
  if(dlg.DoModal()==IDOK)
  {
  CString str;
  GetWindowText(str);  //获取编辑正文
  str+="/r/n";//换行
  str+="姓名:";
  str+=dlg.m_strName;
  str+="/r/n";
  str+="性别:";
  str+=dlg.m_nSex?"女":"男";
  str+="/r/n";
  str+="年龄:";
  CString str1;
  str1.Format("%d",dlg.m_nAge); //将数据格式输出到字符串对象中
  str+=str1;
  str+="/r/n";
  str+="婚否:";
  str+=dlg.m_bMarried?"已婚":"未婚";
  str+="/r/n";
  str+="就业状况:";
  str+=dlg.m_nWork?"下岗":"在职";
  str+="/r/n";
  str+="工作单位:";
  str+=dlg.m_strUnit;
  str+="/r/n";
  str+="单位性质:";
  str+=dlg.m_strKind;
  str+="/r/n";
  str+="工资收入:";
  str+=dlg.m_strIncome;
  str+="/r/n";
  SetWindowText(str); //更新编辑视图中的正文
  }
  }
  在OnEditRegister函数中,首先构建了一个CRegisterDialog对象,然后调用CDialog::DoModal来实现模态对话框。如果DoModal返回IDOK,则说明用户确认了登录数据的操作,程序需要将录入的数据在编辑视图中输出。程序用一个CString对象来作为编辑正文的缓冲区,CString是一个功能强大的字符串类,它的最大特点在于可以存储动态改变大小的字符串,这样,用户不必担心字符串的长度超过缓冲区的大小, 使用十分方便。
  在输出数据时,程序首先调用CWnd::GetWindowText获得编辑正文,这是一个多行的编辑正文。CWnd::GetWindowText用来获取窗口的标题,若该窗口是一个控件,则获取的是控件内的正文。CRegisterView是CEditView的继承类,而CEditView实际上包含了一个编辑控件,因此在CRegisterView中调用GetWindowText获得的是编辑正文。
  然后,程序在该编辑正文的末尾加入新的数据。在程序中大量使用了CString类的重载操作符“+=”,该操作符的功能是将操作符右侧字符串添加到操作符左侧的字符串的末尾。注意在多行编辑控件中每行末尾都有一对回车和换行符。在程序中还调用了CString::Format来将数据格式化输出到字符串中,Format的功能与sprintf类似。最后,调用CWnd::SetWindowText来更新编辑视图中的正文。
  编译并运行Register,打开登录数据对话框,输入一些数据试试。现在,Register已经是一个简易的数据库应用程序了,它可以将与就业情况有关的数据输出到一个编辑视图中。用户可以编辑视图中的正文,并将结果保存在文本文件中。
  6、处理控件通知消息
  虽然Register已经可以登录数据了,但读者会很快会发现该程序还有一些不完善的地方:
  登录完一个人的数据后,对话框就关闭了,若用户有很多人的数据要输入,则必须频繁地打开对话框,很不方便。在登录数据时,应该使对话框一直处于打开状态。
  登录数据对话框分个人情况和单位情况两组,若被调查人是下岗职工,则不必输入单位情况。程序应该能够对用户的输入及时地作出反应,即当用户选择了“下岗”单选按钮时,应使单位情况组中的控件禁止。一个禁止的控件呈灰色示,并且不能接收用户的输入。
  要解决上述问题,就必须对控件通知消息进行处理。当控件的状态因为输入等原因而发生变化时,控件会向其父窗口发出控件通知消息。例如,如果用户在登录数据对话框中的某一按钮(包括普通按钮、检查框和单选按钮)上单击鼠标,则该按钮会向对话框发送BN_CLICKED消息。对话框根据按钮的ID激活相应的BN_CLICKED消息处理函数,以对单击按钮这一事件作出反应。通过对按钮的BN_CLICKED消息的处理,我们可以使登录数据对话框的功能达到上述要求。
  首先,让我们来解决第一个问题。我们的设想是修改原来的“确定(Y)”按钮,使得当用户点击该按钮后,将数据输出到视图中,并且对话框不关闭,以便用户输入下一个数据。请读者按下面几步进行修改。
  修改登录数据对话框的“确定(Y)”按钮,使该按钮的标题变为“添加(&A)”,ID变为IDC_ADD。这样,当用户点击该按钮后,对话框会收到BN_CLICKED消息。由于这个BN_CLICKED消息对应的按钮ID不是IDOK,不会触发OnOK消息处理函数,因此不会关闭对话框。
  为按钮IDC_ADD的BN_CLICKED消息创建消息处理函数。创建的方法是进入ClassWizard后,选Message Maps页并在Class name栏中选择CRegisterDialog,然后在Object IDs栏中选择IDC_ADD,在Messages栏中双击BN_CLICKED。在确认使用缺省的消息处理函数名OnAdd后,按回车键退出ClassWizard。
  OnAdd要向编辑视图输出正文,就必须获得一个指向CRegisterView对象的指针以访问该对象。为此,请在CRegisterDialog类的说明中加入下面一行
Cwnd* m_pParent;//注意不要加在AFX注释对中。
  为实现IDC_ADD按钮的功能,请按清单5.5和清单5.6修改程序。主要的改动是把原来由CRegiserView::OnEditRegister完成的在视图中输出数据的任务交给CRegisterDialog::OnAdd来完成。
  清单5.5 CRegisterView::OnEditRegister函数
  void CRegisterView::OnEditRegister()
  {
  // TODO: Add your command handler code here
  CRegisterDialog dlg(this);
  dlg.DoModal();
  }
  清单5.6 CRegisterDialog类的部分源代码
  CRegisterDialog::CRegisterDialog(CWnd* pParent /*=NULL*/)
  : CDialog(CRegisterDialog::IDD, pParent)
  {
  //{{AFX_DATA_INIT(CRegisterDialog)
  . . . . . .
  //}}AFX_DATA_INIT
  m_pParent=pParent;
  }
  void CRegisterDialog::OnAdd()
  {
  // TODO: Add your control notification handler code here
  //更新数据
  UpdateData(TRUE);
  //检查数据是否有效
  if(m_strName=="" || m_nSex<0 || m_nWork<0 || m_strUnit==""
  || m_strKind=="" || m_strIncome=="")
  {
  AfxMessageBox("请输入有效数据");
  return;
  }
  CString str;
  //获取编辑正文
  m_pParent->GetWindowText(str);
  //换行
  str+="/r/n";
  str+="姓名:";
  str+=m_strName;
  str+="/r/n";
  str+="性别:";
  str+=m_nSex?"女":"男";
  str+="/r/n";
  str+="年龄:";
  CString str1;
  //将数据格式输出到字符串对象中
  str1.Format("%d",m_nAge);
  str+=str1;
  str+="/r/n";
   
  str+="婚否:";
  str+=m_bMarried?"已婚":"未婚";
  str+="/r/n";
   
  str+="就业状况:";
  str+=m_nWork?"下岗":"在职";
  str+="/r/n";
   
  str+="工作单位:";
  str+=m_strUnit;
  str+="/r/n";
   
  str+="单位性质:";
  str+=m_strKind;
  str+="/r/n";
   
  str+="工资收入:";
  str+=m_strIncome;
  str+="/r/n";
   
  //更新编辑视图中的正文
  m_pParent->SetWindowText(str);
   
  }
  CRegisterDialog的构造函数有一个参数pParent,该参数是一个指向CWnd对象的指针,用于指定对话框的父窗口或拥有者窗口。在CRegisterView:: OnEditRegister函数中,在构建CRegisterDialog对象时指定了this参数,this指针指向CRegisterView对象本身。这样在调用CRegisterDialog的构造函数时,this指针值被赋给了CRegisterDialog的成员m_pParent。OnAdd函数可利用m_pParent来访问对话框的拥有者即CRegisterView对象。
  提示:术语父窗口(Parent)是相对于子窗口而言。若某一个窗口拥有一个子窗口(Child),则该窗口就被称为子窗口的父窗口。子窗口就是具有WS_CHILD风格的窗口,子窗口依赖于父窗口且完全被限制在父窗口内部。拥有者窗口(owner)相对于被拥有者窗口而言。若某一个窗口拥有一个非子窗口,则该窗口被称为拥有者窗口。被拥有窗口(owned)不具有WS_CHILD风格,可在屏幕上任意移动。
  当用户用鼠标点击IDC_ADD按钮时,该按钮的BN_CLICKED消息处理函数CRegisterDialog::OnAdd将被调用。在OnAdd中,首先调用了UpdateData(TRUE)以把数据从控件传给对话框的数据成员变量。然后,程序要对数据的有效性进行检查,如果输入的数据不完全有效,则会显示一个消息对话框,提示用户输入有效的数据。接下来进行的工作是在视图中输出数据,这部分代码与清单5.4类似,读者应该比较熟悉了。
  完成上述工作后,登录数据对话框就变得较为实用了。打开对话框后,用户可以方便地输入多人的数据,只有按了取消按钮后,对话框才会关闭。
  接下来让我们来解决第二个问题。解决该问题的关键在于当用户点击“在职”或“下岗”单选按钮时,程序要对收到的BN_CLICKED消息作出响应。有些读者可能会想到为两个单选按钮分别创建BN_CLICKED消息处理函数,这在只有两个单选按钮的情况下是可以的,但如果一组内有多个单选按钮,则分别创建消息处理函数就比较麻烦了。利用MFC提供的消息映射宏ON_CONTROL_RANGE可以避免这种麻烦,该映射宏把多个ID连续的控件发出的消息映射到同一个处理函数上。这样,我们只要编写一个消息处理函数,就可以对“在职”和“下岗”两个单选按钮的BN_CLICKED消息作出响应。ClassWizard不支持ON_CONTROL_RANGE宏,所以我们必须手工创建单选按钮的消息映射和消息处理函数。
  首先,在CRegisterDialog类的头文件中加入消息处理函数的声明,该函数名为OnWorkClicked,如清单5.7所示。
  清单5.7 BN_CLICKED消息处理函数OnWorkClicked的声明
  . . . . . .
  protected:
  void OnWorkClicked(UINT nCmdID);
  // Generated message map functions
  //{{AFX_MSG(CRegisterDialog)
  virtual BOOL OnInitDialog();
  afx_msg void OnAdd();
  //}}AFX_MSG
  . . . . . .
  然后,在CRegisterDialog类的消息映射中加入ON_CONTROL_RANGE映射,如清单5.8所示。ON_CONTROL_RANGE映射的形式是ON_CONTROL_RANGE
  清单5.8 在CRegisterDialog类的消息映射中加入ON_CONTROL_RANGE映射
  BEGIN_MESSAGE_MAP(CRegisterDialog, CDialog)
  //{{AFX_MSG_MAP(CRegisterDialog)
  ON_BN_CLICKED(IDC_ADD, OnAdd)
  //}}AFX_MSG_MAP
   
  ON_CONTROL_RANGE(BN_CLICKED, IDC_WORK, IDC_WORK1, OnWorkClicked)
  END_MESSAGE_MAP()
  ON_CONTROL_RANGE消息映射宏的第一个参数是控件消息码,第二和第三个参数分别指明了一组连续的控件ID中的头一个和最后一个ID,最后一个参数是消息处理函数名。如果读者是按表5.2的顺序放置控件的则IDC_WORK和IDC_WORK1应该是连续的。这样,无论用户是在IDC_WORK还是在IDC_WORK1单选按钮上单击,都会调用OnWorkClicked消息处理函数。
  提示:如果不能确定两个ID是否是连续的,请用File->Open命令打开resource.h文件,在该文件中有对控件ID值的定义。如果发现两个ID是不连续的,读者可以改变对ID的定义值使之连续,但要注意改动后的值不要与别的ID值发生冲突。
  最后,在CRegisterDialog类所在CPP文件的最后插入消息处理函数CRegisterDialog::OnWorkClicked,如清单5.9所示。
  清单5.9 CRegisterDialog::OnWorkClicked消息处理函数
  void CRegisterDialog::OnWorkClicked(UINT nCmdID)
  {
  //判断“在职”单选按钮是否被选中
  if(IsDlgButtonChecked(IDC_WORK))
  {
  //使控件允许
  GetDlgItem(IDC_UNIT)->EnableWindow(TRUE);
  GetDlgItem(IDC_KIND)->EnableWindow(TRUE);
  GetDlgItem(IDC_INCOME)->EnableWindow(TRUE);
  }
  else
  {
  //清除编辑框的内容并使之禁止
  GetDlgItem(IDC_UNIT)->SetWindowText("");
  GetDlgItem(IDC_UNIT)->EnableWindow(FALSE);
   
  //使组合框处于未选择状态并使之禁止
  CComboBox *pComboBox=(CComboBox *)GetDlgItem(IDC_KIND);
  pComboBox->SetCurSel(-1);
  pComboBox->EnableWindow(FALSE);
   
  //使列表框处于未选择状态并使之禁止
  m_ctrlIncome.SetCurSel(-1);
  m_ctrlIncome.EnableWindow(FALSE);
  }
  }
  OnWorkClicked函数判断“在职”单选按钮是否被选中。若该按钮被选中,则使单位情况组中的控件允许,若该按钮未被选中,则说明“下岗”按钮被选中,这时应使控件禁止,清除编辑框中的正文,并且使组合框和列表框处于未选中状态。
  在OnWorkClicked函数中主要调用了下列函数:
  CWnd::IsDlgButtonChecked函数,用来判断单选按钮或检查框是否被选择,该函数的声明为UINT IsDlgButtonChecked(int nIDButton) const;参数nIDButton为按钮的ID。若按钮被选择,则函数返回1,否则返回0,若按钮处于不确定状态,则返回值为2。
  CWnd::GetDlgItem函数,用来获得指向某一控件的指针,该函数的声明为CWnd* GetDlgItem(int nID) const;参数nID为控件的ID。该函数返回一个指定控件的CWnd对象指针,通过该指针,程序可以对控件进行控制。
  CWnd::EnableWindow函数,该函数使窗口允许或禁止,禁止的窗口呈灰色显示,不能接收键盘和鼠标的输入。该函数的声明是BOOL EnableWindow( BOOL bEnable = TRUE );若参数bEnable的值为TRUE,则窗口被允许,若bEnable的值为FALSE,则窗口被禁止。
  CListBox::SetCurSel和CComboBox::SetCurSel函数功能类似,用来使列表中的某一项被选中,选中的项呈高亮度显示。函数的声明是int SetCurSel(int nSelect);参数nSelect指定了新选项的索引,第一项的索引值为0,若nSelect的值为-1,那么函数将清除以前的选择,使列表处于未选择状态。
  有时,需要将GetDlgItem返回的CWnd指针强制转换成控件对象的指针,以便调用控件对象专有的成员函数对控件进行控制。例如,在程序中GetDlgItem(IDC_KIND)返回的指针被强制转换成CComboBox类型,只有这样,才能调用CComboBox::SetCurSel成员函数。
  为了对控件进行查询和控制,在程序中采用了两种访问控件的方法。一种方法是直接利用ClassWizard提供的控件对象,例如m_ctrlIncome列表框对象。另一种方法是利用CWnd类提供的一组管理对话框控件的成员函数,例如程序中用到的GetDlgItem和IsDlgButtonChecked。这两种方法是在对话框内访问控件的常用方法,读者都应该掌握。表5.5列出了管理对话框控件的Cwnd成员函数。
  表5.5 用来管理对话框控件的CWnd成员函数
      函数名 功能
      CheckDlgButton 选中或不选中按钮控件。
      CheckRadioButton 选择一个指定的单选按钮并使同组内的其它单选按钮不被选择。
      DlgDirList 往一个列表框中添加文件、目录或驱动器的列表。
      DlgDirListComboBox 往一个组合框中的列表框内添加文件、目录或驱动器的列表。
      DlgDirSelect 从一个列表框中获得当前选择的文件、目录或驱动器。
      DlgDirSelectBomboBox 从一个组合框中获得当前选择的文件、目录或驱动器。
      GetCheckedRadioButton 返回指定的单选按钮组中被选择的单选按钮的ID。
      GetDlgItem 返回一个指向一给定的控件的临时对象的指针。
      GetDlgItemInt 返回在一个指定的控件中由正文表示的数字值。
      GetDlgItemText 获得在一个控件内显示的正文。
      GetNextDlgGroupItem 返回一个指向一组控件内的下一个或上一个控件的临时对象的指针。
      GetNextDlgTabItem 返回下一个tab顺序的控件的临时对象的指针。
      IsDlgButtonChecked 返回一个按钮控件的状态。
      SendDlgItemMessage 把一个消息传送给一个控件。
      SetDlgItemInt 将一个整数转换为正文,并将此正文赋给控件。
      SetDlgItemText 设置一个控件显示的正文。
  编译并运行Register看看,现在的登录数据对话框已经比较令人满意了。
  四、非模态对话框
  1、非模态对话框的特点
  与模态对话框不同,非模态对话框不垄断用户的输入,用户打开非模态对话框后,仍然可以与其它界面进行交互。
  非模态对话框的设计与模态对话框基本类似,也包括设计对话框模板和设计CDialog类的派生类两部分。但是,在对话框的创建和删除过程中,非模态对话框与模态对话框相比有下列不同之处:
  ·   非模态对话框的模板必须具有Visible风格,否则对话框将不可见,而模态对话框则无需设置该项风格。更保险的办法是调用CWnd::ShowWindow(SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。
  ·   非模态对话框对象是用new操作符在堆中动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建在堆栈上。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。
  ·   通过调用CDialog::Create函数来启动对话框,而不是CDialog::DoModal,这是模态对话框的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户的输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。众所周知,在MFC程序中,窗口对象的生存期应长于对应的窗口,也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆中构建对话框对象,而不能以局部变量的形式来构建之。
  ·   必须调用CWnd::DestroyWindow而不是CDialog::EndDialog来关闭非模态对话框。调用CWnd::DestroyWindow是直接删除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函数均调用EndDialog,故程序员必须编写自己的OnOK和OnCancel函数并且在函数中调用DestroyWindow来关闭对话框。
  ·   因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。在屏幕上一个窗口被删除后,  框架会调用CWnd::PostNcDestroy,这是一个虚拟函数,程序可以在该函数中完成删除窗口对象的工作,具体代码如下
  void CModelessDialog::PostNcDestroy
  {
  delete this; //删除对象本身
  }
  这样,在删除屏幕上的对话框后,对话框对象将被自动删除。拥有者对象就不必显式的调用delete来删除对话框对象了。
  ·   必须有一个标志表明非模态对话框是否是打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。
  提示:在C++编程中,判断一个位于堆中的对象是否存在的常用方法是判断指向该对象的指针是否为空。这种机制要求程序员将指向该对象的指针初始化为NULL值,在创建对象时将返回的地址赋给该指针,而在删除对象时将该指针置成NULL值。
  根据上面的分析,我们很容易把Register程序中的登录数据对话框改成非模态对话框。这样做的好处在于如果用户在输入数据时发现编辑视图中有错误的数据,那么不必关闭对话框,就可以在编辑视图中进行修改。
  请读者按下面几步操作:
  在登录数据对话框模板的属性对话框的More Styles页中选择Visible项。
  在RegisterView.h头文件的CRegisterView类的定义中加入
  public:
  CRegisterDialog* m_pRegisterDlg;
  在RegisterView.h头文件的头部加入对CRegisterDialog类的声明
  class CRegisterDialog;
  加入该行的原因是在CRegisterView类中有一个CRegisterDialog类型的指针,因此必须保证CRegisterDialog类的声明出现在CRegisterView之前,否则编译时将会出错。解决这个问题有两种办法,一种办法是保证在#include “RegisterView.h”语句之前有#include “RegisterDialog.h”语句,这种办法造成了一种依赖关系,增加了编译负担,不是很好;另一种办法是在CRegisterView类的声明之前加上一个对CRegisterDialog的声明来暂时“蒙蔽”编译器,这样在有#include “RegisterView.h”语句的模块中,除非要用到CRegisterDialog类,否则不用加入#include “RegisterDialog.h”语句。
  在RegisterDialog.cpp文件的头部的#include语句区的末尾添加下面两行
  #include "RegisterDoc.h"
  #include "RegisterView.h"
  利用ClassWizard为CRegisterDialog类加入OnCancel和PostNcDestroy成员函数。加入的方法是进入ClassWizard后选择Message
Maps页,并在Class name栏中选择CRegisterDialog。然后,在Object IDs栏中选择IDCANCEL后,在Messages栏中双击BN_CLICKED,这就创建了OnCancel。要创建PostNcDestroy,先在Object IDs栏中选择CRegisterDialog,再在Messages栏中双击PostNcDestroy即可。
  分别按清单5.10和5.11,对CRegisterView类和CRegisterDialog类进行修改。
  清单5.10 CRegisterView类的部分代码
  CRegisterView::CRegisterView()
  {
  // TODO: add construction code here
   
  m_pRegisterDlg=NULL; //指针初始化为NULL
  }
   
  void CRegisterView::OnEditRegister()
  {
  // TODO: Add your command handler code here
   
   
  if(m_pRegisterDlg)
  m_pRegisterDlg->SetActiveWindow(); //激活对话框
  else
  {
  //创建非模态对话框
  m_pRegisterDlg=new CRegisterDialog(this);
  m_pRegisterDlg->Create(IDD_REGISTER,this);
  }
  }
   
   
  清单5.11 CRegisterDialog的部分代码
  void CRegisterDialog::PostNcDestroy()
  {
  // TODO: Add your specialized code here and/or call the base class
   
  delete this; //删除对话框对象
  }
   
  void CRegisterDialog::OnCancel()
  {
  // TODO: Add extra cleanup here
   
  ((CRegisterView*)m_pParent)->m_pRegisterDlg=NULL;
  DestroyWindow(); //删除对话框
  }
  CRegisterView::OnEditRegister函数判断登录数据对话框是否已打开,若是,就激活对话框,否则,就创建该对话框。该函数中主要调用了下列函数:
  调用CWnd::SetActiveWindow激活对话框,该函数的声明为CWnd* SetActiveWindow( );
  该函数使本窗口成为活动窗口,并返回原来活动的窗口。
  调用CDialog::Create来显示对话框,该函数的声明为BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );
  参数nIDTemplate是对话框模板的ID。pParentWnd指定了对话框的父窗口或拥有者。
  当用户在登录数据对话框中点击“取消”按钮后,CRegisterDialog::OnCancel将被调用,在该函数中调用CWnd::DestroyWindow来关闭对话框,并且将CRegisterView的成员m_pRegisterDlg置为NULL以表明对话框被关闭了。调用DestroyWindow导致了对CRegisterDialog::PostNcDestroy的调用,在该函数中用delete操作符删除了CRegisterDialog对象本身。
  编译并运行Register,现在登录数据对话框已经变成一个非模态对话框了。
  5.4.2 窗口对象的自动清除
  一个MFC窗口对象包括两方面的内容:一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。
  删除窗口最直接方法是调用CWnd::DestroyWindow或::DestroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd::DestroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd::DestroyWindow来删除对话框及其控件。
  窗口对象本身的删除则根据对象创建方式的不同,分为两种情况。在MFC编程中,会使用大量的窗口对象,有些窗口对象以变量的形式嵌入在别的对象内或以局部变量的形式创建在堆栈上,有些则用new操作符创建在堆中。对于一个以变量形式创建的窗口对象,程序员不必关心它的删除问题,因为该对象的生命期总是有限的,若该对象是某个对象的成员变量,它会随着父对象的消失而消失,若该对象是一个局部变量,那么它会在函数返回时被清除。
  对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。初学者在学习C++编程时,对new操作符的使用往往不太踏实,因为用new在堆中创建对象,就不能忘记用delete删除对象。读者在学习MFC的例程时,可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。
  如前面讲述非模态对话框时所提到的,当调用CWnd::DestroyWindow或::DestroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。
  不具有自动清除功能的窗口类如下所示。这些窗口对象通常是以变量的形式创建的,无需自动清除功能。
  所有标准的Windows控件类。
  从CWnd类直接派生出来的子窗口对象(如用户定制的控件)。
  切分窗口类CSplitterWnd。
  缺省的控制条类(包括工具条、状态条和对话条)。
  模态对话框类。
  具有自动清除功能的窗口类如下所示,这些窗口对象通常是在堆中创建的。
  主框架窗口类(直接或间接从CFrameWnd类派生)。
  视图类(直接或间接从CView类派生)。
  读者在设计自己的派生窗口类时,可根据窗口对象的创建方法来决定是否将窗口类设计成可以自动清除的。例如,对于一个非模态对话框来说,其对象是创建在堆中的,因此应该具有自动清除功能。
  综上所述,对于MFC窗口类及其派生类来说,在程序中一般不必显式删除窗口对象。也就是说,既不必调用DestroyWindow来删除窗口对象封装的窗口,也不必显式地用delete操作符来删除窗口对象本身。只要保证非自动清除的窗口对象是以变量的形式创建的,自动清除的窗口对象是在堆中创建的,MFC的运行机制就可以保证窗口对象的彻底删除。
  如果需要手工删除窗口对象,则应该先调用相应的函数(如CWnd::DestroyWindow)删除窗口,然后再删除窗口对象.对于以变量形式创建的窗口对象,窗口对象的删除是框架自动完成的.对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).对于具有自动清除功能的窗口对象,只需调用CWnd::DestroyWindow即可删除窗口和窗口对象。注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象.
  提示:在非模态对话框的OnCancel函数中可以不调用CWnd::DestroyWindow,取而代之的是调用CWnd::ShowWindow(SW_HIDE)来隐藏对话框.在下次打开对话框时就不必调用Create了,只需调用CWnd::ShowWindow(SW_SHOW)来显示对话框.这样做的好处在于对话框中的数据可以保存下来,供以后使用.由于拥有者窗口在被关闭时会调用DestroyWindow删除每一个所属窗口,故只要非模态对话框是自动清除的,程序员就不必担心对话框对象的删除问题.
  5.5 标签式对话框
  5.6 公用对话框
  5.7 小结
正文完
SAP Joule for Developers 全景解析:从 ABAP 到 SAP Build 的一体化开发者 AI 能力 SAP Joule for Developers是一套内嵌于SAP开发工具的设计时AI能力,专为提升SAP开发者效率而设计。它支持自然语言生成应用与自动化流程,帮助理解优化代码,并嵌入单元测试和质量保障功能。Joule深度集成ABAP开发环境,同时服务于Java、JavaScript和低代码开发,覆盖CAP数据模型、UI5/Fiori生成、ABAP代码补全与重构等场景。通过斜杠命令和上下文操作,开发者可在不改变现有平台的前提下,显著提升开发效率和质量。典型案例包括快速构建CAP应用和ABAP Cloud迁移 阅读详情

相关推荐

从零到一:基于DESeq2的转录组差异表达分析实战指南

本文详细介绍了基于DESeq2的转录组差异表达分析实战指南,涵盖数据准备、标准化处理、差异分析及结果解读等关键步骤。DESeq2凭借其稳定性和对低表达基因的友好处理,成为转录组数据分析的首选工具。文章还提供了常见问题排查和优化建议,帮助研究者高效完成从分析到发表的完整流程。

weixin_26759093的博客 272

MFC 对话框程序返回值

控制台程序可以很方便的获取到程序的返回值,MFC对话框程序返回值的获取就没有控制台程序获取那么方便了。 在MFC对话框程序中有两个大类,一个是应用程序类,一个是用于显示见面的对话框类。由于人和程序的交互是通过 对话框类实现,而操作系统获取程序返回值却只能从应用程序类获取返回值。 因此为了获取对话框程序的返回值,可以通过以下步骤实现。 1.在应用程序类中定义一个整型类型的返回值变量,m_iR

学习记录专栏 4289

使用PackStack工具自动化部署OpenStack

搭建 OpenStack 环境,每个组件的安装和配置都需要熟悉其特定的依赖关系、参数设置等,对于初学者来说容易出错。PackStack 工具通过集成一系列脚本和配置文件,便于快速搭建OpenStack练习环境。CPU:支持 Intel VT - x 或 AMD - V 硬件虚拟化技术,并且要在 BIOS 里开启此功能。内存:16GB以上存储:200GB以上可用空间(2)VMware WorkStation16.0以上版本(3)远程连接工具(如Xshell、MobaXterm)

2301_80440447的博客 4094

CMainFrame::OnClose()函数中不能调用wcsncpy()函数,不然会造成内存访问出错

1、进行调试发现读取文件的数据为空:进行调试和修改,最后还是使用TCHAR类型的数组保存读写的密码文件的数据。 2、在改好之后出现:“Run-Time Check Failure #2 - Stack around the variable 'puf' was corrupted”,最后经过网上查找和测试,最后发定义存放读写的TCHAR puf[20]从类函数里的局部变量放到类的成员变量中就好了

asdjy123的专栏 1066

使用Android GPS api 实现一个轨迹记录的小功能

引入 市场上的跑步软件已经是非常多了,如咕咚和悦跑圈还有其他,很多大厂也在涉足这个方面。而最简单的运动跑步,最近几年也因简单,不限场地,运动效果出众备受大众喜欢。 我的这篇文章的内容很多也是从网上学习其他的博客内容,外加利用android自身的提供的api实现的,希望能对有这方面需求的朋友有所帮助。 介绍 先上两张图,界面比较丑,不过一些数据基本都有了,这个图片是临时要用截取的,所以轨迹线很短,之前

尝试着去了解代码 1万+

ESXi 6.7升级17700523补丁全流程:从下载到验证的保姆级教程

本文提供ESXi 6.7升级17700523补丁的完整流程指南,涵盖从下载、上传到验证的详细步骤。通过SSH命令和vSphere Client操作,确保系统管理员能够安全高效地完成补丁升级,提升虚拟化环境的安全性和稳定性。

linux6sysadmin的博客 839

MFC关闭调用的窗口

调用窗体的方法: // chart是一个MFC的窗体类 chart *chartdialog = new chart; //调用窗体,获取返回值 int ReturnValue = chartdialog->DoModal(); // Show the dialog printf("%d\n", ReturnValue); 关闭窗体的语句,在窗体类中执行 //0x01就是窗体关闭后返回给主程序的返回值 EndDialog(0x01); ...

我不是码农的博客~~~ 488

MoveIt2项目想换掉KDL求解器?试试这个trac_ik插件(附源码编译避坑指南)

本文详细介绍了如何在MoveIt2项目中用trac_ik插件替代KDL求解器,提升机械臂运动学求解的效率和成功率。内容涵盖环境准备、源码编译避坑、项目集成及性能调优,特别适合需要高精度控制的协作机械臂应用场景。通过实测数据对比,trac_ik在求解时间和奇异点处理上显著优于传统KDL求解器。

weixin_30730053的博客 694

MFC 窗口关闭消息函数

Called by the framework when the nonclient area is being destroyed, and isthe last member function called when the Windows window is destroyed.   afx_msg void OnNcDestroy( );  //nonclient D

ZjhCN 5031

手把手教你用SNAP做全极化SAR图像分类

引言 最近在研究极化SAR图像分类方面的内容,发现用SNAP软件可以很方便的实现图像的分类。因为在入门过程中踩了很多坑,浪费了一些时间,为了不让大家踩坑,所以决定写下这篇教程。 前期准备 软件下载 SNAP是欧空局旗下的一个SAR图像处理软件,可以点击下载地址进行下载。 数据下载 数据下载地址:https://mdacorporation.com/sample_data/RS2_OK76385_...

zz651044657的博客 1万+

人形机器人电源系统硬件开发:从原理图设计到PCB布局的完整流程

人形机器人是“移动的智能终端”,其电源系统如同“心脏+血管”:既要为大脑(主控)、肌肉(电机)、感官(传感器)提供稳定能量,又要在有限体积内应对高动态负载(如跳跃时电机瞬间大电流)。本文聚焦“硬件开发”维度,覆盖从需求分析到PCB制板的完整流程,适合硬件工程师、机器人爱好者快速掌握工程实战方法。

AI天才研究院 2174

MFC窗口程序三个退出程序消息:WM_CLOSE、WM_DESTROY、WM_QUIT

MFC窗口程序三个退出程序消息:WM_CLOSE、WM_DESTROY、WM_QUIT 1、发送消息SendMessage、PostMessage PostMessage将消息放入消息队列后马上返回,而SendMessage直到窗口过程处理完消息后才返回 2、三个消息的区别 WM_CLOSE: 在系统菜单里选择了“关闭”或者点击了窗口右上角

thanklife的专栏 1万+

SAP Group Reporting入门指南(11)--配置

这里选择的是FS项目目标属性,FS项目有四个目标属性,比如上图配置的是抵消目标属性,所以就去找触发FS项目的抵消目标属性配置的是哪个FS项目,就以这个FS项目作为贷方。这里选择的是FS项目目标属性,FS项目有四个目标属性,比如上图配置的是抵消目标属性,所以就去找源FS项目的抵消目标属性配置的是哪个FS项目,就以这个FS项目作为借方。例如,添加一个包含用于存储百分比的 FS 项的选项。这里选择的是FS项目的角色属性值,如果这里定义了,那么就从FS项目中找到FS项目角色属性值与之匹配的FS项目作为贷方。

bflyfish的博客 568

深入浅出MFC第2版带书签高清资源介绍:助您轻松掌握MFC编程

深入浅出MFC第2版带书签高清资源介绍:助您轻松掌握MFC编程 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 《深入浅出MFC第2版(带书签高清)》是一本专注于MFC(Microsoft Foundation Classes)编程的经典教材。该书由资深开发者撰写,凭借其全面系统的内容、丰富的实例和高清书签功能,成为了众多开发者学习MFC的首选资源。 项目技术分析 MF...

gitblog_06733的博客 925

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

python socket编程 ws2tcpip_MFC下SOCKET编程环境配置

MFC下SOCKET编程环境配置0当初编程建立单文档的时候没有选择window socket,现在想要进行socket编程,应该怎么补救?添加了如下头文件#include "Winsock.h"#include "Ws2tcpip.h"#pragma comment(lib,"Ws2_32.lib")但是编译报错OUTROOM.cppc:\program files\microsoft visua...

weixin_39917894的博客 278

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Windows编程与MFC # 2

即使编写一个简单的窗口程序也需要使用大量的API函数,需要对Windows API编程原理有较深入的理解。 为了解决这一问题,微软开发了一组类库——微软基础类(Microsoft Foundation Class,MFC),将大部分Windows API函数封装在C++类中。 MFC是用来编写Windows应用程序的C++类集,该类集以层次结构组织起来,其中封装了大部分Windows API函数和Windows控件,它所包含的功能涉及到整个Windows操作系统。 MFC不仅为用户提供了 Windows图

skysys的研究小屋 806

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

MFCWindows程序设计第2版修订版源代码:深入探索MFC编程的艺术

MFCWindows程序设计第2版修订版源代码:深入探索MFC编程的艺术 【下载地址】MFCWindows程序设计第2版修订版源代码 《MFC Windows程序设计(第2版修订版)》源代码是Windows编程领域的经典学习资源,涵盖了MFC、COM、OLE和ActiveX等核心技术。随书提供的源代码经过精心校对,包含书...

gitblog_06781的博客 392

MFC编程点滴系列2-MFC的消息机制

前段时间太忙了,今天终于可以闲一下,现在NBA火箭队第6场生死大战46:31领先。期待火箭可以打第7场。今天来聊一聊MFC的消息机制。Windows是一个消息驱动(Message Driven)的系统(应该说所有的现代操作系统都是采用)消息驱动的。Windows API中的SendMessage和PostMessage相信大家都用过吧。消息对于一个系统来说是非常重要的。想想我们做桌面程序开发的大部

superjoel的专栏 2720

Windows编程2 MFC

编译环境vs2010 在文章开始前,我有个疑惑,我新建了个控制台工程,并新建文件创建头文件和类 GlobalVariate.h class CTest { public: CTest(); ~CTest(); }; GlobalVariate.cpp #include "stdafx.h" #include "GlobalVariateTest.h" CTest

Joey博客 520

MFCWindows程序设计第2版修订版源代码资源介绍:深入学习MFC编程的不二之选

MFCWindows程序设计第2版修订版源代码资源介绍:深入学习MFC编程的不二之选 【下载地址】MFCWindows程序设计第2版修订版源代码资源介绍 《MFC Windows程序设计(第2版)修订版+源代码》是一本深入探讨MFC编程的经典教材,由Jeff Prosise撰写,清华大学出版社出版。本书不仅涵盖了MFC的...

gitblog_06711的博客 897

MFC多线程编程2

MFC多线程编程2 六、MFC多线程编程实例   在Visual C++ 6.0编程环境中,我们既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序,二者各有其优缺点。基于Win32的 应用程序执行代码小巧,运行效率高,但要求程序员编写的代码较多,且需要管理系统提供给程序的所有资源;而基于MFC类库的应用程序可以 快速建立起应用程序,类库为程序员提

Sky_Internet的专栏 949

MFC——图形与图像编程(2)

MFC——图形与图像编程 四、颜色处理 1、渐变颜色示例 示例如下:void CGDISampleView::OnMenuitemColorchange() { // TODO: 在此添加命令处理程序代码 CDC* pDC = GetDC(); int i = 0, j = 0; for(i=0;i<255;i++) for (j = 0; j < 255; j++) ...

御承扬的博客 783

卷二:图形编程MFC第2篇

1.菜单栏和工具栏创建 手动创建:资源视图->menu 文件新建菜单; 在主菜单里面单击新建菜单和ID 菜单名称快捷键:(1)画图(&L)\tCtrl+L; (2)在Accelator文件添加快捷键操作。 (3)注意如果建的项目是菜单栏和工具栏的项目快捷键和工具栏可能显示不出来,解决办法:在view类里面插入方法ExitInstance插入代码AfxOleTerm(false); ...

qq_41659901的博客 380

MFC应用程序开发教程2——基于对话框编程

MFC应用程序开发教程2——基于对话框编程

weixin_50497501的博客 1686

《基于MFC的OpenGL编程》Part 2 Drawing Simple 2D Shapes

《基于MFC的OpenGL编程》Part 2 Drawing Simple 2D Shapes 原文链接 剪裁区域      In OpenGL when you create a window to draw in we must specify the coordinate system we want to use and how to map the specif

梁的博客 1141

《基于MFC的OpenGL编程》Part 2 Setting up OpenGL on Windows

源代码下载:OpenGL_ch2.rar WGL – Windows的 OpenGL扩展层 The WGL extension consists of a set of functions (wglCreateContext, wglDeleteContext etc.) and structures (such as PIXELFORMATDESCRIPTOR, GLYPHM

818
上一篇: MFC编程1
macro78
博客等级 码龄22年 3粉丝 9原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值