Export (0) Print
Expand All

Walkthrough: Approaches to Building a Visual Studio .NET 2003 Add-in Project that Enables HTML Tidy

Visual Studio .NET 2003
 

Hank Davis and Todd Grunke
Visual Studio Team
Microsoft Corporation

May 2003

Summary: The HTML Tidy utility developed by Dave Raggett identifies and corrects common HTML markup errors and adjusts markup formatting. The Microsoft® Visual Studio® .NET HTML Designer provides a default markup formatter and various Automatic Formatting options. Web developers who prefer to use HTML Tidy to correct and format their HTML markup can install a Visual Studio .NET 2003 add-in project that calls either the Tidy executable or the HTML Designer to perform Automatic Formatting. This article gives instructions on how to build, install, and run such an add-in project, and provides sample code in several development languages. (43 printed pages)

Contents

Introduction
   HTML Validation and Automatic Formatting
   HTML Tidy
   Visual Studio .NET 2003 Add-in Projects

Procedures
   Downloading and Installing the Tidy Executable File
   Building the Visual Studio .NET 2003 Add-in Project
   Using the HTML Tidy Add-in

Sample Code
   Visual Basic .NET Sample Code
   Visual C# .NET Sample Code
   Visual C++ .NET / ATL Sample Code
   Managed Extensions for C++ Sample Code

Conclusion
   For Further Information

Introduction

The following sections describe HTML validation and formatting in HTML Designer, the HTML Tidy utility, and Visual Studio .NET 2003 add-in projects. Each section provides links to further information.

HTML Validation and Automatic Formatting

When HTML Validation is enabled in the HTML Specific, HTML/XML, Text Editor, Options Dialog Box, mistakes in HTML syntax are listed in the Task List Window. To display these errors, open an HTML page or Web Form for editing in HTML view, set the targetSchema property for the page, then choose Show Tasks from the View menu and select Build Errors. Any HTML markup that is not valid according to the selected schema provokes a Build Error entry. You can double-click the entry in the Task List to jump to the line in the HTML editor where the problem noted occurs. The faulty markup is flagged in the editor with a red, wavy underline. For further information, see Task List Views and Setting the targetSchema Property of an HTML Document.

In Visual Studio .NET 2003, Automatic Formatting options can be set in the Format, HTML/XML, Text Editor, Options Dialog Box.. The available options cause the integrated development environment (IDE) to correct and reformat your HTML markup when you edit a Web page and then save it from Design view or switch to HTML view. You can also correct and reformat your markup manually while in HTML view by running the Format Document command from the Edit menu or selecting the Format button on the HTML Editor toolbar. Various Options settings for the Text Editor and HTML Designer determine how the default formatter adjusts your markup. For a review of these options, see the Help topics Options that Affect Existing HTML, CSS, or XML Markup and VB Specific, Basic, Text Editor, Options Dialog Box.

HTML Tidy

The HTML Tidy utility was developed by Dave Raggett for the World Wide Web Consortium (W3C). HTML Tidy regularizes HTML markup in various useful ways, including these:

  • Detects missing or mismatched end tags.
  • Places the end tags of embedded elements within those of enclosing elements.
  • Corrects end tags lacking the "/" or ">" characters.
  • Places quotes around attribute values.
  • Supplies HTML entity names for certain characters.
  • Indents elements to make markup easier to read.

The documentation for HTML Tidy defines various parameters that specify how this utility operates. For example, the -config option directs Tidy to use a configuration file, which Raggett suggests is "the most convenient way to configure Tidy." Additional inline or block elements available within the <BODY> of the page can be defined in the Tidy configuration file. Advanced users might wish to enhance Tidy's limited support for XML by defining custom XML elements in the "Tidy.cfg" file.

With the -clean option set, HTML Tidy replaces the deprecated <FONT> and <CENTER> elements and <NOBR> extension with CSS style attributes. Tidy does not know how to handle all of the markup included in ASP.NET Web Forms, but it does recognize inline preprocessor directives enclosed in pseudo element syntax (<% ... %>) in Web Form (.aspx) files, and leaves them to be interpreted by the Web server.

The Tidy.exe executable and documentation can be downloaded from http://tidy.sourceforge.net, the Web site that now maintains this utility. The release notes for HTML Tidy are available online at http://www.w3.org/People/Raggett/tidy, and there is a public e-mail list devoted to HTML Tidy at html-tidy@w3.org.

Visual Studio .NET 2003 Add-in Projects

Visual Studio .NET is designed to be extensible, that is, to provide the capacity to enhance and extend the functionality of its integrated development environment (IDE). Add-in projects are compiled applications that modify the menus, commands, and behavior of the IDE. An Add-in Wizard automates the process of constructing a template for a Visual Studio .NET add-in application.

Procedures for running the Add-in Wizard from the Add New Project Dialog Box appear later in this article. After this Wizard generates the basic coding framework, you can paste in the sample code to complete the HTML Tidy add-in application. This article includes code to insert into the Visual Basic .NET, Visual C# .NET, and Visual C++ / Active Template Library (ATL) add-in project templates. There are also procedures and code for building a Managed Extensions for C++ Class Library add-in project.

You can use the Add-in Manager Dialog Box to enable HTML Tidy immediately after you finish creating and installing the add-in project. For more information on add-in projects, see Add-in and Customization User Interface Elements and Creating Add-ins and Wizards.

Procedures

The following procedures show you how to:

  • download and install the Tidy.exe executable file.
  • create a Visual Studio .NET 2003 add-in project.
  • add the desired sample code to complete the add-in application.
  • use the Add-in Manager, and format HTML markup with HTML Tidy.

The Visual Studio .NET 2003 Add-in Wizard can create template projects for Visual Basic .NET and Visual C# .NET add-ins. See the procedure "How to modify the Connect file of your add-in project template" in the section Building the Visual Studio .NET 2003 Add-in Project for instructions on adding Visual Basic .NET Sample Code or Visual C# .NET Sample Code for the HTML Tidy add-in to the default "Connect" files in those templates.

The Add-in Wizard also has a template for Visual C++ / ATL add-ins. To create the Tidy add-in using unmanaged C++ / ATL code, paste the Visual C++ .NET / ATL Sample Code into the default "Connect.cpp" source file and "Connect.h" header file of this template project. For procedures and code to build the HTML Tidy add-in as a Managed Extensions for C++ Class Library project, see Managed Extensions for C++ Sample Code.

Downloading and Installing the Tidy Executable File

First, download and install the HTML Tidy executable file.

How to download and install the Tidy file

  1. Download the version of the Tidy executable for your operating system and the Tidy documentation from the http://tidy.sourceforge.net Web site.
  2. Store the Tidy executable in a folder on your local drive that is included in your development machine's PATH variable.

The PATH system variable lists the directories that your operating system searches for executable files. The path to the directory where the HTML Tidy executable is stored must be included in the PATH variable.

If you create a new directory for the Tidy executable, for example C:\Program Files\HTMLTidy, you can add this directory to the PATH variable using the System Control Panel.

How to add the Tidy directory to your System PATH

  1. In Control Panel, choose System.
  2. On the Advanced tab, select Environment Variables.

    The Environment Variable dialog box opens.

  3. In the list of System Variables, choose the Path variable and select Edit.

    The Edit System Variable dialog box opens with existing paths displayed.

  4. Append the desired file path to the Variable Value field, separated from the previous file paths by a semicolon (;) character.

    For example, ;C:\PROGRA~1\HTMLTidy tells your system to look in the C:\Program Files\HTMLTidy folder for the Tidy.exe executable file.

  5. Select OK to save your entry and close all dialog boxes.

For further information on adding directories to the PATH variable, see the Windows Help for the System Control Panel.

Building the Visual Studio .NET 2003 Add-in Project

Next, create a Visual Studio .NET 2003 add-in project. For guidance on using the Add-in Wizard, see Creating an Add-In.

Note   The HTML Tidy add-in project requires Visual Studio .NET 2003 and a compatible version of the .NET Framework. This add-in will not work correctly when installed in Visual Studio .NET 2002.

