孙鑫VC++第三章 4.窗口类、窗口类对象与窗口三者之间关系

news2024/11/27 19:50:09

目录

1. 创建CWnd

2. WinMain

3. 创建CButton


1. 创建CWnd

模拟CWnd类的封装过程。在解决方案ch04下添加一个新的空项目,项目名称为:WinMain,在项目创建完成后,将WinMain项目设为启动项目。

接下来在WinMain项目中添加一个名为CWnd的类。在该类中定义四个函数:创建窗口函数(CreateEx)、显示窗口函数(ShowWindow)、更新窗口函数(UpdateWindow)和销毁窗口函数(DestroyWindow),并定义一个成员变量(m_hWnd)。CWnd类的头文件(cwnd.h)代码如下所示。

CWnd类的头文件cwnd.h:

#pragma once
 
#include <Windows.h>
class CWnd
{
public:
    CWnd();
    ~CWnd();
public:
    BOOL CreateEx(DWORD dwExStyle,      // extended window style
        LPCTSTR lpClassName,  // registered class name
        LPCTSTR lpWindowName, // window name
        DWORD dwStyle,        // window style
        int x,                // horizontal position of window
        int y,                // vertical position of window
        int nWidth,           // window width
        int nHeight,          // window height
        HWND hWndParent,      // handle to parent or owner window
        HMENU hMenu,          // menu handle or child identifier
        HINSTANCE hInstance,  // handle to application instance
        LPVOID lpParam);        // window-creation data
    BOOL ShowWindow(int nCmdShow);
    BOOL UpdateWindow();
    BOOL DestroyWindow();
public:
    HWND m_hWnd;
};

小技巧: 这些函数的参数可以参照MSDN中相应MFC函数的定义,然后直接复制这些参数即可。

提示: 因为SDK函数数量很多,所以程序员记忆负担很重。MFC中使用的大部分函数名与相应的 SDK 函数名相同,这样做的目的就是为了方便程序员减轻记忆负担。程序员只需要记住两者中的一个就可以了。

接下来在 CWnd 类的源文件(cwnd.cpp)中完成这三个函数的定义,代码如下所示。

CWnd 类的源文件(cwnd.cpp)

#include "cwnd.h"
 
CWnd::CWnd()
{
    m_hWnd = NULL;
}
 
CWnd::~CWnd()
{
    DestroyWindow();
}
 
BOOL CWnd::CreateEx(DWORD dwExStyle,      // extended window style
    LPCTSTR lpClassName,  // registered class name
    LPCTSTR lpWindowName, // window name
    DWORD dwStyle,        // window style
    int x,                // horizontal position of window
    int y,                // vertical position of window
    int nWidth,           // window width
    int nHeight,          // window height
    HWND hWndParent,      // handle to parent or owner window
    HMENU hMenu,          // menu handle or child identifier
    HINSTANCE hInstance,  // handle to application instance
    LPVOID lpParam)        // window-creation data
{
    m_hWnd = ::CreateWindowEx(dwExStyle, lpClassName, lpWindowName, 
        dwStyle, x, y,nWidth, nHeight, hWndParent, hMenu, 
        hInstance,lpParam);
    if (m_hWnd != NULL)
        return TRUE;
    else
        return FALSE;
}
 
BOOL CWnd::ShowWindow(int nCmdShow)
{
    return ::ShowWindow(m_hWnd, nCmdShow);
}
 
BOOL CWnd::UpdateWindow()
{
    return ::UpdateWindow(m_hWnd);
}
 
BOOL CWnd::DestroyWindow()
{
    BOOL bResult = FALSE;
    
    if (m_hWnd != NULL)
    {
        bResult = ::DestroyWindow(m_hWnd);
        m_hWnd = NULL;
    }
    return bResult;
}

我们定义的 CWnd 类的 CreateEx 函数需要完成创建窗口的工作,这可以利用Win32提供的SDK函数:CreateWindowEx函数来实现。该函数返回一个句柄,标识它所创建的窗口。

这里,我们就可以利用已定义的CWnd类的成员变量m_hWnd来保存这个窗口句柄。因为我们定义的 CreateEx 函数返回值是一个BOOL 型,所以应该判断一下这个窗口句柄。根据其值是否为空来决定函数是返回TRUE值,还是FALSE值。

读者应注意的是,在实际开发时,应该初始化m_hWnd变量,这可以在构造函数中实现,给它赋一个初值NULL。这里只是为了演示CWnd类是如何与窗口关联起来的,因此就不进行初始化工作了。

接下来定义ShowWindow函数的实现。同样,需要调用SDK函数,即ShowWindow来完成窗口的显示。为了区分这两个同名函数,在调用这个 SDK 函数时,前面加上作用域标识符(即::)。这种以“::”开始的表示方法表明该函数是一个全局函数,这里表示调用的 ShowWindow 函数是 SDK 函数。因为CreateEx 函数已经获取了窗口句柄并保存到m_hWnd成员变量中,所以ShowWindow函数可以直接把这个句柄变量作为参数来使用。

