Thursday, November 27, 2008

Web Stripper

For my Scramble Cheater program, I needed to get a list of all possible words for a dictionary. I found a good website call Word Snipe, that listed all the words from the game, and orderd them by word length. This resulted in 105,000 words, spread out 50 at a time, on over 2000 pages. I didn't want to have to copy and past all these words by hand, so I created this program to strip them from the website. It took about 35 minutes for it to download all the words, but it was definetly a lot easier than doing it by hand.

Below is the code. Enjoy!


    public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
int value = 1;
while (value <= 105351) {
ParseResponse(GetResponse("http://wordsnipe.com/results.php?start=" + value + "&PHPSESSID=1af1f78a57145ebc9c14fd4441fcf077"));
value += 50;

}
}

private void ParseResponse(string htmlResponse) {
int index = 0, endIndex;
string startDiv = "<div class=\"result\">";
int startDivLength = startDiv.Length;

for (int i = 0; i < 50; i++) {
index = htmlResponse.IndexOf(startDiv, index);
index = htmlResponse.IndexOf(">", index + startDivLength) + 1;
endIndex = htmlResponse.IndexOf("</a>", index);
textBox1.AppendText(string.Format("AllWords.Add(\"{0}\");{1}", htmlResponse.Substring(index, endIndex - index), Environment.NewLine));
index = endIndex;
}
}

private string GetResponse(string urlAddress) {
Uri uri = new Uri(urlAddress);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
string result = string.Empty;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
using (Stream responseStream = response.GetResponseStream()) {
using (StreamReader readStream = new StreamReader(responseStream, Encoding.UTF8)) {
result = readStream.ReadToEnd();
}
}
}
return result;
}
}



Beating Scramble On FaceBook




I was playing this Scramble game for Facebook over The Thanksgiving Holiday, and decided that I wanted to write a program to cheat at the game, and get high scores that would blow my friends out of the water. So did. I was able to score 1553 points, which is about 1400 points more than my nearest friend on Facebook. So much fun.
The Source Code and a more indepth description is on CodeProject. http://www.codeproject.com/KB/cs/WordScramble.aspx

Friday, October 31, 2008

Automated CSLA Testing Helper

I was trying to come up with my first test project using the Tester built into VS 2008. I wanted to be able to do some basic CRUD testing on my Csla Business Objects.  I quickly realized that I didn't want to have to go through and set all of my properties by hand for every single class.  Even if I had all the time in the world to do that, if they changed the database schema, which would result in me having to re-code-gen the BOs, I would have to edit my test scripts to handle any new columns (which I would always forget) and remove any columns that no longer exist.  What a pain.

Enter Reflection.  I was able to create a generic SetPropertyValues function that loops through all the properties of any BusinessObject and automatically assigns all them all a different value.  
This is how it works:
    1. Using reflection, I loop through all the properties on the BO with a counter that is incremented each time
    2. I look at the type of the property and assign it a value based off of its type, using the counter as a base
    3. I then save the value off to a generic list of PropertyInfo, and string values to compare with later.
Thats it.

I then wraped that funciton with two other functions, one to perform the intial insert, and one with a different seed value, to perform an update test.  Below is the .Net test class I setup for my customer class, and the SetPropertyValues function that is used to simplify the test.