How to create an add-in project

  1. Select New from the File menu, and choose Project.

    The New Project Dialog Box opens.

  2. In the Project Types pane, expand the Other Projects folder, and select the Extensibility Projects folder.
  3. In the Templates pane, select the Visual Studio .NET Add-in icon.
  4. Type a file Name for the add-in project (for example, "HTMLTidyAddIn"), provide a path to the folder where you will store it, and select OK.

    The Welcome page of the Add-in Wizard opens.

  5. Select Next.
  6. Choose the desired development language, and select Next.
  7. Select the Microsoft Visual Studio .NET check box, clear the Microsoft VSMacros IDE check box, and select Next.
  8. Type a Name ("HTMLTidy") and a Description ("HTML Tidy Formatter") to display in the Add-in Manager Dialog Box, and select Next.
  9. You need not select any options. To have the add-in load automatically whenever you launch Visual Studio .NET, select the check box beside that option. Clear all other options, and select Next.
  10. Clear the option to create an About box, and select Next.
  11. Select Finish.

Next, you must modify the default "connect.vb" (Visual Basic .NET) or "connect.cs" (Visual C# .NET) file in the default project template.

How to modify the Connect file of your add-in project template

For each step in this procedure, you can copy and paste Visual Basic .NET Sample Code or Visual C# .NET Sample Code into the Connect.vb file (VB template) or Connect.cs file (VC# template) of your add-in project.

  1. At the beginning of the top-level namespace defined in your Connect file, add a statement that imports the System Namespace and System.IO Namespace. If you will use Windows forms such as the System.Windows.Forms.MessageBox Class, also import the System.Windows.Forms Namespace.

    Solution Explorer displays the References of your add-in project. By default, the Add-in Wizard provides a project reference to System.dll, which includes the System and System.IO namespaces. A reference to System.Windows.Forms.dll must be added if you also intend to use Windows forms.

    1. In Solution Explorer, right-click on the References node of the add-in project and select Add Reference.

      The Add Reference Dialog Box opens.

    2. On the .NET tab, select "System.Windows.Forms.dll" in the Components list, then click the Select and OK buttons to add a project reference to that dynamic-link library (.dll) file.
  2. At the top of the Connect, class, declare a commandEvents variable.

    This variable stores the prettyFormatCommandID to be matched later.

  3. Replace the default OnConnection() method with a revised one.

    This revised method calls the BeforeExecute() method when it matches the specified prettyFormatCommandID.

  4. Replace the default OnDisconnection() method with a revised one.

    This revised method lets you enable or disable the Tidy formatter by selecting or clearing the check box beside the add-in's name in the Add-in Manager Dialog Box.

  5. Add a custom BeforeExecute() method.

    This added method, the longest snippet of added code, causes the HTML view editor to use HTML Tidy to correct and reformat HTML markup.

  6. Add any desired HTML Tidy command-line arguments.

    The -config argument, for example, directs Tidy to use an external configuration file. The HTML Tidy Quick Reference provides a list of Configuration Options.

  7. If you trap errors (which is always a good practice), add a command or subroutine for displaying them.

    You can, for example, use the .NET Framework MessageBox.Show Method (String) to display error messages in a standard Windows MessageBox. Alternatively, to persist error messages in a handy scrolling window, you can add a subroutine that writes exceptions and other HTML Tidy messages to the Output Window.

When you build the add-in project, it will create a compiled add-in application. When you build the Setup project, it will create a Windows Installer for your add-in.

How to build and debug the HTML Tidy add-in project

  1. From the Build menu, select Configuration Manager.

    The Configuration Manager Dialog Box opens.

  2. Configure the Debug solution configuration to compile the Debug configuration of the add-in project only. Configure the Release solution configuration to compile the Release configurations of the add-in and setup projects. For further information, see Build Configurations.
  3. Build the Debug solution build configuration.
  4. Fix any errors identified in the Task List Window.
  5. Rebuild the Debug solution build configuration until all errors in the add-in project are fixed.
  6. Build the Release solution build configuration.

The add-in project should now be ready to use on your development computer. However, you might also need to launch Setup.msi, which runs the Windows Installer (.msi) file built by the setup project. This is required in any of the following scenarios:

  • You are installing the HTML Tidy add-in on a different computer.
  • You chose Yes when a message asked if you wished to remove this add-in.
  • The registry of the machine where the add-in was installed has become corrupted.

How to run the HTML Tidy Installer on your development computer

  1. Open the setup project for the HTML Tidy add-in on your development computer.
  2. In Solution Explorer, right-click on the Setup project and select Properties.

    The Build, Configuration Properties, Deployment Project Properties Dialog Box opens.

    1. Select the Browse button beside the Output file name field, and navigate to the directory where the Windows Installer (.msi) file and Setup.msi will be built.
    2. In the Package files dropdown menu, select "In setup file." This value specifies that the add-in application, registry entries, and installation instructions will all be packaged within the Windows Installer (.msi) file.
  3. Build the Retail solution configuration that includes both the add-in and setup projects.
  4. Right-click on the Setup project in Solution Explorer, and select Install from the shortcut menu.

    This launches the Installation Wizard for the HTML Tidy add-in project.

How to install HTML Tidy on another computer

  1. If you need to install the add-in on another computer, make sure that Visual Studio .NET 2003, a compatible version of the .NET Framework, and the Tidy executable file are all installed on the target computer.
    Note   If the target computer does not have the software required, the installation is rolled back and the computer returns to its pre-installation state.
  2. Copy the Windows Installer (.msi) and Setup.msi files from the Output file directory on the development computer to a directory on the target computer.
  3. On the target computer, use Windows Explorer to navigate to the directory that contains the Setup.msi and Windows Installer files.
  4. Double-click on Setup.msi to run the Installation Wizard for the HTML Tidy add-in project.
    Note   You must have install permissions on the target computer in order to run the Windows Installer.

The Installation Wizard configures the target computer's Registry for the add-in. For more information, see Setup Projects and Walkthrough: Deploying a Windows Application.

How to modify the HTML Tidy add-in

To modify the HTML Tidy add-in after using it, you must first disable the add-in.

  1. On the Tools menu, select Add-in Manager, or type the key combination ALT+T, A.

    The Add-in Manager Dialog Box opens.

  2. Clear the check box to the left of the add-in's name to disable HTML Tidy.
  3. Shut down all instances of Visual Studio .NET running on the machine where the add-in application will be deployed.

    If you have installed and run the add-in on your development machine, you must shut down and restart Visual Studio .NET before modifying the add-in.

  4. Edit the add-in project on your development machine.
  5. Rebuild the add-in and setup projects.
  6. To reinstall the modified HTML Tidy add-in on another machine, copy the Setup.msi and Windows Installer (.msi) files built by the Setup project to the target machine, and run Setup.msi on that machine.

For further information, see Setup Projects and Walkthrough: Deploying a Windows Application.

Using the HTML Tidy add-in

Visual Studio .NET 2003 can run your HTML Tidy add-in immediately after you finish creating it. Use the Add-in Manager Dialog Box to turn HTML Tidy on and off.

How to turn HTML Tidy on and off

  1. Launch Visual Studio .NET 2003 on the target machine.
  2. On the Tools menu, select Add-in Manager, or type the key combination ALT+T, A.

    The Add-in Manager Dialog Box opens.

  3. Select the check box to the left of the add-in's name to enable HTML Tidy.

    - or -

    Clear this check box to enable the default HTML markup formatter.

How to correct and format HTML markup

  1. Use the Add-in Manager to select the desired markup formatter.
  2. Open a Web project in Visual Studio .NET 2003.
  3. Open an HTML page or Web Form for editing in HTML Designer.
  4. Use either Automatic Formatting or the manual Format Document command to adjust your markup.

To use Automatic Formatting Options

  1. Select the desired Automatic Formatting options in the Format, HTML/XML, Text Editor, Options Dialog Box.
  2. Edit your document. For example, move a control on the Design view surface.
  3. Save your document from Design view, or switch to HTML view.

To use the manual Format Document command:

  1. Clear the Automatic Formatting options in the Format, HTML/XML, Text Editor, Options Dialog Box.
  2. Edit your document.
  3. If you are in Design view, switch to HTML view.
  4. Run the Format Document command, using any of the following methods:
    • Choose Advanced from the Edit menu and select Format Document.
    • Select the Format button on the HTML Editor toolbar.
    • Type the key chord combination CTRL+K, CTRL+D.

Visual Basic .NET Sample Code

Here are snippets of Visual Basic .NET code to use for each step of the procedure "How to modify the Connect file of your add-in project template" in the section Building the Visual Studio .NET 2003 Add-in Project.

VB Step 1: Import System, System.IO, and System.Windows.Forms

The added namespaces appear in bold.

   Imports System
   Imports Microsoft.Office.Core
   Imports Extensibility
   Imports System.Runtime.InteropServices
   Imports EnvDTE
   Imports System.IO
   Imports System.Windows.Forms

VB Step 2: Declare the commandEvents variable

The added declaration appears in bold.

   public class Connect 
Implements Extensibility.IDTExtensibility2
      <System.ContextStaticAttribute()> Public WithEvents _
CommandEvents As EnvDTE.CommandEvents

VB Step 3: Replace the OnConnection() method

This customized method creates an event handler that calls the added BeforeExecute() method (see Step 5) when it receives the prettyFormatCommandID. Two object variables representing the active instances of the Visual Studio .NET integrated development environment (IDE) and the HTML Tidy add-in application are passed to this OnConnection() method as its application and addInInst arguments.

      Public Sub OnConnection(ByVal application As Object, _
ByVal connectMode As Extensibility.ext_ConnectMode, _
ByVal addInInst As Object, _
ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnConnection

         applicationObject = CType(application, EnvDTE.DTE)
         addInInstance = CType(addInInst, EnvDTE.AddIn)

         Dim vsCommandGroup As String = _
         "{1496a755-94de-11d0-8c3f-00c04fc2aae2}"
         Dim prettyFormatCommandID As Integer = 319

         ' Create command event that handles the prettyFormatCommandID
         '   and receives notification before the default handler.
         CommandEvents = _
applicationObject.Events.CommandEvents(vsCommandGroup, _
prettyFormatCommandID)
      End Sub

VB Step 4: Replace the OnDisconnection() method

This customized method lets you switch the HTML Tidy utility on and off by selecting or clearing the check box beside the add-in's name in the Add-in Manager Dialog Box.

      Public Sub OnDisconnection( _
ByVal RemoveMode As Extensibility.ext_DisconnectMode, _
ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnDisconnection
         commandEvents = Nothing
      End Sub

VB Step 5: Add a BeforeExecute() method

This added method directs IDE calls for HTML formatting to the HTML Tidy utility. It also catches and displays exceptions and other HTML Tidy messages.

Note   The UseShellExecute property is set to false in this method. This allows code to redirect the input, output, and error streams. When this property is set to false, the WorkingDirectory property cannot be used to locate the Tidy executable. For further information, see ProcessStartInfo.UseShellExecute Property and ProcessStartInfo.WorkingDirectory Property.
      Private Sub BeforeExecute(ByVal Guid As String, _
ByVal ID As Integer, ByVal CustomIn As Object, _
ByVal CustomOut As Object, ByRef CancelDefault As Boolean) _
Handles commandEvents.BeforeExecute
         ' Indicate that we've handled this command event,
         '   so there is no need to pass the event on.
         CancelDefault = True

         Try
            Dim doc As Document = _
applicationObject.Documents.Item(CustomIn)
            Dim td As TextDocument = _
CType(doc.Object("TextDocument"), TextDocument)
            Dim sp As TextPoint = td.StartPoint
            Dim ep As TextPoint = td.EndPoint
            Dim editStartPt As EditPoint = sp.CreateEditPoint()
            Dim editEndPt As EditPoint = ep.CreateEditPoint()

            ' Get content of the document.
            Dim txtHTMLDoc As String = editStartPt.GetText(editEndPt)

            ' Set up process info.
            Dim psi As New System.Diagnostics.ProcessStartInfo
            psi.FileName = "tidy.exe"
            psi.Arguments = "-i"
            psi.CreateNoWindow = True
            psi.UseShellExecute = False
            psi.RedirectStandardInput = True
            psi.RedirectStandardOutput = True
            psi.RedirectStandardError = True

            ' Create the process.
            Dim p As New System.Diagnostics.Process

            ' Associate process info with the process.
            p.StartInfo = psi

            ' Run the process.
            Dim fStarted As Boolean = p.Start()
            If Not fStarted Then _
Throw New Exception("Unable to start Tidy process.")

            ' Set up streams to interact with process's stdin/stdout.
            Dim sw As StreamWriter = p.StandardInput
            Dim sr As StreamReader = p.StandardOutput
            Dim strFormattedDoc As String = Nothing

            ' Write content of HTML document to process's stdin.
            sw.Write(txtHTMLDoc)
            sw.Close()

            ' Read process's stdout and store in strFormattedDoc.
            strFormattedDoc = sr.ReadToEnd()
            sr.Close()

            ' Handle no stdout text, instead display error text.
            If strFormattedDoc = "" Then
               Dim srError As StreamReader = p.StandardError
               Dim strError As String = srError.ReadToEnd()
               srError.Close()
               Throw New Exception("Tidy failed with error " _
& "information: " & strError)
            End If

            ' Replace document's original text with process's output.
            editStartPt.ReplaceText(editEndPt, strFormattedDoc, _
CInt(vsEPReplaceTextOptions.vsEPReplaceTextTabsSpaces))

         Catch ex As Exception
            System.Windows.Forms.MessageBox.Show(ex.ToString())
         End Try
      End Sub

VB Step 6: Add your preferred HTML Tidy Configuration Options

To specify how HTML Tidy will operate, add configuration options to the string value assigned to the psi.Arguments property in the Step 5 sample code for the BeforeExecute() method:

            psi.Arguments = "-i";

In the following example, the -config option has been added. This option tells Tidy to use an external Configuration File named "config.txt":

            psi.Arguments = "-config config.txt -i";
Note   Make sure that you place the -i option at the end of the string of arguments.

The HTML Tidy Quick Reference provides a list of Configuration Options.

VB Step 7: Catch and display any exceptions

If you throw exceptions, remember to add code to catch and display them. The catch statement included in the Step 5 sample code for the BeforeExecute() method uses the .NET Framework MessageBox.Show Method (String) to display each Tidy message in a standard Windows MessageBox:

         Catch ex As Exception
            System.Windows.Forms.MessageBox.Show(ex.ToString())

You might prefer instead to display exceptions and other messages from HTML Tidy in the scrolling Output Window. If so, replace the previous catch statement with a call to an error-handling subroutine:

         Catch ex As Exception
            ErrToOutputWindow(ex)

Then add the method named to the Connect class. The following sample method adds a new pane to the Output window, and writes the text of exception ex to this new pane:

      Sub ErrToOutputWindow(ByVal ex As Exception)
         ' Create a tool window handle for the Output window.
         Dim win As Window = _
applicationObject.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
         ' Access the Output window.
         Dim OW As OutputWindow = win.Object
         ' Add a new pane to the Output window.
         Dim OWp As OutputWindowPane = _
OW.OutputWindowPanes.Add("HTML Tidy Add-in") 
         ' Write exception text to this new pane.
         OWp.OutputString(ex.ToString())
      End Sub

For further information, see Controlling the Output Window, Exception Handling Fundamentals, Using the Try/Catch Block to Catch Exceptions, Try...Catch...Finally Statements, and Visual Basic .NET Code Sample: Try-Catch-Finally.

Visual C# .NET Sample Code

Here are snippets of Visual C# .NET code to use for each step of the procedure "How to modify the Connect file of your add-in project template" in the section Building the Visual Studio .NETAdd-in Project.

VC# Step 1: Import System, System.IO, and System.Windows.Forms

The added namespaces appear in bold.

   using System;
   using Microsoft.Office.Core;
   using Extensibility;
   using System.Runtime.InteropServices;
   using EnvDTE;
   using System.IO;
   using System.Windows.Forms;

VC# Step 2: Declare the commandEvents variable

The added declaration appears in bold.

   public class Connect : Object, Extensibility.IDTExtensibility2
   {
      CommandEvents commandEvents;

VC# Step 3: Replace the OnConnection() method

This customized method creates an event handler that calls the added BeforeExecute() method (see Step 5) when it receives the prettyFormatCommandID. Two object variables representing the active instances of the Visual Studio .NET integrated development environment (IDE) and the HTML Tidy add-in application are passed to this OnConnection() method as its application and addInInst arguments.

      public void OnConnection(object application, 
Extensibility.ext_ConnectMode connectMode, 
object addInInst, ref System.Array custom)
      {
         applicationObject = (_DTE)application;
         addInInstance = (AddIn)addInInst;
         
         string vsCommandGroup = "{1496a755-94de-11d0-8c3f-00c04fc2aae2}";
         int prettyFormatCommandID = 319;

         // Create command event that handles the prettyFormatCommandID 
         //   and receives notification before the default handler.
         commandEvents = 
applicationObject.Events.get_CommandEvents(vsCommandGroup, 
prettyFormatCommandID);
         commandEvents.BeforeExecute += new 
_dispCommandEvents_BeforeExecuteEventHandler(BeforeExecute);
      } 

VC# Step 4: Replace the OnDisconnection() method

This customized method lets you switch the HTML Tidy utility on and off by selecting or clearing the check box beside the add-in's name in the Add-in Manager Dialog Box.

      public void OnDisconnection(Extensibility.ext_DisconnectMode 
disconnectMode, ref System.Array custom)
      {
         commandEvents.BeforeExecute -= new 
_dispCommandEvents_BeforeExecuteEventHandler(BeforeExecute);
         commandEvents = null;
      } 

VC# Step 5: Add a BeforeExecute() method

This added method directs IDE calls for HTML formatting to the HTML Tidy utility. It also catches and displays exceptions and other Tidy messages.

Note   The UseShellExecute property is set to false in this method. This allows code to redirect the input, output, and error streams. When this property is set to false, the WorkingDirectory property cannot be used to locate the Tidy executable. For further information, see ProcessStartInfo.UseShellExecute Property and ProcessStartInfo.WorkingDirectory Property.
      private void BeforeExecute(string Guid, int ID, 
object CustomIn, object CustomOut, ref bool CancelDefault)
      {
         // Indicate that we've handled this command event, 
         //   so there is no need to pass the event on.
         CancelDefault = true;

         try
         {
            Document doc = applicationObject.Documents.Item(CustomIn);
            TextDocument td = (TextDocument) doc.Object("TextDocument");

            TextPoint sp = td.StartPoint;
            TextPoint ep = td.EndPoint;
            EditPoint editStartPt = sp.CreateEditPoint();
            EditPoint editEndPt = ep.CreateEditPoint();

            // Get content of the document.
            string txtHTMLDoc = editStartPt.GetText(editEndPt);
            
            // Set up process info.
            System.Diagnostics.ProcessStartInfo psi = 
new System.Diagnostics.ProcessStartInfo();
            psi.FileName = "tidy.exe";
            psi.Arguments = "-i";
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;

            // Create the process.
            System.Diagnostics.Process p = 
new System.Diagnostics.Process();
            
            // Associate process info with the process.
            p.StartInfo = psi;

            // Run the process.
            bool fStarted = p.Start();

            if (!fStarted)
               throw new Exception("Unable to start Tidy process");

            // Set up streams to interact with process's stdin/stdout.
            StreamWriter sw = p.StandardInput;
            StreamReader sr = p.StandardOutput;
            string strFormattedDoc = null;

            // Write content of html document to process's stdin.
            sw.Write(txtHTMLDoc);
            sw.Close();

            // Read process's stdout and store in strFormattedDoc.
            strFormattedDoc = sr.ReadToEnd();
            sr.Close();

            // Handle no stdout text, instead display stderr text.
            if (strFormattedDoc == "")
            {
               StreamReader srError = p.StandardError;

               string strError = srError.ReadToEnd();
               srError.Close();

               throw new Exception("Tidy failed with error " 
+ "information: " + strError);
            }
            
            // Replace document's original text with process's output.
            editStartPt.ReplaceText(editEndPt, strFormattedDoc, 
(int) vsEPReplaceTextOptions.vsEPReplaceTextTabsSpaces);
         }
         catch (Exception ex)
         {
            System.Windows.Forms.MessageBox.Show(ex.ToString());
         }
      }   

VC# Step 6: Add your preferred HTML Tidy Configuration Options

To specify how HTML Tidy will operate, add configuration options to the string value assigned to the psi.Arguments property in the Step 5 sample code for the BeforeExecute() method:

            psi.Arguments = "-i";

In the following example, the -config option has been added. This option tells Tidy to use an external Configuration File named "config.txt":

            psi.Arguments = "-config config.txt -i";
Note   Make sure that you place the -i option at the end of the string of arguments.

The HTML Tidy Quick Reference provides a list of Configuration Options.

VC# Step 7: Catch and display any exceptions

If you throw exceptions, remember to add code to catch and display them. The catch statement included in the Step 5 sample code for the BeforeExecute() method uses the .NET Framework MessageBox.Show Method (String) to display each Tidy message in a standard Windows MessageBox:

         catch (Exception ex)
         {
            System.Windows.Forms.MessageBox.Show(ex.ToString());
         }

You might prefer instead to display exceptions and other messages from HTML Tidy in the scrolling Output Window. If so, replace the previous catch statement with a call to an error-handling subroutine:

         catch (Exception ex)
         {
            ErrToOutputWindow(ex);
         }

Then add the method named to the Connect class. The following sample method adds a new pane to the Output window, and writes the text of exception ex to this new pane:

      private void ErrToOutputWindow(Exception ex) 
      {
         // Create a tool window handle for the Output window. 
         Window win = 
applicationObject.Windows.Item(EnvDTE.Constants.vsWindowKindOutput);
         // Access the Output window. 
         OutputWindow OW = (OutputWindow) win.Object;
         // Add a new pane to the Output window. 
         OutputWindowPane OWp = 
OW.OutputWindowPanes.Add("HTML Tidy Add-in"); 
         // Write exception text to this new pane. 
         OWp.OutputString(ex.ToString()); 
      }

For further information, see Exception Handling Fundamentals, Using the Try/Catch Block to Catch Exceptions, Exception Handling Statements, and try-catch.

Visual C++ .NET / ATL Sample Code

The Add-in Wizard provides a project template written in unmanaged Visual C++ .NET / Active Template Library (ATL) code. However, you will not follow the procedure "How to modify the Connect file of your add-in project template" in the section Building the Visual Studio .NET 2003 Add-in Project.

For further guidance on how to develop Visual Studio .NET add-ins using either unmanaged C++/ATL code or Managed Extensions for C++, see Peter Huene's tutorial on the DevHood Web site, http://www.devhood.com.

VC++/ATL: Connect.cpp

Replace the code in the Connect.cpp source file included in the Add-in Wizard's "C++/ATL add-in" template with the following code. This is unmanaged native code.

This version of the HTML Tidy add-in works only with ASCII-encoded files. The steps that will be required when HTML Tidy can handle other encodings appear in a header comment. Note that this code employs a wrapper class to reduce the number of CloseHandle() calls. A subroutine displays exceptions and other messages from Tidy in the Output Window.

// Connect.cpp : Implementation of CConnect

#include "stdafx.h"

#include "AddIn.h"

#include "Connect.h"

#include <atlstr.h>

extern CAddInModule _AtlModule;

/*   PLEASE NOTE:

* At the time this was written, tidy.exe contained a bug that

* prevented it from being able to output UTF16 files. Due to this

* Tidy bug, this code will not work properly with files that are not

* ASCII-encoded. When this Tidy issue is resolved, the following steps

* will be necessary to make this sample work with UTF16 encoding:

* 1) The -utf16 switch must be passed to Tidy.exe instead of -ascii.

* 2) A byte order mark (BOM) of 0xFEFF must be written to Tidy.exe

*       before the file data.

* 3) The BSTR that contains the HTML text from the document must be

*       written as-is following the BOM.   

* 4) The response from Tidy.exe must be read into an array of bytes

*       for processing.

* 5) Depending on the BOM of the response, you might need to reorder

*       the entire response such that it starts with 0xFEFF.

* 6) A BSTR can then be allocated from the resultant array of bytes

*       (subtracting the BOM) using the SysAllocStringByteLen API call.

* 7) The BSTR produced in step 6 will be the one that is used in the

*       call to ReplaceText().

*/

// CConnect

STDMETHODIMP CConnect::OnConnection(IDispatch *pApplication, \
AddInDesignerObjects::ext_ConnectMode ConnectMode,

                           IDispatch *pAddInInst, SAFEARRAY** custom)

{

   HRESULT hr = S_OK;

   pApplication->QueryInterface(__uuidof(EnvDTE::_DTE), \
(LPVOID*)&m_pDTE);

   pAddInInst->QueryInterface(__uuidof(EnvDTE::AddIn), \
(LPVOID*)&m_pAddInInstance);

   // If command events has not yet been initialized.

   if (m_spCommandEvents == NULL)

   {

      CComPtr<EnvDTE::Events> spEvents;

      hr = m_pDTE->get_Events(&spEvents);

      if (FAILED(hr))

         return hr;

      // Create a command event to handle the prettyFormatCommandID

      //   that receives notification before the default handler.

      CComBSTR bstrCommandGroup = \
L"{1496a755-94de-11d0-8c3f-00c04fc2aae2}";

      int prettyFormatCommandID = 319;

      hr = spEvents->get_CommandEvents(bstrCommandGroup, \
prettyFormatCommandID, &m_spCommandEvents);

      if (FAILED(hr))

         return hr;

      // Advise on the command events.

      CommandEvents::DispEventAdvise(m_spCommandEvents);

   }

   

   return hr;

}

STDMETHODIMP \
CConnect::OnDisconnection(AddInDesignerObjects::ext_DisconnectMode \
/*RemoveMode*/,

         SAFEARRAY ** /*custom*/ )

{

   CommandEvents::DispEventUnadvise(m_spCommandEvents);

   m_pDTE = NULL;

   m_spCommandEvents = NULL;

   return S_OK;

}

HRESULT CConnect::GetDocumentText(VARIANT Document, \
EnvDTE::EditPoint** EditStartPoint, EnvDTE::EditPoint** \
EditEndPoint, BSTR* Text)

{

   if (m_pDTE == NULL || EditStartPoint == NULL || \
EditEndPoint == NULL || Text == NULL)

      return E_FAIL;

   HRESULT hr;

   *EditStartPoint = NULL;

   *EditEndPoint = NULL;

   *Text = NULL;

   // Get the documents collection.

   CComPtr<EnvDTE::Documents> spDocuments;

   hr = m_pDTE->get_Documents(&spDocuments);

   if (FAILED(hr) || spDocuments == NULL)

      return E_FAIL;

   // Get the document specified.

   CComPtr<EnvDTE::Document> spDoc;

   hr = spDocuments->Item(Document, &spDoc);

   if (FAILED(hr) || spDoc == NULL)

      return E_FAIL;

   // Get the text document object.

   CComPtr<IDispatch> spTextDocumentDisp;

   hr = spDoc->Object(CComBSTR(L"TextDocument"), &spTextDocumentDisp);

   CComQIPtr<EnvDTE::TextDocument> spTextDoc = spTextDocumentDisp;

   if (FAILED(hr) || spTextDoc == NULL)

      return E_FAIL;

   // Get the start point.

   CComPtr<EnvDTE::TextPoint> spStartPoint;

   hr = spTextDoc->get_StartPoint(&spStartPoint);

   if (FAILED(hr) || spStartPoint == NULL)

      return E_FAIL;

   // Get the end point.

   CComPtr<EnvDTE::TextPoint> spEndPoint;

   hr = spTextDoc->get_EndPoint(&spEndPoint);

   if (FAILED(hr) || spEndPoint == NULL)

      return E_FAIL;

   

   // Create an edit point at the start point.

   hr = spStartPoint->CreateEditPoint(EditStartPoint);

   if (FAILED(hr) || *EditStartPoint == NULL)

      return E_FAIL;

   // Create an edit point at the end point.

   hr = spEndPoint->CreateEditPoint(EditEndPoint);

   if (FAILED(hr) || *EditEndPoint == NULL)

      return E_FAIL;

   // Get the text from the start to the end.

   hr = (*EditStartPoint)->GetText(CComVariant(*EditEndPoint), \
Text);

   if (FAILED(hr) || *Text == NULL)

      return E_FAIL;

   return S_OK;

}

HRESULT CConnect::CreateTidyProcess(HANDLE* StdIn, \
HANDLE* StdOut, HANDLE* StdErr)

{

   if (StdIn == NULL || StdOut == NULL || StdErr == NULL)

      return E_FAIL;

   SECURITY_ATTRIBUTES sa;

   ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));

   sa.nLength = sizeof(SECURITY_ATTRIBUTES);

   sa.bInheritHandle = TRUE;

   sa.lpSecurityDescriptor = NULL;

   // Create the stdin pipe for the child.

    CSmartHandle hChildStdinRd, hChildStdinWr;

   if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &sa, 0))

      return E_FAIL;

   

   // Duplicate the write handle so it can't be inherited.

   CSmartHandle hChildStdinWrDup;

   if (!DuplicateHandle(GetCurrentProcess(), hChildStdinWr, \
GetCurrentProcess(),

         &hChildStdinWrDup, 0, FALSE, DUPLICATE_SAME_ACCESS))

      return E_FAIL;

   // Close the inheritable write handle.

   hChildStdinWr.Close();

   // Create the stdout pipe for the child.

   CSmartHandle hChildStdoutRd, hChildStdoutWr;

   if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &sa, 0))

      return E_FAIL;

   // Duplicate the read handle so it can't be inherited.

   CSmartHandle hChildStdoutRdDup;

   if (!DuplicateHandle(GetCurrentProcess(), \
hChildStdoutRd, GetCurrentProcess(),

         &hChildStdoutRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS))

      return E_FAIL;

   // Close the inheritable read handle.

   hChildStdoutRd.Close();

   // Create the stderr pipe for the child.

   CSmartHandle hChildStderrRd, hChildStderrWr;

   if (!CreatePipe(&hChildStderrRd, &hChildStderrWr, &sa, 0))

      return E_FAIL;

   // Duplicate the read handle so it can't be inherited.

   CSmartHandle hChildStderrRdDup;

   if (!DuplicateHandle(GetCurrentProcess(), \
hChildStderrRd, GetCurrentProcess(),

         &hChildStderrRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS))

      return E_FAIL;

   // Close the inheritable read handle.

   hChildStderrRd.Close();

   

   STARTUPINFO si;

   ZeroMemory(&si, sizeof(STARTUPINFO));

   // Set up start info (hidden window with redirected I/O).

   si.cb = sizeof(STARTUPINFO);

   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

   si.hStdOutput = (HANDLE) hChildStdoutWr;

   si.hStdInput = (HANDLE) hChildStdinRd;

   si.hStdError = (HANDLE) hChildStderrWr;

    si.wShowWindow = SW_HIDE;

   // Spawn the Tidy process.

   PROCESS_INFORMATION pi;

   ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

   if (!CreateProcess(NULL, "tidy.exe -i -ascii", NULL, \
NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi))

      return E_FAIL;

   

   *StdIn = hChildStdinWrDup.Detach();

   *StdOut = hChildStdoutRdDup.Detach();

   *StdErr = hChildStderrRdDup.Detach();

   return S_OK;

}

