Visual Studio Project Files

Summary

In this blog post I’m going to talk about Visual Studio’s proj file which is nothing more than an XML file used to keep track of the resources inside a project.  


The Setup

Open your Visual Studio (I’m currently using VS 2012) and create a console application.  If you have your solution explorer window open you should see something like this:


Now, let’s create a cs file in the solution and make an empty class.  I just right-clicked on the project (ProjectFileBlogPost in this example)  then selected “Add” then “Class”.  Now click on your project to make sure it’s selected then go to your project menu (on the main menu bar) and select “Show All Files”.  Right-click on your TestClass.cs file and select “Exclude From Project”.  The file will turn white, indicating that it is not part of the project and will not be compiled with your solution.

If you’ve been working with Visual Studio for any length of time, you probably already know how all this works because you can copy a cs file from another project from Windows Explorer and then include it in your project to make it appear in the solution explorer.  Now I’m going to show how the XML project file works.


The Project File

Now we’re going to use Visual Studio to edit the xml file.  First, you need to close your console application (because it locks the file).  Next you’ll need to go to the File menu and open a file (just a plain file, not a project).  Once you open the file, VS will recognize it as an XML file and color code the text accordingly.  

If you scroll through the XML, you’ll noticed that there are a bunch of ItemGroup sections.  The one we’re interested in is one that contains a bunch of “Compile” nodes:


As you can see only the Program.cs file and the AssemblyInfo.cs files are in this group.  If you copy the Program.cs node and change the file name to TestClass.cs and save this file, you’ll be able to open this project in VS and the TestClass.cs file will be included in the project.


If you delete any of these “Compile” nodes, then the file will no longer be included in the project by VS.


Folders

If your cs file is in a folder then the full path can be spelled out in the “Compile” node and the folder will be included in your project as well as the file.  However, if you want an empty folder to be included in your project, you’ll need to add it to a different ItemGroup node set.  These nodes are named Folder nodes:


If a folder node exists and there are references in the group of Compile nodes, VS will be OK with that, but it’s not necessary.


Manipulating The Project File

At this point, you probably have a program that generates some sort of C# files and you want those files to be automatically added to some utility project.  It’s best to use the XmlDocument object to manipulate the data in the project file to add any of your C# files and/or directories.  Text parsing can be a nightmare.  You’ll need to use a name space to make it work:

XmlDocument doc = new XmlDocument();
doc.Load(projectFileName);

