Version 9 of MFC Control in Tk toplevel

Updated 2006-05-04 17:25:11

http://www.tfh-berlin.de/~s716193/pics/MFCinTk.jpg [ author: Sascha Leuthold ]

Using MFC Controls as Tk widgets and Combining GUI applications developed with Tk and 'native' Windows toolkits gives you an introduction, but it is not really a copy-paste implementation I was always looking for.

I saw this implemented in commercial applications, and asked myself: How did that work?

Somebody could ask, why embedding an MFC-Control into a Tk-toplevel:

  • devide GUI from core implemetation
  • building huge OpenGl or DirectX applications with platform-independent GUI (CAE,CAD,etc.)
  • make use of the impressing Tcl/Tk automation potential, and hide the core implementation
  • etc.

I will show you one complete implementation on the windows platform. It would be great, if somebody would do something similar on Unix/Linux(X11). I used VC++6.0, Tcl/Tk 8.4 and build this successfully on Win98 and WinXP. Maybe MFC and VC++6.0 is out of fashion, but I think it is still a nice topic, and it can result in a nice designed application. And at the end, the Win32-API is older than MFC, so for me, Tk is the real out of date problem in this marriage. This design makes it possible, to build applications for Linux and Windows, with minimum platform-dependence.

Comments are welcome considering implementation-details and discussions on the subject itselfs (widget embedding).

Is it possible to achieve the same easier?


We will end up in a *.exe, that sources a *.tcl.

Features

  • toplevel contains button, frame -container true and a menu
  • frame will contain one CFrameWnd
  • interface of resize to resize the CFrameWnd connected to any toplevel-resize-operation
  • interface and override of exit to make sure, that the application not crashes on closing via exit command

MFCinTk.tcl

        package provide MfCinTk 1.0

        namespace eval ::mfcintk {}

        # used by C++
        proc ::mfcintk::CreateTopLevel { args } {

                set nsPath [namespace current]

                # Toplevel
                variable toplevel_window .

                wm geometry $toplevel_window 800x600+50+50

                # Menu for the look
                set menu [menu ${toplevel_window}menubar]
                $toplevel_window configure -menu $menu
                foreach m {File Edit View Tools Window Help} {
                        set $m [menu $menu.m$m -tearoff 0]
                        $menu add cascade -label $m -underline 0 -menu $menu.m$m
                }

                variable mfcContainer [frame ${toplevel_window}mfcContainer -container true]
                set testButton [button ${toplevel_window}testButton \
                        -text "button and the toplevel are Tk-widgets" \
                        -width 100 -height 8 -command {source C:/home/MFCinTk/tcl/tkcon.tcl}]

                pack $mfcContainer -expand true -fill both -padx 5 -pady 10 
                pack $testButton -padx 5 -pady 5

                variable toplevel_id [winfo id $toplevel_window];        # used by C++
                variable mfc_id [winfo id $mfcContainer];                # used by C++

                # Bindings
                eval "bind $mfcContainer <Configure> {+ ${nsPath}::OnGraphicsSize}"
        }

        proc ::mfcintk::OnGraphicsSize { args } {

                variable mfcContainer
                return [ResizeMfC [winfo width $mfcContainer] [winfo height $mfcContainer]]
        }
  • mfcContainer -container true, to enable embedding
  • variable mfcContainer, to access via C++, so the name is fixed
  • variable toplevel_id and variable mfc_id, to access via C++
  • ::mfcintk::CreateTopLevel called from C++ during application runup (after sourcing the tcl-file)
  • bind to resize the toplevel, ResizeMfC is an interface proc provided by C++ (without the MFC Control would not resize)

Derive CWinApp to host the application/thread/event-loop

