渴望飞翔
Fly in the C++ Sky...
posts - 9,  comments - 6,  trackbacks - 0

做一个MFC程序的时候碰到一个需求。就是需要根据定制情况,动态生成菜单,菜单的具体结构和信息是之前不知道的(因此不能利用工具构造),点击不同类型的菜单会触发特定的一类事件(需要动态绑定事件)。这种需求实际是蛮不BT的,很多场合下都可能会有,用C#写了个Demo花了不到半个小时。但转到MFC下来写,就费尽周折。其实这个问题代表了在MFC中动态创建资源绑定事件的一般性问题,所以总结一下。

动态创建菜单需要先了CMenu类。通常我们利用工具绘制一个菜单,每一个菜单项下都可以视为有一个CMenu类。它们联系在一起,形成树状。典型的一个菜单对应过来是如下图这个样子:

 

如上,CMenu可以分成三种,一个是Popup(黄色),一个是Separator(灰色),一个是Item(红色)。前两种都是没有ID信息的,Popup有一个指针,指向其SubMenu;Item保存各种信息有ID可以响应事件;Separator,恩,基本是一穷二白的。

CMenu的CreateMenu方法可以创建一个菜单资源,用DeleteMenu(包含所有子菜单)或DestoryMenu可以销毁菜单资源,用AppendMenu可以添加一个菜单。了解这些内容,就可以开工了,现实现上图所示的MainSubMenu1下菜单的动态创建,代码如下:

    // 假设在ChildFrm中,调用该方法获得当前的主菜单指针
    CMenu* mainMenu = AfxGetMainWnd()->GetMenu();
    CMenu* subMenu = NULL;


    // 遍历主菜单下的各级菜单寻找名为MainSubMenu1的菜单

    int menuCount = mainMenu->GetMenuItemCount();

    for(int i = 0; i < menuCount; i++)
    {
        CString menuName;
        if(mainMenu->GetMenuStringA(i, menuName, MF_BYPOSITION)
            && menuName == "&MainSubMenu1")
        {
            drawingMenu = mainMenu->GetSubMenu(i);
            break;
        }
    }

    // 移除原有的菜单项
    int subMenu1Count = subMenu->GetMenuItemCount();
    for(int i = subMenu1Count - 1; i >= 0; i--)
    {
        subMenu->DeleteMenu(i, MF_BYPOSITION);
    }

    // 动态添加Item菜单项
    for(int i = 0; i < 2; i++)
    {

        CString message = "";

        subMenu->AppendMenuA(MF_STRING, ID_BEGIN + i, message.Format("SubSubMenu%i", i);

    }


    // 添加分隔符

    subMenu->AppendMenuA(MF_SEPARATOR);


    // 添加弹出式子菜单

    CMenu * popupMenu = new CMenu();
    popupMenu->CreateMenu();
    for(int i = 0; i < 2; i++)
    {

        CString message = "";

        popupMenu->AppendMenuA(MF_STRING, ID_BEGIN + 2 + i, message.Format("PopupSubMenu%i", i));

    }
    subMenu->AppendMenuA(MF_POPUP, (UINT_PTR)popupMenu->operator HMENU(), "PopupMenu");

有几个需要注意的地方,一个是主菜单的指针获得,可以参考《MFC框架各部分指针获取方式》一文。另一个是Popup的菜单建立,策略是分成两部分,先new出内存在Create出资源,缺一不可。最后一个是为每个Item菜单合理分配ID,这些ID须事先预留出来,在MFC中,至少40000到49000通常都是没人用。

这也就引出下一个问题,即菜单事件的动态绑定。我们知道在.net中,事件是真正动态绑定的,而MFC中的事件都是只能静态绑定,这是由两者的编译方式决定的。所以,在MFC中需要定义菜单事件,你需要先挖好坑(预留足够ID),规定每个坑种什么罗卜(将不同类型的ID绑定到不同类别的事件处理函数上),最后才能按坑种罗卜(为执行相应事件的菜单设置相应的ID)。

可以有两种方式来绑定对应ID处理的事件,一个是通过ON_COMMAND_RANGE宏(想一下ON_COMMAND宏会不会派上用场?)在MessageMap里绑定批量处理事件的函数;另一个是重载PreTranslateMessage函数,截获并判断ID来进行处理。思想都是类似的。值得注意的是,通常还需要配套使用ON_UPDATE_COMMAND_UI_RANGE来保证动态创建的菜单Enable为True,否则很可能菜单不可以点击(我就被郁闷过很久:()。



duguguiyu 2007-07-21 19:34 发表评论

文章来源:http://www.cnblogs.com/duguguiyu/archive/2007/07/21/826816.html
posted @ 2007-07-21 19:34 duguguiyu 阅读(3304) | 评论 (4)编辑 收藏
前人在CSDN总结的,曾经帮助过我,整理总结一下,希望也能帮助一下别人。
    

 

获得CWinApp

获得CMainFrame

获得CChildFrame

获得CDocument

获得CView

在CWinApp中

 

AfxGetMainWnd()

m_pMainWnd

AfxGetMainWnd()->MDIGetActive()

AfxGetMainWnd()->GetActiveFrame()

SDI:AfxGetMainWnd()->GetActiveView()->GetDocument()

MDI:AfxGetMainWnd()->MDIGetActive()->GetActiveView()->GetDocument()

SDI:AfxGetMainWnd()->GetActiveView()  
MDI:AfxGetMainWnd()->MDIGetActive()->GetActiveView() 
在CMainFrame中

AfxGetApp()

theApp

MDIGetActive()

GetActiveFrame()

SDI:GetActiveView()->GetDocument()  
MDI:MDIGetActive()->GetActiveView()->GetDocument()  
SDI:GetActiveView()  
MDI:MDIGetActive()->GetActiveView() 
在CChildFrame中

AfxGetApp()

theApp

GetParentFrame() 

 

GetActiveView()->GetDocument()   GetActiveView()
在CDocument中

AfxGetApp()

theApp

AfxGetMainWnd()  

AfxGetMainWnd()->MDIGetActive()

AfxGetMainWnd()->GetActiveFrame()

POSITION   pos   =   GetFirstViewPosition();GetNextView(pos)  
在CView中

AfxGetApp()

theApp

AfxGetMainWnd()   GetParentFrame()   GetDocument()
在其他类中

AfxGetApp()

AfxGetMainWnd()  

AfxGetMainWnd()->MDIGetActive()

AfxGetMainWnd()->GetActiveFrame() 

SDI:AfxGetMainWnd()->GetActiveView()->GetDocument()

MDI:AfxGetMainWnd()->MDIGetActive()->GetActiveView()->GetDocument()

SDI:AfxGetMainWnd()->GetActiveView()  
MDI:AfxGetMainWnd()->MDIGetActive()->GetActiveView() 
 
理一理MFC的这几个类的关系,可以很容易明白上面的这些乱七八糟的逻辑。
App是应用域,所有的域中的东西都可以通过全局函数访问到它。
MainFrame是主框架,也基本可以用全局函数访问到。
MainFrame下是若干个ChildFrame,ChildFrame中若干个View和Document(可能不成对),ChildFrame管理着View,View和Document进行互操作。
因此整体框架就出来了,一般除了直接应用的关系都可以通过MainFrame-->Active ChildFrame-->Active View-->Document这条线进行访问,这应该叫什么来自?万能方法吧^_^。
恕我懒惰,不愿意画一个更详细的图解,凑合着看看吧。



duguguiyu 2007-06-22 01:33 发表评论

文章来源:http://www.cnblogs.com/duguguiyu/archive/2007/06/22/792511.html
posted @ 2007-06-22 01:33 duguguiyu 阅读(475) | 评论 (0)编辑 收藏

最近在写一些关于MFC的东西,是因为做了些MFC的项目,一些零散的东西需要总结一下。但这并不代表我有些喜欢他了,其实还是蛮讨厌的。毕竟看了.Net Framework,再看N多年前的MFC,多少是有些不顺眼的。机理上的东西不敢多说,有些变量和方法名字的设置,站在FCL的设计角度看了,多少觉得有些不爽。也许是我太弱,也许是因为它的设计上考虑还是没有足够的经验,列举一些,娱乐娱乐。

1. 在构造一个自定义的Dialog对象时,我们会传入一个CWnd的指针进去,那个名为pParent的对象。但当我在Dialog中调用GetParent函数的时候,无论传进的是什么指针(比如CYourView),得到的返回值都是指向CMainFrame的指针。曾经由于这个我调了N久的程序,我承认这是由于我对MFC的了解不够造成的,但这名字也太具有迷惑性了吧。

2. 当然还有臭名昭著的UpdateData函数,很荣幸在这点上我和大师一样,每次用到的时候都要停下来查看一下才能分清楚那个诡异的bool变量的意义。也许这在有IDE的时候不是问题,但问题是我看书的时候就会很麻烦了。分离成两个明确命名的函数,也许会好很多。

3. ShowWindow。恩就是它。看到这个名字,不熟悉MFC的人应该都会觉得它是用来显示一个窗口的。但...,其实它兼有显示和隐藏窗口的功能,决定这一切的又是一个诡异的bool量...

4. MoveWindow和SetWindowPos。这一对不够专注的兄弟。它们太强大了,所能做的事远不止Move和SetPos那么简单,当我想改变一个窗体大小的时候,我翻来覆去的找函数,万万没想到,原来是这对名不副实的兄弟的工作,Faint...

5. GetWindowRect和GetClientRect。其实他们也许没有问题,有问题的是我,我不喜欢用引用量代替一个貌似应该由返回值完成的东西...

6. SelectObject。为什么你传入一个新的Object,它返回一个老的Object呢?不止我一个人,很多人都被这个搞晕过,其实我也不明白,一个函数做两个函数的事,这确实有点热心过度了吧...

7. afx_msg。一个永远都没被用到的预留量,这样的东西在MFC中有很多...



duguguiyu 2007-06-21 23:18 发表评论

文章来源:http://www.cnblogs.com/duguguiyu/archive/2007/06/21/792426.html
posted @ 2007-06-21 23:18 duguguiyu 阅读(208) | 评论 (0)编辑 收藏

利用MFC向导建立一个工程,然后开始编码。这就是我通常做一个MFC工程的开始。但向导可不是一个守规矩的东西,它会为你添加很多的代码,为你设置大量的编译和链接选项。大部分时候这种工作是善意的,但是好心不一定办好事,你不好好了解它,它会给你带来很多的麻烦。

在配置一个基于OpenCasCade的程序中,我就遇到了很多麻烦。MFC向导在它所生成的View, Document等架构类中都添加了一段如下代码:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

在Debug状态下(VS会为你默认添加一个_DEBUG的预编译项),你在该类中调用的new操作符都会被DEBUG_NEW所取代,请警惕这个行为,如果你重载过某个类的new,很可能就会由于它导致无法编译通过或运行不正确。

除此之外一些默认的设置也要注意,在VS2005中是默认支持Unicode的,它会在你的编译选项中加入/D "_UNICODE" /D "UNICODE"。这就会使得CString和你可能用到的std::string存在很麻烦的转换问题。你需要修改项目属性中General-->Character Set为not set,将其设为ununicode,保证与std::string的一致(当然你还可以运用其他的解决方法满足你的需求)。

有时候IDE也会“好心办坏事”,比如在一个解决方案中有两个工程,你为A添加B的编译依赖,在A的链接选项中就会悄悄加上对B生成的dll的引用。当你某天整理代码取消了这个依赖的时候,你突然发现莫名的出现了很多link错误。不要慌张,在A中添加上B链接项就好了,这项工作其实是你必须自己做的,只是你添加了依赖编译器非常主动的帮你完成了。

也许你看上面的错误都很简单,但如果不小心,也许有天也会像我一样深陷其中半天爬不出来。总之,在天天用VS2005建MFC工程的时候,提前做好两件事。一件是通读一遍系统默认生成的代码,做到心中有数,每一条莫名其妙的东西都要了解一下它的用途;另一件是在刚开始和改变了工程属性之后查看一下你的编译和链接命令,搞清楚它做了什么事,有时候命令行虽然难记一点,但确实是一目了然,你可以不必每天用命令行编译程序,但一定要对这些命令心如明镜,了如指掌才好。



duguguiyu 2007-06-21 00:20 发表评论

文章来源:http://www.cnblogs.com/duguguiyu/archive/2007/06/21/791161.html
posted @ 2007-06-21 00:20 duguguiyu 阅读(405) | 评论 (0)编辑 收藏
仅列出标题
共2页: 1 2 
Welcome to my c++ home...

<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(1)

随笔分类(9)

随笔档案(9)

搜索

  •  

积分与排名

  • 积分 - 10338
  • 排名 - 1144

最新评论

阅读排行榜

评论排行榜