代码改变世界

翻译: 如何改变MFC应用程序主窗口的类名

2011-06-10 07:23  menggucaoyuan  阅读(1918)  评论(3编辑  收藏  举报

说明
    MFC自动创建的应用程序中,主窗口的类名是固定的。但在许多情况下,你可能希望自己拟定MFC的主窗口的类名。
    如果你想进行进程间通信(IPC),这种技术就更显得简洁有效。最早的实现不同进程间通信的方法是发送消息,但是发送消息需要知道发送的目标对象,即确定目标窗口,识别出它的ID。
    一种方法是迭代所有具有最上面显示属性的窗口,然后选出目标窗口的类名或者窗口名称,或者你也可以向所有窗口广播一个注册的窗口消息。这两种方法显得过于繁琐,因为我们通过一种方法可以直接一个特定的窗口。
    Windows提供了一个函数FindWindow,它可以根据一个特定的名称或者类名找到特定的窗口。
    windows中的窗口的标题常常由于打开文件的不同而发生改变,甚至用户改变了本地语言(当然前提是程序支持多种语言),窗口标题也会发生改变。但是如果你能给一个类预定义一个类名,那么你就可以根据这个唯一的类名找到这个窗口。
    唯一的问题是,MFC创建的程序的类名在MFC程序创建时已经被预定义了。对于一个给予dialog的程序来说,这个预定义的类名是#32770。

背景知识
    根据上面的分析,程序员现在面临的唯一的问题就是如何改变MFC程序的预定义的名称。
    对于MFC SDI/MDI程序来说,我们可以通过函数 CMainFrame::PreCreateWindow 来改变类名。
    对于基于dialog的程序来说,我们可以定义一个资源模板,在这个资源里指定类名,然后在VS编译程序时强制它加载这个资源。

SDI/MDI程序的代码示例
    如果你用MFC创建一个SDI/MDI程序,你会发现一个函数CMainFrame::PreCReateWindow,在窗口创建过程中,这个函数会被调用很多次,应为它里面提供了窗口的类信息、窗口的风格等相关的窗口信息。
    你可以重写这个函数,把一个窗口类名为你定义的名称的窗口类对象注册后,然后把窗口类名赋给返回值的类名属性,以供后续步骤继续使用。
    函数CMainFrame::PreCreateWindow至少会被调用两次。MFC创建窗口过程中会检查由窗口结构体(window class struct)定义的icon值与CWinApp::LoadFrame定义和使用的icon值是否一致。如果MFC产生窗口过程中(一般是函数 CFrameWnd::GetIconWndClass)发现窗口结构体对象定义了一个不同的icon,则MFC会自己使用它认为是正确的icon值来产 生一个窗口类。一般的,icon的ID是IDR_MAINFRAME。
    下面给出了一个程序员自己重写的函数CMainFrame::PreCreateWindow的示例。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;

    // Just clear the styles we don't want.
    cs.dwExStyle &= ~WS_EX_CLIENTEDGE;

    // Check if our class is already defined
    LPCTSTR pszClassName = _T("OwnClassName");
    WNDCLASS wndcls;
    if (!::GetClassInfo(AfxGetInstanceHandle(), pszClassName, &wndcls))
    {
        // Get the current requested window class
        VERIFY(GetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndcls));

        // We want to register this info with our name
        wndcls.lpszClassName = pszClassName;

        // Need to preset the icon otherwise the function GetIconWndClass
        // calling us will overwrite our class.
        LPCTSTR pszIcon = MAKEINTRESOURCE(IDR_MAINFRAME);
        HINSTANCE hInst = AfxFindResourceHandle(pszIcon, ATL_RT_GROUP_ICON);
        _ASSERTE(hInst!=NULL);
        wndcls.hIcon =     ::LoadIcon(hInst, pszIcon);

        // Register our class now and check the outcome
        if (!::RegisterClass(&wndcls))
        {
            _ASSERTE(!__FUNCTION__ "RegisterClass failed");
            return FALSE;
        }
    }

    // Now use our class
    cs.lpszClass = pszClassName;
    return TRUE;
}

基于dialog程序的代码示例
    在一个基于dialog的程序中,你需要做两件事。
    一般的,基于对话框的程序中,其对话框的类名是"#32770"。当你想用一个不同的类来创建一个对话框时,那就需要告知程序对话框模板的类名,因为我们 不能不能像处理SDI/MDI程序一样干预对话框的创建。在以往的VS程序中,我们可以打开资源编辑器,改变对话框的类名,但是VS-2008和VS- 2010里面有一个bug,所以它们俩不允许我们指定类名。在资源编辑器中这个属性值的属性框是灰色的,所以不能改变类名。即使你通过其他手段改变了类 名,这个地方也显示不出改变后的正确值。
    所谓的其他手段,就是你可以在你的程序打开前,用其他编辑器,如记事本程序代开你的资源文件,找到你的对话框模板后,在其中添加一行CLASSNAME属性。添加完毕后,VS编译程序时会根据这行属性确定的类名来定义一个窗口,它不会删除这行值。

示例代码:

IDD_OWNCLASSNAMEDLG_DIALOG DIALOGEX 0, 0, 199, 28
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE |
WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "OwnClassNameDlg"
CLASS "OwnClassNameDlg"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
...
    剩下的事情,就是在待用函数dlg.DoModal()之前,注册一个新的基于系统类#32770的窗口类,这个类的名称就是你刚才指定的类名。
    代码并不是很复杂,示例如下:...
    // Just get default class for the dialogs
    WNDCLASS wndcls;
    ::GetClassInfo(NULL,MAKEINTRESOURCE(32770),&wndcls);
   
    // Set our own class name
    wndcls.lpszClassName = _T("OwnClassNameDlg");

    // Just register the class
    if (!::RegisterClass(&wndcls))
    {
        _ASSERTE(! __FUNCTION__ " Failed to register window class");
        return FALSE;
    }
   
    COwnClassNameDlgDlg dlg;
    m_pMainWnd = &dlg;
    INT_PTR nResponse = dlg.DoModal();
...

    补充说明:实际上,"#32770"并不能被称为一个窗口类名。它只是一个注册的对话框类的ID值,代码MAKEINTRESOURCE(32770)会把它变成类的名称。你也可以使用字符串"32770"来代表这个类。
    啰嗦了一大堆,希望你喜欢我上面给你的这个小技巧。

原文地址:http://www.codeproject.com/KB/dialog/CustomClassName.aspx。

原文还有附属的资源,我已经上传到csdn上,到这个网址http://menggucaoyuan.download.csdn.net/,寻找一个与文章题目同名的资源。cnblogs我不知道能否上传资源,知道的请告诉我,谢谢。