<TABLE style="TABLE-LAYOUT: fixed">
以上摘自Visual C++ Team Blog上的一篇文章,终于看到MS更新MFC了,:-)。
<TABLE style="TABLE-LAYOUT: fixed">
以上摘自Visual C++ Team Blog上的一篇文章,终于看到MS更新MFC了,:-)。
<TABLE style="TABLE-LAYOUT: fixed">
The fact that the static and dynamic types of references and pointers can differ is the cornerstone of how C++ supports polymorphism.
When we call a function defined in the base class through a base-class reference or pointer, we do not know the precise type of the object on which the function is executed. The object on which the function executes might be of the base type or it might be an object of a derived type. If the function called is nonvirtual, then regardless of the actual object type, the function that is executed is the one defined by the base type. If the function is virtual, then the decision as to which function to run is delayed until run time. The version of the virtual function that is run is the one defined by the type of the object to which the reference is bound or to which the pointer points. From the perspective of the code that we write, we need not care. As long as the classes are designed and implemented correctly, the operations will do the right thing whether the actual object is of base or derived type.On the other hand, an object is not polymorphicits type is known and unchanging. The dynamic type of an object (as opposed to a reference or pointer) is always the same as the static type of the object. The function that is run, virtual or nonvirtual, is the one defined by the type of the object.
Virtuals are resolved at run time only if the call is made through a reference or pointer. Only in these cases is it possible for an object’s dynamic type to be unknown until run time.
<TABLE style="TABLE-LAYOUT: fixed">
<DIV class=cnt id=blog_text>
CWnd::WindowProc
if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return FALSE; } CWnd::OnCommand 如果是命令消息加速键等用户接口对象的WM_COMMAND通知消息(有些控件也会发送此消息比如按钮的单击),则调用OnCommand处理,而在OnCommand中会判断是一般命令消息还是控件消息,其中如果不是控件消息的话会调用OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);看到了吧这里就调用了从CCmdTarget继承来的虚函数(我只找到了CWnd的这个地方调用了改消息)。 接着是控件消息 // special case for notifies if (message == WM_NOTIFY) { NMHDR pNMHDR = (NMHDR)lParam; if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult)) goto LReturnTrue; return FALSE; } 关于WM_NOTIFY的一点说明: Win32使用新的WM_NOFITY来处理复杂的通知消息。WM_COMMAND类型的通知消息仅仅能传递一个控制窗口句柄(lparam)、控制窗ID和通知代码(wparam)。WM_NOTIFY能传递任意复杂的信息。 因此控件消息也得以处理,WindowProc后面的消息处理可想而知了即其他WM开头的Windows标准消息。 CFrameWnd中改写了OnCmdMsg BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { CPushRoutingFrame push(this); // pump through current view FIRST CView* pView = GetActiveView(); if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // then pump through frame if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // last but not least, pump through app CWinApp* pApp = AfxGetApp(); if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; } 由于在CFrameWnd中产生的消息一般都是WM_COMMAND消息,因此当CWnd::WindowProc被调用时会转到OnCommand而CFrameWnd又改写了CWnd的OnCommand那么CFrameWnd的OnCmdMsg是如何被调用的呢,原来在CFrameWnd::OnCommand中调用了CWnd的OnCommand,显然CFrameWnd::OnCmdMsg就被调用了,由上面的CFrameWnd::OnCmdMsg可以清楚的看到CFrameWnd对标准消息传递的过程的改写首先让当前的活动视图处理,通过查看视图的源代码可以发现视图的OnCmdMsg先让CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)处理(最终就是调用CCmdTarget::OnCmdMsg,通过查看源代码可以发现// look through message map to see if it applies to us,其实就是查找此视图及相关基类的消息映射表进行相应的处理),接着是让m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);处理,不用查看文档的代码也可以猜到文档模板的OnCmdMsg会调用,当视图的OnCmdMsg返回后如果没有处理的话又会调用CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)(此时是CFrameWnd处理),最后如果还没有处理的话又给App对象处理,一直到让DefWindowProc处理。 对话框的消息处理与此不同,具体可以根据源代码追踪呵呵,第一次跟踪查找MFC源代码,不知道以上结论正确与否,不过对我理解消息传递的过程还是有点帮助,写下来以免忘了。
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_MDISQUTYPE,
RUNTIME_CLASS(CSquaresDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CSquaresView));
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
。。。。。。。。
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
上面为MDI,下面为SDI
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CSquaresDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CSquaresView));
AddDocTemplate(pDocTemplate);
。。。。。。。。。。
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
主要是在生产文档模板对象时RUNTIME_CLASS(CChildFrame), // custom MDI child frame
VS RUNTIME_CLASS(CMainFrame), // main SDI frame window
MDI传递的是childframe,而SDI传递的mainframe,因此MDI的ProcessShellCommand创建的是ChildFrame而SDI创建的MainFrame,所以MDI中就多出了
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
必须自己创建MainFrame.
<TABLE style="TABLE-LAYOUT: fixed">
昨天去新华书店看的今天趁还记得就写下了.
窗口创建:首先定义一个CWnd对象,然后调用CWnd::Create,其实Create又会调用CreateEx,与之对应的API函数也是这样,在CreateEx调用AfxCtxCreateWindowEx之前会调用PreCreateWindow,这个时候偶CWnd对应的窗口的句柄还未被Windows分配,之后调用AfxCtxCreateWindowEx,在调用这个函数的过程中会发送WM_CREATE消息从而引发OnCreate函数调用注意调用OnCreate时CWnd中的m_hWnd已经有值了,在OnCreate中可以进行一些初始化以及创建字窗口之类的事情。至此窗口创建完毕。
窗口销毁:可以发送一个WM_CLOSE消息给指定窗口使其调用OnClose函数,此函数中会发送WM_DESTROY从而引发OnDestroy的调用,该函数又会引发最后一个消息WM_NCDESTROY的发送,OnNcDestroy即被调用,在OnNcDestroy的最后会调用PostNcDestroy,CWnd的PostNcDestroy为
void CWnd::PostNcDestroy()
{
// default to nothing
},什么也不做,但是CFrameWnd重载了此函数
void CFrameWnd::PostNcDestroy()
{
// default for frame windows is to allocate them on the heap
// the default post-cleanup is to ‘delete this’.
// never explicitly call ‘delete’ on a CFrameWnd, use DestroyWindow instead
delete this;
}即我们看到的在App中new的Frame没有删除就是这个原因,但是我们自己从CWnd派生的窗口就不同了,如果你new了就要delete,而且最好就是在这个函数里delete
一直对GID 不是很懂,今天狠下心来看了一天的GDI,主要是参照着MSDN看,感觉还是有点收获的。根据MSDN写了一个小小的程序,仅仅就是截取屏幕,另外可以将其保存到剪贴板里。
以下为源代码:
// Capturing_Image.cpp : 定义应用程序的入口点。
//
#include “stdafx.h”
#include “Capturing_Image.h”
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
void CaptureScreen(HWND,HDC);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CAPTURING_IMAGE, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CAPTURING_IMAGE));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
// 注释:
//
// 仅当希望
// 此代码与添加到 Windows 95 中的“RegisterClassEx”
// 函数之前的 Win32 系统兼容时,才需要此函数及其用法。调用此函数十分重要,
// 这样应用程序就可以获得关联的
// “格式正确的”小图标。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW|CS_OWNDC;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CAPTURING_IMAGE));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CAPTURING_IMAGE);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc=NULL;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_CAPTUR:
CaptureScreen(hWnd,hdc);
break;
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码…
CaptureScreen(hWnd,hdc);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
void CaptureScreen(HWND hWnd,HDC hdc)
{
hdc=GetDC(hWnd);
RECT clientRect;
GetClientRect(hWnd,&clientRect);
HDC hdcScreen;
HDC hdcCompatible;
HBITMAP hbmScreen;
// 创建一个普通的包含整个显示器屏幕的设备描述表和一个与之兼容
// 的内存设备描述表,普通的设备描述表提供一个屏幕内容的快照
// 内存设备描述表将该快照的一份拷贝与一个bitmap相连,但是该bitmap
//的表面显示的是一个单色的一个像素的bitmap,因此应该选入一个何时的
//bitmap到该内存设备描述表
//以下摘自MSDN
//A memory DC exists only in memory. When the memory DC is created,its display
//surface is exactly one monochrome pixel wide and one monochrome pixel high.
//Before an application can use a memory DC for drawing operations, it must select a
//bitmap of the correct width and height into the DC.
hdcScreen = CreateDC(“DISPLAY”, NULL, NULL, NULL);
hdcCompatible = CreateCompatibleDC(hdcScreen);
// 创建一个与屏幕设备描述表相兼容的bitmap,然后将其选入先前创建的
// 内存设备描述表
hbmScreen = CreateCompatibleBitmap(hdcScreen,
GetDeviceCaps(hdcScreen, HORZRES),
GetDeviceCaps(hdcScreen, VERTRES));
if (hbmScreen == 0)
MessageBox(hWnd,”hbmScreen”,”错误”,MB_OK);
if (!SelectObject(hdcCompatible, hbmScreen))
MessageBox(hWnd,”Compatible Bitmap Selection”, “错误”,MB_OK);
//将与屏幕设备描述表相关的颜色信息拷贝到内存设备描述表相关
if (!BitBlt(hdcCompatible,
0,0,
GetDeviceCaps(hdcScreen, HORZRES),GetDeviceCaps(hdcScreen, VERTRES),
hdcScreen,
0,0,
SRCCOPY))
MessageBox(hWnd,”Screen to Compat Blt Failed”,”错误”,MB_OK);
StretchBlt(hdc,
0, 0,
clientRect.right-clientRect.left, clientRect.bottom-clientRect.top,
hdcCompatible,
0, 0,
GetDeviceCaps(hdcScreen, HORZRES), GetDeviceCaps(hdcScreen, VERTRES),
SRCCOPY);
if (OpenClipboard(hWnd))
//hWnd为程序窗口句柄
{
//清空剪贴板
EmptyClipboard();
//把屏幕内容粘贴到剪贴板上,
SetClipboardData(CF_BITMAP, hbmScreen);
//关闭剪贴板
CloseClipboard();
}
DeleteObject(hbmScreen);
DeleteDC(hdcCompatible);
DeleteDC(hdcScreen);
ReleaseDC(hWnd,hdc);
}
附上一个截图:
<TABLE style="TABLE-LAYOUT: fixed">
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public: //为了便于取地址
int m_data1;
int m_data2;
int m_data3;
public:
void mem_fun1();
void mem_fun2();
//注意虽然后面没用到这些虚函数 ,但是要使程序可以编译通过
//需要实现他们,因为建立vtable会出错
virtual void vir_fun1(){}
virtual void vir_fun2(){}
};
class Derive:public Base
{
public://为了便于取地址
int m_data1;
int m_data3;
int m_data4;
public:
void mem_fun1();
virtual void vir_fun2(){}
};
int main()
{
cout<<”sizeof(Base)=> “<<sizeof(Base)<<endl;
cout<<”sizeof(Derive)=> “<<sizeof(Derive)<<endl;
Base b;
Derive d;
cout<<”Address of b=> “<<&b<<endl;
cout<<”Address of b.m_data1=> “<<&(b.m_data1)<<endl;
cout<<”Address of b.m_data2=> “<<&(b.m_data2)<<endl;
cout<<”Address of b.m_data3=> “<<&(b.m_data3)<<endl;
cout<<”Address of d=> “<<&d<<endl;
cout<<”Address of d.m_data1=> “<<&(d.m_data1)<<endl;
cout<<”Address of d.Base::m_data2=> “<<&(d.Base::m_data2)<<endl;
cout<<”Address of d.Base::m_data3=> “<<&(d.Base::m_data3)<<endl;
cout<<”Address of d.m_data3=>”<<&(d.m_data3)<<endl;
cout<<”Address of d.m_data4=>”<<&(d.m_data4)<<endl;
system(“pause”);
return 0;
}
运行结果如图:
基类的大小为16是因为,三个int变量加上一个vptr,对应的Derive的大小就是在此基础上加三个int的大小,各数据成员的地址很清晰,也可以反映对象在内存中的存放情况。
下面两幅图是对象的内存模拟表示,看图应该就很清楚了:
学习MFC的时间不长,但是感觉开始学习的方法不对,只知道随便摆摆几个控件然后想怎么把个控件弄漂亮点(不停的在网上下别人写的控件,但是看不懂),而忽视了基础的重要性,比如说MFC和API之间的关系,消息映射机制的实现,Windows窗口对象和CWnd的C++对象之间的联系等等。
这样学习的结果可想而知,只懂皮毛,只会用ClassWizard弄点简单的小程序,遇到稍微需要自己写代码的地方就不知从何写起了。因此曾一度决定放弃MFC的,觉得这种编程方式太麻烦了,尤其是写UI时,自我觉得还是对JAVA,QT那种写界面的方式好接受点,知道自己写了哪些东西以及怎么写的。
前几天在网上发现一在线教程,感觉写得还蛮好的,比起那些各式各样的VC的书讲得好多了,尤其是当我看完消息映射后,发现这个对我真是受益匪浅,再结合源码看看,有些以前难以理解的东西现在觉得有点眉目了,还开始感叹写MFC那些人的技术之精,能写出如此庞大(看到网上有些人觉得MFC太臃肿,我暂时可能还没有发现这个的能力),效率又高的库真是不简单,觉得学习C++面向对象的思想,MFC源码是个不错的东东。
想把自己现在对MFC的一些基础部分整理一个小的系列文章,:-),当然绝大部分内容都是网上的,我可能只是修改一些错误的地方,可惜现在时间不是很多,尽力了,也算是对自己学习MFC的一点点初级的总结吧!