Version 21 of MFC Control in Tk toplevel

Updated 2006-11-08 17:04:34

http://www.tfh-berlin.de/~s716193/pics/MFCinTk.jpg


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. This design makes it possible, to build applications for Linux and Windows, with minimum platform-dependence.

Tk frame in MFC CFrameWnd covers the opposite direction. More MFC and less Tk.

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


We end up in MFCinTk.exe, that sources a MFCinTk.tcl.

Features:

  • toplevel contains button, frame -container true and a menu
  • frame will contain one CFrameWnd - Object
  • interface 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 400x300+50+50

                # Menu for the look
                set menu [menu ${toplevel_window}menubar]
                $toplevel_window configure -menu $menu
                set fileMenu [menu $menu.fileMenu -tearoff 0]
                $menu add cascade -label "File" -underline 0 -menu $menu.fileMenu 
                $fileMenu add cascade -label "Exit" -underline 0 -command {exit}

                variable mfcContainer [frame ${toplevel_window}mfcContainer -container true]
                set testFrame [frame ${toplevel_window}testFrame]    
                set tkconButton [button $testFrame.tkconButton  \
                        -text "tkcon" \
                        -width 20 -height 3 -command {source C:/home/MFCinTk/tcl/tkcon.tcl}]

                pack $mfcContainer         -expand true -fill both -padx 5 -pady 10 
                pack $testFrame         -padx 5 -pady 5 -fill x
                pack $tkconButton          -padx 5 -pady 5 -side left

                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 that, the MFC Control would

not resize)

  • fixed path-name to source tkcon.tcl with the button

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.22 2006-11-08 19:00:26 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>

        #define NO_CONST
        #include <tk.h>
        #include <tkPlatDecls.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 to define 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

You can find a similar thing here: Using MFC Controls as Tk widgets


[ Category GUI | Category Windows | Category Example ]