Enjoy.




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BusinessObjectsUnitTest {
static class G2TestHelper {

public static List<PropertyCallerValuePair> SetInitialPropertyValues<T>(Csla.BusinessBase<T> target) where T : Csla.BusinessBase<T> {
return SetPropertyValues(target, 0);
}

public static List<PropertyCallerValuePair> SetUpdatedPropertyValues<T>(Csla.BusinessBase<T> target) where T : Csla.BusinessBase<T> {
return SetPropertyValues(target, 1);
}

private static List<PropertyCallerValuePair> SetPropertyValues<T>(Csla.BusinessBase<T> target, int intialValue) where T : Csla.BusinessBase<T> {
List<PropertyCallerValuePair> values = new List<PropertyCallerValuePair>();
string stringValue = string.Empty;

foreach (PropertyInfo property in target.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(p => p.CanWrite && !(p.PropertyType is Csla.Core.IBusinessObject))) {

if(typeof(string).IsAssignableFrom(property.PropertyType)){
stringValue = intialValue.ToString();
property.SetValue(target, stringValue, null);
}
else if (typeof(bool).IsAssignableFrom(property.PropertyType)) {
}
else if (typeof(long).IsAssignableFrom(property.PropertyType) ||
typeof(int).IsAssignableFrom(property.PropertyType) ||
typeof(decimal).IsAssignableFrom(property.PropertyType)) {
stringValue = intialValue.ToString();
property.SetValue(target, intialValue, null);
}
else if (typeof(Csla.SmartDate).IsAssignableFrom(property.PropertyType)) {
Csla.SmartDate time = new Csla.SmartDate(DateTime.Now.AddDays(intialValue));
stringValue = time.ToString();
property.SetValue(target, time, null);
}
else if (typeof(DateTime).IsAssignableFrom(property.PropertyType)){
DateTime time = DateTime.Now.AddDays(intialValue);
stringValue = time.ToString();
property.SetValue(target, stringValue, null);
}
else {
throw new Exception("Unrecognized Type");
}

values.Add(new PropertyCallerValuePair(property, stringValue));
intialValue++;
}

return values;
}

public static void AssertAreEqual<T>(Csla.BusinessBase<T> target, List<PropertyCallerValuePair> values) where T : Csla.BusinessBase<T> {
foreach (PropertyCallerValuePair property in values) {
Assert.AreEqual(property.Value, property.Property.GetValue(target, null).ToString());
}
}
}

public class PropertyCallerValuePair
public PropertyInfo Property { get; set; }
public string Value { get; set; }

public PropertyCallerValuePair(PropertyInfo property, string value) {
Property = property;
Value = value;
}
}
}






Hear is the extremely simple test script would could easily be code generated:
        /// <summary>
///A test for Customer CRUD
///</summary>
[TestMethod()]
public void CustomerCRUD() {
//Begin Create
Customer expected = Customer.NewCustomer();
List<PropertyCallerValuePair> values = G2TestHelper.SetInitialPropertyValues(expected);
Customer actual = expected.Save();
//Test Create
G2TestHelper.AssertAreEqual(actual, values);
//End Create

expected = actual;

//Begin Read
actual = Customer.GetCustomer(expected.CustId);
//Test Read
G2TestHelper.AssertAreEqual(actual, values);
//End Read

expected = actual;

//Begin Update
values = G2TestHelper.SetUpdatedPropertyValues(expected);
expected = expected.Save();
G2TestHelper.AssertAreEqual(expected, values);
actual = Customer.GetCustomer(expected.CustId);
//Test Update
G2TestHelper.AssertAreEqual(actual, values);
//End Update

expected = actual;

//Begin Delete
Customer.DeleteCustomer(expected.CustId);
try {
actual = Customer.GetCustomer(expected.CustId);
}
catch {
actual = null;
}
//Test Delete
Assert.IsNull(actual);
//End Delete
}


Friday, October 17, 2008

Updating A Private Automatic Property

I had an issue at work today where I was trying to update a public get, private set automatic propety using reflection.

My first issue was I didn't now that when you reflect a type, you can access all the inherited public scoped methods and properties and fields, but you can't access any inherited private methods, properties, and fields. Once I had that figured out, I just had to figure out what the name of the field was.

I downloaded Red Gate's .Net Reflector (an awesome tool) and dissambled my sample test program. I then looked at the property generated by the compiler,

// Fields
[CompilerGenerated]
private long <CustId>k__BackingField;

// Properties
public long CustId

[
CompilerGenerated]
get
{
return this.<CustId>k__BackingField;
}
private [CompilerGenerated]
set
{
this.<CustId>k__BackingField = value;
}






And as you can quickly see, the name of the backing field for an automatic property is as follows, k__BackingField. Once I had the backing name figured out, I could update just like any other nonpublic field:

