VC++在窗口中显示按钮 CButton::Create Button Styles Window Styles

VC++在窗口中显示按钮 CButton::Create Button Styles Window Styles,第1张

目录

CButton::Create

OnCreate函数(MainFrm.cpp)CMainFrame类:

小结

接上:VC++窗口类、窗口类对象与窗口

接下:Button Styles Window Styles


为了更好地理解窗口类、窗口类对象和窗口之间的关系,接下来实现在窗口中显示一个按钮这一功能,在已有的Test程序(设为启动项目)中实现。首先需要创建一个按钮类对象,按钮对应的MFC类是CButton类,其继承层次结构如图所示,得知 CButton类派生于CWnd类。

在MFC提供的资源类中,有些类的对象的构造(包括对象构造与初始化)直接通过其构造函数就可以完成。也就是说,这些对象的构造函数包含了这个对象的初始化 *** 作。但有些对象的产生除了调用构造函数外,还需要调用其他一些函数来进行初始化的工作,然后才能使用该对象。对于一个CButton对象,在定义之后就可以使用了。但是作为一个窗口类对象,即CWnd对象,如果在构造之后还需要产生这个窗口的话,则需要调用CreateEx函数来完成初始化工作。也就是说,如果要显示一个按钮,那么在定义这个 CButton类对象之后(即调用CButton类的构造函数之后)还需要调用CButton的Create函数创建这个按钮窗口,从而把按钮窗口与CButton对象关联起来。

CButton的Create函数声明如下。

CButton::Create
virtual BOOL Create( 
   LPCTSTR lpszCaption, 
   DWORD dwStyle, 
   const RECT& rect, 
   CWnd* pParentWnd, 
   UINT nID  
);

各个参数的意义如下所述。
■ lpszCaption
指定按钮控件的文本。
■ dwStyle
指定按钮控件的风格。按钮控件不仅具有按钮风格类型,还具有窗口风格类型。多种风格类型可以通过位或 *** 作加以组合。
■ rect
指定按钮控件的大小和位置。该参数是RECT结构体类型,通过指定左上角和右下角两个点的坐标定义一个矩形。结构体也是一种特殊的类,所以可以用类CRect来构造一个RECT结构体。
■ pParentWnd
指定按钮控件的父窗口。这是一个 CWnd 类型的指针。MFC 中不再通过窗口句柄,而是通过一个与窗口相关的C++窗口类对象指针来传递窗口对象。
■ nID
指定按钮控件的标识。

为了在框架窗口上产生一个按钮控件,显然应该是在框架窗口产生之后,再创建该按钮控件,否则没有地方放置它。窗口创建时都会产生WM_CREATE消息,CMainFrame类提供一个OnCreate函数,该函数就是用来响应这条窗口创建消息的。该函数的默认实现代码如下所示。

OnCreate函数(MainFrm.cpp)CMainFrame类:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
	{
		TRACE0("未能创建工具栏\n");
		return -1;      // 未能创建
	}

	if (!m_wndStatusBar.Create(this))
	{
		TRACE0("未能创建状态栏\n");
		return -1;      // 未能创建
	}
	m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));

	// TODO: 如果不需要可停靠工具栏,则删除这三行
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);

	return 0;
}

从上面代码可知,CMainFrame类的OnCreate函数首先调用基类CFrameWnd的 OnCreate 函数,创建一个窗口,然后创建工具栏(m_wndToolBar)和状态栏(m_wndStatusBar)对象。我们可以在该函数的最后完成按钮的创建工作,即在 return 语句之前添加Create代码。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ...
    CButton btn;
	m_btn.Create(L"风铃",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), this, 2633);
	return 0;
}

提示: 注意到字符串“按钮”前面有一个大写的字母L,Visual Studio2017默认使用Unicode字符集,这是和VC++ 6.0不同的地方,大写字母L的使用就是告诉编译器,该字符串应该编译为一个Unicode字符串。不过,这种方式只能对字面常量使用。

连接:VC++ 2019 “const char*“类型的实参与“LPCTSTR“类型的形参不兼容,的解决办法

