【Windows编程】系列第十一篇:多文档界面框架

前面我们所举的例子中都是单文档界面框架,也就是说这个窗口里面的客户区就是一个文档界面,可以编写程序在里面输入或者绘制文本图形输出,但是不能有出现多个文档的情况。比如下面的UltraEdit就是一个典型的多文档界面,他可以同时编辑多个文档,每个文档还可以最大化,最小化等等,我们今天就来看看多文档的基本框架是怎么实现的。

多文档界面的框架创建需要几下几步。

  • 主框架窗口创建

主框架窗的创建跟普通的窗口没有什么区别,就是自己注册一个类并用该类创建一个重叠窗口,这个可以用CreateWindow/CreateWindowEx函数完成,主框架窗口可以用自己的菜单和状态栏。

  • 客户区窗口创建

客户区创建的创建同样用你CreateWindow,但需要指定类为“MDICLIENT”,用这个类会创建多文档的客户区窗口。或者采用CreateWindowEx函数,指定扩展风格为WS_EX_MDICHILD。客户区不需要菜单和状态栏。

  • 视图窗口创建

创建工作或者视图窗口作为实际文档窗口,这个也是需要自己注册类并创建自己需要的视图窗口。视图窗口可以有自己的菜单,一般不需要状态栏。当视图窗口激活时,主窗口的菜单需要替换成视图窗口的菜单,因为这个时候往往是对视图窗口进行操作,菜单替换也是理所当然的。菜单设置通过WM_MDISETMENU消息完成。该消息定义如下:

SendMessage(hWndControl, WM_MDISETMENU, wParam, lParam)
wParam表示新框架窗口菜单,
lParam表示要设置的菜单句柄

OK,基本的API就这些,下面我们直接上demo程序来演示,由于视图一般是一个多文档的实例,所以demo代码中用类来存储视图,每一个视图就是一个类的实例:

主文件,该文件包括主框架窗口、客户窗口已经视图窗口调用:

#include <windows.h>

#define CLIENT_WND_BGROUND

#define IDM_FIRSTCHILD 1001
#define IDM_FILE_NEW   1002

HINSTANCE g_hInst;
TCHAR     szMDIMainFrame[]= TEXT("MDI Main Frame");
TCHAR     gszAppName[]    = TEXT("MDI Demo");
HWND      ghWndMDIClient;
HWND      ghWndMainFrame;
HMENU     ghMainFrameMenu;

void CreateChildWndMenu(void);
HWND CreateDocView(HWND hParentWnd, HINSTANCE hInstance);
static LRESULT CALLBACK MainFrameProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

#ifdef CLIENT_WND_BGROUND
static WNDPROC pfOrigClientWndProc;
LRESULT CALLBACK ClientWndSubClassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_ERASEBKGND:
        {
            RECT rect;
            HDC hDC = (HDC)wParam;
            GetClientRect(hwnd, &rect);
            HBRUSH hbr = CreateSolidBrush(RGB(125, 150, 125));
            HBRUSH holdbr = (HBRUSH)SelectObject(hDC, hbr);
            FillRect(hDC, &rect, hbr);
            SelectObject(hDC, holdbr);
            DeleteObject(hbr);
        }
        return TRUE;
    default:
        break;
    }

    return CallWindowProc(pfOrigClientWndProc, hwnd, uMsg, wParam, lParam); 
}
#endif