WinPart parent = new WinPart(_mainForm);
parent.GetType().GetField("<CustId>k__BackingField", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(this, newCustId);

Easily Determine Execution Time

Most everyone has gone through at one point in time or another, and tried to determine which of two statements, would be quicker to execute. Usually this involves doing two DateTime.Now() function calls and then doing a DateDiff, and having to search the internet for how to format the output correctly.

My coworker was exploring the System.Diagnostics namespace and stumbled upon the Stopwatch class which makes solving this issue much easier. Observe the snippet at the bottom of this post. Not that you'd ever want to do this particular example, but look at the power it gives you.

In this example, I'm wanting to see how long it takes to perform the GetEditLevel() function, while allowing the user to read the name of the customer. By starting the stopwatch only after the message box has been closed, and stopping it after the function has finished executing, it will keep track of the total number of ticks, then you can call ElapsedMilliseconds to get the total amount of time.

I think its a beautiful layer of abstraction.


int totalEditLevel = 0;
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
foreach(CustomerInfo cust in CustomerList.GetCustomerListAll()){
if(watch.IsRunning){
//First time, Reset it
watch.Reset();
}
MessageBox.Show("Customer is " + cust.CustName);
watch.Start();
totalEditLevel += cust.GetEditLevel();
watch.Stop();
}
MessageBox.Show("Total Seconds was " + ((double)watch.ElapsedMilliseconds / 100.0).ToString());


Thursday, September 18, 2008

Abusing the String.Format() function, or an efficent way to check if a record can be deleted?

In my main project at work, I'm building an application to configure a database. An issue I had to overcome was how to determine if a record could be deleted, or if its deletion would result in orphan records.


I have come up with a model of creating an AssertCanDelete() function that checks all of the tables that could be referencing a given record, and throws an exception saying the name of the table that is referencing the record. The default format for this method is as follows:


Create a Select statement that has a Select Count(*) sub query for each table that could reference the object.

Go through the result set and if any value is greater than 0, then I know that it is being referenced, and can't be deleted.

I used the the String.Format in my Command Text and ended up something like this:



            cm.CommandText = String.Format("SELECT * FROM " & _
"(SELECT COUNT(*) FROM {0}{3} WHERE {3}.{1} = :{1} AND {3}.{2} = :{2} ), " & _
"(SELECT COUNT(*) FROM {0}{4} WHERE {4}.{1} = :{1} AND {4}.{2} = :{2} ), " & _
"(SELECT COUNT(*) FROM {0}{5} WHERE {5}.{1} = :{1} AND {5}.{2} = :{2} ) ", _
DatabaseOwner, "PRTNR_ID", "SECT_NAME", "PRTNR_SECT_DE", "PRTNR_SECT_SRVC_FIL", "PRTNR_SECT_WF_STEP_ROLE")
cm.Parameters.Add("PRTNR_ID", OracleType.Number).Value = criteria.PrtnrId
cm.Parameters.Add("SECT_NAME", OracleType.VarChar).Value = criteria.SectName





Which I kind of like. The first "{0}" is the database table schema or database owner. The second "{}" for each line is the Table Name, which then gets repeated with a . and the first part of the primary key "{1}" and AND'ed to the second part of the primary key "{2}". It saved me alot of time having to write every SQL statement and every table name multiple times. I could also just copy a line, and insert the table name once, and I was good to go. Another useful way to not repeat yourself.


When I was following this patern for another table, things quickly got ugly when I realized that there where multiple columns that would be referencing my record. Below is the code, what do you think? Is it an abuse of the String.Format() function and the entire process needs to be trashed, or is it an efficient way to check if a record can be deleted?



    Private Shared Sub AssertCanDelete(ByVal tr As OracleTransaction, ByVal criteria As Criteria)
Using cm As OracleCommand = tr.Connection.CreateCommand()
cm.Transaction = tr
cm.CommandType = CommandType.Text
cm.CommandText = String.Format("SELECT * FROM " & _
"(SELECT COUNT(*) FROM {0}{2} WHERE :{1} in ({2}.{1}, {2}.INIT_{1}, {2}.COMPARE_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{3} WHERE {3}.{1} = :{1} ), " & _
"(SELECT COUNT(*) FROM {0}{4} WHERE {4}.{1} = :{1} ), " & _
"(SELECT COUNT(*) FROM {0}{5} WHERE {5}.{1} = :{1} ), " & _
"(SELECT COUNT(*) FROM {0}{6} WHERE {6}.{1} = :{1} ), " & _
"(SELECT COUNT(*) FROM {0}{7} WHERE {7}.{1} = :{1} ), " & _
"(SELECT COUNT(*) FROM {0}{8} WHERE :{1} in ({8}.{1}, {8}.INIT_{1}, {8}.COMPARE_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{9} WHERE :{1} in ({9}.{1}, {9}.INIT_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{10} WHERE :{1} in ({10}.{1}, {10}.INIT_{1}, {10}.COMPARE_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{11} WHERE :{1} in ({11}.{1}, {11}.INIT_{1}, {11}.COMPARE_{1}), {11}.NEXT_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{12} WHERE :{1} in ({12}.{1}, {12}.INIT_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{13} WHERE :{1} in ({13}.{1}, {13}.INIT_{1}, {13}.COMPARE_{1}) ), " & _
"(SELECT COUNT(*) FROM {0}{14} WHERE :{1} in ({14}.{1}, {14}.INIT_{1}, {14}.COMPARE_{1}), {14}.NEXT_{1}) ) ", _
DatabaseOwner, "SECT_NAME", "sect_valid_ref", "sect_de_ref", "pkg_sect_de_ref", "prtnr_sect_de", "prtnr_sect", "pkg_sect_ref", "pkg_sect_valid_ref", "pkg_sect_de_fltr_ref", "pkg_sect_de_valid", "pkg_sect_de_nav_ref", "sect_de_fltr_ref", "sect_de_valid_ref", "sect_sect_de_nav")
cm.Parameters.Add("SECT_NAME", OracleType.VarChar).Value = criteria.SectName


Using dr As SafeDataReader = New SafeDataReader(cm.ExecuteReader())
dr.Read()
If dr.GetInt32(0) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Section Validation Reference table")
End If
If dr.GetInt32(1) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Section Data Element Reference table")
End If
If dr.GetInt32(2) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Package Section Data Element Reference table")
End If
If dr.GetInt32(3) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Partner Section Data Element table")
End If
If dr.GetInt32(4) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Partner Section table")
End If
If dr.GetInt32(5) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Package Section Reference table")
End If
If dr.GetInt32(6) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Package Section Validation Reference table")
End If
If dr.GetInt32(7) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Package Section Data Element Filter Reference table")
End If
If dr.GetInt32(8) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Package Section Data Element Validation Reference table")
End If
If dr.GetInt32(9) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Package Section Data Element Navigation Reference table")
End If
If dr.GetInt32(10) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Section Data Element Filter Reference table")
End If
If dr.GetInt32(11) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Section Data Element Validation Reference table")
End If
If dr.GetInt32(12) > 0 Then
Throw New ConstraintException("Section (" & criteria.SectName & ") can not be deleted because it is currently referenced in the Section Data Element Navigation Reference table")
End If
End Using
End Using
End Sub


Monday, September 15, 2008

Creating a VS.Net 2008 Addin

In trying to take the RTF Converter to a new level, I decided to create it as an Addin. As this is my first time creating an add in, I decided to share what I have learned.

VS 2008 has a Project wizard that is used to create the project that works quite well when trying to debug your add in, but when trying to actually run it, doesn't work at all. Some of the Enumerations that happen when running the addin in debug mode, never happen when trying to run it for real.

Some other issues that I ran into was a complete lack of examples for creating a link on the right click menu of the code. I did find a website and was able to add mix and match it with the auto generated code from the wizard to come up with a workable solution.

The first step in creating an Add in, is to Implement the Extensibility.IDTExtensibility2 interface and IDTCommand Target. There are three main functions that you need to be concerned about

1. OnConnection()
- This function gets called either when VS starts, or when the app is loaded. This is where code that is needed to either add your add int to the tool bar, or (as in my case) add it as a CommandBarPopup( Basically a menu that has children commands that pop out to the side). You have to find the "Code Window" Command bar, then add a CommandBarPopup, then add any commands that you want to have displayed.
I refactored a new AddContextMenuChildItem method to handle adding the new commands that I wanted to implement to my context menu. On of the most confusing parts was trying to determine what I call the IconId to use. I ended up having to create a tempory project that created every single icon that was possible, to go through and select the ones that made the most sense. I know you can add your own personal icons, but I didn't have the time or the patience.
I also ran into an issu with the command already existing if I opened up another version of VS, but not being added to the PopupBar. So I had to add extra logic if it already exists, to just add it. I guess I should probably change this even further to check to see if it exists first, not the other way around.

2. QueryStatus()
- This function is used to hide or show items. I wanted to show the Convert Selected option, only if something was selected, so I had to add logic for this. If you always wanted them to be displayed, I think you could always just return true.

3. Exec()
- This is the function that gets fired when the command gets clicked. It takes some simple logic to determine what got clicked, and then I'm on my way.

The source code is listed below. Hopefully this will be helpful for others.


using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;
using System.Windows.Forms;
namespace CodeToHtml

/// <summary>The object for implementing an Add-in.</summary>
/// <seealso class='IDTExtensibility2' />
public class Connect : IDTExtensibility2, IDTCommandTarget

private const string ConvertSelection = "ConvertSelectionToHtml";
private const string OpenWindowForConvert = "OpenConversionWindow";
private bool hasBeenLoaded = false;

/// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
public Connect()



/// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
/// <param term='application'>Root object of the host application.</param>
/// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
/// <param term='addInInst'>Object representing this Add-in.</param>
/// <seealso class='IDTExtensibility2' />
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)

