buf

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

Custom SDI 1 —— 向视图添加控件

Posted on 2008-04-12 00:09 buf 阅读(4581) 评论(7)  编辑 收藏 引用 所属分类: MFC


介绍

在使用MFC Application Wizard时,如果选择Dialog Based App,添加控件是很容易的事情——只需要移动几下鼠标就可以轻松完成。但是,如果选择SDI或者MDI,添加控件就不是那么直接了。在此总结一下向视图添加控件的方法。

子窗体的创建

首先,使用App Wizard生成一个标准的SDI程序,我将它命名为AddCtrl。因为文档与这里要讨论的重点无关,所以去掉了文档/视图结构的支持。IDE为我们生成CAddCtrlApp、CAboutDlg、CMainFrame、CChildView等几个类。注意到CMainFrame有以下几个成员变量:

protected:  // control bar embedded members

    CStatusBar  m_wndStatusBar;//状态栏

    CToolBar    m_wndToolBar;//工具栏

    CChildView    m_wndView;//视图

我想,既然CMainFrame可以像一个容器那样容纳状态栏、工具栏等子窗体,那可不可以按照同样的方法向CChildView添加控件呢?

循此思路,先来看看状态栏和工具栏的创建过程。检查CMainFrame的几个成员函数,很明显OnCreate与控件的创建关系密切:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

       return -1;

    // create a view to occupy the client area of the frame

    if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,

       CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL))

       return -1;

    // create toolbar

    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))

       return -1;

    // create status bar

    if (!m_wndStatusBar.Create(this) ||

       !m_wndStatusBar.SetIndicators(indicators,

         sizeof(indicators)/sizeof(UINT)))

       return -1;      // fail to create

    ...

    return 0;

}

我们看到了一连串的Create(Ex)调用,MSDN上对CWnd::Create有这样的说明:
You construct a child window in two steps. First, call the constructor, which constructs the CWnd object. Then call Create, which creates the Windows child window and attaches it to CWnd. Create initializes the window's class name and window name and registers values for its style, parent, and ID.

哈!这下很清楚了,状态栏作为CMainFrame的子窗体,其创建也同样经历了两个步骤:首先,状态栏的构造函数在构造CMainFrame时被调用;然后,状态栏的Create函数在CMainFrame::OnCreate中被调用。

这下简单了,我们依葫芦画瓢向视图添加一个树控件。首先给CChildView添加两个成员变量:

class CChildView : public CWnd

{

...

// Attributes

protected:

    CTreeCtrl m_tree;// add a tree control to view

    CImageList m_imgList;// image list for the tree control

...

};

然后,重载CChildView::OnCreate,在其中创建树控件和初始化image list

int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

    if (CWnd::OnCreate(lpCreateStruct) == -1)

       return -1;

    // create tree control

    DWORD dwStyle = WS_CHILD|WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS;

    if (!m_tree.Create(dwStyle, CRect(0, 0, 0, 0), this, 0x1001))

    {

       TRACE0("Failed to create tree control\n");

       return -1;      // fail to create

    }  

    // prepare image list

    m_imgList.Create(16, 16, ILC_COLOR32, 1, 1);

    m_imgList.SetBkColor(RGB(255, 255, 255));

    m_imgList.Add(AfxGetApp()->LoadIcon(IDI_ICON_ITEM));

    // setup image list for the tree control

    m_tree.SetImageList(&m_imgList, TVSIL_NORMAL);

   

    return 0;

}

为了测试新添加的树控件能否正常工作,我给主菜单添加了一个Test命令,点击时会向树控件添加一些项目:

void CChildView::Test()

{

    CString item;

   

    for(int i = 0; i < 26; i++)

    {

       item = _T('A') + i;

       m_tree.InsertItem(item.GetBuffer(0), 0, 0);

    }

}

事情到这还不算完,编译运行点击Test视图里空白如故,就像啥也没发生过一样。哪里出了问题呢?回头再看看树控件Create函数:

virtual BOOL Create(

   DWORD dwStyle,

   const RECT& rect,

   CWnd* pParentWnd,

   UINT nID

);

dwStylepParentWnd不用再解释了,第二个参数rect应该怎样获得呢?我希望树控件总是充满视图,于是修改CChidView::OnCreate函数中创建树控件的部分:

// create tree control

DWORD dwStyle = WS_CHILD |WS_VISIBLE |WS_BORDER |TVS_HASLINES

        | TVS_HASBUTTONS;

CRect rect;

GetClientRect(&rect);

if (!m_tree.Create(dwStyle, rect, this, 0x1001))

{

    TRACE0("Failed to create tree control\n");

    return -1;      // fail to create

}

