Combining GUI applications developed with Tk and 'native' Windows toolkits

Three senses:

Jeffrey Hobbs and David Gravereaux know the most about these. None of them (except for MFC controls as widgets) really work as of January 2002 without a lot of fiddling ...

Arjen Markus There may be a connection with Drawing Into Foreign Windows.

Frame and toplevel bear on this topic. If an external X11 application has a command-line option to configure its placement (as, for example, xanim's +Wid), then mumble-mumble used with the -container of toplevel and frame. "man winfo" gives information on X11-id-s. Credit Andreas Leitgeb with this observation.

Theo Verelst There is at least a connection with tclhttpd ..

James Wettenhall <[email protected]> has experimented with embedding Tk windows within GraphApp (a GUI toolkit in the C language which uses 'native' Microsoft Windows widgets): http://bioinf.wehi.edu.au/folders/james/tkEmbed/


placing the Tk widgets in IE has been simplified by using the TclControl activeX. Using the IE version 6.0, I have found it to be the most stable...

TclControl can be found at: http://www.sys.uea.ac.uk/~fuzz/optcl/default.html

I have made little to no changes to many Tcl/Tk apps that can run within the IE window... Yes you still need to have a copy of Tcl/Tk installed (but so does java requires a VM to be resident)... Of coarse you need to worry about security issues by allowing the activeX to access Tcl...

James Garrison <[email protected]>

[Explain tangential connections of TclScript and GPS work.]


The point of TclScript is to plug in the Tcl language as an IE extension. This would mean that IE would be able to parse pages with the following type of script code:

   <script language="TclScript">
      package require base64
      proc encode_text {str} {
          return [base64::encode $str]
      }
      proc show_dialog {message} {
          toplevel .dlg
          ....
      }
   </script>
   <form ....>

This isn't quite the same as the ActiveX component which has more to do with placing a Tk toplevel widget onto the browser window and processing Tcl script within itself.

Of course at present (Jan 2002) TclScript remains vapour-ware (or maybe liquid-ware as there is code, it's just not useable :) ) but I hope to find the time to continue at some point this year. PT


NEM 19May2003 - Here's a quick example of embedding an application into a Tk widget using BLT's container and bgexec commands. The application being embedded in this case is Vim running in an Eterm under Linux. I tried quickly to get this working with xterm, but I couldn't, whereas Eterm played nicely.

 package require Tk
 package require BLT

 # Create a unique name for the new process
 set name "EmbedTk[pid][clock seconds]"
 eval blt::bgexec wait [list Eterm -n $name -e vi] $argv
 pack [blt::container .c -name $name] -fill both
 wm title . "Vim running in Tk"
 # Wait for app to exit (could use trace + callback here)
 vwait wait
 destroy .

This allows you to embed the program based on its name. The reason for using bgexec is that we need a method of detecting when the application has finished (yet it needs to run in the background), so that we can destroy the widget or do whatever. BTW, to use with xterm you have to change the "-n" switch to "-name". However, for some reason, this still didn't work. Note that resizing the widget won't resize the embedded window, so don't pack with -expand 1.


DG -- Here is a C++ example for how to use external windows as containers. Use [toplevel <newPath> -use <window id>] to attach to it. Notice the four special windows messages used for the "container protocol".

#include <windows.h>

HINSTANCE hInstance;

class MainWnd {
public:
   MainWnd(int nCmdShow);
   ~MainWnd();
   HWND GetWindowId();
private:
   friend LRESULT CALLBACK MainWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);

   static bool first;
   int UserSizeChanging;
   HWND hMain;
   HWND hAttach;
};


MainWnd::MainWnd (int nCmdShow)
   : hAttach(NULL), UserSizeChanging(0)
{
   // do first time inits ?
   //
   if (first)
   {
       WNDCLASSEX wndcls;

       first = false;

       // register our StatBox window class
       //
       wndcls.cbSize = sizeof (WNDCLASSEX);
       wndcls.style = 0;
       wndcls.cbClsExtra = 0;
       wndcls.cbWndExtra = sizeof (HANDLE);
       wndcls.hInstance = hInstance;
       wndcls.hCursor = ::LoadCursor (NULL, IDC_ARROW);
       wndcls.lpszMenuName = NULL;
       wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
       wndcls.hIcon = ::LoadIcon (NULL, IDI_APPLICATION);
       wndcls.hIconSm = ::LoadIcon (NULL, IDI_APPLICATION);
       wndcls.lpfnWndProc = MainWndProc;
       wndcls.lpszClassName = "TkFriendly";

       ::RegisterClassEx (&wndcls);
   }

   hMain = ::CreateWindowEx (
           WS_EX_LEFT,
           "TkFriendly",
           "Tk container protocol example.",
           WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
           0L, 0L, hInstance, this
           );

   ::ShowWindow(hMain, nCmdShow);
   ::UpdateWindow(hMain);
}

MainWnd::~MainWnd () {
   if (hMain) ::SendMessage(hMain, WM_CLOSE, 0, 0);
}

HWND MainWnd::GetWindowId() {
   return hMain;
}

bool MainWnd::first = true;


LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
   MainWnd *pMain = reinterpret_cast <MainWnd *>
           (::GetWindowLongPtr(hwnd, GWLP_USERDATA));

   switch (iMsg)
   {
       case WM_CREATE:
           // Supposedly, lpCreateParams has a DWORD alignment problem.
           // I don't know what this means for pointers.  I doubt much.
           //
           pMain = reinterpret_cast <MainWnd *>(((LPCREATESTRUCT)lParam)->lpCreateParams);

           // move the MainWnd class pointer into the
           // userdata of the window.
           //
           ::SetWindowLongPtr (hwnd, GWLP_USERDATA, reinterpret_cast <LONG_PTR>(pMain));
           return 0;

       case WM_CLOSE:
           ::DestroyWindow(hwnd);
           return 0;

       case WM_DESTROY:
           pMain->hMain = NULL;
           delete pMain;
           ::PostQuitMessage(0);
           return 0;

// ---------  Begin Tk container specials ----------

#define TK_CLAIMFOCUS     (WM_USER)
#define TK_GEOMETRYREQ    (WM_USER+1)
#define TK_ATTACHWINDOW   (WM_USER+2)
#define TK_DETACHWINDOW   (WM_USER+3)

       case TK_CLAIMFOCUS:
           if (wParam || (GetFocus() != NULL)) {
               ::SetFocus(pMain->hAttach);
           }
           return 0;

       case TK_GEOMETRYREQ:
           if (!pMain->UserSizeChanging) {

               // Make the client area to be the size given, but
               // we move the whole window.  Calc the additions
               // to get us there.

               wParam += 2 * GetSystemMetrics(SM_CXEDGE) + 3;
               lParam += GetSystemMetrics(SM_CYCAPTION) +
                       (2 * GetSystemMetrics(SM_CYEDGE) + 3);
               ::SetWindowPos(hwnd, NULL, 0, 0, wParam, lParam,
                       SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
           }
           return 0;

       case TK_ATTACHWINDOW:
           pMain->hAttach = (HWND)wParam;
           ::SetParent(pMain->hAttach, hwnd);
           return 0;

       // We have found that as written, the return value of 0
       // prevents the generation of the TK_GEOMETRYREQ message in 
       // UpdateWrapper(TkWindow*) - see tk8.5.6/win/tkWinWm.c, 
       // lines 2299-2306.  When we returned a value of 1, the 
       // remainder of this code behaved as we expected.
       // -- AJS

       case TK_DETACHWINDOW:
           pMain->hAttach = NULL;
           ::PostMessage(hwnd, WM_CLOSE, 0, 0);
           return 0;

// ---------  End Tk container specials ----------

       case WM_ENTERSIZEMOVE:
           pMain->UserSizeChanging = 1;
           break;

       case WM_EXITSIZEMOVE:
           pMain->UserSizeChanging = 0;
           break;

       case WM_SIZE:
           if (wParam != SIZE_MINIMIZED && pMain->hAttach) {
               ::SetWindowPos(pMain->hAttach, NULL, 0, 0,
                       LOWORD(lParam), HIWORD(lParam),
                       SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
           }
           return 0;

       case WM_PARENTNOTIFY:
           switch (LOWORD(wParam)) {
               case WM_LBUTTONDOWN:
               case WM_MBUTTONDOWN:
               case WM_RBUTTONDOWN:
                   ::SetFocus(pMain->hAttach);
                   return 0;

               case WM_DESTROY:
                   ::PostQuitMessage(0);
                   return 0;
           }
           break;

       case WM_ERASEBKGND:
           if (pMain->UserSizeChanging) return 1;
           break;
   }

   return ::DefWindowProc(hwnd, iMsg, wParam, lParam);
}


int WINAPI
WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
{
   MSG msg;
   MainWnd *main;
   char buf[50];

   main = new MainWnd(nCmdShow);
   wsprintf(buf, "%ld", main->GetWindowId());
   MessageBox(main->GetWindowId(), buf, "Window ID is:", MB_OK);

   while (::GetMessage(&msg, NULL, 0, 0)) {
       ::TranslateMessage(&msg);
       ::DispatchMessage(&msg);
   }

   return msg.wParam;
}

There's a problem when this technique is used in-process regarding resizing. No solution is known at this time. See what it looks like in my IRC client. [IRC::makeblank] creates a new MDI child and returns the HWND of it using code nearly identical to the above.

http://tomasoft.sourceforge.net/x2_tkembed.gif