try {
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
//if (connectMode == ext_ConnectMode.ext_cm_UISetup) { //For some Reason, this never gets called, so I've changed it
if (connectMode != ext_ConnectMode.ext_cm_CommandLine) {
object[] contextGUIDS = new object[] { };
Commands2 commands = (Commands2)_applicationObject.Commands;

string toolsMenuName;

try {
//If you would like to move the command to a different menu, change the word "Tools" to the
// English version of the menu. This code will take the culture, append on the name of the menu
// then add the command to that menu. You can find a list of all the top-level menus in the file
// CommandBar.resx.
string resourceName;
ResourceManager resourceManager = new ResourceManager("CodeToHtml.CommandBar", Assembly.GetExecutingAssembly());
CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);

if (cultureInfo.TwoLetterISOLanguageName == "zh") {
System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent;
resourceName = String.Concat(parentCultureInfo.Name, "Tools");
}
else {
resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
}
toolsMenuName = resourceManager.GetString(resourceName);
}
catch {
//We tried to find a localized version of the word Tools, but one was not found.
// Default to the en-US word, which may work for the current culture.
toolsMenuName = "Tools";
}

//Place the command on the tools menu.
//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
Microsoft.VisualStudio.CommandBars.CommandBar codeWindowContextCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["Code Window"];

CommandBarPopup toolsPopup = (CommandBarPopup)codeWindowContextCommandBar.Controls.Add(MsoControlType.msoControlPopup, System.Type.Missing, System.Type.Missing, System.Type.Missing, System.Type.Missing);
toolsPopup.Caption = "Code To Html";
//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
// just make sure you also update the QueryStatus/Exec method to include the new command names.
AddContextMenuChildItem(ref contextGUIDS, commands, toolsPopup, ConvertSelection, 223, "Convert Selection To HTML", "Converts the selected code to html");
AddContextMenuChildItem(ref contextGUIDS, commands, toolsPopup, OpenWindowForConvert, 178, "Open HTML Convert Window", "Opens a window to paste text in to convert");
hasBeenLoaded = true;
}

}
catch(Exception ex) {
MessageBox.Show(ex.ToString(), "Code To HTML");
}