var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace(“a“, 
        “http://schemas.microsoft.com/developer/msbuild/2003“);
XmlNodeList itemGroupNodes = doc.SelectNodes(

        “//a:Project/a:ItemGroup“, nsmgr);

// add and delete nodes here

doc.Save(projectFileName);


You can spin through all the ItemGroup nodes and look for child nodes that have the name “Folder” or “Compile”, then add Folder or Compile nodes as desired.  You’ll have to account for the possibility that there are no ItemGroup nodes with Folder or Compile nodes in them.  You can use something like this to find the proper ItemGroup nodes:

foreach (XmlNode itemGroupNode in itemGroupNodes)
{
    XmlNodeList childNodes = itemGroupNode.ChildNodes;

    foreach (XmlNode childNode in childNodes)
    {
        if (childNode.Name == “Folder“)
        {
            // your code here
        }
    }
}

In order to append a new node to your Folder node, you can do something like this:

XmlNode folderNode = doc.CreateNode(XmlNodeType.Element, 
     “Folder“, 
     “http://schemas.microsoft.com/developer/msbuild/2003“);
XmlAttribute xKey = doc.CreateAttribute(“Include“);
xKey.Value = “TestFolder“;
folderNode.Attributes.Append(xKey);
itemGroupNode.AppendChild(folderNode);



Another thing to note: You should check the namespace name in the top of your project file.  I copied that text into these sample snippets, but I’m using VS 2012 and there is no guarantee that the namespace url won’t change for newer versions of VS.




 

Visio Automation

Microsoft Office products have the ability to be integrated with Visual Studio in what is called “Automation.”  I view this technique as a dumb and dirty capability that I use for extracting data from Word, Excel, Visio or other Office applications.  In this example I’m going to show a method to read some basic information from Visio.  MS Visio is a drawing tool.  This is different from a paint-like tool, in that, the objects drawing (circles, rectangles, lines, text, etc.) remain as distinct objects that can be resized, rotated and modified after they have been positioned.  MS Visio is a great tool for doing flow charts and diagrams.  It can also be used to create simple maps or floor plans. 

Let’s say that you want to create a floor plan and you want to read the x,y coordinates of each line defined in a visio diagram so you can store them in your program for use in an SVG viewer application.  First, you need to create a file with your floor plan.  Here’s my sample floor plan:


OK, now we need to create a blank project in Visual Studio.  Just create a new forms application and drop a button on the front of the form.  I titled my button “Read Plan”, but you can make up any name you want, or just leave it on the default.  I also added a multi-line text box (use the text box object, and change the attribute to multi-line):

 

Remember, this is just a quick and dirty program to read the map data.  We’re the only one’s to use this program.  If you are planning to build a program that will be used by other people, you’ll need to clean this up and put in a file-picker to choose the file to import.

OK, so now you need to include the using statement:

using Microsoft.Office.Interop.Visio;

Add the Microsoft.Office.Interop.Visio assembly to the references of the project.  Then use the following code to read the end points of the lines:

private void btnFloorPlan_Click(object sender, EventArgs e)
{
    // I stored the visio file in a temp folder on the C: drive
    string docPath = @”c:tempfloor_plan.vsd”;
    Microsoft.Office.Interop.Visio.Application app = new
        Microsoft.Office.Interop.Visio.Application();

    Document doc = app.Documents.Open(docPath);

    // read items from the first page
    Page page = doc.Pages[1];

    foreach (Shape shape in page.Shapes)
    {
        double lnStartX = shape.Cells[“BeginX“].ResultIU;
        double lnStartY = shape.Cells[“BeginY“].ResultIU;
        double lnEndX = shape.Cells[“EndX“].ResultIU;
        double lnEndY = shape.Cells[“EndY“].ResultIU;

        txtResults.Text +=
            lnStartX.ToString(0.00“) + “,” +
            lnStartY.ToString(“0.00“) + “,” +
            lnEndX.ToString(“0.00“) + “,” +
            lnEndY.ToString(“0.00“) + “rn”;
    }

    app.Quit();
}


OK, so now we have some code and it opens the Visio file and reads each shape on page 1.  In my example, the Visio file only contains lines, so when I run it, I expect to read only lines and I don’t need to verify which shapes are being read.  I just read the end points.  You’ll notice when you run the app, that Visio starts up (and yes you need Visio installed to make this code work).  The last line, “app.Quit();” causes Visio to exit.  If you don’t close the Visio application, then a new copy will start up if you run the program a second time.

Now you can run the program and you should get some numbers (not necessarily the same numbers displayed here):


Now you can select the text and copy to the clipboard.  I created a web application with a Default.aspx web page and deleted all the text (except the first line) from the front page:

 
 
 
Next, I wrote an empty method in the code behind:
 
 

protected void Page_Load(object sender, EventArgs e)

{
    RenderFloorPlan();
}

private void RenderFloorPlan()

{

}

And now I paste in the text from the Visio reader application, formatted the information to fit into a list of strings and came up with this result:
 

private void RenderFloorPlan()
{
    List<string> laLines = new List<string>() {
        “2.00,10.00,6.00,10.00“,
        “2.00,10.00,2.00,7.00“,
        6.00,10.00,6.00,7.00“,
        “2.00,7.00,6.00,7.00“,
        “3.00,10.00,3.00,9.50“,
        “2.00,9.50,2.50,9.50“,
        “2.75,9.50,3.25,9.50“,
        “3.50,9.50,4.25,9.50“,
        “4.00,9.50,4.00,10.00“,
        “4.50,9.50,5.25,9.50“,
        “5.00,9.50,5.00,10.00“,
        “5.50,9.50,6.00,9.50“,
        “2.00,8.00,2.25,8.00“,
        “2.50,8.00,3.50,8.00“,
        “3.75,8.00,4.00,8.00“,
        “4.00,7.00,4.00,8.00“};

}

You can format the output to include quotes and commas if you have a really big list and don’t want to do it manually.  That can save some time.

Now, we need to do something with this list.  First, I’m going to setup a generic SVG html output, right after the list defined above:

 
Response.Write(“<!DOCTYPE html>“);
Response.Write(<html>“);
Response.Write(<body>“);
Response.Write(<svg id=’SVGObject’ xmlns=‘http://www.w3.org/2000/svg’ version=’1.1′>“);

// content goes here
        
Response.Write(</svg>“);
Response.Write(</body>“);
Response.Write(</html>“);

Now we need to put something in that spot that says “content goes here”.  That’s where we loop through the list and print the lines.  I’m going to warn you up front, that the coordinates that are read from the Visio drawing are not in the same scale as the SVG interface.  So you’ll need to play with a multiplier on each coordinate to get your diagram to fit the area on the screen that you want.  So make sure you set this up so you can do that:

for (var i = 0; i < laLines.Count; i++)
{
    string[] lsLineCoords = laLines[i].Split(‘,’);

    double lnStartX = Convert.ToDouble(lsLineCoords[0]) * 50;
    double lnStartY = Convert.ToDouble(lsLineCoords[1]) * 50;
    double lnEndX = Convert.ToDouble(lsLineCoords[2]) * 50;
    double lnEndY = Convert.ToDouble(lsLineCoords[3]) * 50;

    Response.Write(“<line x1='” + lnStartX + “‘ y1='” + lnStartY +
        “‘ x2='” + lnEndX + “‘ y2='” + lnEndY +
        “‘ style=’strokewidth:1px;stroke:black;’ />“);
}


Now run the program.  You should see something like this rendered in your browser:

Now you’re probably wondering “why is it upside down?”  There’s a reason for that and it’s easy to fix.  First, Visio has an origin in the bottom left, so all coordinates go from bottom to top and left to right.  SVG has an origin in the top left corner.  So SVG’s coordinates go from top to bottom and left to right.  All we need to do to fix this situation is subtract our “Y” coordinates from the height of the display area:

for (var i = 0; i < laLines.Count; i++)
{
     string[] lsLineCoords = laLines[i].Split(‘,’);
     double lnStartX = Convert.ToDouble(lsLineCoords[0]) * 50;
     double lnStartY = 600 – Convert.ToDouble(lsLineCoords[1]) * 50;
     double lnEndX = Convert.ToDouble(lsLineCoords[2]) * 50;
     double lnEndY = 600 – Convert.ToDouble(lsLineCoords[3]) * 50;

     Response.Write(“<line x1='” + lnStartX + “‘ y1='” + lnStartY +
         “‘ x2='” + lnEndX + “‘ y2='” + lnEndY +
         “‘ style=’strokewidth:1px;stroke:black;’ />“);
}


I chose 600 just to make sure it fit on the browser.  You can add or subtract a constant from the x and y coordinates to shift the image on the screen.  Here’s the final result:

Ta-da!

The whole purpose of this example is to show how to use some basic MS Visio automation in Visual Studio and then use the data read to render something in SVG to match the Visio image. 

If you find any errors or have something to add, please leave a comment.


 

VS2012 Reference Has Yellow Triangle

This seems to be a common problem where a reference to another project has a yellow triangle on it.  Like this:



Check your warnings:

In my case the project targeted had a .NET library set to 4.5 and my main project had a .NET of 3.5.  The warning, however, gives all the details needed:

 

For me, the project in question was created with .NET 4.5 automatically, so I had no idea what was going on when I ran into this original problem.  To fix it, right click on the project and select properties:

 
 
Then change the “.NET Framework” dropdown to the same version as all other projects.  You’ll be presented with a warning that the current project should be closed and re-opened after changing.  Click the “Yes” and continue.  Then close and re-open your solution.