Same code to create parameters in Revit 2015 + 201

2019-09-01 09:09发布

问题:

So the ExternalDefinitionCreationOptions had a spelling error in the Revit 2015 API that was corrected in the 2016 API.

I try to make my app as compatible as possible with the current version + previous, but this time I'm not even able to compile it since I can only reference one of the two API DLL's, and the ExternalDefinitionCreationOptions plays a big role in the process.

The code is the following:

private static Definition GetSimpleParameterDefinition(UIApplication uiApp, Document doc, DefinitionGroup defGroup, string name)
{
    var definition = defGroup.Definitions.FirstOrDefault(d => d.Name == name);
    if (definition != null) return definition;

    var parameterType = ParameterType.Text;

    var defOptions = new ExternalDefinitionCreationOptions(name, parameterType);
    BuiltInCategory target = BuiltInCategory.OST_Furniture;
    var cat = doc.Settings.Categories.get_Item(target);

    var catSet = uiApp.Application.Create.NewCategorySet();
    catSet.Insert(cat);

    definition = defGroup.Definitions.Create(defOptions);

    return definition;
}

I'm reading about DI and IoC, but all the samples have all the code under control, not referencing a third-party API and dealing with it. I've run out of ideas.

Any thoughts on how to accomplish this?

回答1:

The dynamic in C# allow you to use late-binding. Then I would suggest some reflection to instante the object, like the "logic" below (not tested, needs to be completed)

Type t = System.Reflection.Assembly.GetExecutingAssembly().GetType("ExternalDefinitionCreationOptions");
dynamic defOptions = t.GetConstructor().Invoke();

Note that dynamic is different from var. Your code is using var just as a way to let the compiler decide the type... now dynamic will only define the type on runtime.



回答2:

Using the base code from Augusto (upvoted) and a lot more research on Reflection I was able to write this solution:

var assemblies = System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies();
var assemblyName = assemblies.First(a => a.Name == "RevitAPI");
Assembly revitAssembly = Assembly.Load(assemblyName);

Type t = revitAssembly.GetType("Autodesk.Revit.DB.ExternalDefinitionCreationOptions"); // For Revit2016
if (t == null) t = revitAssembly.GetType("Autodesk.Revit.DB.ExternalDefinitonCreationOptions"); // For Revit2015

var types = new Type[1] { t };
var constructor = t.GetConstructors()[0];
dynamic defOptions = constructor.Invoke(new object[] { item.Name, parameterType });


回答3:

Why not use conditional compilation?

#if REVIT2015
   var defOptions = new ExternalDefinitonCreationOptions(name, parameterType);
#else
   var defOptions = new ExternalDefinitionCreationOptions(name, parameterType);
#endif

You have to define a REVIT2015 conditional compilation symbol in your Revit 2015 project (Project options, Build tab).

Of course, this only work if you have two separate VS projects, with a source project and a project where files are linked to the files in the source project.



回答4:

i fully agree with maxence' approach above.

i also suggest placing all conditional compilation into one single compatibility module and exporting its functionality as methods or properties, e.g. by defining a method like this:

ExternalDefinitionCreationOptions
  NewExternalDefinitionCreationOptions(
    string name,
    ParameterType parameterType )
{
  #if REVIT2015
    return new ExternalDefinitonCreationOptions(name, parameterType);
  #else // if not REVIT2015
    return new ExternalDefinitionCreationOptions( name, parameterType );
  #endif // REVIT2015
}

i implemented this here:

https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/Util.cs#L1209-L1225

have fun!

cheers

jeremy



回答5:

Updated answer for handling both Revit 2015 and Revit 2016 API within the same add-in.

Do you really want to do that?

Well, if you insist, here goes:

#region Compatibility fix for spelling error change
/// <summary>
/// Wrapper to fix a spelling error prior to Revit 2016.
/// </summary>
public class SpellingErrorCorrector
{
  static bool _in_revit_2015_or_earlier;
  static Type _external_definition_creation_options_type;

  public SpellingErrorCorrector( Application app )
  {
    _in_revit_2015_or_earlier = 0
      <= app.VersionNumber.CompareTo( "2015" );

    string s
      = _in_revit_2015_or_earlier
        ? "ExternalDefinitonCreationOptions"
        : "ExternalDefinitionCreationOptions";

    _external_definition_creation_options_type
      = System.Reflection.Assembly
        .GetExecutingAssembly().GetType( s );
  }

  object NewExternalDefinitionCreationOptions(
    string name,
    ParameterType parameterType )
  {
    object[] args = new object[] { 
      name, parameterType };

    return _external_definition_creation_options_type
      .GetConstructor( new Type[] { 
        _external_definition_creation_options_type } )
      .Invoke( args );
  }

  public Definition NewDefinition(
    Definitions definitions,
    string name,
    ParameterType parameterType )
  {
    //return definitions.Create( 
    //  NewExternalDefinitionCreationOptions() );

    object opt
      = NewExternalDefinitionCreationOptions(
        name,
        parameterType );

    return typeof( Definitions ).InvokeMember(
      "Create", BindingFlags.InvokeMethod, null,
      definitions, new object[] { opt } )
      as Definition;
  }
}
#endregion // Compatibility fix for spelling error change

Usage:

static Util.SpellingErrorCorrector
  _spellingErrorCorrector = null;

Application app = doc.Application;

if( null == _spellingErrorCorrector )
{
  _spellingErrorCorrector
    = new Util.SpellingErrorCorrector( app );
}

DefinitionGroup group = sharedParametersFile
    .Groups.Create( "Reinforcement" );

def = _spellingErrorCorrector.NewDefinition(
    group.Definitions, "ReinforcementParameter",
    ParameterType.Text );

Untested!

Cheers,

Jeremy