HRESULT CConnect::WriteTextToTidy(HANDLE hTidyStdIn, \
BSTR bstrText)

{

   // Get the byte length of the string.

   DWORD dwWritten;

   // Convert to ASCII (NOTE: breaks with non-ASCII files!).

   CStringA strTest(bstrText);

   

   BYTE* pBuffer = (BYTE*) strTest.GetBuffer();

   DWORD dwTotalLen = strTest.GetLength();

   

   // While there is still stuff to write to the child process.

   for (DWORD dwTotalWritten = 0; dwTotalWritten < dwTotalLen; \
dwTotalWritten += dwWritten)

   {

      // Write whatever we can out.

      if (!WriteFile(hTidyStdIn, &pBuffer[dwTotalWritten], \
dwTotalLen - dwTotalWritten, &dwWritten, NULL))

         return E_FAIL;

   }

   

   strTest.ReleaseBuffer();

   return S_OK;

}

HRESULT CConnect::ReadResponseFromTidy(HANDLE hTidyStdOut, \
BSTR* bstrText)

{

   DWORD      dwRead;

   BYTE      byteBuf[4096 + 1];   // +1 for NULL

   CStringW   strBuffer;

   if (bstrText == NULL)

      return E_FAIL;

   while (true)

   {

      // Read in some of the output from the child process.

      if (!ReadFile(hTidyStdOut, byteBuf, 4096, &dwRead, NULL))

      {

         if (GetLastError() != ERROR_SUCCESS && \
GetLastError() != ERROR_BROKEN_PIPE)

            return E_FAIL;

      }

      

      // If nothing was read, then we're done.

      if (dwRead == 0)

         break;

      byteBuf[dwRead] = 0;

      // NOTE: this assumes output from the Tidy process is ASCII!

      strBuffer += (LPSTR) byteBuf;

   }

   *bstrText = strBuffer.AllocSysString();

   return S_OK;

}

