运行时类信息(RTTI)
C++:
##是拼接
#是替换成字符串
// RTTI.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <afxwin.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CWinApp theApp;
int main()
{
//CListBox是MFC自带的控件类
CListBox* pListBox = new CListBox;
//GetRuntimeClass方法返回运行时类信息
CRuntimeClass* pRuntimeClass = pListBox->GetRuntimeClass();
std::cout << pRuntimeClass->m_lpszClassName << std::endl;
//运行时类的IsDeriverFrome方法可以判断该类是否继承于某类
if (pRuntimeClass->IsDerivedFrom(RUNTIME_CLASS(CWnd))) {
std::cout<<"CListBox类继承于CWnd类"<<std::endl;
}
if (pRuntimeClass->IsDerivedFrom(RUNTIME_CLASS(CView))) {
std::cout << "CListBox类继承于视图类" << std::endl;
}
//运行时类的m_pfnGetBaseClass方法可以获取父类的信息
CRuntimeClass* pParentClass = pRuntimeClass->m_pfnGetBaseClass();
if (pParentClass->IsDerivedFrom(RUNTIME_CLASS(CWnd))) {
std::cout << "运行时类的父类继承于CWnd类" << std::endl;
}
//动态创建一个对象:
CWnd* pWnd = (CWnd*)pParentClass->m_pfnCreateObject();
return 0;
}
这是MFC提供的运行时类信息的使用,如果我们自己创建一个类,如果想用这些方法,必须要满足三个条件:
- 这个类必须继承于CObject类
- 类内必须声明DECLARE_DYNAMIC
- 类外必须实现IMPLENENT_DYNAMIC
我们来看看是如何实现的:
拆分宏
DECLARE_DYNAMIC(SHape)
public:
//静态的结构体
//本来是static const CRuntimeClass class##class_name;,拼接之后:
static const CRuntimeClass classSHape;
//虚函数
virtual CRuntimeClass* GetRuntimeClass() const;
//IMPLEMENT_DYNAMIC(SHape,CObject)
IMPLEMENT_RUNTIMECLASS(SHape, CObject, 0xFFFF, NULL, NULL)
AFX_COMDAT const CRuntimeClass SHape::classSHape =
{
"SHape",
sizeof(class SHape),
0xFFFF,
NULL,
RUNTIME_CLASS(CObject),//返回父类静态结构体的地址
NULL,
NULL
};
CRuntimeClass* SHape::GetRuntimeClass() const
{
return RUNTIME_CLASS(SHape);
}
struct CRuntimeClass
{
LPCSTR m_lpszClassName; //类名称
int m_nObjectSize; //类大小
UINT m_wSchema; //类版本
CObject* (PASCAL* m_pfnCreateObject)(); //动态创建才会使用 暂时NULL函数指针
CRuntimeClass* m_pBaseClass; //父类信息
CRuntimeClass* m_pNextClass; //NULL
const AFX_CLASSINIT* m_pClassInit; //NULL
}
#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
((CRuntimeClass*)(&SHape::classSHape))
}
这里给出RTTI的图,每一个类中都保存了这样一个结构,相当于链表,我们有当前的类信息,就可以得到所有父类信息:
动态创建机制
如果想在MFC中实现动态创建:
- 也必须继承与CObject类
- 类内声明DECLARE_DYNCREATE
- 类外实现IMPLEMENT_DYNCREATE
class SHape : public CObject
{
public:
DECLARE_DYNCREATE(SHape)
};
IMPLEMENT_DYNCREATE(SHape,CObject)
class CLine : public SHape
{
public:
DECLARE_DYNCREATE(CLine)
};
IMPLEMENT_DYNCREATE(CLine, SHape)
需要注意的是,动态创建宏中包含了动态信息的宏
使用:
int main()
{
HMODULE hModule = ::GetModuleHandle(nullptr);
AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0);
//定义直线类:
CLine line;
//方法内部this指针指向line
//该方法用于判断是否继承与某个类
if (line.IsKindOf(RUNTIME_CLASS(SHape))) {
std::cout << "是图形" << std::endl;
}
if (line.IsKindOf(RUNTIME_CLASS(CWnd))) {
std::cout << "是窗口" << std::endl;
}
else {
std::cout << "不是窗口" << std::endl;
}
CObject* pLine = RUNTIME_CLASS(CLine)->CreateObject();
if (pLine->IsKindOf(RUNTIME_CLASS(SHape))) {
std::cout << "创建成功" << std::endl;
}
return 0;
}
动态创建包括了类信息
//函数跟踪
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
//拿到链表头节点
CRuntimeClass* pClassThis = this->GetRuntimeClass();
pClassThis->IsDerivedFrom(参数是判断的结构体地址)
{
while (pClassThis != NULL)
{
if (pClassThis == 参数)
return TRUE;
if (pClassThis->m_pfnGetBaseClass == NULL)
break;
//获取父类静态结构体地址
pClassThis = pClassThis->m_pBaseClass;
}
}
}
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
CObject* PASCAL CLine::CreateObject()
{
return new CLine;
} \
IMPLEMENT_RUNTIMECLASS(CLine, SHape, 0xFFFF, class_name::CreateObject, NULL)
AFX_COMDAT const CRuntimeClass class_name::class##class_name =
{
"CLine",
sizeof(class CLine),
0xFFFF,
pfnNew,
RUNTIME_CLASS(base_class_name),
NULL,
class_init
};
视图分割
CSplitterWnd:专门负责窗口切分
创建对话框,视图:Create函数
重载父类框架类的虚函数CFrandWnd::OnCreateClient,这个函数专门用于切分
动态创建:Create函数
静态创建:CreateStatic函数
-
静态分割(在窗口创建的时候就已经分割好了)
在我们的框架类中:
class CMyFrameWnd:public CFrameWnd{ public: CSplitterWnd spWnd; CSplitterWnd spWnd1; virtual BOOL OnCreateClient(LPCREATESTRUCT* lpcs,CCreateContext* pContext){ spWnd.CreateStatic(this,2,1); spWnd1.CreateStatic(this,1,2,WS_CHILD|WS_CISIBLE,IdFromRowCol(0,0)); return true; } } }
我们这样写完了之后,发现运行不起来,这是因为我们只是创建了框架,但是没有创建视图
我们需要给视图类添加动态创建机制,我们重新写代码:
//我们自己的视图类: class MyView:public CView{ DECLARE_DYNREATE(MyView); virtual void OnDrow(DCD* pDC){ } } IMPLEMENT_DYNCREATE(MyView,CView) //我们自己的框架窗口类: class CMyFrameWnd:public CFrameWnd{ public: CSplitterWnd spWnd; CSplitterWnd spWnd1; virtual BOOL OnCreateClient(LPCREATESTRUCT* lpcs,CCreateContext* pContext){ spWnd.CreateStatic(this,2,1); spWnd1.CreateStatic(&spWnd,1,2,WS_CHILD|WS_CISIBLE,spWnd,IdFromRowCol(0,0)); //创建视图: spWnd1.CreateView(0,0,RUNTIME_CLASS(MyVIew),CSize(50,50),pContext); spWnd1.CreateView(0,1,RUNTIME_CLASS(CTreeView),CSize(50,50),pContext); spWnd.CreateView(1,0,RUNTIME_CLASS(CHtmlView),CSize(50,50),pContext); //设置行信息: spWnd.SetRowInfo(0,200,100); //设置列信息 spWnd1.SetColumnInfo(0,200,100); spWnd1.SetColumnInfo(1,200,100); CHtmlView* html = (CHtmlView*)spWnd.GetPane(1,0); html->Navigate(L"e:/"); return true; } }
现在我们创建了三个窗口
这些控件都被MFC接管了,Win32 EDIT处理函数,微软定义的,
如果想要处理消息,就要使用Win32子类化(笔记在本篇最后面)
-
现在我们想处理树视图上的消息
-
Create方法:
virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs,CCreateContext* pContext){ swWnd.CreateStatic(CWnd* pParentEnd,//分隔器窗口的父框架窗口句柄 int nRows,//行数,这个值必须不超过16 int nCols,//列数,这个值必须不超过16 dwStyle,//指定窗口的风格 nID//此窗口的ID,如果这个分隔器窗口不是嵌套在另一个分隔器窗口中的,则这个ID可以是AFX_IDW_PANE_FIRSH )
-
获取窗口ID:CSplitterWnd::IdFromRowCol方法:
int IdFromRowCol(int row,int col);//参数:行数,列数 返回值:返回此窗格的子窗口的ID
-
在分隔器中创建一个窗格:CreateVIew方法:
virtual BOOL CreateView(int row,int col,CRuntimeClass* pVIewClass,SIZE sizeInit,CCreateContext* pCOntext); 参数:行数,列数,运行时类信息,初始尺寸,用来创建此试图的创建环境的指针
-
-
动态分隔:
//我们自己的视图类: class MyView:public CView{ DECLARE_DYNREATE(MyView); virtual void OnDrow(DCD* pDC){ } } IMPLEMENT_DYNCREATE(MyView,CView) //我们自己的框架窗口类: class CMyFrameWnd:public CFrameWnd{ public: CSplitterWnd spWnd; CSplitterWnd spWnd1; virtual BOOL OnCreateClient(LPCREATESTRUCT* lpcs,CCreateContext* pContext){ CCreateContext Context; Context.m_pNewViewClass = RUNTIME_CLASS(MyView); spWnd.Create(this,2,2,CSize(50,50),&Context); return true; } }
- 动态切分,Create方法:
BOOL CSplitterWnd::Create( CWnd* pParentWnd,//分隔器父窗口的句柄 int nMaxRows,//分隔器窗口的最大行数,这个值不能超过2 int nMaxCols,//分隔器窗口的最大列数,这个值不能超过2 SIZE sizeMin,//指出显示一个窗格所需的最小尺寸 CCreateCOntext* pContext,//指向一个CCreateContext结构的指针 DWORD dwStyle = WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|SPLS_DYNAMIC_SPLIT,//窗口风格 UINT nID = AFX_IDW_PANE_FIRST//此窗口的子窗口ID。如果这个分隔器窗口不是嵌套在另一个分隔器窗口中的,则这个ID可以是AFX_IDW_PANE_FIRST )
- 动态切分,Create方法:
Win32子类化
在win32编辑框,可以设置属性,风格,字母不可见,****等
现在有一个需求:只允许输入小写字母和数字
C++中,只要继承,重写虚函数,在交给父类处理
在Win32中,添加一个编辑框,和一个按钮,
添加一个全局变量,保存原来的edit消息处理函数
WNDPROC OldProc;
然后添加按钮回调函数:
LRESULT CALLBACK MyProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam){
if(uMsg == WM_CHAR){
if(wParam>='a'&&wParam<='z'||wParam>='0'&&wParam<='9'){
//交给原来的处理函数处理
return OldProc(hDlg,uMsg,wParam,lParam);
}else{
return 0;
}
}
}
消息处理函数中:
if(LOWORD(wParam)==IDC_BUTTON1){
//要先找到窗口句柄:
HWND hEdit = GetDlgItem(hDlg,IDC_EDIT1);
//修改原来的过程函数(原来是操作系统默认的
//OldProc = (WNDPROC)SetWindowLogn(hEdit,GWL_WNDPROC,(LONG)MyProc);
//第二种方式,之前创建的编辑框不好使,但是之后创建的对话框,可以使用
//这个子类化方式实际上是改了类的回调,就是EDIT类的回调,之后创建的EDIt类就好使了
OldProc = (WNDPROC)SetWindowLogn(hEdit,GCL_WNDPROC,(LONG)MyProc);
}
第二种方式:
再添加一个按钮
HISRANCE hInst;
消息回调:
if(LPWORD(wParam)==IDC_BUTTON2){
CreateWindow(L"EDIT","ads",WS_CHILD|WS_VISIBLE,100,100,100,100,hDlg,NULL,hInst);
}
MFC子类化
这里我们把第一个视图换成一个窗口(对话框)
- 创建一个对话框
- 为该对话框添加一个类:CMyFormView
- 主cpp中,包含刚才添加的类的头文件
- 然后把第一个改为我们创建的类
//我们自己的视图类: class MyView:public CView{ DECLARE_DYNREATE(MyView); virtual void OnDrow(DCD* pDC){ } } IMPLEMENT_DYNCREATE(MyView,CView) //我们自己的框架窗口类: class CMyFrameWnd:public CFrameWnd{ public: CSplitterWnd spWnd; CSplitterWnd spWnd1; virtual BOOL OnCreateClient(LPCREATESTRUCT* lpcs,CCreateContext* pContext){ spWnd.CreateStatic(this,2,1); spWnd1.CreateStatic(&spWnd,1,2,WS_CHILD|WS_CISIBLE,spWnd,IdFromRowCol(0,0)); //创建视图: spWnd1.CreateView(0,0,RUNTIME_CLASS(MyFormView),CSize(50,50),pContext); spWnd1.CreateView(0,1,RUNTIME_CLASS(CTreeView),CSize(50,50),pContext); spWnd.CreateView(1,0,RUNTIME_CLASS(CHtmlView),CSize(50,50),pContext); //设置行信息: spWnd.SetRowInfo(0,200,100); //设置列信息 spWnd1.SetColumnInfo(0,200,100); spWnd1.SetColumnInfo(1,200,100); CHtmlView* html = (CHtmlView*)spWnd.GetPane(1,0); html->Navigate(L"e:/"); return true; } }
- 需要把我们创建的对话框修改为子窗口类,否则会报错
-
DDX/DDV虚函数:
就是空间绑定变量/数据,调用UpdataTA(),交互数据用
控件绑定变量
值绑定变量