---- [[ author: [Sascha Leuthold] ]] [http://www.tfh-berlin.de/~s716193/pics/MFCinTk.jpg] [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 {+ ${nsPath}::OnGraphicsSize}" } proc ::mfcintk::OnGraphicsSize { args } { variable mfcContainer return [ResizeMfC [winfo width $mfcContainer] [winfo height $mfcContainer]] } comment * '''mfcContainer''' ''-container true'', to enable embedding * ''variable'' '''mfcContainer''', to access via C++, so the name is fixed * ''variable'' '''toplevel_id''' and ''variable'' '''container_id''', to access via C++ * '''::mfcintk::CreateToplevel''' called from C++ during application runup (after sourcing the tcl-file) * '''bind''' to resize the toplevel, '''Resize''' 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 comment * ... '''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.8 2006-05-04 18:01:01 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; } comment * ... 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 comment * ... '''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); } comment * ... '''stdafx.h''' #ifndef STDAFX_H #define STDAFX_H #define VC_EXTRALEAN #include // MFC-Kern- und -Standardkomponenten #define NO_CONST #include #include #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 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] ]]