STDMETHODIMP CConnect::BeforeExecute(BSTR Guid, long ID, \
VARIANT CustomIn, VARIANT CustomOut,

         VARIANT_BOOL* CancelDefault)

{

   // Indicate that we've handled this command event,

   //   so there is no need to pass the event on.

   *CancelDefault = VARIANT_TRUE;

   // We expect the input to be a by ref VARIANT.

   if (CustomIn.vt != (VT_VARIANT | VT_BYREF))

      return E_UNEXPECTED;

   HRESULT hr;

   // Get the HTML text.

   CComBSTR bstrHTMLDoc;

   CComPtr<EnvDTE::EditPoint> spEditStartPoint;

   CComPtr<EnvDTE::EditPoint> spEditEndPoint;

   hr = GetDocumentText(*CustomIn.pvarVal, \
&spEditStartPoint, &spEditEndPoint, &bstrHTMLDoc);

   if (FAILED(hr) || bstrHTMLDoc == NULL)

   {

      StringToOutputWindow(CComBSTR(L"Error: could not \
get the HTML document text from the editor."));

      return E_FAIL;

   }

   // Create the Tidy process with redirected I/O.

   HANDLE hTidyStdIn, hTidyStdOut, hTidyStdErr;

   hr = CreateTidyProcess(&hTidyStdIn, &hTidyStdOut, &hTidyStdErr);

   if (FAILED(hr))

   {

      StringToOutputWindow(CComBSTR(L"Error: could not \
spawn Tidy process. Make sure the executable lies in your \
%PATH% environment variable."));

      return E_FAIL;

   }

   // Write HTML text to Tidy process and close

   //   the input handle to let them know we're done.

   hr = WriteTextToTidy(hTidyStdIn, bstrHTMLDoc);

   CloseHandle(hTidyStdIn);

   if (FAILED(hr))

   {

      CloseHandle(hTidyStdOut);

      StringToOutputWindow(CComBSTR(L"Error: could not write \
the HTML document to the Tidy process."));

      return E_FAIL;

   }

   // Get response from Tidy process and close the output handle.

   CComBSTR bstrResponse;

   hr = ReadResponseFromTidy(hTidyStdOut, &bstrResponse);

   CloseHandle(hTidyStdOut);

   if (FAILED(hr))

   {

      StringToOutputWindow(CComBSTR(L"Error: could not \
read the response from the Tidy process."));

      return E_FAIL;

   }

   // Replace document's original text with process output

   //   if there was a response.

   if (bstrResponse != L"")

   {

      spEditStartPoint->ReplaceText(CComVariant(spEditEndPoint), \
bstrResponse, (int) EnvDTE::vsEPReplaceTextTabsSpaces);

   }

   // Regardless of success or failure, get the error response and

   //   put it in the Output window. Even if Tidy succeeds,

   //   some important info might be output on stderr.

   CComBSTR bstrErrResponse;

   hr = ReadResponseFromTidy(hTidyStdErr, &bstrErrResponse);

   CloseHandle(hTidyStdErr);

   if (FAILED(hr))

   {

      StringToOutputWindow(CComBSTR(L"Error: could not \
read the error response from the Tidy process."));

      return E_FAIL;

   }

   StringToOutputWindow(bstrErrResponse);

   return S_OK;

}

