Friday, October 31, 2008

.Net Pie Chart challenge

Note* This is still running at : https://www.customconfiguration.net/ASP/DefaultPie.aspx
My first response to this was very blah. Having seen this stuff done by every product since Access 2.0, made it unexciting to think about. But then my friend added a twist : make the pie chart clickable in a drill-down way. So as easy as it is, to create pie slices from an image of a circle, I was stumped at how to handle a "hotspot", and did not even know there was an ImageMap control available for a .Net webform.

So to hurry up and get something to chart, I created a dataset with one column of numeric values, and a web user control to house the ImageMap control. My control exposes a method called "Draw" and raises an event called "HotSpotClicked" which is declared as follows :


public event ImageMapEventHandler HotSpotClicked;


The graphics to write the circle and pie slices are easy to call in the System.Drawing namespace. Here is Draw method, which calls a separate method to add a percentage column to the DataSet table passed to it. It uses random colors for the pie slices, and selects the colors from the Brushes namespace of System.Drawing using Reflection. It creates a GUID for a temporary file name in which to store the Bitmap. For each pie slice it draws, it saves coordinates for where to put the map hotspot in another column in the dataset. So how to compute X,Y coordinates of the outer edge of a pie slice. My good enough answer is to use a triangle defined by center and two end points of the slice. Those should be easy enough to figure out by conversion from angles and knowing the radius of the bounding circle. But not when you have a large slice, say greater than 100 degrees. For that size and higher the triangle it defines is squeezed thinner and thinner and loses usability as a hot regaion. So in that case I added one more coordinate dividing the "sweep" angle by 2 in my computation.


public void Draw(DataSet ds)
{
// Rectangle of that is the width of the ImageMap control.
Rectangle rect =
new Rectangle(0, 0,
Convert.ToInt32(ImageMap1.Width.Value),
Convert.ToInt32(ImageMap1.Height.Value));

/ Using floats as they are small enough and liked by the Math namespace.
float radius = rect.Width / 2;

string imageName = System.Guid.NewGuid().ToString();
Bitmap bitmap = new Bitmap(rect.Width, rect.Height);

Graphics g = Graphics.FromImage(bitmap);

g.DrawRectangle(Pens.Khaki, rect);
g.FillRectangle(Brushes.LightBlue, rect);

g.DrawEllipse(Pens.Khaki, rect);
g.FillEllipse(Brushes.LightPink, rect);

calcPercents(ds);

// Using reflection to get an array of colors from which to choose.
PropertyInfo[] penInfos = typeof(Pens).GetProperties();
PropertyInfo[] brushInfos = typeof(Brushes).GetProperties();

float startAngle = 0;

// For selecting colors.
Random rand = new System.Random();

// Add a column to the dataset to record color used.
ds.Tables[0].Columns.Add("ChartColor");

for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
DataRow row = ds.Tables[0].Rows[i];

decimal fSweepAngle = 360 * (Convert.ToDecimal(row[1]) / 100);
float sweepAngle = (float)Math.Truncate(fSweepAngle);

if (i == ds.Tables[0].Rows.Count - 1)
{
sweepAngle += 360 - (startAngle + sweepAngle);
}

int colorIndex = rand.Next(brushInfos.Length - 1);

Brush myBrush = (Brush)brushInfos[colorIndex].GetValue(null, null);

g.DrawPie((Pen)penInfos[colorIndex].GetValue(null, null),
rect,
startAngle,
sweepAngle);

g.FillPie(myBrush,
rect,
startAngle,
sweepAngle);


// Calculate the Hot Spot polygon

// First line from center of the pie out to the edge.
string coords = radius.ToString() + "," +
radius.ToString() + ",";

coords += getCoordinates(startAngle, radius);

if (sweepAngle > 90)
{
//TODO: add more coordinates to get better hotspot coverage.
float intermediateAngle = startAngle + (sweepAngle / 2);

coords += getCoordinates(intermediateAngle, radius);
}

// move the needle up to where we just stopped for the next draw operation.
startAngle += sweepAngle;

coords += getCoordinates(startAngle, radius);

row[2] = coords;
row[3] = ((System.Drawing.SolidBrush)myBrush).Color.Name;
}