将该按钮的名称设置为“风灵”,其位置由CRect(0,0,100,100)这一矩形确定,ID号为2633。按钮控件不仅具有按钮风格类型,还具有窗口风格类型,因此,在按钮的 Create 函数中指定该按钮具有 WS_CHILD 窗口风格类型,同时还具有BS_DEFPUSHBUTTON按钮风格类型,即下按按钮风格。另外,我们知道每个对象都有一个this指针,代表对象本身。为了让按钮控件的父窗口是框架窗口,这里可以直接将代表CMainFrame对象的this指针作为参数传递给按钮的Create函数。

编译并运行Test程序,却发现按钮并没有显示出来。问题的原因有两个:一是这里定义的btn对象是一个局部对象,当执行到OnCreate函数的右花括号(})时,该对象的生命周期就结束了,就会发生析构。前面已经讲过,如果一个窗口与一个C++窗口类对象相关联,那么当这个C++对象生命周期结束时,该对象在析构时通常会把与之相关联的窗口资源进行回收。这就是说,当执行到如上所示的OnCreate函数的右花括号时,刚刚创建的btn窗口就被与之相关的C++对象销毁了。因此,不能将这个按钮对象定义为一个局部对象。解决方法是:将其定义为CMainFrame类的一个成员变量,可以将其访问权限定义为private类型以实现信息隐藏。

有多种方法可以定义一个类的成员变量,可以直接在该类的定义中添加成员变量定义代码,也可以利用Visual Studio提供的便捷功能来定义。后者的方法是:在类视图窗口中的类名CMainFrame上单击鼠标右键,从d出的快捷菜单上选择【添加】→【添加变量】,将d出“添加变量”对话框。通常,在定义类的成员变量名称时都以“m_”为前缀,表明这个变量是类的一个成员变量。将变量名称设置为m_btn,类型输入 CButton,访问权限设置为private,如下图所示。

单击【确定】按钮,即可以在CMainFrame类的头文件中看到新成员变量的定义,代码如下:

class CMainFrame : public CFrameWnd
{
	
protected: // 仅从序列化创建
	CMainFrame() noexcept;
	DECLARE_DYNCREATE(CMainFrame)

// 特性
public:

//  *** 作
public:

// 重写
public:
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// 实现
public:
	virtual ~CMainFrame();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // 控件条嵌入成员
	CToolBar          m_wndToolBar;
	CStatusBar        m_wndStatusBar;

// 生成的消息映射函数
protected:
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	DECLARE_MESSAGE_MAP()
private:
	CButton m_btn;
};

修改OnCreate函数(MainFrm.cpp)CMainFrame类:OnCreate函数中创建按钮的代码,删除局部按钮对象的定义,并将调用Create函数的按钮对象名称改为m_btn,结果如下所示。

再次运行Test程序,会发现按钮依然没有出现。这一问题的第二个原因就是在一个窗口创建完成之后,应该将这个窗口显示出来。因此,需要在调用Create函数之后再添加一条窗口显示的代码,如下所示。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
	{
		TRACE0("未能创建工具栏\n");
		return -1;      // 未能创建
	}

	if (!m_wndStatusBar.Create(this))
	{
		TRACE0("未能创建状态栏\n");
		return -1;      // 未能创建
	}
	m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));

	// TODO: 如果不需要可停靠工具栏,则删除这三行
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);

	m_btn.Create(L"风铃",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), this, 2633);
	m_btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

再次运行Test程序,这时就可以看到按钮出现了,如下图所示。 

根据运行结果,可以看到该按钮显示在工具栏上了,这是因为按钮当前的父窗口是CMainFrame类窗口,即主框架窗口。在该窗口中,标题栏和菜单都位于非客户区,而工具栏位于它的客户区(关于窗口的客户区和非客户区的内容将在后面讲)。程序中的按钮是在主框架窗口的客户区出现的,其位置由CRect(0,0,100,100)参数指定,说明其左上角就是其父窗口客户区的(0,0)点,因此,该按钮就在程序的菜单下、工具栏上显示出来了。

如果我们改在CTestView类中创建这个按钮,那么会是什么样的结果呢?首先,我们CMainFrame中创建按钮的代码注释起来,然后为CTestView类定义一个CButton类型的成员变量m_btn。但是接下来,我们发现CTestView类中没有OnCreate函数。Windows下的程序都是基于消息的,无论MFC程序,还是SDK程序都是这样的。既然窗口在创建时都会产生一个WM_CREATE消息,那么就可以让CTestView响应这个消息,也就是为这个类添加WM_CREATE消息的处理函数。在Visual Studio中,为一个类添加某个消息的处理函数的方法是:在ClassView标签页上,在该类名上单击鼠标右键,从d出的快捷菜单上选择【类向导】菜单命令,这时将d出如下图所示的类向导对话框。