结果还是一样,推测是不是因为调用GetClientRectCChildView还没有创建完成,此时视图的客户区域大小依然为0,调试发现果然如此。于是向CChildView添加一个函数用来做一些初始化工作:

void CChildView::InitialUpdate()

{

    CRect rect;

    GetClientRect(&rect);   

    m_tree.MoveWindow(&rect, TRUE);

}

然后在CMainFrame::OnCreate返回前添加对这个InitialUpdate的调用:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

    ...

    RecalcLayout();

    m_wndView.InitialUpdate();

    return 0;

}

再次编译运行,终于看到视图里有东西了 ...


重载
WM_SIZE消息处理函数调整子窗体大小

最后还有一个小问题,当改变框架大小时,树控件的大小并没有跟着改变。这个也很好解决,重载CChildView::OnSize,让树控件的大小随着视图大小调整:

void CChildView::OnSize(UINT nType, int cx, int cy)

{

    CWnd::OnSize(nType, cx, cy);

    // Resize tree control to fill the whole view.

    m_tree.MoveWindow(0, 0, cx, cy, TRUE);

}


视图子窗体的消息处理

为了演示如何对视图子窗体的消息进行处理,我向CChildView再次添加了一个按钮,添加过程与树控件的情况相同。那么,如何为按钮的点击事件编写处理函数呢?Framework提供的默认处理函数为OnCmdMsg,此函数的参数包含了发生事件的控件ID(也就是在调用控件Create函数时指定的那个值)、事件的类型等信息。因此可以重载视图的OnCmdMsg函数,在默认处理前检测是否按钮的点击事件并作相应处理:

BOOL CChildView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)

{

    if (IDC_BTN_REFRESH == nID && CN_COMMAND == nCode)

    {

        AfxMessageBox(_T("Refresh Button Clicked!"));

        return TRUE;

    }

    return CWnd ::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

}

总结

向视图添加控件的基本过程如下:

      1、向视图类添加控件成员变量

      2、重载视图的OnCreate函数,添加对控件Create函数的调用

      3、视图创建完成后,初始化控件的大小

      4、重载视图的OnSize函数,添加对控件大小的设置

      5、如果要对控件消息进行处理,重载视图的OnCmdMsg函数

 

p.s.本文是从自己的学习笔记整理来的,本来有几张图片,这里贴起来还挺麻烦,罢了。
p.s.其实都挺简单,不过我个人的体会,从Dialog Based App到Doc/View结构还真是挺不适应的,由于对整个框架的不了解,想加点自己的代码也不知道安插在哪里 ... 写给向我一样的新手

Feedback

# re: Customized SDI —— 向视图添加控件  回复  更多评论   

2008-04-12 02:29 by zhkza99c
其实所有的控件都是可以动态创建出来的。
在初始化或者程序运行的过程中都可以调用
Create函数进行创建,不同的控件之间略有变化,
最后添加消息响应函数就OK了。
DIg和Doc/View之间并没有本质的区别(在创建控件方面。)

# re: Customized SDI —— 向视图添加控件  回复  更多评论   

2008-04-12 02:34 by 田园的拾荒者
还有一种相应控件消息的方法不是在OnCmdMsg里响应,而是使用控件的宏
类似ON_NOTIFY之类的,类似ON_MESSAGE之类,根据之前定义好的控件ID和函数进行绑定,最后在函数体内写他的响应动作,这是常用的方法,在OnCmdMsg中响应会显的很乱,尤其是在大量控件动态生成的时候,拥有大量的消息响应函数。

# re: Customized SDI —— 向视图添加控件  回复  更多评论   

2008-04-12 22:24 by buf
谢谢楼上,像你说的那样写的确要标准和清楚很多。不过,button好像不支持ON_NOTIFY宏,只能ON_COMMAND/ON_BN_CLICKED,道道还挺多。

# re: Custom SDI 1 —— 向视图添加控件  回复  更多评论   

2008-12-16 15:47 by xyz
看了之后豁然开朗,非常感谢

# re: Custom SDI 1 —— 向视图添加控件  回复  更多评论   

2009-09-17 05:41 by passenger
谢谢你的文章,看了后明白了很多

# re: Custom SDI 1 —— 向视图添加控件  回复  更多评论   

2011-09-23 21:27 by downlone
为什么我构建单文档视图的时候在框架类中没有CChildView m_wndView;//视图???????

# re: Custom SDI 1 —— 向视图添加控件  回复  更多评论   

2011-10-02 14:13 by buf
@downlone
应该有的吧?你仔细找一找,相应的cpp文件中都引用了这个变量啊?
只有注册用户登录后才能发表评论。