提示:在定义自己的成员函数时,如果调用的API函数名与自己的函数名不同,那么该 API 函数名前可以加也可以不加“::”符号,编译器会自动识别API函数。但是如果当前定义的成员函数与内部调用的API函数名相同,那么后者前面必须加“::”符号,否则程序在编译或运行时就会出错。

自己定义的 UpdateWindow 函数的实现比较简单,直接调用 SDK 函数:UpdateWindow完成更新窗口的工作。从上所示代码可知,定义的 CWnd 类的后两个函数(ShowWindow 和UpdateWindow)内部都需要一个窗口句柄,即需要知道对哪个窗口进行操作。现在就实现了一个窗口类:CWnd。但已知道如果要以类的方式来完成窗口的创建、显示和更新操作,那么首先还需要编写一个WinMain函数。并不需要记忆这个函数的写法,只要机器上有MSDN就可以了,在MSDN中找到该函数的帮助文档,直接复制其定义即可。

2. WinMain

在项目中添加一个源文件,名字为winmain.cpp,然后编写WinMain函数。这里,我们只是想讲解在这个函数内部所做的工作,并不是真正的实现,因此只是写出其主要的代码,如例下所示。

WinMain函数(winmain.cpp):


#include "cwnd.h"
 
int WINAPI WinMain(
    HINSTANCE hInstance,      // handle to current instance
    HINSTANCE hPrevInstance,  // handle to previous instance
    LPSTR lpCmdLine,          // command line
    int nCmdShow              // show state
)
{
    //首先是设计窗口类,即定义一个WNDCLASS,并为相应字段赋值。
    WNDCLASS wndcls;
    wndcls.cbClsExtra = 0;
    wndcls.cbWndExtra = 0;
    ......
        //注册窗口类
        RegisterClass(&wndcls);
 
    //创建窗口
    CWnd wnd;
    wnd.CreateEx(...);
 
    //显示窗口
    wnd.ShowWindow(SW_SHOWNORMAL);
 
    //更新窗口
    wnd.UpdateWindow();
    //接下来就是消息循环,此处省略
    ......
    return 0;
}

回想一下VC++关于WinMain程序的学习笔记(1)利用SDK编程时为创建窗口、显示窗口和更新窗口所编写的代码(如例下所示),并比较上面和下面这两段代码的区别。

    HWND hwnd;
    hwnd = CreateWindowEx();
    ::ShowWindow(hwnd, SW_SHOWNORMAL);
    ::UpdateWindow(hwnd);

可以发现,SDK 程序中多了一个 HWND 类型的变量 hwnd。该变量用来保存由CreateWindowEx 函数创建的窗口句柄,并将其作为参数传递给随后的显示窗口操作(ShowWindow 函数)和更新窗口操作(UpdateWindow 函数)。而我们自定义的实现代码中,CWnd类定义了一个HWND类型的成员变量:m_hWnd,用于保存这个窗口句柄。首先CWnd类的CreateEx函数创建窗口,并将该窗口句柄保存到这个成员变量,接着在调用CWnd类的ShowWindow函数显示窗口时,就不需要再传递这个句柄了,因为它已经是成员变量,该函数可以直接使用它。CWnd类的UpdateWindow函数也是一样的道理。

许多程序员在进行MFC程序开发时,容易混淆一点:认为这里的CWnd类型的wnd这个C++对象所代表的就是一个窗口。因为在实践中,他们看到的现象是:当C++窗口类对象被销毁时,相应的窗口也就没了。有时正好巧合,当窗口被销毁时,C++窗口类对象的生命周期也到了,从而也被销毁了。正因为如此,许多程序员感觉C++窗口类对象就是窗口,窗口就是这个C++窗口类对象。事实并非如此。可以想像一下,如果我们关闭了一个窗口,这个窗口就被销毁了,那么该窗口对应的C++窗口类对象真的销毁了吗?当然没有。当一个窗口销毁时,它会调用CWnd类的DestroyWindow函数,该函数销毁窗口后,将CWnd成员变量m_hWnd设为NULL。

C++窗口类对象的生命周期和窗口的生命周期不是一致的。当一个窗口被销毁时,与C++窗口类对象没有关系,它们之间的纽带仅仅在于这个 C++窗口类内部的成员变量:m_hWnd,该变量保存了与这个C++窗口类对象相关的那个窗口的句柄。当我们设计的这个 C++窗口类对象被销毁的时候,与之相关的窗口也是应该被销毁的,因为它们之间的纽带(m_hWnd)已经断了。另外,窗口也是一种资源,它也占据内存。这样,在C++窗口类对象析构时,也需要回收相关的窗口资源,即销毁这个窗口。

