First off, my apologies in taking so long to post the next step in creating the policy.

 

Down below the framework for policy creation is listed; this template could be used whenever creating a new policy.  Let start coding some more:

 

 

Let us update the constructor:

public NoTabsPolicy()

{

    interestedExtensions = new SortedList();

}

 

* This SortedList contains the files extensions we are interested in; certainly we do not want to run this on every file type VS supports.

 

 

The IPolicyDefinition is sufficient for now; we will add a UI to the Edit method later which will allow the user to customize this policy.

 

 

Now to the IPolicyEvaluation…

 

We need to update the Initialize method to keep a hold of the IPendingCheckin and to add an event handler.

 

public void Initialize(IPendingCheckin pendingCheckin)

{

    pendingCheckinsDialog = pendingCheckin;

 

    pendingCheckin.PendingChanges.CheckedPendingChangesChanged += new EventHandler(pendingCheckin_CheckedPendingChangesChanged);

}

 

This event will notify our policy when items (files) are selected/unselected from the pending checkins toolwindow (or checkin dialog); when files are selected/unselected, we will re-run the policy to only search for tabs in those specific files (and the files matching the extensions we are interested in).

 

 

Update the Dispose method:

 

public void Dispose()

{

    pendingCheckinsDialog.PendingChanges.CheckedPendingChangesChanged -= new EventHandler(pendingCheckin_CheckedPendingChangesChanged);

 

    policyDisposed = true;

    PolicyStateChanged = null;

 

    for (int i = 0; i < fileWatchers.Count; i++)

    {

        fileWatchers[i] = null;

    }

    fileWatchers = null;

}

 

Notice that we remove the event handler – we do not want to receive events when we are disposed and not cleaned up.  Also, notice the cleanup of the fileWatchers elements – for each file checked in the pending checkins (and matching the extensions we are interested in) a FileWatcher is created; this file watcher notifies us if the file changes.  If the file does change, we need to check to see if file contains tabs again.  Once complete, we can tell VS of the policy failures or clear existing failures.  This is useful, so the user can make changes to files and see the policy failures disappear as the problems are addressed.

 

 

Next, lets make the evaluate methods actually do something useful:

public PolicyFailure[] Evaluate()

{

    if (policyDisposed)

    {

        throw new ObjectDisposedException(Strings.policyType,   Strings.policyDisposedMessage);

    }

 

    ArrayList changes = new ArrayList();

    PendingChange[] checkedFiles = pendingCheckinsDialog.PendingChanges.CheckedPendingChanges;

 

    foreach (PendingChange change in checkedFiles)

    {

        if (InterestedFileExtension(change.LocalItem))

        {

            if ((change.ChangeType == ChangeType.Edit) || (change.ChangeType == ChangeType.Add))

            {

                if (DoesFileContainTabs(change.LocalItem))

                {

                    PolicyFailure failure = new PolicyFailure(change.ChangeType + "  :  " + change.LocalItem, this);

                    changes.Add(failure);

                 }

 

                 RegisterForFileEvent(change.LocalItem);

            }

        }

    }

 

    return (PolicyFailure[])changes.ToArray(typeof(PolicyFailure));

}   

 

First off, throw exception is we are disposed; we should never be disposed and a method call on the policy.  Next, we need to create a list of failures which we will return; I broke out the next few lines to make it easier to read; the FileContainedInAffectedPortfolioProject method verifies that the file in question is in a team project (formally, known as portfolio project) which has this policy installed.  It is possible to have two files checked out of the same solution where each file resides in a different team project (and those team projects may have different policies installed).

 

Next the InterestedFileExtension verifies that the file we are looking at contains a file extension we are interested in.

 

We are only interested in Edit and Add (basically, ignoring renames, deletes, branches, …).

 

Finally, lets see if the file does contains a tab.  If it does, create a new policy failure and add it to the ArrayList of policy failures.  The first value in the PolicyFailure constructor is the message the user will see in VS.  Also, whether the file contains tabs or not, we want to place a FileWatcher on it to see if it is updated later.

 

Return the array back to VS.

 

 

Code up the helper methods listed above:

private bool InterestedFileExtension(string filePathAndName)

{

    string ext = Path.GetExtension(filePathAndName);

 

    if (interestedExtensions.ContainsKey(ext))

    {

        return true;

    }

 

    return false;

}

 

private bool DoesFileContainTabs(string filePathAndName)

{

    try

    {

        StreamReader sr = new StreamReader(filePathAndName, true);

        string fileContents = sr.ReadToEnd();

        bool result = fileContents.Contains(Strings.tab);

        sr.Close();

 

        return result;

    }

    catch (Exception)

    {

        return false;

    }

}

 

 

Add the SortedList to class (in the serialize portion).

 

SortedList interestedExtensions;

 

 

Now, let us finish up with implementation of the eventing methods.

 

private void RegisterForFileEvent(string filePathName)

{

    if (policyDisposed)

    {

        return;

    }

 

    FileSystemWatcher watcher = new FileSystemWatcher(Path.GetDirectoryName(filePathName), Path.GetFileName(filePathName));

    watcher.IncludeSubdirectories = false;

    watcher.Changed += new FileSystemEventHandler(OnSourcesChanged);

    watcher.Renamed += new RenamedEventHandler(OnSourceRenamed);

    watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;

    watcher.EnableRaisingEvents = true;

 

    if (fileWatchers.ContainsKey(filePathName))

    {

        return;

    }

 

    fileWatchers.Add(filePathName, watcher);

}

 

* You may be wondering why the Changed and Renamed are both added above; the reason is that Change is needed if the file is modified outside of VS; the renamed is needed for file which change in VS (VS actually writes file X to Y then does a rename on the save).

 

Handle the renamed event…

 

private void OnSourcesRenamed(object sender, RenamedEventArgs e)

{

    OnSourcesChanged();

}

 

Handle the Changed event…

 

private void OnSourcesChanged(object sender, FileSystemEventArgs e)

{

    OnSourcesChanged();

}

 

At this point, we know a file has been modified/saved, so lets just evaluate it again and send event back to VS.

 

private void OnSourcesChanged()

{

    PolicyFailure[] failures = Evaluate();

 

    OnPolicyStateChanged(failures);

}

 

Do the actual eventing back to VS.

 

protected virtual void OnPolicyStateChanged(PolicyFailure[] failures)

{

    PolicyStateChangedHandler handler = PolicyStateChanged;

    if (handler != null)

    {

        handler(this, new PolicyStateChangedEventArgs(failures, this));

    }

}

 

Here, we know that the file selection has changed in VS, so let re-evaluate the file again.

 

private void pendingCheckin_CheckedPendingChangesChanged(Object sender, EventArgs e)

{

    OnPolicyStateChanged(Evaluate());

}

 

 

Finally, update the [NonSerialized] section:

 

[NonSerialized]

private bool policyDisposed = false;

private IPendingCheckin pendingCheckinsDialog = null;

private Hashtable fileList = new Hashtable(StringComparer.InvariantCultureIgnoreCase);

private Hashtable fileWatchers = new Hashtable(StringComparer.InvariantCultureIgnoreCase);

 

public event PolicyStateChangedHandler PolicyStateChanged;