setHotSpots(ds);

ImageMap1.ImageUrl = "images/" + imageName + ".bmp";

bitmap.Save(Server.MapPath("images/" + imageName + ".bmp"),
System.Drawing.Imaging.ImageFormat.Bmp);
}



Setting hotspots in the map is easy once the coordinates have been solved :


private void setHotSpots(DataSet ds)
{
ImageMap1.HotSpots.Clear();

foreach (DataRow row in ds.Tables[0].Rows)
{
PolygonHotSpot phs = new PolygonHotSpot();
phs.Coordinates = row[2].ToString();
phs.AlternateText = row[0].ToString() + " "
+ row[1].ToString() + " "
+ row[3].ToString();
phs.PostBackValue = row[0].ToString();
phs.HotSpotMode = HotSpotMode.PostBack;
phs.NavigateUrl = "";
ImageMap1.HotSpots.Add(phs);
}
}


Code for calculating the percentage value of each data point provided in the dataset is very trivial ,but I list it here for future reference :


private void calcPercents(DataSet ds)
{
DataColumn PercentColumn = ds.Tables[0].Columns.Add("DataPercentage");
decimal total = 0;

foreach (DataRow row in ds.Tables[0].Rows)
{
total += Convert.ToDecimal(row[0]);
}

decimal totalPercent = 0;
foreach (DataRow row in ds.Tables[0].Rows)
{

decimal percentage = Convert.ToDecimal(row[0]) / total;
row[1] = Math.Round(percentage * 100, 1);
totalPercent += Convert.ToDecimal(row[1]);
}


if (totalPercent < 100)
{
DataRow lastRow = ds.Tables[0].Rows[ds.Tables[0].Rows.Count - 1];
decimal lastRowPercent = Convert.ToDecimal(lastRow[1]) + (100 - totalPercent);
lastRow[1] = lastRowPercent;
}

// add a column for coordinate set.
DataColumn coordinatesColumn = ds.Tables[0].Columns.Add("MapAreaCoordinates");

ds.AcceptChanges();
}


The real brain candy of the project was how to get the X.Y coordinates for an ImageMap knowing the size of the circle in the drawing and the "Sweep Angle" or angle of the pie slice. After some trial and error and merely remembering where to look from high school math, I wrote some calculations using SIN and COSINE. The fatal trap from hell, is that MATH.SIN does not accept Degrees, it accepts Radians. Intellisense doesn't tell us that. So here are the computations :


float Sin90 = (float)Math.Sin(ToRadian(90));

private string getCoordinates(float startAngle,
float radius)
{
float rise = 0;
float run = radius;
string coords = string.Empty;

rise = (radius * SinOfDegree(startAngle)) / Sin90;
run = (radius * CosOfDegree(startAngle)) / Sin90;

coords += Math.Round(radius + run, 0).ToString() + "," +
Math.Round(radius + rise, 0).ToString() + ",";

return coords;
}

private float SinOfDegree(float Degrees)
{
float radianOfDegree = ToRadian(Degrees);
return (float)Math.Sin(radianOfDegree);
}


private float CosOfDegree(float Degrees)
{
float radianOfDegree = ToRadian(Degrees);
return (float)Math.Cos(radianOfDegree);
}

private static float ToRadian(float Degrees)
{
return Degrees * (float)Math.PI / 180;
}

Sunday, October 26, 2008

Update to Config Builder

It has been requested that my tool create configuration elements that look more like old .Net Framework configuration elements, without the "add, remove, clear" syntax.

The new syntax looks like the following :

<BaseballConfig>
<WorldSeriess>
<add Year="2008">
<Teams>
<add Name="Rays" />
</Teams>
</add>
</WorldSeriess>
</BaseballConfig>


While the older, more traditional format looks like :

<BaseballConfig>
<WorldSeriess>
<WorldSeries Year="2008">
<Teams>
<Team Name="Phillies" />
<Team Name="Rays" />
</Teams>
</WorldSeries>
</WorldSeriess>
</BaseballConfig>


In order to get the xml elements to match the configuration class names, an override can be added to the ElementCollection class as follows :


public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.BasicMap;
}
}