private void AddContextMenuChildItem(ref object[] contextGUIDS, Commands2 commands, CommandBarPopup toolsPopup, string name, int iconId, string buttonText, string helpText) {
try {
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance, name, buttonText, helpText, true, iconId, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

//Add a control for the command to the tools menu:
if ((command != null) && (toolsPopup != null)) {
command.AddControl(toolsPopup.CommandBar, toolsPopup.CommandBar.accChildCount + 1);
}
else if(command == null){
MessageBox.Show("Command Was Null", "Code To HTML");
}
else
MessageBox.Show("Tools Popup Was Null", "Code To HTML");
}
}
catch (System.ArgumentException) {
if(!hasBeenLoaded){
// For some reason this was having issues adding the command, so if it already exists, I need to re-add it to the Command Bar.
foreach(Command command in commands){
if (command.Name == "CodeToHtml.Connect." + name) {
command.AddControl(toolsPopup.CommandBar, toolsPopup.CommandBar.accChildCount + 1);
return;
}
}

MessageBox.Show("Shows exists, when it hasn't been loaded", "Code To HTML");
}
}
}

/// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>
/// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
/// <param term='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)



/// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
/// <param term='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnAddInsUpdate(ref Array custom)



/// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
/// <param term='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnStartupComplete(ref Array custom)



/// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
/// <param term='custom'>Array of parameters that are host application specific.</param>
/// <seealso class='IDTExtensibility2' />
public void OnBeginShutdown(ref Array custom)



/// <summary>Implements the QueryStatus method of the IDTCommandTarget interface. This is called when the command's availability is updated</summary>
/// <param term='commandName'>The name of the command to determine state for.</param>
/// <param term='neededText'>Text that is needed for the command.</param>
/// <param term='status'>The state of the command in the user interface.</param>
/// <param term='commandText'>Text requested by the neededText parameter.</param>
/// <seealso class='Exec' />
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)

try {
//if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone) {
if (commandName == "CodeToHtml.Connect." + OpenWindowForConvert || commandName == "CodeToHtml.Connect." + ConvertSelection && UserHasTextSelected(false)) {
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
return;
}
//}
//MessageBox.Show(commandName + " not supported\n" + neededText.ToString());
} catch (Exception ex) {
MessageBox.Show(ex.ToString(), "Code To HTML");
}


/// <summary>Implements the Exec method of the IDTCommandTarget interface. This is called when the command is invoked.</summary>
/// <param term='commandName'>The name of the command to execute.</param>
/// <param term='executeOption'>Describes how the command should be run.</param>
/// <param term='varIn'>Parameters passed from the caller to the command handler.</param>
/// <param term='varOut'>Parameters passed from the command handler to the caller.</param>
/// <param term='handled'>Informs the caller if the command was handled or not.</param>
/// <seealso class='Exec' />
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)

handled = false;
if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)

switch (commandName.Substring(commandName.LastIndexOf('.') + 1))
{
case OpenWindowForConvert:
RTF2HTMLConverter.RTF2HTMLForm form = new RTF2HTMLConverter.RTF2HTMLForm();
form.Show();
handled = true;
break;
case ConvertSelection:
if(UserHasTextSelected(true)){
((
EnvDTE.TextSelection)_applicationObject.ActiveDocument.Selection).Copy();
string text = Clipboard.GetText(System.Windows.Forms.TextDataFormat.Rtf);
Clipboard.SetText(RTF2HTMLConverter.ConvertLogic.GetFormattedHtml(text));
}
break;
}



private bool UserHasTextSelected(bool assertError) {
string error = string.Empty;
if(_applicationObject.ActiveDocument == null){
error = "No Document Selected";
}
else if (_applicationObject.ActiveDocument.Selection == null) {
error = "Nothing Selected";
}
else {
try {
EnvDTE.TextSelection text = (EnvDTE.TextSelection)_applicationObject.ActiveDocument.Selection;
if (text.Text == string.Empty) {
error = "No Text Selected";
}
}
catch {
error = "No Text Selected";
}
}

if(error != string.Empty){
if(assertError){
MessageBox.Show(error, "CodeToHtml");
}
return false;
}
else
return true;
}
}
private DTE2 _applicationObject;
private AddIn _addInInstance;