Introduction
.NET has been in action for many years, yet project and solution files are not familiar to most programmers. The reason behind this is that Visual Studio takes care of project and solution files for us. However, if the project or solution contents are large, it takes a long time to load and display them in Solution Explorer for VS.
In most situations we just need to know the files, references and contents of a project -- or list of projects -- present in a solution. The application I have created will allow us to see any project's or solution's contents as in Solution Explorer view, without having to load process-heavy VS.NET. I designed this application using C# and Windows Forms in .NET. Before explaining its design and functionality, I first will explain a little bit about its features.
Application features
This application...
- Loads any C# project and allows exploration of its contents.
- Loads any solution and shows a list of the projects and their contents.
- Has built-in support for displaying XML data in both XML and Data (Grid) View.
- Supports reference exploration (.dll) like an object browser in VS.
- Loads quickly when compared to Solution Explorer.
- Can maintain a list of files recently opened through this application.
Creating the application
First, create a new Windows Forms project in C# and name it SolutionExplorer
. Then place the controls as shown in the figure below:
Start-up form controls
I will now explain the purpose of each control on the start-up form, Form1
:
- First, I placed a
TreeView
control and an ImageList
to display the selected project or solution contents with images on each node. - Then I added a
TabControl
; in one tab I placed a RichTextBox
and in the other, a datagrid
control. - Next I added
menuitems
to MainMenu
as shown in the figure above. A context menu was added with the following items:
- Expand
- Collapse
- Full Expand
- Grid View
- The context menu property of
TreeView
was then set to this control. - Finally,
OpenFileDialog
and tooltip
controls were added.
By using the FileDialog
control, we can select any C# project or solution file to analyze and display its contents in TreeView
. ContextMenu
for TreeView
is used to expand, collapse and display XML in Grid.
Code for the Click event
I will now explain what I have done in the Click event of Browse menuitem
:
if(DialogResult.OK == dglopensoln.ShowDialog())
{
string selectedprjfile = dglopensoln.FileName.ToString();
txtsolnpath.Text = selectedprjfile;
toolTip1.SetToolTip(txtsolnpath,selectedprjfile);
MenuItem testitem = new MenuItem(selectedprjfile);
testitem.Click += new EventHandler(testitem_Click);
mnurecent.MenuItems.Add(testitem);
obj1.Text = selectedprjfile;
menuItem7_Click(sender,e);
}
By using the above code, we can browse the selected project (.csproj) or solution (.soln). Then we can create a new menuitem
with text as the selected file path and a handler for the click event of that menuitem
. We can then internally call a click to analyze menuitem
.
Code for loading the solution
Next, I will explain the logic of loading the solution and its contents into the TreeView
control. Similar logic can be used to load the project, as well.
string prevselfilecontents = selfilecontents.Text;
try
{
selfilecontents.Text = "";
treeView1.Nodes.Clear();
menuItem7.Enabled = false;
TreeNode mainnode = null;
string httpprjs = null;
if(txtsolnpath.Text.EndsWith(".sln"))
{
string tmpsolndata = @"c:\tempsoln.xml";
string solnname = Path.GetFileNameWithoutExtension(txtsolnpath.Text);
mainnode = new TreeNode(solnname);
treeView1.Nodes.Add(mainnode);
if(Directory.Exists(@"C:\"+solnname))
{
Directory.Delete(@"C:\"+solnname,true);
}
Directory.CreateDirectory(@"C:\"+solnname);
prjsdetailsinsoln = new Hashtable();
if(!File.Exists(tmpsolndata))
{
File.Create(tmpsolndata);
}
string solncontents = "";
StreamReader solnreader = new StreamReader(txtsolnpath.Text.Trim());
while(solncontents != null)
{
solncontents = solnreader.ReadLine();
if(solncontents != null)
try
{
if(solncontents.StartsWith("Project"))
{
string[] prjprops = solncontents.Split(',');
string prjdispname =
prjprops[0].Substring(prjprops[0].LastIndexOf(
"=")+1).Replace("\"",
"").Trim();
string prjpath1 = prjprops[1].ToString().Replace(
"\"","").Trim();
prjsdetailsinsoln.Add(prjdispname,prjpath1);
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
IDictionaryEnumerator enprj = prjsdetailsinsoln.GetEnumerator();
while (enprj.MoveNext())
{
string strprjname = enprj.Key.ToString();
if(enprj.Value.ToString().IndexOf(
"http://") == -1 &&
enprj.Value.ToString().EndsWith(".csproj"))
{
string prjpath = txtsolnpath.Text.Substring(
0,txtsolnpath.Text.LastIndexOf(
@"\")+1)+enprj.Value.ToString();
if(File.Exists(prjpath))
{
StreamReader prjreader = new StreamReader(prjpath);
string prjcontents = prjreader.ReadToEnd();
prjreader.Close();
tmpxmlfileforsolndata =
@"C:\"+solnname+"\\"+enprj.Key.ToString()+".xml";
if(prjcontents.Length != 0)
{
StreamWriter solntoxmlconverter =
new StreamWriter(tmpxmlfileforsolndata,false);
solntoxmlconverter.Write("");
solntoxmlconverter.Write(prjcontents);
solntoxmlconverter.Close();
}
else
{
MessageBox.Show(
"Selected Project File is empty...");
txtsolnpath.Text = "";
}
TreeNode rootnode = new TreeNode();
rootnode.Text = strprjname;
mainnode.Nodes.Add(rootnode);
TreeNode referencenode = new TreeNode();
referencenode.Text = "References";
rootnode.Nodes.Add(referencenode);
TreeNode includedaspxfilesnode = new TreeNode();
includedaspxfilesnode.Text = "ASPX Files";
rootnode.Nodes.Add(includedaspxfilesnode);
TreeNode includedaspxcsfilesnode = new TreeNode();
includedaspxcsfilesnode.Text = "ASPX.CS Files";
rootnode.Nodes.Add(includedaspxcsfilesnode);
TreeNode includedcsfilenode = new TreeNode();
includedcsfilenode.Text = "Class Files";
rootnode.Nodes.Add(includedcsfilenode);
TreeNode includedusercntrlfilenode = new TreeNode();
includedusercntrlfilenode.Text = "User Controls";
rootnode.Nodes.Add(includedusercntrlfilenode);
TreeNode includedwebconfignode = new TreeNode();
includedwebconfignode.Text = "Web Config Files";
rootnode.Nodes.Add(includedwebconfignode);
TreeNode includedjsfilenode = new TreeNode();
includedjsfilenode.Text = "JavaScript Files";
rootnode.Nodes.Add(includedjsfilenode);
TreeNode includedcssfilenode = new TreeNode();
includedcssfilenode.Text = "CSS Files";
rootnode.Nodes.Add(includedcssfilenode);
TreeNode includedimgfilenode = new TreeNode();
includedimgfilenode.Text = "Image Files";
rootnode.Nodes.Add(includedimgfilenode);
TreeNode includedxmlfilenode = new TreeNode();
includedxmlfilenode.Text = "XML Files";
rootnode.Nodes.Add(includedxmlfilenode);
TreeNode includedxslfilenode = new TreeNode();
includedxslfilenode.Text = "XSL Files";
rootnode.Nodes.Add(includedxslfilenode);
TreeNode includedunknownfilenode = new TreeNode();
includedunknownfilenode.Text = "Unknown Files";
rootnode.Nodes.Add(includedunknownfilenode);
XPathDocument prjfile = new XPathDocument(
tmpxmlfileforsolndata);
XPathNavigator nav = prjfile.CreateNavigator();
XPathNodeIterator referenceiterator =
nav.Select(@"/VisualStudioProject/CSHARP/Build" +
"/References/Reference");
while(referenceiterator.MoveNext())
{
string reffile =
referenceiterator.Current.GetAttribute(
"Name","").ToString();
TreeNode refnode = new TreeNode(reffile);
if(reffile.StartsWith("System"))
{
refnode.ForeColor = Color.Green;
}
else
{
refnode.ForeColor = Color.Orange;
}
referencenode.Nodes.Add(refnode);
}
XPathNodeIterator aspxfileiterator =
nav.Select(@"/VisualStudioProject/" +
"CSHARP/Files/Include/File");
LoadselectedDetails(
"aspx",includedaspxfilesnode,aspxfileiterator);
RemoveUnwatedNodes(includedaspxfilesnode);
XPathNodeIterator aspxcsfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
LoadselectedDetails("aspx.cs",
includedaspxcsfilesnode,aspxcsfileiterator);
RemoveUnwatedNodes(includedaspxcsfilesnode);
XPathNodeIterator webconfigfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP/" +
"Files/Include/File");
LoadselectedDetails("config",
includedwebconfignode,webconfigfileiterator);
RemoveUnwatedNodes(includedwebconfignode);
XPathNodeIterator jsfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
LoadselectedDetails("js",
includedjsfilenode,jsfileiterator);
RemoveUnwatedNodes(includedjsfilenode);
XPathNodeIterator cssfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
LoadselectedDetails("css",
includedcssfilenode,cssfileiterator);
RemoveUnwatedNodes(includedcssfilenode);
XPathNodeIterator imgfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
LoadselectedDetails("gif",
includedimgfilenode,imgfileiterator);
RemoveUnwatedNodes(includedimgfilenode);
XPathNodeIterator xmlfileiterator =
nav.Select(@"/VisualStudioProject/" +
"CSHARP/Files/Include/File");
LoadselectedDetails("xml",
includedxmlfilenode,xmlfileiterator);
RemoveUnwatedNodes(includedxmlfilenode);
XPathNodeIterator xslfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
LoadselectedDetails("xsl",
includedxslfilenode,xslfileiterator);
RemoveUnwatedNodes(includedxslfilenode);
XPathNodeIterator csfileiterator =
nav.Select(@"/VisualStudioProject/" +
"CSHARP/Files/Include/File");
LoadselectedDetails("cs",
includedcsfilenode,csfileiterator);
RemoveUnwatedNodes(includedcsfilenode);
XPathNodeIterator usercntrlsfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
LoadselectedDetails("ascx.cs",
includedusercntrlfilenode,usercntrlsfileiterator);
RemoveUnwatedNodes(includedusercntrlfilenode);
XPathNodeIterator unknownsfileiterator =
nav.Select(@"/VisualStudioProject/CSHARP" +
"/Files/Include/File");
while(unknownsfileiterator.MoveNext())
{
string filename = unknownsfileiterator.Current.GetAttribute(
"RelPath","").ToString().ToLower();
if(!filename.EndsWith(".aspx") && !
filename.EndsWith(".aspx.cs") &&
!filename.EndsWith(".cs") && !
filename.EndsWith(".ascx.cs") &&
!filename.EndsWith(".js") && !
filename.EndsWith(".xml") &&
!filename.EndsWith(".xsl") &&
filename.EndsWith(".css") &&
!filename.EndsWith(".gif") && !
filename.EndsWith(".config"))
{
includedunknownfilenode.Nodes.Add(
filename.Substring(
filename.LastIndexOf(@"\")+1));
}
}
RemoveUnwatedNodes(includedunknownfilenode);
}
else
{
TreeNode httpprjnode = new TreeNode(enprj.Value.ToString());
httpprjnode.ForeColor = Color.Red;
mainnode.Nodes.Add(httpprjnode);
}
}
if(enprj.Value.ToString().IndexOf("http://") != -1)
{
httpprjs+=enprj.Value.ToString()+"#";
}
}
foreach(string httpprj in httpprjs.Split(new char[]{'#'}))
{
if(httpprj.Trim() != "")
{
TreeNode node = new TreeNode(httpprj.Trim());
mainnode.Nodes.Add(node);
}
}
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
selfilecontents.Text = prevselfilecontents;
}
finally
{
menuItem7.Enabled = true;
}
In this click event, I am first finding whether the selected file is a project file or solution file. This is done by referring to its extension, since a solution file will not be in XML format. I am therefore using some text search patterns to get the project details of the solution file.
I am looping for each project present in the solution, creating a temporary XML file to store the project contents. Then I create a structure in TreeView
with nodes named as ASPX user controls. In this way, we can add any number of nodes based on the types of files present in the project. I am using XPath to navigate through the project file that is present in XML format.
I am using the following Xpath statement to get all of the project's references:
@"/VisualStudioProject/CSHARP/Build/References/Reference"
...and the following to get all of the files included in the project:
@"/VisualStudioProject/CSHARP/Files/Include/File"
Based on its extension, I am adding each item to a specific node (i.e. adding .aspx files to the ASPX node). Then I am removing nodes which don't have any items in them. Finally, I am formatting TreeView
for aesthetics.
To display the contents of the selected file in TreeView
, I chose the AfterSelect
event. This way, I am going to get the absolute physical path of the selected file by using HashTable
. A HashTable
is created at the time of node creation for each file type present in the project or solution. I am getting the absolute path of the selected file by using the selected item's text and searching its path attribute in its corresponding project file. After getting its path, I am reading its entire contents and displaying it in richtextbox
.
Then I add another form to get the internal details of the selected reference and create UI as shown:
Here I placed a menu
, FileDialog
and tooltip
followed by a TreeView
to display selected reference details. In Load Click, I am getting the selected reference path and using reflection. This displays the namespace
s, class
es, event
s and method
s in TreeView
.
The final output will look like this:
Conclusion
We can still enhance this application with better UI, support of more file types and multiple-language project file exploration. Please see this article's download for further reference. I hope this code will be useful to all.
History
- May 9, 2007 - Original version posted
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.