选中消息标签页,在消息列表中找到并选中 WM_CREATE 消息,然后单击“添加处理程序”按钮,如下图所示。

单击“编辑代码”按钮,这样就直接定位到了响应 WM_CREATE 消息的处理函数OnCreate函数中。我们在该函数的尾部添加显示按钮的代码,与CMainFrame中的代码相同,可以直接复制过来,结果如下所示。 

int CTestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  在此添加您专用的创建代码
	m_btn.Create(L"风铃",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), this, 2633);
	m_btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

编译并运行Test程序,结果如图所示。 

可以看到按钮显示出来了,但位置发生了变化。因为这时给按钮的Create函数传递的this指针指向的是 CTestView 类的对象,因此,这时按钮的父窗口就是视类窗口,所以按钮在视窗口的客户区中显示。如果这时仍想让按钮的父窗口为CMainFrame类窗口,即视类窗口的父窗口,那么可以调用GetParent函数来获得视类的父窗口对象的指针,并将该指针传递给按钮的Create函数。这时的CTestView类OnCreate函数定义代码如下所示。

int CTestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  在此添加您专用的创建代码
	m_btn.Create(L"风铃",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), GetParent(), 2633);
	m_btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

结果:

 运行Test程序,会发现按钮的位置与在CMainFrame中创建按钮的位置一样,可见按钮的位置与其父窗口有关,而不是与创建它的代码所在的类有关。另外,如果想在创建按钮之后立即显示,则可以将其窗口风格指定为WS_VISIBLE,这时,就不需要再调用ShowWindow函数了。此时按钮的创建和显示只需要下面这一条代码即可:

m_btn.Create(L"风铃",WS_CHILD | WS_VISIBLE | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), GetParent(), 2633);

小技巧:在Windows中很多函数名都是一些有意义的单词的组合,并且每个单词的首字母大写。例如,如果想要得到某个类的父窗口,那么我们可以猜想这个函数名应该是Get再加上ParentWindow这样的名字。打开MSDN的索引标签页,键入GetParentWindow,发现没有这个函数,但有一个 GetParent函数,打开这个函数,发现就是我们所要的函数。在编程时,通过这种方法,可以快速找到所需要的函数。

本例中,我们选择的是BS_DEFPUSHBUTTON按钮风格类型,读者可以试着使用其他类型的风格,例如BS_AUTORADIOBUTTON、BS_CHECKBOX等,看看结果如何。通过这个CButton对象的创建,希望能更好地理解C++窗口类对象和窗口之间的关系。当我们将按钮窗口销毁时,它所对应的m_btn这个C++对象并没有被销毁,因为它是 CTestView 类的一个成员变量,它的生命周期与 CTestView 对象是一致的。只要CTestView对象没有被销毁,该按钮对象就一直存在,在程序中仍可以访问这个对象。

另外,我们发现在调用CButton的ShowWindow函数时,并没有传递一个窗口句柄,因为 CButton 类是 CWnd 类的子类,因此,它已有一个用于保存窗口句柄的成员变量m_hwnd。这样,CButton 的成员函数就可以直接使用这个变量,并不需要再传递窗口句柄了。另一点需要注意的是,按钮的父窗口不同,其显示位置也会有所差异。最后,我们在写程序时,如果不知道某个函数的名称,那么可以凭感觉利用单词的组合来拼写,通过这种方法一般都能在MSDN中找到需要的函数。

小结:

主要剖析了MFC框架的运行机制,可以发现在其框架内部也有与Win32 SDK程序相应的 *** 作,包括设计窗口类、注册窗口类、创建窗口、显示和更新窗口、消息循环,以及窗口处理过程,只不过它使用的是一个默认的窗口处理函数。当然,MFC最终的消息处理是利用消息映射来完成的,将在后面的章节中介绍。另外,还介绍了窗口类的封装过程。我们发现很多窗口类的函数调用都不再需要传递窗口句柄了,因为它们都在内部维护了一个窗口句柄成。

接上:VC++窗口类、窗口类对象与窗口 接下:Button Styles Window Styles

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/716859.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存