So to handle this update (or not) I have created the following function for my custom configuration tool:


private static void addConfigurationElementCollectionTypeProperty(CodeTypeDeclaration configElements)
{
CodeTypeReferenceExpression ctre = new CodeTypeReferenceExpression(typeof(ConfigurationElementCollectionType));
CodeTypeReference ctr =
new CodeTypeReference(typeof(ConfigurationElementCollectionType));
CodeMemberProperty propCollectionType = new CodeMemberProperty();
propCollectionType.Name = "CollectionType";
propCollectionType.Attributes = MemberAttributes.Override | MemberAttributes.Public;
propCollectionType.HasSet = false;
propCollectionType.HasGet = true;
propCollectionType.Type = ctr;
propCollectionType.GetStatements.Add(
new CodeMethodReturnStatement(
new CodePropertyReferenceExpression(ctre, "BasicMap")));
configElements.Members.Add(propCollectionType);
}

Thursday, August 21, 2008

CodeDom NHibernate Mapping File Builder

I am currently watching the 'Summer of NHibernate' videos and have found myself wanting a tool for generating the HBM Mapping file. This was a painful place to be looking at a white screen to hand-fill with XML, as if anybody wants to state again all of the columns and data types already typed into a class and a "Create Table" script.


I have code already in all my other tools for getting database schema into my "columns" typed dataset. I ran XSD.EXE over the HBM file which I built by hand while watching 'Summer of NHibernate' to get a Class for easily authoring the XML document. Populating this class and then Serializing it was a nice way to stay in C# for the whole operation.

So here is what I am starting out of the gate with.



public static string GetMappingFile(SqlConnection Connection, string TableName)
{
string fileName = TableName + "hbm.xml";
hibernatemapping newMapping = new hibernatemapping();
newMapping.@namespace = "Acme.DataLayer";
newMapping.@class = new hibernatemappingClass();

hibernatemappingClass newClass = newMapping.@class;
newClass.name = TableName;
newClass.table = TableName;

Columns cols = PopulateColumns(Connection, TableName, CommandType.TableDirect);
newClass.property = new hibernatemappingClassProperty[cols.Column.Count];

int index = 0;

foreach (Columns.ColumnRow cRow in cols.Column)
{
newClass.property[index] = new hibernatemappingClassProperty();
hibernatemappingClassProperty prop = newClass.property[index];
prop.column = cRow.Column_Name;
prop.name = cRow.Column_Name;
prop.type = cRow.Data_Type;

switch (cRow.Data_Type.ToUpper())
{
case "DECIMAL":
break;
case "INT":
prop.type = "Int32";
break;
case "SMALLINT":
prop.type = "Int32";
break;
case "BIT":
prop.type = "Int32";
break;
case "VARCHAR":
prop.type = "string";
break;
default:
prop.type = "string";
break;
}
index++;
}

System.Xml.Serialization.XmlSerializer xmlSerializer =
new System.Xml.Serialization.XmlSerializer(typeof(hibernatemapping));

StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);

xmlSerializer.Serialize(sw, newMapping);

return sb.ToString();
}

Saturday, August 09, 2008

CodeDom LWDO Builder

Seeing alot of classes that are "Lightweight Data Objects", simple representations of table entries that can be manipulated locally and then sent to a database, or passed around in application code. So I built a small generator using the CodeDom Namespace. Not intended to compete with MyGenerate or any other tool, just wanted to have this quick one handy.
This is for SQL Server database tables. The main function below :


public static string GetClientClass(SqlConnection Connection, string TableName)
{
CodeTypeReference codeTypeRef;
CodeMemberField memberField;
CodeMemberProperty memberPropPublic;

Columns cols = PopulateColumns(Connection, TableName, CommandType.TableDirect);

CodeNamespace codeNS = new CodeNamespace("ACME.Data");

CodeTypeDeclaration ct = new CodeTypeDeclaration(TableName);
ct.IsClass = true;
codeNS.Types.Add(ct);

CodeExpression markDirty = new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(),
"MarkDirty",
new CodeExpression[] { });