HMENU CreateMainFrameMenu(void)
{
    //主框架菜单
    HMENU hMenu = CreateMenu();

    //文件菜单
    HMENU hFileMenu = CreateMenu();
    AppendMenu(hFileMenu, MF_STRING, IDM_FILE_NEW, TEXT("&New"));
    AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("&File"));

    return hMenu;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    MSG msg;
    WNDCLASSEX wndclass = {0};

    wndclass.cbSize		= sizeof(WNDCLASSEX);
    wndclass.style		= CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc	= MainFrameProc;
    wndclass.hInstance		= hInstance;
    wndclass.hIcon		= NULL;
    wndclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszClassName	= szMDIMainFrame;
    if(!RegisterClassEx(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), gszAppName, MB_ICONERROR);
        return 0;
    }
    g_hInst = hInstance;

    ghMainFrameMenu = CreateMainFrameMenu();
    CreateChildWndMenu();

    //Create the main MDI frame window
    ghWndMainFrame = CreateWindow(szMDIMainFrame, 
                                gszAppName, 
                                WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                CW_USEDEFAULT,	// allows system choose an x position
                                CW_USEDEFAULT,	// allows system choose a y position
                                400,
                                300,
                                NULL,		// handle to parent window
                                ghMainFrameMenu,// handle to menu
                                hInstance,	// handle to the instance of module
                                NULL);		// Long pointer to a value to be passed to the window through the 
    ShowWindow(ghWndMainFrame,  SW_SHOW);
    UpdateWindow(ghWndMainFrame);

    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg) ;
        DispatchMessage(&msg) ;
    }

    return msg.wParam ;
}

LRESULT CALLBACK MainFrameProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_CREATE:
        {
            CLIENTCREATESTRUCT ccs;
            ccs.hWindowMenu	 = NULL;
            ccs.idFirstChild = IDM_FIRSTCHILD;
            ghWndMDIClient = CreateWindow(TEXT("MDICLIENT"), NULL,
                                        WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
                                        0, 0, 0, 0, hWnd, NULL,
                                        g_hInst, (void *)&ccs);
#ifdef CLIENT_WND_BGROUND
            pfOrigClientWndProc = (WNDPROC)SetWindowLong(ghWndMDIClient, GWL_WNDPROC, (LONG)ClientWndSubClassProc);
#endif
        }
        return 0;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDM_FILE_NEW:
            CreateDocView(ghWndMDIClient, g_hInst);
            return 0;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefFrameProc(hWnd, ghWndMDIClient, message, wParam, lParam);
}

下面是视图窗口,每个视图窗口由一个类来描述,本文仅仅是一个demo,只有实例化一个视图窗口作为演示:

#include <windows.h>

#define IDM_EDIT_COPY 1003
#define IDM_EDIT_PASTE 1004

class CViewInfo
{
public:
    CViewInfo(HINSTANCE hInst);
    ~CViewInfo();
    HWND CreateViewWindow(HWND hParentWnd);
private:
    HINSTANCE m_hInst;
    HWND m_hWndView;
};

static TCHAR szViewWndName[] = TEXT("View Window");
static CViewInfo *g_pSystemInfo;
static HMENU      ghChildWndMenu;
extern HMENU      ghMainFrameMenu;

CViewInfo::CViewInfo(HINSTANCE hInst)
{
    m_hWndView = NULL;
    m_hInst = hInst;
}

CViewInfo::~CViewInfo()
{
    m_hWndView = NULL;
}

LRESULT CALLBACK ViewWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_MDIACTIVATE:
        {
            HWND hWndClient = GetParent(hWnd);
            HWND hWndFrame  = GetParent(hWndClient);

            HMENU hMenu = GetSubMenu(ghChildWndMenu, 0);
            if (lParam == (LPARAM)hWnd)
            {
                SendMessage(hWndClient, WM_MDISETMENU, (WPARAM)ghChildWndMenu, (LPARAM)hMenu);
                EnableMenuItem(ghChildWndMenu, IDM_EDIT_COPY, MF_GRAYED);
            }

            // Set the framewindow menu if losing focus
            if (lParam != (LPARAM)hWnd)
            {
                SendMessage(hWndClient, WM_MDISETMENU, (WPARAM)ghMainFrameMenu, (LPARAM)NULL);
            }

            // call DrawMenuBar after the menu items are set
            DrawMenuBar(hWndFrame);

            return 0 ;
        }

    case WM_CLOSE:
        delete g_pSystemInfo;
        break;

    case WM_DESTROY:
        return 0;
    }

    return DefMDIChildProc(hWnd, message, wParam, lParam); //Frame window calls DefFrameProc rather than DefWindowProc
}

