---- [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 {+ ${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.21 2006-10-30 07:00:23 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 argv[0] 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(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(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 #define NO_CONST #include #include #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 ---- [[ [Category GUI] | [Category Windows] | [Category Example] ]]