Tk frame in MFC CFrameWnd

http://www.tfh-berlin.de/~sleuthold/files/tcl/TkinMFC.jpg


This is the opposite direction to MFC Control in Tk toplevel.

I think this design is not not so interesting. The platform dependent part is huge, so this design makes not so much sense. But the implementation is still kind of fascinating.

But you still have divided core and GUI in some manner.

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


At the end we have a TkinMFC.exe that embeds a Tk frame. During runup we source TkinMFC.tcl and define the UI.


TkinMFC.tcl

set buttonTkcon [button .panel.buttonTkcon \
      -text "tkcon" -height 3 -width 20 \
      -command {source C:/home/TkinMfC/tcl/tkcon.tcl}]

pack $buttonTkcon -padx 5 -pady 5 -side left -anchor n

set frameAdd [frame .panel.frameAdd -relief sunken -bd 2]

pack $frameAdd -padx 5 -pady 5 -side left -expand true -fill both
  • note .panel, this widget-name is fixed in C++
  • but except this, we have a complete Tk frame

Derive CWinApp to host the application/thread/event-loop

TkinMFC.h

#ifndef TKINMFC_H
#define TKINMFC_H

#include "resource.h"
#include "stdafx.h"

class CTkinMfCApp : public CWinApp
{
public:
  tktinfo     *tinfo_panel;
private:
  Tcl_Interp  *interp;
  char        **argv;
  int         argc;

private:
  bool        Tk_Initialize();
  void        ParseCmdLine(int* argc, char*** argv);

public:
  CTkinMfCApp();

  //{{AFX_VIRTUAL(CTkinMfCApp)
  public:
  virtual int ExitInstance( );
  virtual BOOL OnIdle(LONG lCount);
  virtual BOOL InitInstance();
  //}}AFX_VIRTUAL

public:
  //{{AFX_MSG(CTkinMfCApp)
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
};

#endif // TKINMFC_H

TkinMFC.cpp

#include "stdafx.h"
#include "TkinMfC.h"
#include "MainFrm.h"

BEGIN_MESSAGE_MAP(CTkinMfCApp, CWinApp)
  //{{AFX_MSG_MAP(CTkinMfCApp)
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()

CTkinMfCApp::CTkinMfCApp()
{
  tinfo_panel = new tktinfo;
  tinfo_panel->tk_path = "";
}

CTkinMfCApp theApp;

bool CTkinMfCApp::Tk_Initialize()
{
  ParseCmdLine(&argc,&argv);
  
  Tcl_FindExecutable(argv[0]);
  
  interp = Tcl_CreateInterp(); 
  
  /*
   * argc, argv0 and argv
   */
  char *args;
  Tcl_DString argString;
  char buf[TCL_INTEGER_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);
  ckfree(args);
  sprintf(buf, "%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);

  /*
   * Initialize packages
   */
  if (Tcl_Init(interp) == TCL_ERROR) {
    return TCL_ERROR;
  }
  if (Tk_Init(interp) == TCL_ERROR) {
    return TCL_ERROR;
  }
  
  if (Tcl_Eval(interp, "wm withdraw .") != TCL_OK) {
    return false;
  }

  CWnd* cwndPtr = AfxGetMainWnd( );
  CMainFrame* cmainPtr = static_cast<CMainFrame*>(cwndPtr);
  HWND DlgBar_hWnd = cmainPtr->m_wndDlgBar;

  char tkwin_panel[] = ".panel";
  tinfo_panel->tk_path = tkwin_panel;
  tinfo_panel->interp = interp;
  SetProp(DlgBar_hWnd, "tktinfo", static_cast<HANDLE>(tinfo_panel));
  
  char buffer[1000];
  _snprintf(buffer, sizeof(buffer), "toplevel %s -use 0x%x", tkwin_panel, DlgBar_hWnd);
  if (Tcl_Eval(interp, buffer) != TCL_OK) {
    return false;
  }

  Tk_Window tkmain_panel = Tk_MainWindow(interp);
  tinfo_panel->tktkwin = Tk_NameToWindow(interp, static_cast<char *>(tkwin_panel), tkmain_panel);
  RECT topsz_panel;
  GetClientRect(DlgBar_hWnd, &topsz_panel);
  Tk_MoveResizeWindow(tinfo_panel->tktkwin, 0, 0, topsz_panel.right, topsz_panel.bottom);
  
  int i;
  _snprintf(buffer, sizeof(buffer), "winfo id %s", tinfo_panel->tk_path);
  Tcl_Eval(interp, buffer);
  Tcl_GetInt(interp, interp->result, &i);
  tinfo_panel->tkhwnd = reinterpret_cast<HWND>(i);
 
  Tcl_Eval(interp, "source [file join C:/ home TkinMfC tcl TkinMfC.tcl]");
  
  Tcl_PkgProvide(interp, "tkinmfc", "1.1");
  
  return true;
}

BOOL CTkinMfCApp::InitInstance()
{
  SetRegistryKey(_T("Local AppWizard-Generated Applications"));

  CMainFrame* pFrame = new CMainFrame;
  m_pMainWnd = pFrame;
  
  pFrame->LoadFrame(IDR_MAINFRAME);

  pFrame->ShowWindow(SW_SHOW);
  pFrame->UpdateWindow();

  if (!Tk_Initialize())
    return FALSE;
  
  return TRUE;
}

int CTkinMfCApp::ExitInstance( )
{
  delete tinfo_panel;
  
  CWinApp::ExitInstance();

  return 0;
}

BOOL CTkinMfCApp::OnIdle(LONG lCount)
{

  while(Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT))
  {
  }
  return 0;
}