void CConnect::StringToOutputWindow(BSTR bstrText)

{

   HRESULT hr;

   CComPtr<EnvDTE::Windows> spWindows;

   hr = m_pDTE->get_Windows(&spWindows);

   if (FAILED(hr) || spWindows == NULL)

      return;

   CComPtr<EnvDTE::Window> spWindow;

   hr = spWindows->Item(CComVariant(CComBSTR(EnvDTE::\

vsWindowKindOutput)), &spWindow);

   if (FAILED(hr) || spWindow == NULL)

      return;

   CComPtr<IDispatch> spOutputWindowDisp;

   hr = spWindow->get_Object(&spOutputWindowDisp);

   if (FAILED(hr) || spOutputWindowDisp == NULL)

      return;

   CComQIPtr<EnvDTE::OutputWindow> spOutputWindow = \
spOutputWindowDisp;

   if (spOutputWindow == NULL)

      return;

   CComPtr<EnvDTE::OutputWindowPanes> spOutputWindowPanes;

   hr = spOutputWindow->get_OutputWindowPanes(\
&spOutputWindowPanes);

   if (FAILED(hr) || spOutputWindowPanes == NULL)

      return;

   CComPtr<EnvDTE::OutputWindowPane> spOutputWindowPane;

   hr = spOutputWindowPanes->Add(CComBSTR(L"HTML Tidy \
Add-in"), &spOutputWindowPane);

   if (FAILED(hr) || spOutputWindowPane == NULL)

      return;

   spOutputWindowPane->OutputString(bstrText);

}

