Question

All,

I have a T4 template that generates boiler-plate code that handles my property-changed notification and automatically registers dependancyproperties for me based on attributes I have assigned to the class. I accomplish this using EnvDTE to walk up and down the project and retrieve an IEnumerable of ClassInfo objects. I then enumerate through the ClassInfo.Attributes to retrieve ClassInfo objects that have certain custom attributes I created (i.e. INotifyPropertyChangedAttributeAttribute:System.Attribute) with all the relavent information I need to have the template write the boiler-plate code for me.

Now, my question is, is it possible to (using EnvDTE) check for an Interface implementation (such as INotifyPropertyChanged) which might be inheritied from a base class so that I don't end up with two PropertyChanged events in my class (one in the inherited class and one in the code-generated partial class)?

For examle:

public class vmBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null) PropertyChanged(this, e);
    }
}

[INotifyPropertyChangedAttribute(Test1, typeof(string))] //NOTE: By including this attribute, T4 template will automatically generate properties.  What I need to know, though, is if the EnvDTE.ClassInfo can show Internface implementations as well so that I don't recreate the INotifyPropertyChanged Event
public partial class vm: vmBase //Implements INotifyPropertyChanged
{
    //....
}

[INotifyPropertyChangedAttribute(Test2, typeof(string))]
public partial class SomeClassThatDoesNotImplementInotifyPropertyChangedAlready
{
    //....
}

Hopefully that makes some sense.

See http://www.scottlogic.co.uk/blog/colin/2009/08/declarative-dependency-property-definition-with-t4-dte/ for an example of using envDTE and T4 to take care of dependancyproperty registrations. Concepts in my project are the same, only I'm adapting it to handle INotifyPropertyChanged boiler-plate code.

Thanks in advance.

Was it helpful?

Solution 2

It took me a little, but yes - there is a way to find out via EnvDTE if a given class somehow inherits a given interface.

This code fragment only detects classes that inherit directly from another class that implements INotifyPropertyChanged. So before using it, one would add some recursive logic here...

<#
// get a reference to the project of this t4 template
var project = VisualStudioHelper.CurrentProject;
// get all class items from the code model
var allClasses = VisualStudioHelper.GetAllCodeElementsOfType(project.CodeModel.CodeElements, EnvDTE.vsCMElement.vsCMElementClass, false);

// iterate all classes
foreach(EnvDTE.CodeClass codeClass in allClasses)
{
    // get all interfaces implemented by this class

    var allInterfaces = VisualStudioHelper.GetAllCodeElementsOfType(codeClass.ImplementedInterfaces, EnvDTE.vsCMElement.vsCMElementInterface, true);
    if (allInterfaces.OfType<EnvDTE.CodeInterface>()
                     .Any(i => i.Name == "INotifyPropertyChanged"))
    {
        #>Implements Interface Directly: <#= codeClass.FullName #>
        <#

        // find classes that derive from this code class
        foreach(EnvDTE.CodeClass potentialDerivingClass in allClasses)
        {
            IEnumerable<string> theBases = VisualStudioHelper.GetAllCodeElementsOfType(potentialDerivingClass.Bases, EnvDTE.vsCMElement.vsCMElementClass, true).OfType<EnvDTE.CodeClass>().Select(cc => cc.FullName);
            if (theBases.Any(b => b == codeClass.FullName))
            {
                #>Derives from implementing class: <#= potentialDerivingClass.FullName #>
                <#
            }
        }
    }
}
#>

So given a class A that implements INotifyProperty changed, a class B deriving from A and another class C deriving from B, this code would come up with classes A and B that implement INotifyPropertyChanged.

Note: Because using the EnvDTE classes is not that nice, I used a reusable template from tangible T4 Editor's free template gallery named "tangible Visual Studio Automation Helper" - that makes it much easier to use!

OTHER TIPS

An alternative (which might be more robust) is to leverage the IsDerivedFrom property of the CodeClass interface. For example, your template might look like:

<#@ template  debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core" #>
<#@ assembly name="EnvDte" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 
<#    
  foreach(var ns in GetNamespaceElements())
  {
    foreach(var cc in ns.Members.OfType<EnvDTE.CodeClass>())
    {
      if (cc.IsDerivedFrom["System.ComponentModel.INotifyPropertyChanged"]) {
#>YOUR_CODE_HERE<#  
      }
    }
  }
#>

<#+
  public IEnumerable<EnvDTE.CodeNamespace> GetNamespaceElements()
  {
    var visualStudio = (this.Host as IServiceProvider).GetService(typeof(EnvDTE.DTE))
                        as EnvDTE.DTE;
    var project = visualStudio.Solution.FindProjectItem(this.Host.TemplateFile)
                  .ContainingProject as EnvDTE.Project;

    var projItems = new List<EnvDTE.ProjectItem>();
    FillProjectItems(project.ProjectItems, projItems);
    var names = new HashSet<string>(projItems
      .Where(i => i.FileCodeModel != null)
      .SelectMany(i => i.FileCodeModel.CodeElements.OfType<EnvDTE.CodeElement>())
      .Where(e => e.Kind == EnvDTE.vsCMElement.vsCMElementNamespace)
      .Select(e => e.FullName));

    var codeNs = new List<EnvDTE.CodeNamespace>();
    FillCodeNamespaces(project.CodeModel.CodeElements.OfType<EnvDTE.CodeNamespace>(), codeNs);

    return codeNs.Where(ns => names.Contains(ns.FullName));
  }

  public void FillCodeNamespaces(IEnumerable<EnvDTE.CodeNamespace> parents, List<EnvDTE.CodeNamespace> all)
  {
    foreach (var parent in parents)
    {
      all.Add(parent);
      FillCodeNamespaces(parent.Members.OfType<EnvDTE.CodeNamespace>(), all);
    }
  }

  public void FillProjectItems(EnvDTE.ProjectItems items, List<EnvDTE.ProjectItem> ret)
  {
    if (items == null) return;
    foreach(EnvDTE.ProjectItem item in items)
    {
      ret.Add(item);
      FillProjectItems(item.ProjectItems, ret);
    }
  }
#>

There might be a better way, but what I decided to do was create a new Attribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class INotifyPropertyChangedInheretedAttributeAttribute : Attribute
{
    public INotifyPropertyChangedInheretedAttributeAttribute():base()
    {
    }
}

This way, I can test for if this attribute exists without fundamentally altering my T4 template. If there is a better way, though, than I will gladly mark that as the answer.

As an addition to all solutions (I answered beacuse I'cant add comment yet :( ). I used

 foreach (CodeInterface iface in cc.ImplementedInterfaces)
 {
     if(iface.Name == "INotifyPropertyChanged")
     {
           MessageBox.Show(cc.Name);
     }
  }

this code is for finding Implemented classes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top