HWND CViewInfo::CreateViewWindow(HWND hParentWnd)
{
    WNDCLASSEX wcDoc;
    wcDoc.cbSize	= sizeof(WNDCLASSEX);
    wcDoc.style		= CS_HREDRAW | CS_VREDRAW;
    wcDoc.lpfnWndProc	= ViewWindowProc;
    wcDoc.cbClsExtra	= 0;
    wcDoc.cbWndExtra	= 0;
    wcDoc.hInstance	= m_hInst;
    wcDoc.hIcon		= NULL;
    wcDoc.hCursor	= LoadCursor(NULL, IDC_ARROW);
    wcDoc.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
    wcDoc.lpszMenuName	= NULL;
    wcDoc.lpszClassName	= szViewWndName;
    wcDoc.hIconSm	= NULL;

    if(!RegisterClassEx(&wcDoc))
    {
        DWORD dw_LastError = GetLastError();
        if(ERROR_CLASS_ALREADY_EXISTS != dw_LastError)
        {
            return 0;
        }
    }

    m_hWndView = CreateWindowEx(WS_EX_MDICHILD, szViewWndName, 
                                TEXT("New view"), 0, CW_USEDEFAULT,
                                CW_USEDEFAULT, 300, 200,
                                hParentWnd, NULL, m_hInst, NULL);

    return m_hWndView;
}

HWND CreateDocView(HWND hParentWnd, HINSTANCE hInstance)
{
    HWND hView;

    g_pSystemInfo = new CViewInfo(hInstance);
    hView = g_pSystemInfo->CreateViewWindow(hParentWnd);
    if (hView == NULL)
    {
        delete g_pSystemInfo;
        g_pSystemInfo = NULL;
    }

    return hView;
}

void CreateChildWndMenu(void)
{
    //View菜单
    ghChildWndMenu = CreateMenu();

    //编辑菜单
    HMENU hEditMenu = CreateMenu();
    AppendMenu(hEditMenu, MF_STRING, IDM_EDIT_COPY, TEXT("&Copy"));
    AppendMenu(hEditMenu, MF_STRING, IDM_EDIT_PASTE, TEXT("&Paste"));
    AppendMenu(ghChildWndMenu, MF_POPUP, (UINT_PTR)hEditMenu, TEXT("&Edit"));
}

本实例运行后,界面如下:

选择File->New新建一个视图后demo程序如下,可以看到菜单编程视图的菜单:

最大化后可以看到视图窗口和填满客户窗口:

虽然本程序只实现了一个视图的实例,但是再增加一个是很容易的,只要想办法在菜单中调用CreateDocView函数,并且正确处理对象指针即可。实例并没有增加状态栏,因为这个对多文档并不是必须的,要增加的读者可以参考前面的创建Toolbar和Statusbar一文。

本实例实现了一个基本的多文档窗口框架,读者朋友可以在此基础上加上工具栏、状态栏、视图窗口创建对类的处理,多实例以及具体的需求,完成实用化的多文档界面。

更多经验交流可以加入Windows编程讨论QQ群454398517

关注微信公众平台:程序员互动联盟(coder_online),你可以第一时间获取原创技术文章,和(Java/C/C++/Android/Windows/Linux)技术大牛做朋友,在线交流编程经验,获取编程基础知识,解决编程问题。程序员互动联盟,开发人员自己的家。

转载请注明出处http://www.coderonline.net/?p=2052,谢谢合作!

时间: 2024-11-10 06:47:11

【Windows编程】系列第十一篇:多文档界面框架的相关文章

Windows Azure系列 -《基础篇》- 存储

打开windows azure管理门户,点击下方的"新建", 定义一个存储名称,并设定好地缘组和位置,冗余的方式可以是地域之间的冗余,也可以是本地区域内的冗余,可以根据自己的实际情况选择. 目前windows azure在中国有两个数据中心可供选择,以托管订阅的虚机,建议选择离自己所在地位置更近一点的,访问效率会比较高. 设置完后,点击创建存储账号,既可以完成存储账号的创建工作. 在列表中我们可以看到创建的过程及状态, 通过点击所创建的存储,可以查看它的状态和修改存储配置, 在配置标签

