Export (0) Print
Expand All

Walkthrough: Building a Managed Code Add-in to Check PWA Compatibility

Office 2010

Published: May 2010

This article shows how to develop and deploy a managed code add-in for Microsoft Project Professional 2010 by using Microsoft Office development tools in Microsoft Visual Studio 2010. The add-in checks the project for conditions that can prevent it from being edited in Project Web App. (This article is adapted from content and a code sample by Mike Shughrue, Microsoft Corporation.)

Visual Studio 2010 contains project templates for Microsoft Office add-ins that make it easier to write, debug, and deploy managed code add-ins. Add-ins are useful for extending and automating functionality in Project. Because the add-ins are written in Microsoft Visual C#, Microsoft Visual Basic, or any other managed language, the syntax is already familiar to programmers who use the Microsoft .NET Framework and provides full access to the .NET Framework. You can debug an add-in within the Visual Studio interactive development environment (IDE) in the same way that you debug other types of applications. Deploying COM add-ins was previously a challenge, but Visual Studio 2010 makes deployment easy by using ClickOnce.

The add-in uses the Project client object model to access project data and runs a series of checks to determine whether the project is editable in Project Web App. If the project does not meet the criteria, the add-in shows a dialog box with a list of conditions that can make the project read-only in Project Web App. The add-in automatically runs by using a ProjectBeforeSave2 event handler and can also be run interactively by using a custom ribbon button.

Scenario:   In Project Server 2010, projects are editable in the Project Center page of Project Web App. Because of server-side scheduling engine constraints, the following conditions render a project read-only in Project Web App:

  • The project is a master project.

  • The project contains one or more null tasks. A null task is a blank task row between two other tasks.

  • A task is a fixed work type. By default, automatically scheduled tasks are fixed units. Tasks can also be set to fixed duration.

  • A task has a task calendar, which in the Project client can be specified in addition to the project calendar and resource calendars. By default, a task calendar is "None".

Although the previous conditions are acceptable in Project Professional 2010, a project manager should be warned that a project may not be editable in Project Web App before saving the project. The best way to achieve that is to implement an add-in that checks the project for those conditions.

This article includes the following sections:

Note Note

Because Project2010EditableAddin uses classes and members that are common to both Project clients, the add-in can be developed and installed with Microsoft Project Standard 2010 or with Project Professional 2010. However, only Project Professional 2010 can save to Project Server.

In this article, the Visual Studio 2010 solution name, project name, namespace name, and add-in assembly name are all Project2010EditableAddIn. Procedure 1 shows how to create the main add-in project. Procedure 2 shows how to create a second project that includes storage classes for the validation rules of the add-in. In Procedure 3, you add an interface definition for the validation rules class.

Procedure 1. To create the add-in

  1. Run Visual Studio 2010 as an administrator, and then create a Project 2010 Add-in project. For example, name the project Project2010EditableAddIn (Figure 1).

  2. In the drop-down menu at the top of the New Project dialog box, select .NET Framework 3.5. Clear the Create directory for solution check box, and then click OK.

    Note Note

    For an explanation of the differences between using the .NET Framework 3.5 and the .NET Framework 4.0, see Designing and Creating Office Solutions.

    Figure 1. Creating a Project 2010 add-in

    Creating a Project 2010 add-in
  3. In the ThisAddIn.cs file, type the following line inside the ThisAddIn_Startup method: this.Application.ProjectBeforeSave2 +=

    After you type the += characters and then press Tab, Intellisense automatically adds the remainder of the statement and includes the Application_ProjectBeforeSave2 method name. When you press Tab again, Visual Studio adds the Application_ProjectBeforeSave2 event handler method with the NotImplementedException statement, as follows:

    using System;
    using System.Data;
    using MSProject = Microsoft.Office.Interop.MSProject;
    using Office = Microsoft.Office.Core;
    
    namespace Project2010EditableAddIn
    {
        public partial class ThisAddIn
        {
            private void ThisAddIn_Startup(object sender, System.EventArgs e)
            {
                this.Application.ProjectBeforeSave2 +=
                    new MSProject._EProjectApp2_ProjectBeforeSave2EventHandler(
                        Application_ProjectBeforeSave2);
            }
    
            void Application_ProjectBeforeSave2(MSProject.Project pj, bool SaveAsUi, 
                MSProject.EventInfo Info)
            {
                throw new NotImplementedException();
            }
    
            private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
            {
            }
    
            // Code generated by VSTO.
            //    . . .
        }
    }
    

