This article provides an example of a managed code add-in that uses the Microsoft Visual Studio 2008 template for Microsoft Office Project 2007. When you develop add-ins for Project 2007 and the other 2007 Microsoft Office system client applications, Microsoft Visual Studio Tools for the Microsoft Office system (3.0) is the preferred development platform. In general, when you develop Office Business Applications (OBAs) or complex Microsoft Visual Basic for Applications (VBA) macros, you should consider moving to Visual Studio Tools for Office for the advantages that the Visual Studio product offers in debugging, security, deployment, and ease of maintenance, and for all of the advantages of the Microsoft .NET Framework.
The ImportProject sample add-in shows how to use both the Project 2007 and the Microsoft Office Excel 2007 object model through the primary interop assemblies. Integrating Project and Excel is a common development task.
Many people use Excel for storing project task, resource, assignment, and cost data. Project managers who use Project Standard or Project Professional have a much better tool for project management, but might find it difficult or time-consuming to import existing project data from Excel because there is no standard format or organization of project data in Excel.
With Project 2007, you can open Excel 2003 workbooks and import worksheets if they are dedicated to task, resource, or assignment data. The Import Wizard in Project helps you map the fields from Excel to Project, and you can also save the map to use with other Excel files if they have the same kind and organization of data. Figure 1 shows that the EPM Guide Phase field in the sample Excel file is not mapped; however, you can assign it to a task custom field such as Text1.
Figure 1. Using the Import Wizard in Project
The Import Wizard might not always perform as you expect in all cases; the imported data can require many manual changes. In those cases, particularly when there are many similar Excel files to import, a custom add-in can help solve the problem. For example, the following jobs are just a few of the things that an add-in can do when importing tasks that the Import Wizard cannot do:
-
Insert, modify, or delete specific tasks based on criteria you set.
-
Change task dates.
-
Add local custom fields based on criteria you specify.
-
Rename local custom fields.
-
Convert from local time to Coordinated Universal Time (UTC).
-
Validate task data and allow you to interactively change data that meets certain conditions.
-
Select a subset of tasks to import.
-
Separate tasks into master and subprojects.
-
Combine data from multiple worksheets. For example, add cost data from one worksheet to the tasks from another worksheet.
-
Integrate with Microsoft Office Project Server 2007 by using the Project Server Interface (PSI). For example, add enterprise custom fields to tasks.
There are similar lists of jobs that an add-in can do when importing resource and assignment data from Excel.
This article shows how to develop and deploy an add-in for Project 2007. The add-in example imports task data and does the following:
-
Adds a toolbar and button to Project that starts the Import From Excel dialog box (see Figure 2).
-
Shows the application version and the ClickOnce published version of the add-in.
-
Enables the user to select the Excel file and worksheet, specify the column letters with the types of task data, and specify the row numbers to import for header and task data. The add-in enables the user to specify a subset of tasks in the Excel worksheet, and then converts the task data so that it is consistent for importing to Project.
-
Uses a DataGridView control to show all of the Excel data to import, before the import occurs.
-
Converts dates to UTC, and shows both local dates and UTC dates before import. For more information, see Coding Best Practices Using DateTime in the .NET Framework.
-
Sets the project start date to the start date of the first task.
-
Renames the Text1 task custom field to the name of the Excel column that contains the custom field data.
-
Does not allow setting a summary task as a predecessor (that causes a COM exception in the Task.TaskDependencies.Add method). Instead, the add-in enables the user to ignore the predecessor task, constrain the task start date, or choose a non-summary task as the predecessor.
-
Handles multiple predecessor tasks.
-
Indents tasks to the correct level according to the specified work breakdown structure (WBS).
-
Helps the user add the task custom field column to the Gantt Chart view of the imported project.
Figure 2. Using the Import From Excel dialog box of the ImportProject add-in
Prerequisites
To use the download sample, you must have the following installed on the development computer:
The sample Visual Studio add-in projects use version 12.0 of the Microsoft.Office.Interop.MSProject and Microsoft.Office.Interop.Excel assemblies. You can modify the Visual Studio projects to use Project 2003 and Excel 2003 by setting references to version 11.0 of the Interop assemblies.
You can use either Visual Studio 2008 Professional Edition or Visual Studio Team System 2008; both versions include Visual Studio Tools for the Office system. If you use a version later than Visual Studio 2008 Beta 2, some of the default reference assembly names might differ. In that case, create a new Project 2007 add-in, and then copy the code files in the download into the new Visual Studio project.
Users who install the ImportProject add-in must also have the same versions of Project and Excel installed that the development computer uses.
For more information about Visual Studio 2008, see the Visual Studio Developer Center.
ClickOnce Deployment
Before Visual Studio 2008, it was difficult to deploy, manage security of, and install add-ins for Microsoft Office applications. Publishing the add-in to a network share or an Intranet Web site with ClickOnce makes it much easier to distribute the add-in, handle security, and manage prerequisites and updates.
When a user runs Setup.exe from the published site, the ClickOnce installer checks for the .NET Framework 3.5, Microsoft Visual Studio Tools for Office Runtime, and Windows Installer 3.1 prerequisites, and installs them if needed. You can set the interval at which the add-in checks the published site for updates (for example, daily or weekly), and automatically installs an update to the latest ClickOnce version.
Note: |
|---|
| Installing the .NET Framework 3.5 is time-consuming. You should notify users who do not already have it installed, and provide the Microsoft URL for downloading the .NET Framework 3.5 as an alternative. You should also notify users to uninstall any earlier version of the .NET Framework 3.5 and Visual Studio Tools for Office Runtime before they try to install the version you deploy. |
ClickOnce handles the security issues easily. When you are developing an add-in, you create a test certificate to sign the ClickOnce manifest files. If the publisher cannot be verified, a user can choose whether to install the add-in (see Figure 3). If you add a signed certificate, installation does not show the dialog box for the unknown publisher.
Figure 3. ClickOnce installation with an unknown publisher
For more information, see Configuring ClickOnce for Deployment in this article.
Differences Between Visual C# and Visual Basic for Add-in Development
Visual Studio 2008 includes templates for developing application-level add-ins for Project 2003 and Project 2007, in both Microsoft Visual C# and Microsoft Visual Basic. The choice of language primarily depends on your experience and preferences; both have advantages and disadvantages for developing add-ins. Table 1 describes some advantages and disadvantages of each language.
Table 1. Advantages and disadvantages of Visual C# and Visual Basic for add-in development
| Language | Advantages | Disadvantages |
| Visual Basic | -
Microsoft IntelliSense shows optional parameters (see Figure 4). -
No need to include unspecified optional parameters, for example, Application.FileNew(). -
Supports named arguments, for example, thisProject.Tasks.Add(Name:="Task Name"). -
Offers direct use of properties with parameters, such as the Worksheet.Range property: headerRange = xlWorksheet.Range(startCell, endCell) | -
Wordy implementation of class properties. -
More complex casts, for example, Dim startDate As Double = DirectCast(valueField, Double) works because Double is a value type. The TryCast method fails because it requires a reference type. -
Awkward to create arrays with a variable number of elements, for example: | Dim numPreds as Integer = 3
Dim adjustedPredValues() _
As Integer = New Integer() {}
ReDim adjustedPredValues(numPreds - 1)
|
|
| Visual C# | -
New language features in Visual C# 2008, such as autoimplemented class properties: public string Wbs { get; set; } -
Simple use of casts, for example: double startDate = (double)valueField. -
Simple creation of arrays with a variable number of elements, for example: | int numPreds = 3;
int[] adjustedPredValues =
new int[numPreds];
|
| -
Does not support optional parameters. -
Requires System.Type.Missing for unspecified parameters, for example, Application.FileNew(missing, missing, missing, missing). -
Does not support named arguments. -
Cannot directly access properties with parameters, such as the Worksheet.Range property. Requires other methods, for example, headerRange = xlWorksheet.get_Range(startCell, endCell). |
Whether you see the items in the Disadvantages column of Table 1 as actual disadvantages depends on your point of view. The language I primarily use is Visual C#, so I find it easiest to develop the sample add-in first in Visual C# and then port it to Visual Basic. You can organize the code in any solution in many ways, and there are probably some constructs in the ImportProject sample add-in code that could be improved.
For more information about using Visual C# and Visual Basic for add-in development, see Programming Office Applications Using Visual C#.
Finding Help for the Excel and Project Object Models
The primary interop assemblies for Project and Excel, Microsoft.Office.Interop.MSProject.dll and Microsoft.Office.Interop.Excel.dll, are the managed code interfaces to the Project and Excel client application object models. When you use an object, property, method, or event in a VBA macro, it generally has the equivalent interface in the related primary interop assembly. Visual Studio Tools for Office extends some of the native objects in Excel, such as the Range object, to provide host controls such as NamedRange for use in document-level extensions in Excel. Visual Studio Tools for Office provides an application-level add-in template only for Project, not a document-level template.
The classes and members of the Microsoft.Office.Interop.Excel namespace are documented in Visual Studio 2008. You can search for a class or member in Visual Studio Help, or select a class or member and press F1 to go to the Help page for that item. For more information about using the Excel object model in Visual Studio Tools for Office, search for "Excel Object Model Overview" on MSDN online. For the pre-release documentation, see Excel Object Model Overview (the link might change in later documentation releases).
The Microsoft.Office.Interop.MSProject namespace is not documented in Visual Studio 2008. Because the MSProject primary interop assembly is the managed code interface for the Project object model, you can use VBA Help in Project to find information about the classes and members. Visual Studio IntelliSense also shows the class members and parameters. In Figure 4, Visual Basic shows that all of the parameters for the MSProject.Application.FileNew method are optional. Visual C# does not support optional parameters.
Figure 4. IntelliSense in Visual Studio for the MSProject object model
To access VBA Help, open the Visual Basic Editor in Project, and then click the Help menu. VBA Help for all 2007 Office system client applications is also available in the MSDN Library. For the Project VBA reference online, see the Microsoft Office Project 2007 Developer Reference. The Project 2007 SDK also includes some VBA documentation: see VBA Object Model Changes and Tables of VBA Object Model Changes.
To access updated help topics for VBA when you are working in the Visual Basic Editor, click the Connection Status menu in the lower-right section of the Project Help window, and then click Show content from Office Online. For example, the VBA object model maps (such as the Application and Projects Object Map) are available only through online help. For descriptions of fields in Project, see the online Fields Reference.
The primary components of the ImportProject add-in are the ThisAddIn class, which handles all of the interaction with Project, and the ImportDialogBox form, which handles all of the interaction with Excel. The main jobs of the components are as follows:
-
ThisAddIn class. Includes the startup and shutdown event handlers for managing the add-in toolbar and the click event handler for the toolbar button.
-
ImportButtonClick event handler for the toolbar button. Instantiates the ImportDialogBox form.
-
ImportDialogBox. Opens the specified Excel file and worksheet, reads the specified header and task data, validates and adjusts the WBS and task predecessor data if necessary, creates a TaskRow object for each task to import, and then shows the adjusted data in a grid before calling the ThisAddIn.CreateTasks method to import the tasks to Project.
-
CreateTasks method. Opens a new project, sets the project start date to the start date of the first imported task, and renames the local Text1 task custom field to the Excel column header name of the custom field. The CreateTasks method then iterates through an array of the TaskRow objects, adds each task to the project, adds the custom field value, sets the task predecessors, and sets the task outline level. If a task predecessor is a summary task, CreateTasks allows the user to ignore the predecessor or to use the ChoosePredecessor dialog box to constrain the task start date or to choose a non-summary task for the predecessor. Finally, CreateTasks brings up the Column Definition dialog box in Project for adding the task custom field column to the view.
Note: |
|---|
| The Project object model does not include a way to programmatically set values in the Column Definition dialog box, so the add-in simply explains what the user can interactively set. |
A class diagram helps to see all of the main classes and members together. The Visual C# project in the download named ImportProject includes a class diagram named ClassDiagram1. In the Visual Basic project, in Solution Explorer, right-click ImportProject_VB, click View Class Diagram to add the diagram, and then expand and arrange the classes the way you want.
The following procedures show how to develop the ImportProject add-in:
-
Creating the ImportProject add-in
-
Adding a toolbar and button
-
Creating the import dialog box
-
Getting the Excel header data
-
Importing the task data from Excel
-
Validating and converting the task data
-
Cleaning up and calling the ThisAddIn.CreateTasks method
-
Developing the CreateTasks method
-
Adding task predecessors
-
Adding a column in the Gantt Chart view for the task custom field
Procedure 1. To create the ImportProject add-in
-
Start Visual Studio 2008, click New Project, and then expand the nodes in the Project types column under the language you want to use.
Note: |
|---|
| If you working on Windows Vista, run Visual Studio as an administrator. Right-click Microsoft Visual Studio 2008 Beta 2, and then click Run as administrator. |
-
Create a new project by using the Office 2007 Project Add-in template (see Figure 5). For example, name the project ImportProject. Figure 5 shows the name as ImportProject_VB because both the Visual C# and Visual Basic projects were created on the same development computer for this article.
Figure 5. Creating a Visual Studio Tools for Office project in Visual Studio 2008
Note: |
|---|
| Microsoft Visual Studio Tools for the Microsoft Office system (3.0) projects use the .NET Framework 3.5. |
-
If you are developing with Visual Basic, in Solution Explorer, click Show All Files, so you can see the References and My Project nodes.
-
Add the following references:
-
Microsoft.Office.Interop.Excel, version 12.0.0.0, matches the MSProject version reference for the Project 2007 add-in.
-
System.Deployment, version 2.0.0.0, is required for showing ClickOnce properties.
Note: |
|---|
| Version 12 of the Excel primary interop assembly can read both Excel 2003 and Excel 2007 files (just as Excel 2007 can). If you are developing an add-in for Project 2003, use Microsoft.Office.Interop.Excel version 11.0.0.0 with Microsoft.Office.Interop.MSProject version 11.0.0.0. If the primary interop assembly reference version does not match the installed Project or Excel version, you might encounter problems in compiling and ClickOnce deployment. |
-
Add the following declarations to the ThisAddIn class (open ThisAddIn.vb or ThisAddIn.cs).
|
Imports System
Imports System.Collections
Imports System.Text
Imports System.Windows.Forms
Imports MSProject = Microsoft.Office.Interop.MSProject
Imports Office = Microsoft.Office.Core
|
|
using System;
using System.Collections;
using System.Text;
using System.Windows.Forms;
using MSProject = Microsoft.Office.Interop.MSProject;
using Office = Microsoft.Office.Core;
|
Adding a Toolbar and Button
The add-in checks whether a toolbar named ImportBar exists. If the toolbar does not exist, create it, and then add the button and event handler.
Procedure 2. To create the CommandBar and button objects
-
In the ThisAddIn class, add class variables for the CommandBar and CommandBarButton objects.
-
Create a method named AddImportToolbar, and then call the method from the ThisAddIn_Startup event handler.
|
Public Class ThisAddIn
Private commandBar As Office.CommandBar
Private importButton As Office.CommandBarButton
Private Sub ThisAddIn_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
AddImportToolbar()
End Sub
. . .
Private Sub AddImportToolbar()
End Sub
End Class
|
|
namespace ImportProject
{
public partial class ThisAddIn
{
private Office.CommandBar commandBar;
private Office.CommandBarButton importButton;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
AddImportToolbar();
}
. . .
private void AddImportToolbar()
{
}
}
}
|
Note: |
|---|
| The Visual Basic add-in uses the ImportProject_VB root namespace specified on the Application tab of the ImportProject_VB properties page. The Option Explicit compile option is also on by default on the Compile tab of the ImportProject_VB properties page. |
-
In the AddImportToolbar method, try reading a toolbar named ImportBar. If ImportBar does not exist, the CommandBars object throws an ArgumentException. Catch the exception and do nothing, and then create the CommandBar if necessary.
|
Try
commandBar = Application.CommandBars("ImportBar")
Catch ex As ArgumentException
' The toolbar named ImportBar does not exist, so create it.
End Try
If (commandBar Is Nothing) Then
' Check the list of command bars.
'Dim numBars As Integer
'Dim msg As String
'numBars = Application.CommandBars.Count
'msg = String.Format("There are currently {0} command bars: " + vbCrLf, numBars)
'For i As Integer = 1 To numBars
' msg += String.Format("{0}: {1}" + vbCrLf, i, Application.CommandBars(i).Name)
'Next i
'MessageBox.Show(msg, "Command Bars in Project", _
' MessageBoxButtons.OK, MessageBoxIcon.Information)
' Add a command bar named ImportBar to the toolbar area.
Dim barPosition As Integer = 1
Dim isTemporary As Boolean = True
commandBar = Application.CommandBars.Add("ImportBar", barPosition, , isTemporary)
End If
|
|
try
{
commandBar = Application.CommandBars["ImportBar"];
}
catch (ArgumentException e)
{
// The toolbar named ImportBar does not exist, so create it.
}
if (commandBar == null)
{
// Add a commandbar named ImportBar.
/* //Check the list of command bars.
int numBars = Application.CommandBars.Count;
string msg = string.Format("There are currently {0} command bars: \r\n",
numBars);
for (int i = 1; i <= numBars; i++)
{
msg += string.Format("\t{0}: {1}\r\n", i, Application.CommandBars[i].Name);
}
MessageBox.Show(msg, "Command Bars in Project", MessageBoxButtons.OK,
MessageBoxIcon.Information);
*/
//int barPosition = (int)Office.MsoBarPosition.msoBarLeft;
// Add the toolbar to the toolbar area.
int barPosition = 1;
bool isMenuBar = false;
bool isTemporary = true;
commandBar = Application.CommandBars.Add("ImportBar", barPosition,
isMenuBar, isTemporary);
}
|
-
In the AddImportBar method, create a toolbar button named ImportProject that uses a click event handler named ImportButtonClick.
|
Try
importButton = _
commandBar.Controls.Add(Type:=Office.MsoControlType.msoControlButton)
importButton.Style = Office.MsoButtonStyle.msoButtonCaption
importButton.Caption = "Import Project VB"
importButton.Tag = "importProjectVB"
importButton.TooltipText = "Import a project from Excel, by using the Visual Basic add-in."
AddHandler importButton.Click, AddressOf ImportButtonClick
commandBar.Visible = True
Catch ex As Exception
MessageBox.Show(ex.Message, "Error adding toolbar button", _
MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
|
|
try
{
importButton = (Office.CommandBarButton)commandBar.Controls.Add(
Office.MsoControlType.msoControlButton, missing, missing, missing, missing);
importButton.Style = Office.MsoButtonStyle.msoButtonCaption;
importButton.Caption = "Import Project";
importButton.Tag = "importProject";
importButton.TooltipText = "Import a project from Excel.";
importButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(ImportButtonClick);
commandBar.Visible = true;
}
catch (ArgumentException e)
{
MessageBox.Show(e.Message, "Error adding toolbar button",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
|
-
Add the ImportButtonClick event handler that instantiates a dialog box class named ImportDialogBox (see Procedure 3 to create ImportDialogBox).
|
Private Sub ImportButtonClick(ByVal ctrl As Office.CommandBarButton, _
ByRef cancel As Boolean)
Dim importDialog As ImportDialogBox = New ImportDialogBox()
importDialog.Show()
End Sub
|
|
private void ImportButtonClick(Office.CommandBarButton ctrl, ref bool cancel)
{
ImportDialogBox importDialog = new ImportDialogBox();
importDialog.Show();
}
|
-
To test the add-in after Step 5, comment out the two statements in the ImportButtonClick event handler, and then add a simple message box.
Creating the Import Dialog Box
For the complete code of ImportDialogBox and property settings of the controls, see the download.
Procedure 3. To create the ImportDialogBox form
-
Add a Windows Form named ImportDialogBox, and then add the following declarations to the ImportDialogBox.vb or ImportDialogBox.cs file:
|
Imports System
Imports System.Collections
Imports System.Deployment.Application ' For ClickOnce properties.
Imports System.Text
Imports System.Windows.Forms
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
|
|
using System;
using System.Collections;
using System.Deployment.Application; // For ClickOnce properties.
using System.Text;
using System.Windows.Forms;
using Office = Microsoft.Office.Core;
using Excel = Microsoft.Office.Interop.Excel;
|
-
On the Project menu, click ImportProject Properties, and then click the Settings tab. The default settings are matched to the test EPM_Example.xlsx file. Create the default user settings for the following properties:
-
Column1 through Column9, type string, values A through I.
-
ExcelDirectory, type string, value C:\.
-
ExcelFile, type string, value [empty].
-
RowBegin, RowEnd, and RowHeader, type int (Integer in Visual Basic), values 3, 12, and 1.
-
Add the following class constants and variables. The Visual C# code uses the variable xx for missing parameters in calls to Project methods.
|
Private Const DOT_SEPARATOR As Char = "."c
Private Const COMMA_SEPARATOR As Char = ","c
Dim xlDirectory As String
Dim xlFile As String
Dim xlApp As Excel.Application
Dim xlWorkbook As Excel.Workbook
Dim xlWorksheet As Excel.Worksheet
Dim headerRange As Excel.Range
Dim projectRange As Excel.Range
Dim taskRows As ArrayList = New ArrayList()
Dim excelColumns As ArrayList = New ArrayList()
Dim columnLetters As Hashtable = New Hashtable()
|
|
private const char DOT_SEPARATOR = '.';
private const char COMMA_SEPARATOR = ',';
private System.Object xx = System.Type.Missing;
private string xlDirectory;
private string xlFile;
private Excel.Application xlApp;
private Excel.Workbook xlWorkbook;
private Excel.Worksheet xlWorksheet;
private Excel.Range headerRange;
private Excel.Range projectRange;
private ArrayList taskRows = new ArrayList();
private ArrayList excelColumns = new ArrayList();
private Hashtable columnLetters = new Hashtable();
|
-
Create the Column enumeration for column names in Excel.
|
Enum Column
TaskId = 1
WBS = 2
TaskName = 3
TaskCF = 4
Duration = 5
Start = 6
Finish = 7
Predecessors = 8
Notes = 9
End Enum
|
|
enum Column
{
TaskId = 1,
WBS = 2,
TaskName = 3,
TaskCF = 4,
Duration = 5,
Start = 6,
Finish = 7,
Predecessors = 8,
Notes = 9
}
|
-
Create the following controls on the ImportDialogBox form (see Figure 6):
-
BindingSource control: Set the bindingSource1.DataSource value to the array to TaskRow objects in the btnImport_Click event handler.
-
DataGridView control: The btnImport_Click event handler initializes dataGridView1 by using the BindingSource control.
-
OpenFileDialog control: The btnBrowse_Click event handler initializes the openFileDialog1 control.
-
ListBox control: Create the GetWorksheet method to start Excel and initialize listWorksheets. Call GetWorksheet from the btnBrowse_Click event handler and also from the ImportDialogBox constructor (ImportDialogBox_Load event handler in Visual Basic).
|
Sub GetWorksheet(ByVal xlfilePath As String)
xlApp = New Excel.Application()
' Do not interrupt with alert dialogs.
xlApp.DisplayAlerts = False
xlWorkbook = xlApp.Workbooks.Open(xlfilePath)
listWorksheets.Items.Clear()
For Each worksheet As Excel.Worksheet In xlWorkbook.Worksheets
listWorksheets.Items.Add(worksheet.Name)
Next
End Sub
|
|
private void GetWorksheet(string xlFilePath)
{
xlApp = new Excel.Application();
// Do not interrupt with alert dialogs.
xlApp.DisplayAlerts = false;
xlWorkbook = xlApp.Workbooks.Open(xlFilePath,
xx, xx, xx, xx, xx, xx, xx,
xx, xx, xx, xx, xx, xx, xx);
listWorksheets.Items.Clear();
foreach (Excel.Worksheet worksheet in xlWorkbook.Worksheets)
{
listWorksheets.Items.Add(worksheet.Name);
}
}
|
-
Button control: The btnBrowse.Text value is three dots. The btnBrowse_Click event handler uses openFileDialog1 to select and open an Excel file.
|
Private Sub btnBrowse_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnBrowse.Click
Dim xlsFiles As String
xlsFiles = "Excel files (*.xls;*.xlsx)|*.xls;*.xlsx"
Me.OpenFileDialog1.Filter = xlsFiles
Me.OpenFileDialog1.Multiselect = False
Me.OpenFileDialog1.Title = "Select an Excel File with Project Data"
Me.OpenFileDialog1.InitialDirectory = xlDirectory
Me.OpenFileDialog1.FileName = xlFile
Dim dr As DialogResult = Me.OpenFileDialog1.ShowDialog()
If (dr = DialogResult.OK) Then
Try
xlFile = Me.OpenFileDialog1.FileName
Dim pastLastSlash As Integer = xlFile.LastIndexOf("\\") + 1
Dim filenameLength = xlFile.Length - pastLastSlash
xlDirectory = xlFile.Substring(0, pastLastSlash)
xlFile = xlFile.Substring(pastLastSlash, filenameLength)
Dim xlfilePath As String = xlDirectory + xlFile
txtExcelFile.Text = xlfilePath
GetWorksheet(xlfilePath)
Catch ex As System.Security.SecurityException
. . .
End Try
End If
End Sub
|
|
private void btnBrowse_Click(object sender, EventArgs e)
{
string xlsFiles = "Excel files (*.xls;*.xlsx)|*.xls;*.xlsx";
this.openFileDialog1.Filter = xlsFiles;
this.openFileDialog1.Multiselect = false;
this.openFileDialog1.Title = "Select an Excel File with Project Data";
this.openFileDialog1.InitialDirectory = xlDirectory;
this.openFileDialog1.FileName = xlFile;
DialogResult dr = this.openFileDialog1.ShowDialog();
if (dr == DialogResult.OK)
{
try
{
xlFile = this.openFileDialog1.FileName;
int pastLastSlash = xlFile.LastIndexOf(@"\") + 1;
int filenameLength = xlFile.Length - pastLastSlash;
xlDirectory = xlFile.Substring(0, pastLastSlash);
xlFile = xlFile.Substring(pastLastSlash, filenameLength);
string xlFilePath = xlDirectory + xlFile;
txtExcelFile.Text = xlFilePath;
GetWorksheet(xlFilePath);
}
catch (System.Security.SecurityException ex)
{
. . .
}
}
}
|
-
Button control: The btnImport.Text value is 3. Read Excel Data. The btnImport_Click event handler initializes the columnLetters hash table, imports the Excel column data and task rows, and shows the TaskRow objects in the DataGridView control. For the GetExcelColumnInfo and StoreColumnInfo methods, see Procedure 4. For the ImportTasksFromExcel and ConvertExcelData methods, see Procedure 5.
|
Private Sub btnImport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnImport.Click
btnCancel.Enabled = True
' Load the columnLetters hash table.
columnLetters.Clear()
columnLetters.Add(DirectCast(Column.TaskId, Integer), mtxtTaskId.Text.Trim())
. . .
columnLetters.Add(DirectCast(Column.Notes, Integer), mtxtNotes.Text.Trim())
Dim headerCells As Array = GetExcelColumnInfo(xlFile)
StoreColumnInfo(headerCells)
Dim projectCells As Array = ImportTasksFromExcel(xlFile)
ConvertExcelData(projectCells)
' Show the TaskRow objects in the DataGridView.
BindingSource1.DataSource = taskRows
dataGridView1.DataSource = BindingSource1
dataGridView1.Enabled = True
btnCreateTasks.Enabled = True
End Sub
|
|
private void btnImport_Click(object sender, EventArgs e)
{
btnCancel.Enabled = true;
columnLetters.Clear();
columnLetters.Add((int)Column.TaskId, mtxtTaskId.Text.Trim());
. . .
columnLetters.Add((int)Column.Notes, mtxtNotes.Text.Trim());
Array headerCells = GetExcelColumnInfo(xlFile);
StoreColumnInfo(headerCells);
Array projectCells = ImportTasksFromExcel(xlFile);
ConvertExcelData(projectCells);
// Show the TaskRow objects in the DataGridView.
bindingSource1.DataSource = taskRows;
dataGridView1.DataSource = bindingSource1;
dataGridView1.Enabled = true;
btnCreateTasks.Enabled = true;
}
|
-
Label controls: The listWorksheets_SelectedIndexChanged event handler sets lblUsingWorksheet.Text to the selected worksheet. The ImportDialogBox constructor (ImportDialogBox_Load event handler in Visual Basic) sets lblAppVersion.Text to the add-in application version and sets lblPublishedVersion.Text to the ClickOnce published version. If the add-in is not installed by using ClickOnce, the ApplicationDeployment.IsNetworkDeployed value is false, and the lblPublishedVersion.Text value remains an empty string.
|
lblAppVersion.Text = lblAppVersion.Text + Me.ProductVersion
If (ApplicationDeployment.IsNetworkDeployed) Then
' This application is installed with ClickOnce.
Dim currentVersion As String = _
ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString()
lblPublishedVersion.Text = lblPublishedVersion.Text + currentVersion
Else
lblPublishedVersion.Text = String.Empty
End If
|
|
lblAppVersion.Text = lblAppVersion.Text + this.ProductVersion;
if (ApplicationDeployment.IsNetworkDeployed)
{
// This application is installed with ClickOnce.
string currentVersion =
ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
lblPublishedVersion.Text = lblPublishedVersion.Text + currentVersion;
}
else
{
lblPublishedVersion.Text = string.Empty;
}
|
-
TextBox control: The txtExcelFile.Text property is set by the ImportDialogBox constructor (ImportDialogBox_Load event handler in Visual Basic) and by the btnBrowse_Click event handler.
-
MaskedTextBox controls: The ImportDialogBox constructor (ImportDialogBox_Load event handler in Visual Basic) sets the Text property of each MaskedTextBox control to the value specified in the user settings, as in the following example.
|
mtxtTaskId.Text = MySettings.Default.Column1
. . .
mtxtRowHeader.Text = MySettings.Default.RowHeader.ToString()
|
|
mtxtTaskId.Text = Properties.Settings.Default.Column1;
. . .
mtxtRowHeader.Text = Properties.Settings.Default.RowHeader.ToString();
|
Figure 6. Designing the import dialog box in Visual Studio
The btnImport_Click event handler (for the button labeled 3. Read Excel Data) calls GetExcelColumnInfo, and then calls StoreColumnInfo to store the column letters and names in the excelColumns array of ColumnInfo objects.
Procedure 4. To get the Excel header data
-
Add a class named ColumnInfo. The class constructor stores the Excel column name, letter, and number of the column in the corresponding class properties.
|
Imports System
Public Class ColumnInfo
Private _name As String
Private _letter As String
Private _number As Integer
Public Sub New(ByVal colName As String, _
ByVal colLetter As String, _
ByVal nextColNumber As Integer)
Name = colName
Letter = colLetter
Number = nextColNumber
End Sub
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Property Letter() As String
Get
Return _letter
End Get
Private Set(ByVal value As String)
_letter = value
End Set
End Property
Public Property Number() As Integer
Get
Return _number
End Get
Set(ByVal value As Integer)
_number = value
End Set
End Property
End Class
|
|
using System;
namespace ImportProject
{
class ColumnInfo
{
public ColumnInfo(string colName, string colLetter, int nextColNumber)
{
Name = colName;
Letter = colLetter;
Number = nextColNumber;
}
// Autoimplemented properties are new in Visual C# 2008.
// Read-only properties have private set.
public string Name { get; set; }
public string Letter { get; private set; }
public int Number { get; private set; }
}
}
|
-
Add the GetExcelColumnInfo method to set the range of header cells and the range of task cells.
|
Private Function GetExcelColumnInfo(ByVal file As String) As Array
Dim startCell As String
Dim endCell As String
startCell = columnLetters(DirectCast(Column.TaskId, Integer)) _
+ mtxtRowHeader.Text.Trim()
endCell = columnLetters(DirectCast(Column.Predecessors, Integer)) _
+ mtxtRowHeader.Text.Trim()
headerRange = xlWorksheet.Range(startCell, endCell)
Dim headerCells As Array = Nothing
headerCells = TryCast(headerRange.Cells.Value2, Array)
Return headerCells
End Function
|
|
private Array GetExcelColumnInfo(string file)
{
string startCell = columnLetters[(int)Column.TaskId]
+ mtxtRowHeader.Text.Trim();
string endCell = columnLetters[(int)Column.Predecessors]
+ mtxtRowHeader.Text.Trim();
headerRange = xlWorksheet.get_Range(startCell, endCell);
Array headerCells = (Array)headerRange.Cells.Value2;
return headerCells;
}
|
-
Create the StoreColumnInfo method to save the ColumnInfo objects in the excelColumns array.
|
Private Sub StoreColumnInfo(ByVal headerCells As Array)
Dim colName As String
Dim colLetter As String
Dim numColumns As Integer = headerCells.GetLength(1)
Dim i As Integer
For i = 1 To numColumns
colName = headerCells.GetValue(1, i).ToString()
colLetter = columnLetters(i).ToString()
Dim thisColumn As ColumnInfo
thisColumn = New ColumnInfo(colName, colLetter, i)
excelColumns.Add(thisColumn)
Next i
End Sub
|
|
private void StoreColumnInfo(Array headerCells)
{
string colName, colLetter;
int numColumns = headerCells.GetLength(1);
for (int i = 1; i <= numColumns; i++)
{
colName = headerCells.GetValue(1, i).ToString();
colLetter = columnLetters[i].ToString();
var column = new ColumnInfo(colName, colLetter, i);
excelColumns.Add(column);
}
}
|
Importing the Task Data from Excel
After it gets and stores the Excel column information (see Procedure 4), the btnImport_Click event handler does the following:
-
Calls ImportTasksFromExcel (see Procedure 5, Step 2) to set the range of cells in the worksheet to import.
-
Calls ConvertExcelData (see Procedure 6) to validate the imported task data, make the task predecessors and outline level of each task consistent with the task IDs, create a new TaskRow object for each task, and add the object to the taskRows array.
Procedure 5. To import the task data from Excel
-
Add a class named TaskRow. The class constructor encapsulates the converted task data such as task ID, WBS, task name, start date, duration, and predecessor IDs.
Note: |
|---|
| Dates in a TaskRow object are stored only in UTC. Any date can be retrieved in local time or in UTC, for example, with the StartDate or StartDateUTC property. |
The code for the TaskRow class is shown here only in Visual C#, which includes autoimplemented properties where possible. For the Visual Basic code, see the download.
|
using System;
namespace ImportProject
{
class TaskRow
{
// Private member variables needed only for storing start and finish dates in
// Coordinated Universal Time (UTC).
private DateTime startDateUTC;
private DateTime finishDateUTC;
public TaskRow(double taskId,
string wbs,
int level,
string taskName,
string taskCf,
string duration,
double startDate,
double finishDate,
string predecessors,
string notes)
{
TaskId = Convert.ToInt32(taskId);
Wbs = wbs;
Level = level;
TaskName = taskName;
TaskCustomField = taskCf;
Duration = duration;
Predecessors = predecessors;
Notes = notes;
// Automation imports dates from Excel as type double. Convert to DateTime.
// Store both the local time and the UTC for dates.
StartDate = DateTime.FromOADate(startDate);
FinishDate = DateTime.FromOADate(finishDate);
StartDateUTC = startDateUTC;
FinishDateUTC = finishDateUTC;
}
// Autoimplemented properties. Read-only properties have private set.
public int TaskId { get; private set; }
public string Wbs { get; set; }
public int Level { get; set; }
public string TaskName { get; set; }
public string TaskCustomField { get; set; }
public string Duration { get; set; }
public string Predecessors { get; private set; }
public string Notes { get; set; }
// Store all start and finish times as UTC. Add the DateTimeKind value
// so consumer knows whether the date is local or UTC.
public DateTime StartDate
{
get
{
// Convert UTC to local time for a property get.
return DateTime.SpecifyKind(startDateUTC.ToLocalTime(),
DateTimeKind.Local);
}
set
{
startDateUTC = value.ToUniversalTime();
}
}
public DateTime FinishDate
{
get
{
// Convert UTC to local time for a property get.
return DateTime.SpecifyKind(finishDateUTC.ToLocalTime(),
DateTimeKind.Local);
}
set
{
finishDateUTC = value.ToUniversalTime();
}
}
public DateTime StartDateUTC
{
get
{
// No conversion needed for a property get of UTC time.
return DateTime.SpecifyKind(startDateUTC, DateTimeKind.Utc);
}
set
{
startDateUTC = value;
}
}
public DateTime FinishDateUTC
{
get
{
// No conversion needed for a property get of UTC time.
return DateTime.SpecifyKind(finishDateUTC, DateTimeKind.Utc);
}
set
{
finishDateUTC = value;
}
}
}
}
|
-
Create the ImportTasksFromExcel method.
|
Private Function ImportTasksFromExcel(ByVal file As String) As Array
Dim startCell As String
Dim endCell As String
startCell = columnLetters(DirectCast(Column.TaskId, Integer)) _
+ mtxtRowBegin.Text.ToString()
endCell = columnLetters(DirectCast(Column.Notes, Integer)) _
+ mtxtRowEnd.Text.ToString()
projectRange = xlWorksheet.Range(startCell, endCell)
Dim projectCells As Array = Nothing
projectCells = TryCast(projectRange.Cells.Value2, Array)
Return projectCells
End Function
|
|
private Array ImportTasksFromExcel(string file)
{
string startCell = columnLetters[(int)Column.TaskId]
+ mtxtRowBegin.Text.ToString();
string endCell = columnLetters[(int)Column.Notes]
+ mtxtRowEnd.Text.ToString();
projectRange = xlWorksheet.get_Range(startCell, endCell);
Array projectCells = (Array)projectRange.Cells.Value2;
return projectCells;
}
|