STDMETHODIMP CConnect::AfterExecute(BSTR Guid, long ID, \
VARIANT CustomIn, VARIANT CustomOut)

{

   return S_OK;

}

STDMETHODIMP CConnect::OnAddInsUpdate (SAFEARRAY ** /*custom*/ )

{

   return S_OK;

}

STDMETHODIMP CConnect::OnStartupComplete (SAFEARRAY ** /*custom*/ )

{

   return S_OK;

}

STDMETHODIMP CConnect::OnBeginShutdown (SAFEARRAY ** /*custom*/ )

{

   return S_OK;

}

VC/ATL: Connect.h

Replace the code in the Connect.h header file included in the Add-in Wizard's "C++/ATL add-in" template with the following code. This is unmanaged native code.

// Connect.h : Declaration of the CConnect

#pragma once
#include "resource.h"       // main symbols

#pragma warning( disable : 4278 )
//The following #import imports DTE based on its LIBID.
#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" \
version("7.0") lcid("0") raw_interfaces_only named_guids
#pragma warning( default : 4278 )

class CConnect;

typedef IDispEventImpl<0, CConnect, \
&EnvDTE::DIID__dispCommandEvents, \
&EnvDTE::LIBID_EnvDTE, 7, 0> CommandEvents;

class CSmartHandle
{
public:
   CSmartHandle() : m_hHandle(NULL) { }
   ~CSmartHandle() { Close(); }