void CTkinMfCApp::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: 15861,v 1.9 2006-11-01 07:00:16 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;
} 

Derive CFrameWnd

MainFrame.h

#ifndef MAINFRM_H
#define MAINFRM_H


class CMainFrame : public CFrameWnd
{
  
public:
  CMainFrame();
protected: 
  DECLARE_DYNAMIC(CMainFrame)

public:
  virtual ~CMainFrame();

  CDialogBar  m_wndDlgBar;

protected:
  //{{AFX_MSG(CMainFrame)
  afx_msg int         OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg void OnPaint();
  afx_msg void OnSize(UINT nType, int cx, int cy);
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
};

#endif // MAINFRM_H

MainFrame.cpp

#include "stdafx.h"
#include "TkinMfC.h"
#include "MainFrm.h"

IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
  //{{AFX_MSG_MAP(CMainFrame)
  ON_WM_CREATE()
  ON_WM_PAINT()
  ON_WM_SIZE()
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
    return -1;
  
  if (!m_wndDlgBar.Create(this, IDD_DIALOG1,
      CBRS_BOTTOM|CBRS_TOOLTIPS|CBRS_FLYBY, IDD_DIALOG1))
    return -1;
 
  return 0;
}

void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
  CFrameWnd::OnSize(nType, cx, cy);

  CRect rect;
  m_wndDlgBar.GetClientRect(&rect);
  rect.right = cx;

  rect.top = cy - rect.bottom;
  rect.bottom = cy;
  
  m_wndDlgBar.MoveWindow(rect);

  CTkinMfCApp* appPtr = static_cast<CTkinMfCApp*>(AfxGetApp());
  if(appPtr->tinfo_panel->tk_path != "")
    Tk_MoveResizeWindow(appPtr->tinfo_panel->tktkwin, 0, 0, cx, rect.bottom - rect.top);
}

void CMainFrame::OnPaint() 
{
  
  CPaintDC dc(this);
  CRect rect,rectTk;
  GetClientRect(&rect);
  m_wndDlgBar.GetClientRect(&rectTk);
  
  rect.bottom = rect.bottom - rectTk.bottom;
  
  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);
}
  • OnSize is a little bit tricky; you have to resize the Tk frame according to the CDialog container

stdafx.h

#ifndef STDAFX_H
#define STDAFX_H

#define VC_EXTRALEAN

#include <afxwin.h> 
#include <afxext.h>

#define NO_CONST
#include <tk.h>

struct tktinfo {
  char *tk_path;
  HWND tkhwnd;
  Tk_Window tktkwin;
  Tcl_Interp *interp;
};

#endif // STDAFX_H

DG - I wish the CTkinMfCApp::OnIdle() didn't do polling. For a proper "fluffy" event loop you need to either merge the afx message pump with Tk's event loop (which is based on lower windows message pump), or separate their interactions with threads; i.e., Tcl running idle and alertable in its own thread. All you have to do to stop the MFC GUI from redrawing properly is to use a blocking [gets] or [read] in Tcl. This method won't scale.