MfCinTk.h

        #ifndef MFCINTK_H
        #define MFCINTK_H

        #include "resource.h"

        class CMFCinTkApp : public CWinApp
        {
        public:
                CMFCinTkApp();

                //{{AFX_VIRTUAL(CMFCinTkApp)
                virtual int         ExitInstance( );
                virtual BOOL OnIdle(LONG lCount);
                virtual BOOL InitInstance();
                //}}AFX_VIRTUAL

        private:
                bool        Tk_Initialize();
                void        ParseCmdLine(int* argc, char*** argv);

                Tcl_Interp        *interp;
                char                **argv;
                int                argc;

        public:
                //{{AFX_MSG(CMFCinTkApp)
                //}}AFX_MSG
                DECLARE_MESSAGE_MAP()
        };

        //forward declaration
        int ResizeObjCmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
        int ExitObjCmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);

        #endif // MFCINTK_H 
  • ResizeObjCmd and ExitObjCmd are Tcl-procs

MfCinTk.cpp

        #include "stdafx.h"
        #include "MFCinTk.h"
        #include "MainFrm.h"


        BEGIN_MESSAGE_MAP(CMFCinTkApp, CWinApp)
                //{{AFX_MSG_MAP(CMFCinTkApp)
                //}}AFX_MSG_MAP
        END_MESSAGE_MAP()


        CMFCinTkApp::CMFCinTkApp()
        {
        }

        CMFCinTkApp theApp;

        bool CMFCinTkApp::Tk_Initialize()
        {

                Tcl_FindExecutable(argv[0]);

                interp = Tcl_CreateInterp(); 

                /*
  • argc, argv0 and argv
                 */
                char *args;
                Tcl_DString argString;
                char buf[BUFFER_SPACE];

                args = Tcl_Merge(argc, (CONST char **)argv);
                Tcl_ExternalToUtfDString(NULL, args, -1, &argString);
                Tcl_SetVar(interp, "argv", Tcl_DStringValue(&argString), TCL_GLOBAL_ONLY);
                Tcl_DStringFree(&argString);
                Tcl_Free(args);
                printf(buf, PRINTF_MAX_COUNT, "%d", argc);
                Tcl_ExternalToUtfDString(NULL, argv[0], -1, &argString);
                Tcl_SetVar(interp, "argc", buf, TCL_GLOBAL_ONLY);
                Tcl_SetVar(interp, "argv0", Tcl_DStringValue(&argString), TCL_GLOBAL_ONLY);


                /*
  • Init Tcl/Tk
                 */
                if (Tcl_Init(interp) == TCL_ERROR) {
                        return TCL_ERROR;
                }
                if (Tk_Init(interp) == TCL_ERROR) {
                        return TCL_ERROR;
                }

                /*
  • load framework
                 */
                if (Tcl_Eval(interp, "source [file join C:/ home MFCinTk tcl MfCinTk.tcl]") != TCL_OK) {
                        return TCL_ERROR;
                }
                if (Tcl_Eval(interp, "::mfcintk::CreateTopLevel") != TCL_OK) {
                        return TCL_ERROR;
                }

                Tcl_PkgProvide(interp, "mfcintk", "1.0");
                return TCL_OK;
        }

        BOOL CMFCinTkApp::InitInstance()
        {

                SetRegistryKey(_T("Local AppWizard-Generated Applications"));

                /*
  • Process the command line options
                 */
                ParseCmdLine(&argc,&argv);

                /*
  • Init Tcl
                 */
                if (Tk_Initialize() != TCL_OK) {
                        MessageBox(NULL, "Tcl/Tk Init error", NULL, MB_ICONERROR);
                        return FALSE;
                }

                /*
  • Get the Tk Container Path and build the custom
  • Mfc TkFrame into that Container
  • Wnd - is the CWnd Object from the container
  • id_container - returned by winfo id graphicsContainer
                 */
                char* Path = Tcl_GetVar(interp, "::mfcintk::mfcContainer", TCL_LEAVE_ERR_MSG);

                Tcl_Obj* id_container_obj = Tcl_GetVar2Ex(interp, "::mfcintk::mfc_id", NULL, TCL_LEAVE_ERR_MSG);
                int id_container;
                Tcl_GetIntFromObj(interp,id_container_obj,&id_container);

                Tcl_Obj* id_toplevel_obj = Tcl_GetVar2Ex(interp, "::mfcintk::toplevel_id", NULL, TCL_LEAVE_ERR_MSG);
                int id_toplevel;
                Tcl_GetIntFromObj(interp,id_toplevel_obj,&id_toplevel);

                Tk_Window const tkmainwin = Tk_MainWindow(interp);
                if (tkmainwin==NULL)
                                return FALSE;

                Tk_Window const tkwin = Path==NULL
                                ? tkmainwin
                                : Tk_NameToWindow(interp,Path,tkmainwin);
                if (tkwin==NULL)
                                return FALSE;
                Tk_MakeWindowExist(tkwin);
                Window const window = Tk_WindowId(tkwin);
                if (window==NULL)
                                return FALSE;

                HWND const hwnd = Tk_GetHWND(window);
                if (hwnd==NULL)
                                return FALSE;

                CWnd *const Wnd = CWnd::FromHandle(hwnd);
                if (Wnd==NULL)
                           return FALSE;

                /*
  • Build the TkFrame MfC Object
                 */
                m_pMainWnd = new CMainFrame(Wnd, id_container);
                m_pMainWnd->ShowWindow(m_nCmdShow);
                m_pMainWnd->UpdateWindow();

                Tcl_CreateObjCommand(interp, "::mfcintk::ResizeMfC", ResizeObjCmd, (ClientData)m_pMainWnd, (Tcl_CmdDeleteProc*)NULL);
                Tcl_CreateObjCommand(interp, "::exit", ExitObjCmd, (ClientData)m_pMainWnd, (Tcl_CmdDeleteProc*)NULL);

                return TRUE;
        }

        int CMFCinTkApp::ExitInstance( )
        {
                if (interp != NULL) {
                        Tcl_DeleteInterp(interp);
                        Tcl_Finalize();
                }
                CWinApp::ExitInstance();

                return 0;
        }

        BOOL CMFCinTkApp::OnIdle(LONG lCount)
        {

                while(Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT))
                {
                }
                return 0;
        }

        void CMFCinTkApp::ParseCmdLine(int* argcPtr, char*** argvPtr)
        {
        /*
         * tclAppInit.c --
         *
         *        Provides a default version of the main program and Tcl_AppInit
         *        procedure for Tcl applications (without Tk).  Note that this
         *        program must be built in Win32 console mode to work properly.
         *
         * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
         * Copyright (c) 1998-1999 by Scriptics Corporation.
         *
         * See the file "license.terms" for information on usage and redistribution
         * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
         *
         * RCS: @(#) $Id: 15857,v 1.10 2006-05-04 18:01:03 jcw Exp $

        static void
        setargv(argcPtr, argvPtr)
                int *argcPtr;                Filled with number of argument strings.
                char ***argvPtr;        Filled with argument strings (malloc'd). )

        Code from tcl-distribution
        */

                char *cmdLine, *p, *arg, *argSpace;
                char **argv;
                int argc, size, inquote, copy, slashes;

                /*
  • Set argv0 to *.exe
                 */
                char buffer[MAX_PATH+1];
                GetModuleFileName(m_hInstance, buffer, sizeof(buffer));

                for (p = buffer; *p != '\0'; p++) {
                        if (*p == '\\') {
                                *p = '/';
                        }
                }

                for (p = buffer; ; p++) {
                        if (*p == '\0') {
                                *p = ' ';

                                /*
  • Append the command line options
                                 */
                                for (int i = 0; ; i++) {
                                        if ( m_lpCmdLine[i] == '\0')
                                                break;
                                        p++;
                                        *p = m_lpCmdLine[i];
                                }
                                p++;
                                *p = '\0';
                                break;

                        }

                }

                cmdLine = buffer;

                /*
  • Precompute an overly pessimistic guess at the number of arguments
  • in the command line by counting non-space spans.
                 */

                size = 2;
                for (p = cmdLine; *p != '\0'; p++) {
                if ((*p == ' ') || (*p == '\t')) {        /* INTL: ISO space. */
                        size++;
                        while ((*p == ' ') || (*p == '\t')) { /* INTL: ISO space. */
                        p++;
                        }
                        if (*p == '\0') {
                        break;
                        }
                }
                }
                argSpace = (char *) Tcl_Alloc(
                        (unsigned) (size * sizeof(char *) + strlen(cmdLine) + 1));
                argv = (char **) argSpace;
                argSpace += size * sizeof(char *);
                size--;

                p = cmdLine;
                for (argc = 0; argc < size; argc++) {
                argv[argc] = arg = argSpace;
                while ((*p == ' ') || (*p == '\t')) {        /* INTL: ISO space. */
                        p++;
                }
                if (*p == '\0') {
                        break;
                }

                inquote = 0;
                slashes = 0;
                while (1) {
                        copy = 1;
                        while (*p == '\\') {
                        slashes++;
                        p++;
                        }
                        if (*p == '"') {
                        if ((slashes & 1) == 0) {
                                copy = 0;
                                if ((inquote) && (p[1] == '"')) {
                                p++;
                                copy = 1;
                                } else {
                                inquote = !inquote;
                                }
                                        }
                                        slashes >>= 1;
                                }

                                while (slashes) {
                        *arg = '\\';
                        arg++;
                        slashes--;
                        }

                        if ((*p == '\0')
                                || (!inquote && ((*p == ' ') || (*p == '\t')))) { /* INTL: ISO space. */
                        break;
                        }
                        if (copy != 0) {
                        *arg = *p;
                        arg++;
                        }
                        p++;
                        }
                *arg = '\0';
                argSpace = arg + 1;
                }
                argv[argc] = NULL;


                *argcPtr = argc;
                *argvPtr = argv;

        } 

        int ExitObjCmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
        {
                CMainFrame* frame = reinterpret_cast<CMainFrame*>(data);

                if (objc == 1) {

                        frame->DestroyWindow();

                        Tcl_Obj *resultPtr;
                        resultPtr = Tcl_GetObjResult(interp);
                        Tcl_SetIntObj(resultPtr, 1);
                        return TCL_OK;
                } else {
                        Tcl_WrongNumArgs(interp, 1, objv, "");
                        return TCL_ERROR;
                }
        }

        int ResizeObjCmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
        {

                CMainFrame* frame = reinterpret_cast<CMainFrame*>(data);

                if (objc == 1) {
                        CRect rect;
                        frame->GetWindowRect(&rect);

                        Tcl_Obj *listPtr;

                        listPtr = Tcl_NewListObj(0, NULL);

                        if (Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewLongObj(rect.right)) != TCL_OK)
                                return TCL_ERROR;
                        if (Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewLongObj(rect.bottom)) != TCL_OK)
                                return TCL_ERROR;

                        Tcl_SetObjResult(interp, listPtr);
                        return TCL_OK;
                }

                int width,height;
                if (objc != 3) {
                        Tcl_WrongNumArgs(interp, 1, objv, "width height");
                        return TCL_ERROR;
                }

                if (Tcl_GetIntFromObj(interp, objv[1], &width) != 
                                TCL_OK) {
                        return TCL_ERROR;
                }
                if (Tcl_GetIntFromObj(interp, objv[2], &height) != 
                                TCL_OK) {
                        return TCL_ERROR;
                }

                frame->Resize(width,height);

                Tcl_Obj *resultPtr;
                resultPtr = Tcl_GetObjResult(interp);
                Tcl_SetIntObj(resultPtr, 1);
                return TCL_OK;
        }
  • on load framework you see first, that I source the tcl-file with fixed path-name
  • than ::mfcintk::CreateTopLevel is a proc definded in the tcl-file, and sourced via C++
  • CMFCinTkApp::ParseCmdLine is a long member-function and off topic, but maybe useful for somebody
  • than you find the two Tcl-Obj-Cmd's