   operator HANDLE() const { return m_hHandle; }

   HANDLE* operator &() { return &m_hHandle; }

   void Close() { if (m_hHandle != NULL) \
CloseHandle(m_hHandle); m_hHandle = NULL; }

   HANDLE Detach() { HANDLE hRet = m_hHandle; \
m_hHandle = NULL; return hRet; }

private:
   HANDLE m_hHandle;
};

// CConnect
class ATL_NO_VTABLE CConnect : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CConnect, &CLSID_Connect>,
   public IDispatchImpl<AddInDesignerObjects::\
_IDTExtensibility2, &AddInDesignerObjects::\
IID__IDTExtensibility2, &AddInDesignerObjects::\
LIBID_AddInDesignerObjects, 1, 0>,
   public CommandEvents
{
public:
   CConnect()
   {
   }

DECLARE_REGISTRY_RESOURCEID(IDR_ADDIN)
DECLARE_NOT_AGGREGATABLE(CConnect)

BEGIN_COM_MAP(CConnect)
   COM_INTERFACE_ENTRY(IDispatch)
   COM_INTERFACE_ENTRY(AddInDesignerObjects::\
IDTExtensibility2)
END_COM_MAP()

BEGIN_SINK_MAP(CConnect)
   SINK_ENTRY_EX(0, EnvDTE::DIID__dispCommandEvents, \
0x1, BeforeExecute)
   SINK_ENTRY_EX(0, EnvDTE::DIID__dispCommandEvents, \
0x2, AfterExecute)
END_SINK_MAP()

   DECLARE_PROTECT_FINAL_CONSTRUCT()

public:
   //IDTExtensibility2 implementation:
   STDMETHOD(OnConnection)(IDispatch * Application, \
AddInDesignerObjects::ext_ConnectMode ConnectMode, \
IDispatch *AddInInst, SAFEARRAY **custom);
   STDMETHOD(OnDisconnection)(AddInDesignerObjects::\
ext_DisconnectMode RemoveMode, SAFEARRAY **custom);
   STDMETHOD(OnAddInsUpdate)(SAFEARRAY **custom);
   STDMETHOD(OnStartupComplete)(SAFEARRAY **custom);
   STDMETHOD(OnBeginShutdown)(SAFEARRAY **custom);

// EnvDTE::_CommandEvents
public:
   STDMETHOD(BeforeExecute)(BSTR Guid, long ID, \
VARIANT CustomIn, VARIANT CustomOut, VARIANT_BOOL* CancelDefault);
   STDMETHOD(AfterExecute)(BSTR Guid, long ID, \
VARIANT CustomIn, VARIANT CustomOut);

// Helper methods
private:
   HRESULT GetDocumentText(VARIANT Document, \
EnvDTE::EditPoint** EditStartPoint, EnvDTE::EditPoint** \
EditEndPoint, BSTR* Text);
   HRESULT CreateTidyProcess(HANDLE* StdIn, \
HANDLE* StdOut, HANDLE* StdErr);
   HRESULT WriteTextToTidy(HANDLE hTidyStdIn, BSTR bstrText);
   HRESULT ReadResponseFromTidy(HANDLE hTidyStdOut, \
BSTR* bstrText);
   void StringToOutputWindow(BSTR bstrText);
   
   CComPtr<EnvDTE::_DTE>            m_pDTE;
   CComPtr<EnvDTE::AddIn>            m_pAddInInstance;
   CComPtr<EnvDTE::_CommandEvents>      m_spCommandEvents;
};

OBJECT_ENTRY_AUTO(__uuidof(Connect), CConnect)

Managed Extensions for C++ Sample Code

The Add-in Wizard does not provide a project template that uses Managed Extensions for C++, so you will not follow the procedure "How to modify the Connect file of your add-in project template" in the section Building the Visual Studio .NET 2003 Add-in Project.

The code sample that follows cannot be compiled directly into an add-in application. Additional development tasks must be performed in order to use Managed Extensions for C++ to create the HTML Tidy add-in:

How to create the HTML Tidy add-in using Managed Extensions for C++

  1. Create a new Managed Extensions for C++ Class Library project.
  2. Add a new C++ source file and a new C++ header file to the project.
    Note   The sample code for these files refers to the source file as "ManagedCAddin.cpp" and uses "ManagedCAddin.h" for the header file.
  3. Copy the sample code given later in this section to the files created in step 2.
  4. Add the following attribute before the Connect class definition.

    [Guid(S"A069C016-BAAC-4d46-A843-8D52DE4DA9A5"), \
    ProgId(S"ManagedCAddin.Connect")]

    Note   Replace the GUID with a new value generated by guidgen.exe, and use the ProgID of your own add-in application):
  5. Add a post-build event. Set its command property to:

    regasm /silent /codebase "$(TargetPath)"

    Note   This will provoke a message stating that the assembly is not strongly named. You can ignore this message.

    Set the description property of the post-build event to:

    "Exporting type library and performing registration..."

  6. Build the project.
  7. Using regedit.exe, a reg file, or a VisualStudio.NET Setup Project, add the following registry key under HKEY_CURRENT_USER:

    "HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.1\AddIns\<PROGID>"

    For <PROGID>, substitute the ProgID of your add-in (the same value used in step 4).

    Note   To give all users of Visual Studio .NET on the target machine access to this add-in, add this registry key under HKEY_LOCAL_MACHINE instead of HKEY_CURRENT_USER.
  8. Under the registry key created in step 7, add the following two string values:

    Description = "Description goes here"

    FriendlyName = "Friendly name goes here"

  9. Optional: To register the add-in for the VS Macro IDE, change "VisualStudio" in the key path to "VSA" and repeat steps 7 and 8.

Once you have performed these additional development tasks, your HTML Tidy add-in should be ready to use.

For further guidance on how to develop Visual Studio .NET add-ins using either unmanaged C++/ATL code or Managed Extensions for C++, see Peter Huene's tutorial on the DevHood Web site, http://www.devhood.com.

Managed C++: ManagedCAddin.cpp

Place this code in the source (.cpp) file of your Managed Extensions for C++ Class Library project.

// This is the main DLL file.

#include "stdafx.h"

#include "ManagedCAddin.h"

Managed C++: ManagedCAddin.h

Place this code in the header (.h) file of your Managed Extensions for C++ Class Library project.

// ManagedCAddin.h

#pragma once

using namespace System;
using namespace Microsoft::Office::Core;
using namespace Extensibility;
using namespace System::Runtime::InteropServices;
using namespace EnvDTE;
using namespace System::IO;
using namespace System::Windows::Forms;