foreach (Columns.ColumnRow cRow in cols.Column)
{
switch (cRow.Data_Type.ToUpper())
{
case "DECIMAL":
codeTypeRef = new CodeTypeReference("System.Decimal");
break;
case "INT":
codeTypeRef = new CodeTypeReference("System.Int32");
break;
case "SMALLINT":
codeTypeRef = new CodeTypeReference("System.Int32");
break;
case "BIT":
codeTypeRef = new CodeTypeReference("System.Int32");
break;
case "VARCHAR":
codeTypeRef = new CodeTypeReference("System.String");
break;
default:
codeTypeRef = new CodeTypeReference("System.String");
break;
}

memberField = new CodeMemberField(
codeTypeRef,
"_" + cRow.Column_Name.ToLower());

memberField.Attributes = MemberAttributes.Private;
ct.Members.Add(memberField);
memberPropPublic = new CodeMemberProperty();
memberPropPublic.Name = MakeFieldName(cRow.Column_Name);
memberPropPublic.Attributes = MemberAttributes.Public;
memberPropPublic.Type = codeTypeRef;
ct.Members.Add(memberPropPublic);

// Get Statement
CodeVariableReferenceExpression cvrexp =
new CodeVariableReferenceExpression("_" + cRow.Column_Name.ToLower());
CodeMethodReturnStatement cmrstmnt = new CodeMethodReturnStatement(cvrexp);
memberPropPublic.GetStatements.Add(cmrstmnt);

// Set Statement
memberPropPublic.SetStatements.Add(
new CodeAssignStatement(cvrexp,
new CodePropertySetValueReferenceExpression()));

memberPropPublic.SetStatements.Add(markDirty);
}

CSharpCodeProvider cSharpProvider = new CSharpCodeProvider();
StringBuilder sb = new StringBuilder();
System.IO.TextWriter tw = new System.IO.StringWriter(sb);
cSharpProvider.GenerateCodeFromNamespace(codeNS,
tw,
new System.CodeDom.Compiler.CodeGeneratorOptions());
return sb.ToString();
}



My code uses a DataSet Class I call "Columns.xsd" just for being able to iterate over something that represents the database table, without making a bunch of DataReader calls.



private static Columns PopulateColumns(SqlConnection Connection,
string TableName,
CommandType TableOrSproc)
{
Columns cols = new Columns();

Connection.Open();
SqlCommand sqlCmd = new SqlCommand();

if(TableOrSproc == CommandType.StoredProcedure)
{
// Get Parameters for Command.
sqlCmd = new SqlCommand(
"Select Parameter_Name as Column_Name,Data_Type, Character_maximum_length" +
" from Information_Schema.Parameters Where Specific_Name = '"
+ TableName +
"'",
Connection);
}
else
{
// Get Columns for the Table.
sqlCmd = new SqlCommand(
"Select Column_Name,Data_Type,Character_maximum_length" +
" from Information_Schema.Columns Where Table_Name = '"
+ TableName +
"'",
Connection);
}

sqlCmd.CommandType = CommandType.Text;

SqlDataReader dr = sqlCmd.ExecuteReader();
while (dr.Read())
{
Columns.ColumnRow row = cols.Column.NewColumnRow();
row.Column_Name = dr["Column_Name"].ToString();
row.Data_Type = dr["Data_Type"].ToString();
row.Character_maximum_length = dr["Character_maximum_length"].ToString();
cols.Column.AddColumnRow(row);
}
dr.Close();
Connection.Close();

return cols;
}




Friday, July 25, 2008

More Visual Studio 2008 App Configuration

Much simpler than generating C# code, the class I use to build the XML elements for the .CONFIG file is shown below. Most of the methods take a WinForms TreeView object or some part of it as an input parameter to be able to traverse its nodes and create the XMLElements with the needed Attributes :


using System;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using System.IO;

