Introduction
This article offers a possible solution to the problem of hosting your own
GUI inside of MS Developer Studio 6.x, as an Developer Studio Add-In, without
crashing the app and behaving well with such add-ins as WndTabs.
I have seen several people ask questions on how to do this, and I have wanted
to do this myself, in order to host the visual development tool I have been
working on using the VCF. So a
while ago I spent a couple of weekends fighting with Developer Studio, and came
up with this solution, initially making it work for hosting the VCF
Builder, and then rewriting a bit of it to make the project more general for
this article.
While this is probably not the only solution, it is the only way I was able
to accomplish the following:
- As a Developer Studio Add-In, host a window, to which other child window
could be added, inside of Developer Studio, and specifically map it's
resizing to the area that is used for MDI Child windows.
- Not have any dependencies on any of the advanced Add-in lib's that are
typically required for doing Developer Studio integration. These cost a
great deal of money for membership dues ( I believe at the time I looked it
up the cost was around $50,000 USD a year for membership with a minimum of 5
years), and require the signing of an NDA.
- Cooperate with existing GUI add-ins like WndTabs.
Initial issues
Initially I thought I could simply add a new Document Type, since Developer
Studio is based on MFC Doc-View. This unfortunately cannot be done safely. You
can get the document type to register, and you may even succeed in having your
MDI Child window appear, but once you close the workspace it is attached to, all
hell breaks loose and you crash Developer Studio. Sigh...
So then I though I could just get tricky and capture an existing MDI Child
window and simply add my own windows to the view - once again, while I was able
to attach my child window to the view, the moment I closed the MDI Child, or the
workspace - BOOM - Developer Studio crashed. Grrrrr.
So then I thought maybe I can attach a child window to the MDI Client window
- the parent window for all the MDI Child windows that get created. I tested
this in a simple MFC MDI app, and sure enough it worked OK, no crashes. Cool, so
hack, hack, hack, and I was ready to test it with Developer Studio. Once again,
no dice - the window would appear correctly, and I could control it, but when I
closed a workspace, things would crash again.
The reasons for the above crashes are related to not having the special
extension lib's that are needed to safely extend the IDE GUI, such as adding in
custom Doc Templates. And since these libraries are not available without
getting into the special Developer Studio "club", as mentioned above,
it is impossible to do this.
So the final thing to try was to create a child window that was parented not
to the
MDI Client, or another MDI Child window, but directly to the main Developer
Studio window itself, just like all the toolbar docks are. Essentially the same
thing that Oz Solomonovich does with his WndTabs add-in (damn - kinda makes you
feel stupid at this point!). This host window is then able to be switched on
and off, allowing you to easily go from the Developer Studio MDI interface to
the host view.
Once the host works you can pretty much do whatever you want. You could even
embed a CMDIFrameWnd
in the host and have your own MDI Child windows ! If you
trap the events that the IApplicationEvents
interface exposes, you can get
notified for most of the major GUI events, like opening a new project, and
saving or creating a new document. With this you could even mirror many of the
editor types already in existence or make new ones. Or do something entirely
different, such as host a cool Win-Diff display, or hosting a cool UML Modeling
tool inside of this.
How the Host works
The hosting works by sub-classing the Developer Studio main window, and by
setting up a Windows Hook procedure to trap certain messages. Once the hook is
established it, the procedure traps WM_SIZE and WM_MOVE messages intended for
the MDI Client window and adjusts our host window accordingly. The nitty-gritty
details follow below.
When I built the add-in I used the default Developer Studio Add-in wizard,
and accepted the defaults. An add-in has several parts, the first, which manages
some of the startup and shut down routines, is the CDSAddIn
. The CDSAddIn::OnConnection()
method is called when the add-in is first initialized by Developer Studio, and
it is here that the window sub classing and message hook installation takes
place.
To sub class the window I used a little class I found in the WndTabs source
code called CSubClassWnd
, which I altered just a tad. Basically you
create an instance and pass it an HWND, and then it has another method that you
can call to hook up the rest of the sub classing magic, part of which is done
through a call to CWnd::SubclassWindow()
. By sub classing an
existing window handle you can get access to the WndProc and replace or enhance
it with your own message handling.
So, once in the CDSAddIn::OnConnection()
method we store off the
current CWinApp's hinstance and threadID. Then the AFX_MANAGE_STATE
macro is used to switch the state so that our add-ins access to it's resource's
will function correctly. Note that the manage state macro is done after
the storing of the hinstance and threadID - if we had switched state before,
then the values would be different - what we want is to have the hinstance and
threadID for the Developer Studio app.
The next thing is to grab the active window and then walk up the parent child
chain (using GetParent()
), until we get to the Developer Studio
main window. Then using the main window handle, we create an instance of our CDevStudioMainWnd
.
This class is the part that performs the magic of sub classing the main wnd, and
from this we can get, and hold on to, a handle to the MDI Client window (this is
achieved in the CDevStudioMainWnd::GetMDIClientHWND()
method).
Once we have the MDI Client handle, we can go ahead and create the host view
- this is a simple class that is derived from CView. The host view is created
and parented to the main window that we have wrapped in the CDevStudioMainWnd
instance.
The final step in all of this is the creation of our windows hook - this is
accomplished by calling SetWindowsHookEx()
and passing in the type
of callback (in our case WH_CALLWNDPROC
for general windows
messages), our call back procedure ( in the CDSAddIn
code
), and the hinstance and threadID we stored off earlier. This will now route
these messages to our callback as well as to the regular WndProc for which they
are intended, and allow us to "peek" into the message loop. Once
messages do get to our callback, we can check the window handle the message is
intended for, and if it is the MDI Client handle, and the message types are
WM_SIZE or WM_MOVE, we just resize our host view, and voila!, we have a
working host child window inside of Developer Studio!
The clean up for all of this takes place in the CDSAddIn's CDSAddIn::OnDisconnection()
where the CDevStudioMainWnd
instance is deleted and the UnhookWindowsHookEx()
method is called to detach our windows hook callback.
The Add-in itself has two commands to allow switching from the Developer
Studio Environment, in other words making the host view invisible and displaying
the normal MDI client area, to the Host Environment, which makes the host view
re-positioned so it is on top of the MDI Client area and visible.
Any suggestions on how to either improve this or develop it further would be
greatly appreciated !
Credits
The original CSubClassWnd
code was done by Oz Solomonovich, as
was the technique used for getting at the MDI Client window handle.