
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