namespace CustomConfigBuilder
{
public class ConfigXMLBuilder
{
private void appendChildConfigAttributes(XmlDocument doc, XmlElement element, TreeNode node)
{
foreach (TreeNode childNode in node.Nodes)
if (childNode.Tag.ToString() == Shared._attributeImageIndex.ToString())
{
XmlAttribute attr = doc.CreateAttribute(childNode.Text);
element.Attributes.Append(attr);
}
}

private void appendChildConfigElements(XmlDocument document,
XmlElement parentXmlElement,
TreeNode parentTreeNode)
{
foreach (TreeNode node in parentTreeNode.Nodes)
if (node.Tag.ToString() == Shared._elementImageIndex.ToString())
{
if (!Shared.HasChildAttributes(node))
continue;

XmlElement element = document.CreateElement(node.Text + "s");
parentXmlElement.AppendChild(element);

XmlElement elementAdd = document.CreateElement("Add");
element.AppendChild(elementAdd);

appendChildConfigElements(document, elementAdd, node);
}

appendChildConfigAttributes(document, parentXmlElement, parentTreeNode);
}

///
/// Create the actual XML configuration Text for the Web/App Config file.
///

public string BuildXMLConfigDocument(TreeView tvConfigData, string SaveXmlTo)
{
XmlDocument docConfig = new XmlDocument();
docConfig.LoadXml("<" +
tvConfigData.Nodes[Shared._rootConfigNodeName].Text +
"> tvConfigData.Nodes[Shared._rootConfigNodeName].Text + ">");

appendChildConfigElements(docConfig,
docConfig.DocumentElement,
tvConfigData.Nodes[Shared._rootConfigNodeName]);

try
{
if (SaveXmlTo.Length > 0 && (!SaveXmlTo.ToUpper().Equals("SAVE XML TO:")))
docConfig.Save(SaveXmlTo);
}
catch (System.Exception exception)
{
MessageBox.Show(exception.Message);
}

StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
docConfig.Save(sw);

// txtXMLConfigResults.Text = sb.ToString();

return sb.ToString();
}


}
}

Visual Studio .Net Configuration Tool

I had occasion to have to author some configuration settings at a client and struggled at first with the nesting of the data and getting it out at runtime in an elegant way. The .Net Framework 2.0 has a much improved set of classes for interfacing complex configurations, but it can take a few minutes to learn them all, so I wrote a tool for for the whole operation which uses CodeDom to generate the classes and simple XMLDocument operations for the config file contents.

While I found a ton of tutorials online for how to write it all by hand, nothing automated. Attached are some screenshots of the tool in use :








So once compiled into the target application, the developer can get at the settings by calling the generated method with this signature:

public static MyConfigSection GetSettings()

Here is the whole generated set of classes :



namespace MyApp.NameSpace {
using System.Configuration;
using System;


public class MyConfigSection : System.Configuration.ConfigurationSection {

[System.Configuration.ConfigurationProperty("Teams")]
public virtual TeamCollection Teams {
get {
return ((TeamCollection)(this["Teams"]));
}
}

static MyConfigSection GetSettings() {
return ((MyConfigSection)(System.Configuration.ConfigurationManager.GetSection(MyConfigSection)));
}
}

public class Team : System.Configuration.ConfigurationElement {

[System.Configuration.ConfigurationProperty("Name")]
public virtual string Name {
get {
return ((string)(this["Name"]));
}
}

[System.Configuration.ConfigurationProperty("Players")]
public virtual PlayerCollection Players {
get {
return ((PlayerCollection)(this["Players"]));
}
}
}

public class TeamCollection : System.Configuration.ConfigurationElementCollection {

protected override string ElementName {
get {
return "Team";
}
}

protected override System.Configuration.ConfigurationElement CreateNewElement() {
return new Team();
}

protected override object GetElementKey(System.Configuration.ConfigurationElement element) {
return ((Team)(element)).Name;
}
}

public class Player : System.Configuration.ConfigurationElement {

[System.Configuration.ConfigurationProperty("Name")]
public virtual string Name {
get {
return ((string)(this["Name"]));
}
}

[System.Configuration.ConfigurationProperty("Position")]
public virtual string Position {
get {
return ((string)(this["Position"]));
}
}
}

public class PlayerCollection : System.Configuration.ConfigurationElementCollection {

protected override string ElementName {
get {
return "Player";
}
}

protected override System.Configuration.ConfigurationElement CreateNewElement() {
return new Player();
}

protected override object GetElementKey(System.Configuration.ConfigurationElement element) {
return ((Player)(element)).Name;
}
}
}


Here is the Class where the CodeDom namespace objects create the config classes:

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
using System.Windows;
using System.Windows.Forms;
using Microsoft.CSharp;
using System.IO;

namespace CustomConfigBuilder
{
public static class Shared
{
public const int _elementImageIndex = 1;
public const int _attributeImageIndex = 0;
public const string _rootConfigNodeName = "RootConfigNode";

public static bool HasChildAttributes(TreeNode node)
{
foreach (TreeNode childNode in node.Nodes)
if (childNode.Tag.ToString() == Shared._attributeImageIndex.ToString())
return true;

return false;
}
}

public class configClassBuilder
{

#region "Methods for building classes"
private static void addBaseGetCollectionIndexerFunction(string name, CodeTypeDeclaration parent)
{
CodeMemberProperty propertyIndexer = new CodeMemberProperty();
propertyIndexer.HasGet = true;
propertyIndexer.HasSet = false;
propertyIndexer.Attributes = MemberAttributes.Public;
propertyIndexer.Type = new CodeTypeReference(name);
propertyIndexer.Name = "Item";
propertyIndexer.Parameters.Add(
new CodeParameterDeclarationExpression(
new CodeTypeReference(typeof(int)), "index"));

CodeMethodReferenceExpression baseGet =
new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), "BaseGet");

propertyIndexer.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeCastExpression(
new CodeTypeReference(name),
new CodeMethodInvokeExpression(
baseGet,
new CodeSnippetExpression[] { new CodeSnippetExpression("index") }))));
parent.Members.Add(propertyIndexer);
}