Derive CFrameWnd, the MFC Control

MainFrame.h

        #ifndef MAINFRM_H
        #define MAINFRM_H

        class CMainFrame : public CFrameWnd
        {

        public:
                CMainFrame(CWnd *const Wnd, const int id);
        protected: 
                DECLARE_DYNAMIC(CMainFrame)


        public:
                //{{AFX_VIRTUAL(CMainFrame)
                //}}AFX_VIRTUAL

        public:
                void        Resize(int width, int height);
                virtual ~CMainFrame();

        protected:
                //{{AFX_MSG(CMainFrame)
                afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
                afx_msg void OnSize(UINT nType, int cx, int cy);
                afx_msg void OnPaint();
                //}}AFX_MSG
                DECLARE_MESSAGE_MAP()
        };

        #endif // MAINFRM_H
  • OnSize and OnPaint are neccessary
  • OnSetCursor is practical, to test if the control is well embedded

MainFrame.cpp

        #include "stdafx.h"
        #include "MFCinTk.h"
        #include "MainFrm.h"

        IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)

        BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
                //{{AFX_MSG_MAP(CMainFrame)
                ON_WM_SETCURSOR()
                ON_WM_SIZE()
                ON_WM_PAINT()
                //}}AFX_MSG_MAP
        END_MESSAGE_MAP()


        CMainFrame::CMainFrame(CWnd *const Wnd, const int id)
        {

                CreateEx(WS_EX_CLIENTEDGE,
                                AfxRegisterWndClass(0, ::LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOWFRAME+1)),
                                _T("MfC in Tk!"), WS_CHILD,
                                CRect(0,0,400,400), Wnd, id);

        }

        CMainFrame::~CMainFrame()
        {
        }

        void CMainFrame::Resize(int width, int height)
        {
                MoveWindow( 0, 0, width, height);
                OnSize(NULL,width,height);
        }

        void CMainFrame::OnSize(UINT nType, int cx, int cy) 
        {
                CWnd::OnSize(nType, cx, cy);
        }

        void CMainFrame::OnPaint() 
        {
                CPaintDC dc(this);
                CRect rect;
                GetClientRect(&rect);

                dc.TextOut(rect.right/2-50,rect.bottom/2,"MFC Control");

                rect.left = 10;
                rect.top = 10;
                rect.bottom = rect.bottom-10;
                rect.right = rect.right-10;

                CBrush brush;
                brush.CreateSolidBrush(RGB(0,0,255));
                dc.FrameRect(&rect,&brush);
        }

        BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
        {
                SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS));

                return CWnd::OnSetCursor(pWnd, nHitTest, message);
        }
  • straight forward implemantation, nearly pure C++ - MFC

stdafx.h

        #ifndef STDAFX_H
        #define STDAFX_H

        #define VC_EXTRALEAN                

        #include <afxwin.h>         // MFC-Kern- und -Standardkomponenten


        #define NO_CONST
        #include <tk.h>
        #include <tkPlatDecls.h>
        #include <itcl.h>
        #include <itk.h>

        #define PRINTF_MAX_COUNT        1024
        #define BUFFER_SPACE                1024

        #endif // STDAFX_H

The implementation is straight forward, but it took some time for me, to make it work. And I'm not so familar with MFC. I did not cut any code out, so you can see everything, e.g. the command-line proccessing. As a side note, I have no tcl installation on my machine. So consider defining tcl_library, and look for some fixed path-names in the code.

  1. During runup the application init's Tcl and Tk
  2. The toplevel pops up after Tk_Init
  3. Than we source the tcl-file with the UI
  4. Now we build a CWnd-child-object into the frame with container -true
  5. Finished

What do you think?


[ Category GUI | Category Windows | Category Example ]