buf

BE something YOU love and understand
posts - 94, comments - 35, trackbacks - 0, articles - 2
   :: 首页 :: 新随笔 :: 联系 ::  :: 管理

Custom SDI 3 —— 窗口创建、初始化与定制

Posted on 2008-04-17 22:34 buf 阅读(1895) 评论(0)  编辑 收藏 引用 所属分类: MFC

介绍


正如标题所言,本文讨论
SDI应用程序的窗口创建和初始化过程,并给出对程序的部分可视元素进行定制的方法。

准备工作


首先,使用
MFC App Wizard生成一个标准的SDI应用程序SdiDemo,除了去掉文档/视图结构支持,其余设置均使用默认值。然后,使用Class Wizard生成一个新类CMyView替换App Wizard已经为我们生成的CChildView。注意,CMyView继承自CView(或者CView的衍生类,如CFormViewCListViewCEditView等)。替换CChildView的方法见本系列的第二篇文“Customized SDI 2 —— 替换CChildView”。

接下来,我们将使用SdiDemo这个示例工程,来回答SDI程序的窗口从哪里创建,又籍由什么函数得到初始化这些问题。注意,这里的窗口不仅包括程序的主框架窗口CMainFrame,还包括CMainFrame所统管的子窗口,如视图(CMyView)、工具条(CToolBar)、状态栏(CStatusBar)等。

主框架窗口的创建与定制


主框架窗口类
CMainFrame继承自CFrameWnd,根据MSDN,构建一个CFrameWnd窗口经过了两个步骤:首先,使用new operator构造一个CFrameWnd对象;然后,调用其CreateLoadFrame函数将窗口关联到CFrameWnd对象。CMainFrame的创建过程也是如此:

BOOL CSdiDemoApp::InitInstance()

{

  ...

  // To create the main window, this code creates a new frame window

  // object and then sets it as the application's main window object.

  CMainFrame* pFrame = new CMainFrame;

  m_pMainWnd = pFrame;

  // create and load the frame with its resources

  pFrame->LoadFrame(IDR_MAINFRAME,

     WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL);

  ...

}


上面的代码中,指向新创建主框架窗口的指针被保存到
m_pMainWnd,以后需要主框架窗口的时候可以方便地使用这个变量来引用。m_pMainWndCWinThread的一个public成员,用来存放当前程序对象的主窗口指针。MFC还提供了一个全局函数AfxGetMainWnd来获取程序的主窗口指针。

下面,我们在CSdiDemoApp::InitInstance返回之前添加一些代码来对主框架窗口进行一些定制:重新设置窗口标题和图标。窗口标题的缺省值为工程名SdiDemo,这个值在调用主框架窗口的LoadFrame方法时从资源IDR_MAINFRAME中提取出来,经过一些字符串处理后作为窗口标题传递给Create创建窗口。因此,我们可以直接修改IDR_MAINFRAME中的字符串资源来重设窗口标题。把IDE切换到资源视图,打开String Table,定位并双击IDR_MAINFRAME,在弹出窗口修改Caption就可以了。如果需要在运行时修改,可以调用主框架窗口的SetWindowText重设窗口标题。至于位于窗口左上角的程序图标,我们可以在准备好要使用的图标资源后,通过向窗口发送WM_SETICON消息来完成修改:

BOOL CSdiDemoApp::InitInstance()

{

  ...

    // reset app title

     AfxGetMainWnd()->SetWindowText(_T("Customized SDI Demo"));

    // reset titlebar icon

    HICON icon = LoadIcon(IDI_APP);

if (icon)

  m_pMainWnd->SendMessage(WM_SETICON, ICON_BIG,

                               (LPARAM)icon);

  // The one and only window has been initialized, so show and // update it.

  pFrame->ShowWindow(SW_SHOW);

  pFrame->UpdateWindow();

 

  return TRUE;

}

上面的代码中,IDI_APP是新引入的图标资源的ID。当主框架窗口显示出来时,它看起来是这个样子:

也许这个黄色的五角星你看着挺眼熟,没错,就是IE的收藏夹图标。使用ResourceHacker可以很方便地把它从系统文件%systemroot%\system32\shell32.dll中提取出来。

视图的创建与初始化


说到视图的创建,我觉得已经是老生常谈了。一个
Windows视窗程序是由很多的窗体组成的,框架是窗体,视图是窗体,工具条是窗体……标签、按钮也都是窗体,它们都(直接或间接地)从CWnd继承而来。它们之所以能最终呈现在屏幕上,都经历了CWnd(及其衍生类)对象的构造和Create(Ex)方法调用使之与窗口相关联的过程。视图的创建在其父窗口CMainFrame::OnCreate方法中完成,唯一的疑问是这个方法是被谁调用的呢?

我们知道OnCreateFramework提供的响应WM_CREATE消息的默认处理函数,因此问题转变为WM_CREATE又是从哪里送出的呢?根据MSDN,这个消息是在CreateWindow(Ex)调用返回前发送给新创建的窗口的,而CWnd::Create(Ex)最终需要调用CreateWindow(Ex)这个API函数来完成底层的窗口创建工作。因此,触发视图创建的链路也就连起来了,来看一下执行到CMainFrame::OnCreate时的调用堆栈:

CMainFrame::OnCreate()
CWnd::OnWndMsg()
CWnd::WindowProc()
AfxCallWndProc()
AfxWndProc()
AfxWndProcBase()
USER32!77d18734()
USER32! 77d1d05b()
USER32! 7d1b4c0
USER32! 77d1f9fe()
NTDLL! 7c92eae3()
USER32! 77d1fecc()
USER32! 7d1ff66()
CWnd::CreateEx()
CFrameWnd::Create()
CFrameWnd::LoadFrame()
CSdiDemoApp::InitInstance()
AfxWinMain()
WinMain()
WinMainCRTStartup()
KERNEL32! 7c816fd7()

观察调用堆栈(Call Stack)是分析程序执行情况和函数调用脉络的有效方法,从菜单栏的“View”定位到“Debug Windows”,进入下面的子菜单就能打开调用堆栈。相比VS6.0VS2003.NET提供关于函数调用的更多详情,比如上面代码中内核调用的部分,后者能结合被调函数所在的文件(Kernel32.dllUser32.dllNtdll.dll等)的导出段给出被调函数的函数名,而不是诸如7c816fd7这样的函数地址

接下来看看视图的初始化。可以在视图的OnInitialUpdate函数中安插视图初始化的代码,它会在视图显示前被Framework调用。OnInitialUpdate是响应WM_INITIALUPDATE消息的Handler,而WM_INITIALUPDATECFrameWnd::LoadFrame返回前发送给它的子窗口:

BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,   CWnd* pParentWnd, CCreateContext* pContext)

{

  ...

  if (pContext == NULL)   // send initial update   SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

  return TRUE;

}

 

 

完成时间:2008/4/17

注:

     关于这个问题,我曾试图跟踪由Dialog Resource Template创建出来的对话框的子控件创建过程,未果。

     或者VC6.0也能显示内核调用的函数名,只是我没找到设置的地方L

只有注册用户登录后才能发表评论。