private void addConfigurationElementsCollection(CodeNamespace codeNS,
string name,
CodeTypeDeclaration parent,
CodeTypeDeclaration itemInCollection)
{
CodeTypeReference configPropertyReference =
new CodeTypeReference(typeof(ConfigurationProperty));

// For a collection
CodeTypeDeclaration configElements = new CodeTypeDeclaration(name + "Collection");
configElements.BaseTypes.Add(typeof(ConfigurationElementCollection));
codeNS.Types.Add(configElements);

// Make the Collection a property of the Config Section.
CodeMemberProperty property = new CodeMemberProperty();
property.Name = name + "s";
property.Type = new CodeTypeReference(configElements.Name);
property.HasGet = true;
property.HasSet = false;
property.Attributes = MemberAttributes.Public;
CodeAttributeArgument codeAttributeArgument = new CodeAttributeArgument();
codeAttributeArgument.Value = new CodeSnippetExpression("\"" + name + "s\"");
property.CustomAttributes.Add(
new CodeAttributeDeclaration(configPropertyReference,
new CodeAttributeArgument[] { codeAttributeArgument }));
// Add the elements collection to whatever parent section type is.
parent.Members.Add(property);

CodeIndexerExpression indexer =
new CodeIndexerExpression(new CodeThisReferenceExpression(),
new CodeExpression[] { new CodeSnippetExpression("\"" + name + "s\"") });

CodeCastExpression castIndexer =
new CodeCastExpression(new CodeTypeReference(configElements.Name), indexer);

property.GetStatements.Add(new CodeMethodReturnStatement(castIndexer));

addCreateNewElementMethod(name, configElements);

// protected override object GetElementKey(ConfigurationElement element)
CodeMemberMethod methodGetKey = new CodeMemberMethod();
methodGetKey.Attributes = MemberAttributes.Override | MemberAttributes.Family;
methodGetKey.Name = "GetElementKey";
methodGetKey.Parameters.Add(
new CodeParameterDeclarationExpression(
new CodeTypeReference(typeof(ConfigurationElement)), "element"));
methodGetKey.ReturnType = new CodeTypeReference(typeof(object));
CodeCastExpression castReturn =
new CodeCastExpression(new CodeTypeReference(itemInCollection.Name),
new CodeVariableReferenceExpression("element"));
CodePropertyReferenceExpression propertyOfItem =
new CodePropertyReferenceExpression(castReturn,
itemInCollection.Members[0].Name);

methodGetKey.Statements.Add(new CodeMethodReturnStatement(propertyOfItem));
configElements.Members.Add(methodGetKey);

// Add the "this" indexer from the Base Collection.
addBaseGetCollectionIndexerFunction(name, parent);

// Properties of the Collection
addElementNameProperty(name, configElements);
}