因此,一定要注意:C++窗口类对象与窗口并不是一回事,它们之间唯一的关系是C++窗口类对象内部定义了一个窗口句柄变量,保存了与这个C++窗口类对象相关的那个窗口的句柄。当窗口被销毁时,与之对应的C++窗口类对象被销毁与否,要看其生命周期是否结束。但当C++窗口类对象被销毁时,与之相关的窗口也将被销毁。在我们定义的这个WinMain程序中,当程序运行到WinMain函数的右花括号(})时,该函数内部定义的Wnd窗口类对象的生命周期也就结束了。

这是我们自已定义的CWnd类,那么MFC提供的CWnd类是不是这样实现的呢?在 MSDN 中查看 MFC 提供的 CWnd 类,将会发现该类确实定义了一个数据成员:m_hwnd,用来保存与之相关的窗口的句柄。因为MFC中所有的窗口类都是由CWnd类派生的,于是,所有的窗口类(包括子类)内部都有这样的一个成员用来保存与之相关的窗口句柄。所以,不能认为我们前面创建的 MFC 程序 Test 中的 CMainFrame 类和CTestView类的对象就是一个窗口。

3. 创建CButton

在已有的Test程序(设为启动项目)中实现。首先需要创建一个按钮类对象,按钮对应的MFC类是CButton类,其继承层次结构如图所示,得知 CButton类派生于CWnd类。

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

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"Test",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), this, 2633);
	return 0;
}

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

将该按钮的名称设置为“Test”,其位置由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上单击鼠标右键,从弹出的快捷菜单上选择【添加】→【添加变量】,将弹出“添加变量”对话框。通常,在定义类的成员变量名称时都以“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"Test",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), this, 2633);
	m_btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

根据运行结果,可以看到该按钮显示在工具栏上了,这是因为按钮当前的父窗口是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标签页上,在该类名上单击鼠标右键,从弹出的快捷菜单上选择【类向导】菜单命令,这时将弹出如下图所示的类向导对话框。

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

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

int CTestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
 
	// TODO:  在此添加您专用的创建代码
	m_btn.Create(L"Test",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), this, 2633);
	m_btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

来了,但位置发生了变化。因为这时给按钮的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"Test",WS_CHILD | BS_DEFSPLITBUTTON, CRect(0, 0, 100, 100), GetParent(), 2633);
	m_btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

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