Windows Azure系列 -《基础篇》- 创建虚拟网络

如何在Windows Azure中创建虚拟网络,以构建云环境中的虚拟局域网: 1.登陆Windows Azure平台,点击侧边栏网络按钮,在中间点击"创建虚拟网络". 2.在接下来的配置页面,填写虚拟网络的名称.选择地理外置和地缘组(如果已有),没有则选择创建新的地缘组. 3.接下来填写DNS服务器地址(没有则留空) 4.规划和配置IP网络,选择适用的地址空间. 5.最后点击确认按钮完成. Windows Azure系列 -<基础篇>- 创建虚拟网络,布布扣,bubuko.

Windows Azure系列 -《基础篇》- 如何创建虚拟机

首先,使用自己的windows azure账号登陆管理平台manage.windowsazure.cn,找到并点击"虚拟机"标签,即可看到目前云平台中你所拥有的虚机实例,在我的环境中现在没有任何的虚机,所以我们可以通过点击图示的新建按钮进行选择或直接点击"创建虚拟机"进行创建: 点击"创建虚拟机",在DNS名称位置填写所建虚机的主机名,并选择映像和虚机大小(可选单核至8核,内存从768M至56G),这里说明一下,处于用户名不可以设置为常用的adm

Windows Azure系列 -《基础篇》- 基本设置

打开Windows Azure管理门户,在左侧边栏点击设置,展开设置页面 在这个页面中,我们可以看到你所拥有的订阅的信息和管理信息等. 这里简单说一下各项内容: 1. 订阅 订阅部分显示你目前账号所拥有的订阅信息以及管理帐户,如果你有多个订阅并绑定在一起,那么它将显示你所有的订阅信息. 2. 管理证书 在管理证书页面,我们可以管理用于windows azure的证书,点上传以便将证书导入windows azure. 3. 管理员 在windows azure中,如果需要有多个管理人员,我们可以在

Windows Azure系列 -《基础篇》- 计划程序

打开Windows Azure管理门户,在左侧边栏点击设置,展开计划程序页面,我们可以看到目前的作业情况: 在计划程序中,我们可以做的就是定制特定管理作业,并查看以往作业的执行情况. 点击作业集合,然后点击创建计划程序作业,即可创建你自己的作业, 在接下来的页面选择快速创建,填写作业名称并选择区域,点击下一步 在作业页面,填写作业的相关信息,包括名称.类型.方法等 确认重复周期和启动时间即可. 接下来就可以看到作业集合的状态, 以及可以查看各作业的执行情况,通过筛选可以有选择的查看所要关注的作业

Hello World!这是一篇测试文档

这是用Windows Live Writer写的一篇测试文档仅供测试

Linux 指令篇:文档编辑--col

功能说明:过滤控制字符. 语 法:col [-bfx][-l<缓冲区列数>] 补充说明:在许多UNIX说明文件里,都有RLF控制字符.当我们运用shell特殊字符">"和">>",把说明文件的内容输出成纯文本文件时,控制字符会变成乱码,col指令则能有效滤除这些控制字符. 参 数:  -b   过滤掉所有的控制字符,包括RLF和HRLF.  -f   滤除RLF字符,但允许将HRLF字符呈现出来.  -x   以多个空格字符来表示跳格字

iOS Foundation 框架 224 篇相关文档分类整理

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 截至 2014-05-02 ,苹果官网 Foundation 框架相关文档共计 224 篇,分类如下: Foundation 框架概述文档:常量.

PHPWord生成多篇word文档的时候目录文件冗余bug解决方案

phpword的开源链接在这里:https://github.com/PHPOffice/PHPWord,PHPword是很多服务端技术为php的网站上的word下载的功能支撑技术. 其原理并不难以理解,因为word可以解析xml形式的数据,所以phpword本质是生成一个xml文件. 相关介绍可以参考:https://support.office.com/zh-cn/article/Open-XML-%E6%A0%BC%E5%BC%8F%E5%92%8C%E6%96%87%E4%BB%B6%E