private void addFieldsToElement(TreeNode treeNode, CodeTypeDeclaration elementDataType)
{
foreach (TreeNode attribute in treeNode.Nodes)
if (attribute.Tag.ToString() == Shared._attributeImageIndex.ToString())
addAttributeToElement(elementDataType, attribute.Text);
}

private void addImportsToNamespace(CodeNamespace ns)
{
CodeNamespaceImport configNS = new CodeNamespaceImport("System.Configuration");
ns.Imports.Add(configNS);
configNS = new CodeNamespaceImport("System");
ns.Imports.Add(configNS);
}

private void addClientSettingsAccessor(CodeTypeDeclaration settingsType)
{
CodeMemberMethod settingsGetter = new CodeMemberMethod();
settingsGetter.Name = "GetSettings";
settingsGetter.ReturnType = new CodeTypeReference(settingsType.Name);
settingsGetter.Attributes = MemberAttributes.Static | MemberAttributes.Public ;

CodeMethodInvokeExpression invokeGet =
new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(ConfigurationManager)),
"GetSection",
new CodeSnippetExpression[] { new CodeSnippetExpression(settingsType.Name) });

CodeCastExpression castReturn =
new CodeCastExpression(new CodeTypeReference(settingsType.Name), invokeGet);

settingsGetter.Statements.Add(new CodeMethodReturnStatement(castReturn));

settingsType.Members.Add(settingsGetter);
}

private void addAttributeToElement(CodeTypeDeclaration elementType, string name)
{
CodeTypeReference configPropertyReference =
new CodeTypeReference(typeof(ConfigurationProperty));

CodeMemberProperty property = new CodeMemberProperty();
property.Name = name;
property.Type = new CodeTypeReference(typeof(string));
property.HasGet = true;
property.HasSet = false;
property.Attributes = MemberAttributes.Public;

CodeIndexerExpression propertyIndexer =
new CodeIndexerExpression(new CodeThisReferenceExpression(),
new CodeExpression[] { new CodeSnippetExpression("\"" + name + "\"") });

CodeCastExpression castIndexer =
new CodeCastExpression(new CodeTypeReference(typeof(string)), propertyIndexer);

CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(castIndexer);

property.GetStatements.Add(returnStatement);

CodeAttributeArgument codeAttributeArgument = new CodeAttributeArgument();
// codeAttributeArgument.Name = "Name";
codeAttributeArgument.Value = new CodeSnippetExpression("\"" + name + "\"");

property.CustomAttributes.Add(
new CodeAttributeDeclaration(configPropertyReference,
new CodeAttributeArgument[] { codeAttributeArgument }));

elementType.Members.Add(property);
}

private void addCreateNewElementMethod(string name, CodeTypeDeclaration configElements)
{
CodeMemberMethod methodCreate = new CodeMemberMethod();
methodCreate.ReturnType = new CodeTypeReference(typeof(ConfigurationElement));
methodCreate.Name = "CreateNewElement";
methodCreate.Attributes = MemberAttributes.Override | MemberAttributes.Family;
methodCreate.Statements.Add(
new CodeMethodReturnStatement(
new CodeObjectCreateExpression(name, new CodeExpression[] { })));
configElements.Members.Add(methodCreate);
}

private static void addElementNameProperty(string name, CodeTypeDeclaration configElements)
{
CodeMemberProperty propEleName = new CodeMemberProperty();
propEleName.Name = "ElementName";
propEleName.Attributes = MemberAttributes.Override | MemberAttributes.Family;
propEleName.HasSet = false;
propEleName.HasGet = true;
propEleName.Type = new CodeTypeReference(typeof(string));
propEleName.GetStatements.Add(new CodeMethodReturnStatement(
new CodeSnippetExpression("\"" + name + "\"")));
configElements.Members.Add(propEleName);
}