namespace ManagedCAddin
{
   public __gc class Connect : public Extensibility::IDTExtensibility2
   {
      CommandEvents* commandEvents;
      _dispCommandEvents_BeforeExecuteEventHandler* \
beforeExecuteHandler;

      public: void OnConnection(Object* application, \
Extensibility::ext_ConnectMode connectMode, Object* addInInst, \
Array** custom)
      {
         applicationObject = __try_cast<_DTE*>(application);
         addInInstance = __try_cast<AddIn*>(addInInst);

         String* vsCommandGroup = \
S"{1496a755-94de-11d0-8c3f-00c04fc2aae2}";

         int prettyFormatCommandID = 319;

         // Create a command event to handle the prettyFormatCommandID
         //   that receives notification before the default handler. 
         commandEvents = applicationObject->\
Events->get_CommandEvents(vsCommandGroup, prettyFormatCommandID);

         beforeExecuteHandler = new \
_dispCommandEvents_BeforeExecuteEventHandler(this, BeforeExecute);
         commandEvents->add_BeforeExecute(beforeExecuteHandler);
      }

      public: void OnDisconnection(Extensibility::ext_DisconnectMode \
disconnectMode, Array** custom)
      {
         if (beforeExecuteHandler)
            commandEvents->remove_BeforeExecute(beforeExecuteHandler);

         commandEvents = 0;
      }

      public: void OnAddInsUpdate(Array** custom)
      {
      }

      public: void OnStartupComplete(Array** custom)
      {
      }

      public: void OnBeginShutdown(Array** custom)
      {
      }

      private: void BeforeExecute(String* Guid, int ID, \
Object* CustomIn, Object* CustomOut, Boolean* CancelDefault)
      {
         // Indicate that we've handled this command event, 
         //   so there is no need to pass the event on.
         (*CancelDefault) = true;

         try
         {
            Document* doc = applicationObject->Documents->\
Item(CustomIn);
            TextDocument* td = __try_cast<TextDocument*>\
(doc->Object(S"TextDocument"));

            TextPoint* sp = td->StartPoint;
            TextPoint* ep = td->EndPoint;

            EditPoint* editStartPt = sp->CreateEditPoint();
            EditPoint* editEndPt = ep->CreateEditPoint();

            // Get the contents of the document.
            String* txtHTMLDoc = editStartPt->\
GetText(editEndPt);

            // Setup the process info.
            System::Diagnostics::ProcessStartInfo* psi = \
new System::Diagnostics::ProcessStartInfo();

            psi->FileName = S"tidy.exe";
            psi->Arguments = S"-i";
            psi->CreateNoWindow = true;
            psi->UseShellExecute = false;
            psi->RedirectStandardInput = true;
            psi->RedirectStandardOutput = true;
            psi->RedirectStandardError = true;

            // Create the process.
            System::Diagnostics::Process* p = \
new System::Diagnostics::Process();

            // Associate the process info with the process.
            p->StartInfo = psi;

            // Run the process.
            bool fStarted = p->Start();

            if (!fStarted)
               throw new Exception(S"Unable to start tidy process");

            // Set up streams to interact with process's stdin/stdout.
            StreamWriter* sw = p->StandardInput;
            StreamReader* sr = p->StandardOutput;
            String* strFormattedDoc = 0;

            // Write the content of the html document to 
            //   the stdin of the process. 
            sw->Write(txtHTMLDoc);
            sw->Close();

            // Read process's stdout and store in strFormattedDoc.
            strFormattedDoc = sr->ReadToEnd();
            sr->Close();

            // Handle no stdout text, instead display stderr text.
            if (strFormattedDoc->Equals(S""))
            {
               StreamReader* srError = p->StandardError;
               String* strError = srError->ReadToEnd();

               srError->Close();

               throw new Exception(S\
"Tidy failed with error information: "->Concat(strError));
            }

            // Replace document's original text with process's output.
            editStartPt->ReplaceText(editEndPt, strFormattedDoc, \
(int) vsEPReplaceTextOptions::vsEPReplaceTextTabsSpaces);
         }
         catch (Exception* ex)
         {
            System::Windows::Forms::MessageBox::Show(ex->ToString());
         }
      }

      private: _DTE* applicationObject;
      private: AddIn* addInInstance;

   };
}

Managed C++: Catch and display any exceptions

If you throw exceptions, remember to add code to catch and display them. The catch statement included in the sample code for "ManagedCAddin.h" uses the .NET Framework MessageBox.Show Method (String) to display each Tidy message in a standard Windows MessageBox:

         catch (Exception* ex)
         {
            System::Windows::Forms::MessageBox::Show(ex->ToString());
         } 

You might prefer instead to display exceptions and other messages from HTML Tidy in the scrolling Output Window. If so, replace the previous catch statement with a call to an error-handling subroutine:

         catch (Exception* ex)
         {
            ErrToOutputWindow(ex);
         }

Then add the method named to the Connect class. The following sample method adds a new pane to the Output window, and writes the text of exception ex to this new pane:

      private: void ErrToOutputWindow(Exception* ex)
      {
         // Create a tool window handle for the Output window. 
         Window* win = applicationObject->\
Windows->Item(EnvDTE::Constants::vsWindowKindOutput);
         // Access the Output window. 
         OutputWindow* OW = __try_cast<OutputWindow*>(win->Object);
         // Add a new pane to the Output window. 
         OutputWindowPane* OWp = OW->OutputWindowPanes->\
Add(S"HTML Tidy Add-in");
         // Write exception text to this new pane. 
         OWp->OutputString(ex->ToString());
      }

Conclusion

This project demonstrates the extensible design and broad tool palette of Visual Studio .NET 2003. The HTML Tidy add-in enables the HTML view editor to use the external HTML Tidy utility to correct and format your HTML markup. With the help of project templates provided by the Visual Studio .NET 2003 Add-in Wizard, a minimum of programming time is needed to build this add-in project and get it up and running.

You are invited to compare the parallel code samples for the HTML Tidy add-in included in this article, to see how common programming tasks are handled in each development language. Judge for yourself the risks and benefits of programming with managed or unmanaged code in your applications. Visual Studio .NET allows you to choose the approach with which you are most comfortable and the development path best suited to your own experience and local working environment.

Acknowledgments

HTML Tidy was created by Dave Raggett for the World Wide Web Consortium. Todd Grunke developed the HTML Tidy add-in for Visual Studio .NET 2003 in Visual C# .NET. Van Kichline provided the Visual Basic .NET version of this add-in. Huizhong Long and Kemp Brown helped with the Output window code. Peter Huene developed versions of the add-in using unmanaged Visual C++ / ATL code and Managed Extensions for C++. Craig Skibo, Glen Kowalski, and Yugang Wang reviewed the text, ran the code, and improved both. This article was researched and written by Hank Davis.

For Further Information

HTML Tidy: Download, http://tidy.sourceforge.net | Release Notes, http://www.w3.org/People/Raggett/tidy

Add-in Projects: Add-in and Customization User Interface Elements | Creating Add-ins and Wizards | Creating an Add-In | Add New Project Dialog Box | Add-in Manager Dialog Box | Setup Projects | Walkthrough: Deploying a Windows Application.

.NET Framework: .NET Framework Class Library in Visual Studio | .NET Framework Class Library

Development Languages: Visual Basic and Visual C# | Visual C++ | ATL | Managed Extensions for C++ Programming

Exception handling: Exception Handling Fundamentals | Exception Handling Statements | try-catch.

HTML Designer: Editing HTML | Editing HTML Pages in Design View | Building CSS Styles | Editing Code, HTML, and Text | Outlining and Hiding Code

Options: Options that Affect Existing HTML, CSS, or XML Markup | Format, HTML/XML, Text Editor, Options Dialog Box. | HTML Specific, HTML/XML, Text Editor, Options Dialog Box

Properties: HTML Document Properties, Properties Window | General Tab, DOCUMENT Property Pages Dialog Box | Setting the targetSchema Property of an HTML Document | Setting the pageLayout Property of an HTML Document

Task List: Task List Window | Task List Views

Toolbox: HTML Tab, Toolbox | Web Forms Tab, Toolbox

HTML Elements and CSS Styles: DHTML References | CSS Attributes Reference

Web Server Controls: Controls You Can Use on Web Forms Pages | ASP.NET Server Controls by Function | System.Web.UI.HtmlControls Namespace | System.Web.UI.WebControls Namespace

Web Forms: Web Forms Pages | Creating and Managing Web Forms | Inserting a Form into a Project | Controls You Can Use on Web Forms Pages | Adding Web Server Controls to a Web Forms Page

Scripts (server): Server Event Handling in Web Forms | Code for Web Applications | Creating Event Handlers in Web Forms Pages | Understanding the Event Model | Web Forms Code Model

HTML Forms: Forms Overview | Working with HTML Forms in Internet Explorer | Working with HTML Forms in Internet Explorer, Part 2

Scripts (client): Creating Scripts and Editing Event Handlers in HTML Designer | Event Handling in HTML Elements Sample | DHTML Events | Handling HTML Element Events | Client and Server Scripting in Web Pages

Show:
© 2015 Microsoft