
Introduction
Like so many ideas I was inspired by someone else's work to
develop this. The someone else in this case was Chris himself and the article in
particular was his (and Daniel Lyons') WinDiff GUI front-end. I was very impressed and found it very
useful compared to the native front-end on WinDiff. However, invariably I find
myself needing to compare a file I'm working on in Visual Studio, or it's
containing folder, so I thougth to myself "Wouldn't it be really useful if
there was an add-in that allowed me to do this."
Well, the usual looking around at the CodeProject and wading through the MSDN
didn't produce anything so I decided to do it myself. Now, I haven't
had any exposure to COM development previously so I started off with the
DevStudio Addin Wizard and then delved into the source for Chris' app for the
WinDiff stuff and the source for Oz Solomonovich's excellent WndTabs
add-in.
Four Steps to the Add-in
1. The DevStudio Add-in Wizard
The first thing to do was to generate the project using the Wizard. This
generates loads of code, which to a COM newbie looked pretty weird, however, it
builds and generates a dll. When you set DevStudio to be the executable for
debugging, a new toolbar appears and when you click on the first button a message
box appears. Hey presto! You've got yourself a working add-in.
2. Get the current document
The next step was to start turning this into my add-in, not some mickey
mouse thing that shows message boxes. So I needed to determine the path for the
current document. Thanks to the object model that DevStudio exposes this isn't a
difficult task. So first off I removed the wizard generated code that
displays a MessageBox and set about the MSDN to find out how to get the path for
the current document. The code looks something like this:
STDMETHODIMP CCommands::(WindiffAddinCommand)()
{
AFX_MANAGE_STATE (AfxGetStaticModuleState ());
VERIFY_OK (m_pApplication->EnableModeless (VARIANT_FALSE));
CComPtr<IDispatch> pActiveDocumentDisp;
m_pApplication->get_ActiveDocument (&pActiveDocumentDisp);
CComQIPtr<ITextDocument, &IID_ITextDocument> pActiveDocument(pActiveDocumentDisp);
if (pActiveDocument) {
BSTR bstr;
HRESULT hr = pActiveDocument->get_FullName (&bstr);
if (FAILED (hr)) {
::MessageBox (NULL, "Failed to get path for current document",
"WinDiff Addin", MB_OK | MB_ICONWARNING);
}
else {
CString strFullPath (bstr);
}
}
else {
}
}
3. Do something with WinDiff
Now that we've got the path for the current document, we
can then start doing things with WinDiff. In case you didn't know, WinDiff has a
command line interface (have a look in the help) and this is what we are going to
use.
A quick glance at Chris' code showed the way forward on the approach for using WinDiff and it looks like this:
CString strCmdLine;
strCmdLine.Format (_T("\"%s\" %s \"%s\" \"%s\""), m_strWinDiffPath,
strOptions, strFile1, strFile2);
if (TRUE == m_bReuse)
{
HWND hWnd = ::FindWindow (_T("WinDiffViewerClass"), _T("WinDiff"));
if (hWnd) {
::SendMessage(hWnd, WM_CLOSE, NULL, NULL);
}
}
if (WinExec (strCmdLine, SW_SHOW) <= 31)
{
::MessageBox (NULL,
_T("Unable to run WinDiff - check the location of the WinDiff executable"),
"WinDiff Addin", MB_OK | MB_ICONWARNING);
}
Because I was going to provide a file compare and a
directory compare that essentially do the same thing (call WinDiff with two file paths) I decided to put this functioanlity into a separate function. The only other thing to do before invoking WinDiff, is to find a file to compare against, which was done with CFileDialog
.
4. Add some more commands
So now that something works it's time to tidy it up and add some more commands. The first thing I did was to rename the function, which wasn't quite so cut and dried. In a normal class all you have to do is rename the function declaration in the header file, rename the function definition in the source file and rename any calls to the function. A quick search in DevStudio revealed the function name appearing in many places, so I just renamed them all and rebuilt. To my great surprise it worked!
The next thing was to add some more commands. I wanted to be able to compare the folder of the current file as well and I also wanted to provide access to the useful command line options of WinDiff (outline only and recursive) so I needed a config window.
The commands are defined in the (helpfully named) CCommands
class. So the starting point was to add more functions to this class using the same prototype as that generated for me by the Wizard. Next a definition needed to be added to the .odl file which is used to produce the type library. Finally, we need to inform DevStudio of the commands we've implemented in the OnConnection
function in CDSAddIn
. I used Oz's approach to this and used his AddInCmd
structure. I defined an array of these structures and iterated through them, like this:
#define countof(arr) (sizeof(arr)/sizeof(arr[0]))
struct AddInCmd
{
LPCTSTR szCommand;
int idCmdString;
bool bToolBarByDefault;
};
const AddInCmd AddInCommands[] =
{
{ _T("WindiffCurrentFile"), IDS_WINDIFF_FILE_CMD_STRING, true },
{ _T("WindiffCurrentFolder"), IDS_WINDIFF_FOLDER_CMD_STRING, true },
{ _T("WindiffSettings"), IDS_WINDIFF_SETTINGS_CMD_STRING, true }
};
const int cAddInCommands = countof (AddInCommands);
...
STDMETHODIMP CDSAddIn::OnConnection(IApplication* pApp, VARIANT_BOOL bFirstTime,
long dwCookie, VARIANT_BOOL* OnConnection) {
VARIANT_BOOL bRet;
CString strCmdString;
CComBSTR bszCmdString, bszMethod;
for (int i = 0; i < cAddInCommands; i++) {
strCmdString.LoadString (AddInCommands[i].idCmdString);
const int pos = strCmdString.Find('\n');
if (pos > 0) {
strCmdString.Delete(0, pos);
}
strCmdString = AddInCommands[i].szCommand + strCmdString;
bszCmdString = strCmdString;
bszMethod = AddInCommands[i].szCommand;
VERIFY_OK (pApplication->
AddCommand (bszCmdString, bszMethod, i, dwCookie, &bRet));
if (bRet == VARIANT_FALSE) {
*OnConnection = VARIANT_FALSE;
return S_OK;
}
if (bFirstTime == VARIANT_TRUE) {
VERIFY_OK (pApplication->
AddCommandBarButton (dsGlyph, CComBSTR(AddInCommands[i].szCommand), dwCookie));
}
}
After a bit of faffing about with graphics, splash screens, and toolbar buttons I was finally there, a working DevStudio Add-in that does something useful. I have to admit, I feel quite smug about it. I hope you find it useful, too.
Acknowledgements
The splash screen was implemented using Paul DiLascia's approach detailed in the October 1999 C++ Q&A in MSJ. This shows a specified bitmap resource for a specified number of milliseconds. The splash screen is launched in the OnConnection
function.
The directory browsing dialog was taken from Chris' WinDiff GUI front-end. Not sure if it's his, Daniel Lyons' (co-author) or someone elses. Whowever wrote it, thanks very much :)
The config dialog makes use of Chris Maunder's (he gets everywhere doesn't he ;)) CHyperLink static control
As you can see I like re-use ;)
Enhancements
The only thing that I'd like to do that isn't included is to actually host the WinDiff window in DevStudio. Any ideas on how to do that?
History
2 Jan 2002 - updated source download
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.