m_btn.Create(L"Test",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最终的消息处理是利用消息映射来完成的,将在后面的章节中介绍。另外,还介绍了窗口类的封装过程。我们发现很多窗口类的函数调用都不再需要传递窗口句柄了,因为它们都在内部维护了一个窗口句柄成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++ 学习 ④】- 类和对象(下)

目录 一、初始化列表 1.1 - 定义 1.2 - 使用初始化列表的原因 1.3 - 成员变量的初始化顺序 二、静态成员 2.1 - 静态成员变量 2.2 - 静态成员函数 三、友元 3.1 - 友元函数 3.2 - 友元类 四、内部类 五、匿名对象 5.1 - 匿名对象的特性 5.2 - 匿名对象的使用场景…

3.View的绘制流程

View是在什么时候显示在屏幕上面的?(如:MainActivity的布局文件activity_main.xml) setContentView最终的结果是将解析的xml文件中的View添加到DecorView中. 那么这个DecorView是什么时候添加到Window(PhoneWindow)的呢? DecorView是在ActivityThread.java的handleResumeA…

2-Zookeeper单机版安装

2-Zookeeper单机版安装 本文介绍的是 Linux 系统下 Zookeeper 安装方式 ① 下载 进入官网 https://zookeeper.apache.org/ 点击下载按钮 进入下载页 https://zookeeper.apache.org/releases.html 后选择 最新的稳定版本&#xff0c;如下&#xff1a; 3.7.1 为最新的稳定版本…

号称分割一切的图片分割模型开源了——Segment Anything Meta SAM

头条号:人工智能研究所 微信号:启示AI科技 微信小程序:AI人工智能工具 以前,要解决任何类型的分割问题,有两类方法。第一种是交互式分割,允许分割任何类别的对象,但需要人通过迭代细化掩码来指导。第二种,自动分割,允许分割提前定义的特定对象类别(例如,猫或椅子),…

【计算机系统】指令

leaq指令 一元指令 二元指令 例子 指令addq 指令subq 指令incq 指令subq 移位指令 移位指令用途 特殊运算指令

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分 前言&#xff1a;Web&#xff1a;我Flag呢&#xff1f;导弹迷踪&#xff1a;Follow me and hack me&#xff1a;PHP是世界上最好的语言&#xff01;&#xff01;作业管理系统&#xff1a;Vim yyds&#xff1a;Ping&#xff1a…

Java基础-面向对象总结(2)

这篇文章主要讲解 Java中的 变量方法代码块访问修饰限定符Java 是值传递&#xff0c;还是引用传递&#xff1f;类和对象的生命周期..... 希望给您带来帮助 目录 变量 成员变量与局部变量的区别 静态变量和实例变量的区别&#xff1f;静态方法、实例方法呢&#xff1f; 可以…

数据分析06——Pandas中的数据抽取

1、前言&#xff1a; 在Pandas中进行数据抽取主要有两种方法&#xff0c;一种是loc方法&#xff0c;一种是iloc方法&#xff1b;在获取数据时可以获取的数据有三种形式&#xff0c;一种是Series类型&#xff0c;一种是DataFrame类型&#xff0c;还有一种是直接获取数据值&…

Nginx make报错处理

文章目录 make报错&#xff1a;fatal error:sys/sysctl.h:No such file or directory问题处理 make 报错&#xff1a;error: this statement may fall through [-Werrorimplicit-fallthrough]问题处理 make报错&#xff1a;error: struct crypt_data has no member named curre…

DCGAN--Keras实现

文章目录 一、Keras与tf.keras&#xff1f;二、keras中Model的使用三、使用Keras来实现DCGan1、导入必要的包2.指定模型输入维度&#xff1a;图像尺寸和噪声向量 的长度3、构建生成器4、构造鉴别器5、构建并编译DCGan6、对模型进行训练7、显示生成图像8、运行模型 总结 一、Ker…

力扣sql中等篇练习(二十)

力扣sql中等篇练习(二十) 1 寻找面试候选人 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # 分为以下两者情况,分别考虑,然后union进行处理(有可能同时满足,需要去进行去重) # ①该用户在 三场及更多…

软件测试八股文,软件测试常见面试合集【附答案】

PS&#xff1a;加上参考答案有几十万字&#xff0c;答案就没有全部放上来了&#xff0c;高清打印版本超过400多页&#xff0c;评论区留言直接获取 1、你的测试职业发展是什么? 2、你认为测试人员需要具备哪些素质 3、你为什么能够做测试这一行 4、测试的目的是什么? 5、测…

一图看懂 attrs 模块:一个在类定义时可替换 `__init__`, `__eq__`, `__repr__`等方法的样板,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 attrs 模块&#xff1a;一个在类定义时可替换 __init__, __eq__, __repr__等方法的样板&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1…

吴恩达|chatgpt 提示词工程师学习笔记。

目录 一、提示指南 写提示词的2大原则&#xff1a; 模型的限制 二、迭代 三、总结 四、推断 五、转换 六、扩展 七、对话机器人 吴恩达和openai团队共同开发了一款免费的课程&#xff0c;课程是教大家如何更有效地使用prompt来调用chatgpt&#xff0c;整个课程时长1个…

ctfshow周末大挑战2023/5/12

本周周末大挑战用到的函数讲解 parse_url() 作用&#xff1a;解析URL&#xff0c;返回其组成部分 语法&#xff1a; parse_url ( string $url [, int $component -1 ] ) 参数&#xff1a; url&#xff1a;要解析的 URL。无效字符将使用 _ 来替换。 component&#xff1a; …

Sentinel———隔离和降级

FeignClient整合Sentinel SpringCloud中&#xff0c;微服务调用都是通过Feign来实现的&#xff0c;因此做客户端保护必须整合Feign和Sentinel。 第一步 修改OrderService的application.yml文件&#xff0c;开启Feign的Sentinel功能&#xff08;消费者服务&#xff09; feig…

算法基础第二章

算法基础第二章 第二章&#xff1a;数据结构1、链表1.1、单链表&#xff08;写邻接表&#xff1a;存储图和树&#xff09;1.2、双链表(优化某些问题) 2、栈与队列2.1、栈2.1.1、数组模拟栈2.1.2、单调栈 2.2、队列2.2.1、数组模拟队列2.2.2、滑动窗口&#xff08;单调队列的使用…

操作系统实验二 进程(线程)同步

前言 实验二相比实验一难度有所提升&#xff0c;首先得先掌握好相应的理论知识&#xff08;读者-写者问题和消费者-生产者问题&#xff09;&#xff0c;才能在实验中得心应手。任务二的代码编写可以借鉴源码&#xff0c;所以我们要先读懂源码。 1.实验目的 掌握Linux环境下&a…

linux系统状态检测命令

1、ifconfig命令 用于获取网卡配置于状态状态的等信息&#xff1a; ens33:网卡名称 inet:ip地址 ether:网卡物理地址&#xff08;mac地址&#xff09; RX、TX:接收数据包与发送数据包的个数及累计流量 我们也可以直接通过网卡名称查对应信息&#xff1a; 2、查看系统版本的…