private CodeTypeDeclaration addConfigurationElement(CodeNamespace codeNS, string name)
{
// For an item in the collection
CodeTypeDeclaration configElement = new CodeTypeDeclaration(name);
configElement.BaseTypes.Add(typeof(ConfigurationElement));
codeNS.Types.Add(configElement);
return configElement;
}

public string RunBuild(TreeView tvConfigData, string SaveTo)
{
CodeNamespace configgedNamespace = new CodeNamespace("MyApp.NameSpace");

CodeTypeDeclaration configSectionDec = new CodeTypeDeclaration(tvConfigData.Nodes[0].Text);
configSectionDec.IsClass = true;

configSectionDec.BaseTypes.Add(typeof(ConfigurationSection));

configgedNamespace.Types.Add(configSectionDec);

addImportsToNamespace(configgedNamespace);

foreach (TreeNode tn in tvConfigData.Nodes[Shared._rootConfigNodeName].Nodes)
{
CodeTypeDeclaration newElement = addConfigurationElement(configgedNamespace, tn.Text);

// Add Fields to the new configuration element.
addFieldsToElement(tn, newElement);
// Add a collection class to hold all the instances of the element.
addConfigurationElementsCollection(configgedNamespace, tn.Text, configSectionDec, newElement);

addClientSettingsAccessor(configSectionDec);

// Add Any Child Elements to the new configuration element.
foreach (TreeNode childNode in tn.Nodes)
if (childNode.Tag.ToString() == Shared._elementImageIndex.ToString())
{
CodeTypeDeclaration newChildElement =
addConfigurationElement(configgedNamespace, childNode.Text);

addFieldsToElement(childNode, newChildElement);

addConfigurationElementsCollection(configgedNamespace,
childNode.Text,
newElement,
newChildElement);
}
}

CSharpCodeProvider provider = new CSharpCodeProvider();

System.CodeDom.Compiler.CodeGeneratorOptions opts =
new System.CodeDom.Compiler.CodeGeneratorOptions();

if (SaveTo.Length > 0 && !SaveTo.Contains("Save "))
{
StreamWriter sw = new StreamWriter(SaveTo);
opts.BlankLinesBetweenMembers = true;
provider.GenerateCodeFromNamespace(configgedNamespace, sw, opts);
sw.Close();
}

StringBuilder sb = new StringBuilder();
StringWriter strWriter = new StringWriter(sb);
provider.GenerateCodeFromNamespace(configgedNamespace, strWriter, opts);
// txtClassResults.Text = sb.ToString();
strWriter.Close();
return sb.ToString();
}


#endregion
}
}


And here is the declaration of the new configuration Type in the App.Config ;




<configuration>
<configSections>
<section name="MyConfigSection" type="MyApp.NameSpace.MyConfigSection, CustomConfigBuilder" />
</configSections>

<MyConfigSection>
<Teams>
<add Name="Dallas Cowboys">
<Players>
<add Name="Terrell Owens" Position="Wide Receiver" />
</Players>
</add>
</Teams>
</MyConfigSection>
</configuration>

Sunday, June 15, 2008

Couple of Rants, IE 7.0 and NUnit

I can't use Blogger from Internet Explorer 7. I get infinite redirects I have to kill. Microsoft gets a bad grade here.

Why NUnit is worse than a Visual Studio Test Project :

A. You cannot DEBUG your test while it is running without Attaching to Process 'NUnit.exe' from the menu bar. In Visual Studio, simply set the breakpoint, go to the Test pane, right-click and run the test.

B. You cannot get the Application Configuration to work without renaming and moving around the "app.config" file. In Visual Studio, configuration just works.

C. NUnit requires me to run an application called "NUnit" where Visual Studio requires nothing other than itself with the Test Project as part of the Solution.

D. Visual Studio will automatically author the skeleton of your test methods, and this helps greatly for getting a high Code Coverage.

Tuesday, January 01, 2008

Jesus of the Whichitas

On my trip to Lawton, Oklahoma, home to Fort Sill and the really special people with whom I was ringing in the New Year, I got to see the Wildlife preserve with this nice statue :

We saw a Longhorn and some grazing Bison, but not too many other species.
The Christian attractions are part of the visitors center, which was a pleasant break from the cold air and wind.
More info can be found here :
Wichita Mountains National Wildlife Refuge