Storing Validation Errors

The IValidationRules project defines the interface for the ValidationRules class of the add-in and includes the ValidationError class and ValidationErrors collection class for storing and enumerating validation errors.

Procedure 2. To create classes for storing validation errors

  1. In Solution Explorer, right-click the Project2010EditableAddIn solution (not the project), and then add a new project named IValidationRules.

  2. Optional: Rename the namespace. For example, name the namespace of the IValidationRules project Microsoft.SDK.Project.Samples.ValidationRules.

  3. Optional: Rename the class file. For example, name the file IRules.cs.

  4. Add a ValidationError class that can store one instance of an error message and related task ID data. The class constructor sets the Msg property and the TaskID property of each instance.

    using System;
    using System.Data;
    using System.Collections;
    using System.Collections.Generic;
    using MSProject = Microsoft.Office.Interop.MSProject;
    
    namespace Microsoft.SDK.Project.Samples.ValidationRules
    {
        // Store one instance of a validation error.
        public class ValidationError
        {
            public ValidationError(string msg, int taskId)
            {
                this.Msg = msg;
                this.TaskID = taskId;
            }
    
            // Message property.
            public string Msg
            { get; set; }
    
            // Task ID property.
            public int TaskID
            { get; set; }
        }
        // . . .
    }
    
  5. Add a class named ValidationErrors that can store a collection of ValidationError objects and enumerate them. The class constructor initiates the errors class variable, which is a generic List object of type ValidationError.

    // Store a collection of validation errors.
    public class ValidationErrors : IEnumerable<ValidationError>
    {
        List<ValidationError> errors;
    
        public ValidationErrors()
        {
            errors = new List<ValidationError>();
        }
    
        public void Add(ValidationError error)
        {
            errors.Add(error);
        }
    
        public IEnumerator<ValidationError> GetEnumerator()
        {
            foreach (ValidationError err in errors)
            {
                yield return err;
            }
        }
    
        // Explicit interface implementation.
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    

Specifying an Interface

A class that uses the IRules interface must implement an IsValid method that takes a project parameter, an ErrorList property of type ValidationErrors, and an ErrorTable property of type DataTable.

Procedure 3. To specify an interface for the ValidationRules class

  1. Add the following using statements in the IRules.cs file:

    using System.Collections;
    using MSProject = Microsoft.Office.Interop.MSProject;
    
  2. Add an interface named IRules.

  3. Add an IsValid method definition, an ErrorList read-only property, and an ErrorTable read-only property. Following is the complete code of the IRules interface:

    // Specify the interface for the ValidationRules class.
    public interface IRules
    {
        bool IsValid(MSProject.Project project);
    
        ValidationErrors ErrorList { get; }
        DataTable ErrorTable { get; }
    }
    

To be able to inherit from the IRules interface, compile the IValidationRules project to create the IValidationRules.dll assembly.

Checking for Errors

Create the ValidationRules class in the Project2010EditableAddIn project. The ValidationRules class implements the method and properties of the IRules interface. The IsValid method checks for errors and the ErrorList property stores the error data. The ErrorTable property stores the data for easy use in a DataGridView control for displaying the errors.

Procedure 4. To create a class that checks for errors

  1. Right-click the Project2010EditableAddIn project, and then add a class named ValidationRules.

  2. Set a reference to the IValidationRules.dll assembly in the IValidationRules\bin\Debug subdirectory of the IValidationRules project.

  3. Add using statements for the ValidationRules namespace and the MSProject namespace.

  4. Set ValidationRules as an internal class that inherits from IRules.

  5. Create the application class variable of type MSProject.Application, the errors variable of type ValidationErrors, and the NumErrors property that keeps a count of errors. Initialize the class variables and the NumErrors property in the class constructor, as follows:

    using System;
    using System.Data;
    using MSProject = Microsoft.Office.Interop.MSProject;
    using Microsoft.SDK.Project.Samples.ValidationRules;
    
    namespace Project2010EditableAddIn
    {
        // The ValidationRules class must implement each member specified in the IRules interface.
        internal class ValidationRules : IRules
        {
            private MSProject.Application application;
            private ValidationErrors errors;
    
            internal ValidationRules(MSProject.Application app)
            {
                application = app;
                errors = new ValidationErrors();
                this.NumErrors = 0;
            }
    
            // Keep count of the number of errors.
            public int NumErrors
            { get; set; }
    
        }
    }
    
  6. Create resource strings for the IsValid method to use in the error messages:

    1. In Solution Explorer, expand the Properties folder in the Project2010EditableAddIn project, and then double-click Resource.resx. Create the four resource properties and values, as in Table 1.

      Table 1. Resource properties and values

      Name

      Value

      MASTER_PROJECT

      This project is a master project.

      NULL_TASK

      This task is null.

      FIXED_WORK_TASK

      This task has fixed work.

      TASK_CALENDAR

      This task has a task calendar

    2. Save and close Resources.resx. (The TASK_CALENDAR value has no period at the end.)

  7. Implement the IsValid method that checks for the following conditions:

    • If the project contains any subprojects, it is a master project. Project Web App cannot edit master projects.

    • Check for null tasks.

    • Check for tasks that are a fixed work type.

    • Check for tasks that use a task calendar.

    Increment the NumErrors property for each error.

    // Check the project and all tasks for values that are not valid for editing in Project Web App. 
    // If any value is an error, the project cannot be edited.
    public bool IsValid(MSProject.Project project)
    {
        bool isValid = true;
    
        // If the project is a master project, the project is not valid.
        if (project.Subprojects.Count > 0)
        {
            isValid = false;
            this.NumErrors++;
    
            int taskId = 0;
            string validationErr = project.Name + ": " + Properties.Resources.FIXED_WORK_TASK;
            ErrorList.Add(new ValidationError(validationErr, taskId));
        }
    
        int index = 0;
        foreach (MSProject.Task task in project.Tasks)
        {
            index++;
    
            // If the task is null, the project is not valid.
            if (task == null)
            {
                isValid = false;
                this.NumErrors++;
                ErrorList.Add(new ValidationError(Properties.Resources.NULL_TASK, index));
    
                // Do not check any properties for the null task; 
                // otherwise, you get a null reference exception.
                continue; 
            }
    
            // If the task has fixed work, the project is not valid.
            if (task.Type == MSProject.PjTaskFixedType.pjFixedWork)
            {
                isValid = false;
                this.NumErrors++;
    
                string validationErr = task.Name + ": " + Properties.Resources.FIXED_WORK_TASK;
                ErrorList.Add(new ValidationError(validationErr, index));
            }
    
            // If the task has a task calendar, the project is not valid.
            if (!(task.Calendar == "None"))
            {
                isValid = false;
                this.NumErrors++;
    
                string validationErr = task.Name + ": " + Properties.Resources.TASK_CALENDAR;
                validationErr += ": " + task.Calendar;
                ErrorList.Add(new ValidationError(validationErr, index));
            }
        }
        return isValid;
    }
    
  8. Implement the ErrorList read-only property, which gets the errors object of type ValidationErrors.

    // Retrieve the ErrorList, to add errors or read the collection of errors.
    public ValidationErrors ErrorList
    {
        get { return errors; }
    }
    
  9. Implement the ErrorTable read-only property, which copies each ValidationError item in the ErrorList object into a DataTable object.

    // Build and return a DataSet with the validation errors.
    public DataTable ErrorTable
    {
        get
        {
            DataTable dtErrors = new DataTable("Errors");
            dtErrors.Columns.Add("TaskId", Type.GetType("System.String"));
            dtErrors.Columns.Add("Message", Type.GetType("System.String"));
    
            foreach (ValidationError error in ErrorList)
            {
                DataRow row = dtErrors.NewRow();
                row["TaskId"] = error.TaskID.ToString();
                row["Message"] = error.Msg;
                dtErrors.Rows.Add(row);
            }
            return dtErrors;
        }
    }
    

Displaying the Errors

When Project2010EditableAddIn runs, if there are any errors, a dialog box shows the list of errors that are stored in the ErrorTable object.

Procedure 5. To create a dialog box that displays the errors

  1. In Solution Explorer, right-click the Project2010EditableAddIn project, click Add, and then click Windows Form.

  2. Name the class ErrorDialog.

  3. In the Properties pane, set the Text property of the ErrorDialog class to Validation Errors.

  4. Set the ControlBox property to False.

  5. Add a button to the ErrorDialog form, and name the button btnCancel. Set the Anchor property to Bottom, Right, set the DialogResult property to Cancel, and set the Text property to Cancel: Do Not Save.

  6. Add a button to the ErrorDialog form, and name the button btnOK. Set the Anchor property to Bottom, Right, set the DialogResult property to OK, and set the Text property to OK: Save Anyway.

  7. In the Design view of ErrorDialog.cs, click the Validation Errors form. Set the AcceptButton property to btnOK. Set the CancelButton property to btnCancel.

  8. Add a DataGridView control named dgErrors to the dialog box.

  9. Add a label above the DataGridView control. For this example, name the label lblErrMess. Set the Text property to a temporary value; for example, type Errors in the project. The SetErrorCount method in the ErrorDialog class changes the Text property. Expand the label width to cover the same width as the DataGridView control (Figure 2). Expand the label height so that it can show four lines of text.

    Figure 2. Expanding the Label control in the Validation Errors dialog box

    Setting the size of the label in the dialog box
  10. Add the following code to the ErrorDialog.cs file:

    using System;
    using System.Data;
    using System.Windows.Forms;
    
    namespace Project2010EditableAddIn
    {
        internal partial class ErrorDialog : Form
        {
            private int dlgWidth;
            private int dlgHeight;
    
            internal ErrorDialog()
            {
                InitializeComponent();
            }
    
            internal void SetErrors(DataTable dtErrors)
            {
                dgErrors.DataSource = dtErrors;
    
                // Set values for the grid user interface.
                dgErrors.Columns["TaskId"].HeaderText = "Task Number";
                dgErrors.Columns["TaskId"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
                dgErrors.Columns["Message"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            }
    
            // Change the label text to show the number of errors.
            internal void SetErrorCount(int numErrors)
            {
                lblErrMess.Text = "Number of errors: " + numErrors.ToString();
                lblErrMess.Text +=
                    "\n\nThe following errors in the project can prevent it from being editable";
                lblErrMess.Text += "\nin Project Web App.";
            }
    
            // Get the size of the dialog box before resizing.
            private void ErrorDialog_ResizeBegin(object sender, EventArgs e)
            {
                dlgWidth = this.Width;
                dlgHeight = this.Height;
            }
    
            // Calculate the new size of the DataGrid.
            private void ErrorDialog_ResizeEnd(object sender, EventArgs e)
            {
                dgErrors.Width += this.Width - dlgWidth;
                dgErrors.Height += this.Height - dlgHeight;
            }
        }
    }
    
  11. In the Design view of the ErrorDialog form, click the form, and then in the Properties pane, click the Events icon. For the ResizeBegin event, select ErrorDialog_ResizeBegin in the drop-down list. For the ResizeEnd event, select ErrorDialog_ResizeEnd in the drop-down list.

The ErrorDialog form is ready to be instantiated, initialized, and called in the Application_ProjectBeforeSave2 event handler in ThisAddIn.cs.

Running the Add-In from the Ribbon

Create a ribbon tab and button that enables the user to run the validation check on demand.

Procedure 6. To add a ribbon tab and button that runs the add-in

  1. In Solution Explorer, right-click the Project2010EditableAddIn project, and then add a new item. In the Add New Item dialog box, click Ribbon (Visual Designer). Name the class ValidationRibbon.

  2. Expand the Office Ribbon Controls tab in the Toolbox, and then drag a Button control to group1 in the ValidationRibbon.cs [Design] tab.

  3. Click the group1 control, and then clear the Label property so that no group label is visible.

  4. Click the button1 control, change the Label property to Validate for PWA Editing, and change the Name property to rbtnValidate.

  5. Change the ControlSize property to RibbonControlSizeLarge, and then change the ScreenTip property to the following: Validates the project for editing in Project Web App. If the project is a master project, or contains a null task, a fixed work task, or a task calendar, the project cannot be edited in PWA.

  6. Click the Image property (Figure 3), and then import an image for the button by using the Select Resource dialog box. For example, import the Checkmark.png image in the Samples\VSTO\Project2010EditableAddIn\Resources directory in the Project 2010 SDK download.

    Figure 3. Design view for the validation ribbon button

    Designing the ribbon button for validation
  7. In the Properties pane for the button, click the Events icon, and then double-click the Click event to create the rbtnValidate_Click event handler.

  8. Add code to the click event handler that shows the ErrorDialog form if there are errors. If there are no errors, show a message box. Following is the complete rbtnValidate_Click event handler:

    // Click event handler for validation button on the ribbon.
    private void rbtnValidate_Click(object sender, RibbonControlEventArgs e)
    {
        try
        {
            ValidationRules rules = new ValidationRules(Globals.ThisAddIn.Application);
                    
            if (!rules.IsValid(Globals.ThisAddIn.Application.ActiveProject))
            {
                ErrorDialog dlg = new ErrorDialog();
                dlg.SetErrors(rules.ErrorTable);
                dlg.SetErrorCount(rules.NumErrors);
                dlg.ShowDialog();
            }
            else
            {
                string msg = Globals.ThisAddIn.Application.ActiveProject.Name
                    + ":\nThis project is OK for editing in Project Web App.";
                MessageBox.Show(msg, "No Validation Errors", 
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Exception", 
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    

When the user clicks the Add-Ins tab on the ribbon, and then clicks Validate for PWA Editing, the add-in validates the project and shows a list of errors that can prevent editing in Project Web App. If there are no errors the add-in shows the No Validation Errors message box (Figure 4).

Figure 4. Dialog box showing the project can be edited in Project Web App

Dialog box showing the project is OK to save

Running the Add-In with an Event Handler

In Procedure 1, Visual Studio added the Application_ProjectBeforeSave2 event handler stub when you added the ProjectBeforeSave2 event to check in the ThisAddIn_Startup method. To complete the add-in, add code in the Application_ProjectBeforeSave2 event handler.

Procedure 7. To create the ProjectBeforeSave2 event handler

  1. Open ThisAddIn.cs, and then delete or comment-out the throw new NotImplementedException(); line in the Application_ProjectBeforeSave2 event handler.

  2. Add the following using statement, to enable use of the DialogResult enumeration:

    using System.Windows.Forms;
    
  3. Add code in the Application_ProjectBeforeSave2 event handler that instantiates a rules object and then calls the IsValid method.

    // Event handler for the ProjectBeforeSave2 event.
    void Application_ProjectBeforeSave2(MSProject.Project pj, bool SaveAsUi,  MSProject.EventInfo Info)
    {
        if (pj == null) return;
    
        // Validate the project for editing in Project Web App.
        ValidationRules rules = new ValidationRules(this.Application);
    
        if (!rules.IsValid(pj))
        {
            // Show any validation errors.
            ErrorDialog dlg = new ErrorDialog();
            dlg.SetErrors(rules.ErrorTable);
            dlg.SetErrorCount(rules.NumErrors);
    
            // If the user clicks Cancel, cancel the save operation. 
            if (dlg.ShowDialog() == DialogResult.Cancel) Info.Cancel = true;
        }
    }
    

When the user saves a project, the ProjectBeforeSave2 event handler checks the project and lists errors in the Validation Errors dialog box (Figure 5). If the user clicks Cancel: Do Not Save, the event handler sets the Info.Cancel property to true, and Project cancels the save operation. If the user clicks OK: Save Anyway, Project saves the data. The add-in does not show the No Validation Errors message box when it uses the event handler.

Figure 5. Dialog box showing errors for Project Web App

Dialog box showing errors for Project Web App

Project client add-ins are an application-level type, not a document-level type. Debugging an add-in for the Project client is straightforward. Visual Studio starts the Project client and then stops execution at any breakpoints.

Procedure 8. To test and debug the add-in

  1. Set a breakpoint where you want to start debugging. For example, open the ValidationRules.cs file, and then set a breakpoint at the string validationErr = task.Name + ": " + Properties.Resources.FIXED_WORK_TASK; statement in the IsValid method.

  2. If the Project client is running before you start a debug session, exit Project.

  3. In Visual Studio, press F5 to start debugging. Visual Studio starts Project; you can run Project locally or log on with a Project Server profile. In the Project client, create some tasks, and then set one of the tasks to a fixed work type.

  4. Click the Save icon in the Quick Access Toolbar (or click the Add-Ins tab in the ribbon, and then click Validate for PWA Editing). Visual Studio stops running the add-in at the breakpoint.

  5. Examine values in the Locals pane, and then press F11 to step through the statements and continue into the ValidationError class constructor in the IRules.cs file. After creating a ValidationError object, execution returns to the Errorlist.Add statement, and then goes to the Add method in the ValidationErrors class. When the add-in adds the error object, the Locals pane shows that the this.errors object changes to Count = 1.

You can use the Publish wizard in Visual Studio for simple ClickOnce deployment of a Project add-in. Both of the assemblies in the solution should be signed with a strong name key. For more information, see How to: Deploy an Office Solution by Using ClickOnce in the MSDN Library.

Procedure 9. To deploy the add-in to Project Professional

  1. In Solution Explorer, right-click the IValidationRules project, and then click Properties. On the IValidationRules tab, click the Signing tab, select the Sign the assembly check box, and then create a strong name key file. For example, create IValidationRules.snk.

  2. Similarly, create a strong name key file for the Project2010EditableAddIn project. For example, create Project2010Addin.snk.

  3. Right-click the Project2010EditableAddIn project, and then click Publish.

  4. On the first page of the Publish Wizard, change the default publish\ directory name to make it easy to recognize on another computer. For example, change the name to Project2010EditableAddIn_Published\. Click Next.

  5. On the second page of the Publish Wizard, choose the default From a CD-ROM or DVD-ROM option. Click Next, and then click Finish. Visual Studio rebuilds the projects if necessary, and then creates the Project2010EditableAddIn_Published\ subdirectory and the files for deploying the add-in to another computer.

  6. Copy the Project2010EditableAddIn_Published\ directory to another computer where Project Professional 2010 is installed.

  7. On the other computer, run setup.exe in the Project2010EditableAddIn_Published\ directory. The Microsoft Office Customization Installer wizard checks for prerequisites. On the Publisher cannot be verified page of the wizard, click Install. After the installation is successful, click Close.

When you run Project, you should see Validate for PWA Editing on the Add-Ins tab of the ribbon. Create some tasks; add a fixed work task, a null task, and a task that has a task calendar. When you save the project, you should see the Validation Errors dialog box as in Figure 5. After you fix the errors and then click Validate for PWA Editing on the ribbon, you should see the No Validation Errors message box as in Figure 4.

Summary

Visual Studio 2010 makes building and deploying Project 2010 add-ins easier. The Project2010EditableAddIn sample shows how to build and deploy a Project 2010 add-in that validates whether a project is editable in Project Web App.

Other Resources

Designing and Creating Office Solutions
How to: Deploy an Office Solution by Using ClickOnce

Community Additions

ADD
Show:
© 2014 Microsoft