// MatchPropertiesMgd AutoCAD .NET Code Samples
// Copyright (c) 2012 Tony Tanzillo
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using AcDb = Autodesk.AutoCAD.DatabaseServices;
/// Abstract
///
/// This sample code demonstrates how to programmatically
/// consume the functionality provided by AutoCAD's MATCHPROP
/// command, using the AcDbMatchProperties protocol extension.
///
/// This code has been tested with AutoCAD 2010 and later
namespace MatchPropertiesMgdExamples
{
public static class Commands
{
// This example works specifically with TEXT objects, but
// the method demonstrated here will work with any type of
// entity. See the additional command examples below for
// more-generic matchprop functionality.
// The work done in this example is mainly for illustration of
// using the basic APIs included herein, and should not be viewed
// as the routine or best way of consuming the latter. The other
// sample commands that follow more correctly show the best way to
// consume the included APIs that automate some of the work done
// manually in this example.
[CommandMethod( "MATCHTEXTPROPSMGD" )]
public static void MatchTextPropsMgdCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Get the source TEXT object
PromptEntityOptions peo
= new PromptEntityOptions
( "Select source TEXT object: " ); peo.AllowObjectOnLockedLayer = true;
peo.SetRejectMessage( "requires a TEXT object" );
peo
.AddAllowedClass( typeof( DBText
),
false ); PromptEntityResult perSource = ed.GetEntity( peo );
if( perSource.Status != PromptStatus.OK )
return;
// Get the destination TEXT object
peo.Message = "Select destination TEXT object: ";
peo.AllowObjectOnLockedLayer = false;
PromptEntityResult perDest = ed.GetEntity( peo );
if( perDest.Status != PromptStatus.OK )
return;
if( perDest.ObjectId == perSource.ObjectId )
{
ed.WriteMessage( "\nThe source and destination objects are the same" );
return;
}
// Get the MatchProperties protocol extension object from the
// destination entity's runtime class, using the included
// GetPropertyPainter() extension method.
//
// Note that the protocol extension object must be obtained from the
// runtime class of the destination entity rather than the source entity.
// If the protocol extension object is obtained from the runtime class
// of the source entity, attempting to use it to copy properties to a
// destination entity of a dissimilar type usually results in a failure.
//
// This also requires acmatch.arx to be loaded, which the extension
// method will attempt to do, if not already loaded.
//
// See the GetMatchProperties() method of the MatchPropertiesExtensions
// class below for details on obtaining the protocol extension object.
MatchProperties painter = perDest.ObjectId.GetPropertyPainter();
// We'll copy all common entity properties and TEXT-specific properties:
int flags = (int) ( MatchPropFlags.Entity | MatchPropFlags.Text );
// Open the source for read,
// open the destination for write,
// and bada bing bada boom
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
Entity destination = (Entity) perDest.ObjectId.GetObject( OpenMode.ForWrite );
painter.CopyProperties( source, destination, flags );
tr.Commit();
}
}
/// This example is a generalization of the above, accepting any source
/// and destination entity type. By default, all properties are copied,
/// which means that how this command bahaves depends on the type of the
/// source and destination objects. When they are the same type, and the
/// protocol extension supports type-specific properties, all of same are
/// copied along with common entity properties. Otherwise, only common
/// entity properties are copied.
///
/// This example uses the Entity.CopyPropertiesFrom() extension method
/// included below, which automates the whole shebang.
///
[CommandMethod( "MATCHPROPSMGD" )]
public static void MatchPropMgdCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Get the source entity:
PromptEntityOptions peo
= new PromptEntityOptions
( "\nSelect source object: " ); peo.AllowObjectOnLockedLayer = true;
PromptEntityResult perSource = ed.GetEntity( peo );
if( perSource.Status != PromptStatus.OK )
return;
// Get the destination entity:
peo.Message = "\nSelect Destination object: ";
peo.AllowObjectOnLockedLayer = false;
PromptEntityResult perDest = ed.GetEntity( peo );
if( perDest.Status != PromptStatus.OK )
return;
if( perDest.ObjectId == perSource.ObjectId )
{
ed.WriteMessage( "\nThe source and destination objects are the same" );
return;
}
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
Entity destination = (Entity) perDest.ObjectId.GetObject( OpenMode.ForWrite );
destination.CopyPropertiesFrom( source );
tr.Commit();
}
}
// This example accepts a single source Entity and
// any number of destination entities, and copies the
// properties of the source entity to the destination
// entities.
//
// This example uses the CopyPropertiesFrom<T>() extension
// method of IEnumerable<T> where T: Entity, along with the
// GetObjects<T>() extension method, which together fully-
// automate the entire process.
[CommandMethod( "PAINTPROPSMGD", CommandFlags.UsePickSet )]
public static void PaintPropsMgdCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Get the destination entities:
PromptSelectionOptions pso
= new PromptSelectionOptions
(); pso.MessageForAdding = "Select destination objects: ";
pso.RejectObjectsFromNonCurrentSpace = true;
pso.RejectObjectsOnLockedLayers = true;
PromptSelectionResult psr = ed.GetSelection( pso );
if( psr.Status != PromptStatus.OK )
return;
// Get the source entity:
PromptEntityOptions peo
= new PromptEntityOptions
( "\nSelect source object: " ); peo.AllowObjectOnLockedLayer = true;
PromptEntityResult perSource = ed.GetEntity( peo );
if( perSource.Status != PromptStatus.OK )
return;
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
Entity source = (Entity) perSource.ObjectId.GetObject( OpenMode.ForRead );
psr.Value.GetObjectIds().GetObjects<Entity>( OpenMode.ForWrite ).CopyPropertiesFrom( source );
tr.Commit();
}
}
}
// This enum is used to indicate what properties should be copied:
[Flags]
public enum MatchPropFlags
{
Color = 0x1,
Layer = 0x2,
Linetype = 0x4,
Thickness = 0x8,
LinetypeScale = 0x10,
Text = 0x20,
Dimension = 0x40,
Hatching = 0x80,
Lineweight = 0x100,
PlotStyleName = 0x200,
Polyline = 0x400,
Viewport = 0x800,
Table = 0x1000,
Material = 0x2000,
ShadowDisplay = 0x4000,
Multileader = 0x8000,
All = 0xFFFF,
Entity = Color | Layer | Linetype | Thickness
| LinetypeScale | Material | Lineweight
| PlotStyleName | ShadowDisplay
};
// MatchProperties-related extension methods targeting ObjectId, RXClass, and Entity:
public static class MatchPropertiesExtensions
{
static Dictionary
<RXClass, MatchProperties
> cache
= new Dictionary
<RXClass, MatchProperties
>(); static RXClass matchPropsClass
= RXClass
.GetClass( typeof( MatchProperties
) ); static RXClass entityClass
= RXClass
.GetClass( typeof( Entity
) );
static MatchPropertiesExtensions()
{
SystemObjects
.DynamicLinker.ModuleUnloaded += new ModuleUnloadedEventHandler
( onModuleUnloaded
); }
// This event handler is required to invalidate cached protocol
// extensions in the unlikely event that the module that provides
// them is unloaded. It'll probably never happen in real life, but
// we want to cover our tails just in case.
static void onModuleUnloaded( object sender, DynamicLinkerEventArgs e )
{
cache.Clear();
}
// Adds an extension method to ObjectId that returns a MatchProperties instance
// that can be used to copy properties to the entity having the given ObjectId
// from to another source entity.
public static MatchProperties GetPropertyPainter( this ObjectId id )
{
if( id.IsNull || !id.IsValid )
throw new ArgumentException
( "Null or invalid ObjectId" ); if( !id.ObjectClass.IsDerivedFrom( entityClass ) )
throw new ArgumentException
( "Requires the ObjectId of an entity or derived type" ); return GetPropertyPainter( id.ObjectClass );
}
// This can also be used on entities that are not database-resident:
public static MatchProperties GetPropertyPainter( this Entity entity )
{
if( entity == null )
throw new ArgumentNullException
( "entity" ); return GetPropertyPainter( entity.GetRXClass() );
}
// Adds an extension method to RXClass that returns a MatchProperties
// instance for the given runtime class. It is important to remember
// that you should always pass the runtime class of the 'destination'
// entity (the one whose properties are to be modified to match the
// properties of another 'source' entity), and use the returned object
// to copy properties to that entity using another entity as the source.
// If you instead get and use the MatchProperties instance from the
// source entity's runtime class, a failure will likely result if the
// destination and source entities are of dissimilar types.
public static MatchProperties GetPropertyPainter( this RXClass rxclass )
{
MatchProperties result = null;
if( !cache.TryGetValue( rxclass, out result ) )
{
result = GetMatchProperties( rxclass );
cache.Add( rxclass, result );
}
return result;
}
// It's possible but unlikely that a given runtime class may not
// produce a MatchProperties protocol extension, and this method
// can be used to try to get one without throwing an exception.
// If no protocol extension could be obtained, null is returned,
// and the result should be checked before attempting to use it.
public static MatchProperties TryGetPropertyPainter( this RXClass rxclass )
{
MatchProperties result = null;
if( !cache.TryGetValue( rxclass, out result ) )
{
result = GetMatchProperties( rxclass, false );
if( result != null )
cache.Add( rxclass, result );
}
return result;
}
public static MatchProperties TryGetPropertyPainter( this Entity entity )
{
return TryGetPropertyPainter( entity.GetRXClass() );
}
public static MatchProperties TryGetPropertyPainter( this ObjectId id )
{
if( id.IsNull || !id.IsValid )
throw new ArgumentException
( "Null or invalid ObjectId" ); if( !id.ObjectClass.IsDerivedFrom( entityClass ) )
throw new ArgumentException
( "Requires the ObjectId of an entity or derived type" ); return TryGetPropertyPainter( id.ObjectClass );
}
public static MatchProperties GetMatchProperties( RXClass rxclass )
{
return GetMatchProperties( rxclass, true );
}
// This method shows the basics of getting a MatchProperties
// protocol extension object for a given runtime class:
public static MatchProperties GetMatchProperties( RXClass rxclass, bool throwOnFailed )
{
if( rxclass == null )
throw new ArgumentNullException
( "rxclass" ); IntPtr impobj = rxclass.QueryX( matchPropsClass );
if( impobj == IntPtr.Zero )
{
SystemObjects.DynamicLinker.LoadModule( "acmatch.arx", false, false );
impobj = rxclass.QueryX( matchPropsClass );
if( impobj == IntPtr.Zero && throwOnFailed )
throw new Autodesk
.AutoCAD.Runtime.Exception( ErrorStatus
.LoadFailed ); }
if( impobj != IntPtr.Zero )
return (MatchProperties
) DisposableWrapper
.Create( typeof( MatchProperties
), impobj,
false ); else
return null;
}
// Linq-friendly extension methods that copy properties from a source object
// to one or more destination objects:
public static void CopyPropertiesFrom( this Entity destination, Entity source )
{
CopyPropertiesFrom( destination, source, MatchPropFlags.All );
}
public static void CopyPropertiesFrom( this Entity destination, Entity source, MatchPropFlags flags )
{
if( source == null )
throw new ArgumentNullException
( "source" ); if( destination == null )
throw new ArgumentNullException
( "destination" ); CopyProperties( source, destination, (int) flags );
}
public static void CopyPropertiesFrom<T>( this IEnumerable<T> destination, Entity source )
where T : Entity
{
CopyPropertiesFrom<T>( destination, source, MatchPropFlags.Entity );
}
public static void CopyPropertiesFrom<T>( this IEnumerable<T> destination, Entity source, MatchPropFlags flags )
where T : Entity
{
if( destination == null )
throw new ArgumentNullException
( "destination" ); if( source == null )
throw new ArgumentNullException
( "source" ); int nFlags = (int) flags;
foreach( T dest in destination )
{
CopyProperties( source, dest, nFlags );
}
}
static void CopyProperties( Entity source, Entity destination, int flags )
{
if( source != destination )
{
if( !destination.IsWriteEnabled )
throw new Autodesk
.AutoCAD.Runtime.Exception( ErrorStatus
.NotOpenForWrite ); GetPropertyPainter( destination ).CopyProperties( source, destination, flags );
}
}
}
// Revised/abridged version of the IEnumerable<ObjectId>.GetObjects<T>() extension
// method. This code is excerpted from the AutoCAD Linq Extension Framework
public static class AcDbLinqExtensions
{
public static IEnumerable<T> GetObjects<T>( this IEnumerable<ObjectId> source )
where T : DBObject
{
return GetObjects<T>( source, OpenMode.ForRead, false, false );
}
public static IEnumerable<T> GetObjects<T>( this IEnumerable<ObjectId> source, OpenMode mode )
where T : DBObject
{
return GetObjects<T>( source, mode, false, false );
}
public static IEnumerable<T> GetObjects<T>( this IEnumerable<ObjectId> source, OpenMode mode, bool openErased, bool openLocked )
where T : DBObject
{
if( source == null )
throw new ArgumentNullException
( "source" ); if( source.Any() )
{
AcDb.TransactionManager tm = source.First().Database.TransactionManager;
if( tm.TopTransaction == null )
throw new Autodesk
.AutoCAD.Runtime.Exception( ErrorStatus
.NoActiveTransactions ); RXClass rxclass
= RXClass
.GetClass( typeof( T
) ); {
foreach( ObjectId id in source )
yield return (T) tm.GetObject( id, mode, openErased, openLocked );
}
else if( rxclass.GetHasChildren() )
{
foreach( ObjectId id in source )
{
if( id.ObjectClass.IsDerivedFrom( rxclass ) )
yield return (T) tm.GetObject( id, mode, openErased, openLocked );
}
}
else
{
IntPtr impobj = rxclass.UnmanagedObject;
foreach( ObjectId id in source )
{
if( id.ObjectClass.UnmanagedObject == impobj )
yield return (T) tm.GetObject( id, mode, openErased, openLocked );
}
}
}
}
// Upgrades a sequence of DBObjects from OpenMode.ForRead to OpenMode.ForWrite,
// omitting objects on locked layers from the resulting sequence.
public static IEnumerable<T> UpgradeOpen<T>( this IEnumerable<T> src ) where T : DBObject
{
foreach( T item in src )
{
try
{
if( !item.IsWriteEnabled )
item.UpgradeOpen();
}
catch( Autodesk.AutoCAD.Runtime.Exception ex )
{
if( ex.ErrorStatus == ErrorStatus.OnLockedLayer )
continue;
else
throw;
}
yield return item;
}
}
// Returns true if at least one RXClass is derived from
// the given RXClass.
public static bool GetHasChildren( this RXClass rxclass )
{
IntPtr impobj = rxclass.UnmanagedObject;
foreach( DictionaryEntry e in SystemObjects.ClassDictionary )
{
if( ( (RXClass) e.Value ).MyParent.UnmanagedObject == impobj )
return true;
}
return false;
}
}
}