I saw this implemented in commercial applications, and asked myself: How did that work?Somebody could ask, why embed a MFC-Control into a Tk-toplevel:
- divide GUI from core implementation
- building huge OpenGl or DirectX applications with platform-independent GUI (CAE,CAD,etc.)
- make use of the impressive Tcl/Tk automation potential, and hide the core implementation
- etc.
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 run-up (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
#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); 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
#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() { CinTkApp::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; 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.23 2006-11-09 07:00:12 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; break; } } } cmdLine = buffer; 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); CMainFrame* frame = reinterpret_cast<CMainFrame*>(data); if (objc == 1) { == 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[]) { zeObjCmd(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
- then ::mfcintk::CreateTopLevel is a proc defined in the tcl-file, and sourced via C++
- CMFCinTkApp::ParseCmdLine is a long member-function and off topic, but maybe useful for somebody
- then 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
#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 arly pure C++ - MFC #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.
- During runup the application init's Tcl and Tk
- The toplevel pops up after Tk_Init
- Than we source the tcl-file with the UI
- Now we build a CWnd-child-object into the frame with container -true
- Finished
You can find a similar thing here: Using MFC Controls as Tk widgets