Code Red > .NET
AttSync for .Net
TT:
--- Quote from: Jeff H on August 07, 2012, 06:27:38 pm ---
--- Quote from: TT on August 07, 2012, 05:40:05 pm ---Sacrificing performance to save a few keystrokes? I think not.
--- End quote ---
Thanks again Tony,
Of course the main point of your posts are tremendously helpful, but it is little comments like that; that also help greatly.
Looking through .NET literature and books for some reason makes me think typing out each property is the wrong way, some better neat pattern, a abstraction lies somewhere in there, or less lines. of code is better I guess that's why the examples they use never do or come close to actually doing anything useful. Maybe along the lines of your doing something wrong if you use an elseif statement.
Not sure if that made any sense but was just thanking you and your posts help much more than you may know.
--- End quote ---
Fewer lines of code is better, provided there's no significant cost. I don't like having to type more than I have to either. I've used reflection quite bit in my work over the years, especially with the code I wrote in support of Data Extraction and extending it, and I've been meaning to revise it to use Linq.Expressions when I have a chance, because it can speed things up dramatically.
Reflection (or better, dynamic expressions) are good when you don't know what you're dealing with at compile time (Data Extraction being a case in point), but when you do know, it's probably better to go the route the OP took here.
TT:
--- Quote from: Jeff H on August 07, 2012, 06:27:38 pm ---
--- Quote from: TT on August 07, 2012, 05:40:05 pm ---Sacrificing performance to save a few keystrokes? I think not.
--- End quote ---
Thanks again Tony,
Of course the main point of your posts are tremendously helpful, but it is little comments like that; that also help greatly.
Looking through .NET literature and books for some reason makes me think typing out each property is the wrong way, some better neat pattern, a abstraction lies somewhere in there, or less lines of code is better. I guess that's why the examples they use never do or come close to actually doing anything useful. Maybe along the lines of your doing something wrong if you use an elseif statement.
Not sure if that made any sense but was just thanking you and your posts help much more than you may know.
--- End quote ---
Give this a go:
--- Code - C#: ---// MatchPropertiesMgd AutoCAD .NET Code Example // Copyright (c) 2012 Tony Tanzillo using System;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; namespace MatchPropertiesMgdExample{ public static class MatchPropsMgd { // This example works specifically with TEXT objects, but // the method demonstrated here will work with any type of // entity. [CommandMethod( "MATCHPROPSMGD" )] public static void MatchPropsmMgdCommand() { Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; // Get the runtime class for the AcDbMatchProperties protocol extension: RXClass matchPropsClass = SystemObjects.ClassDictionary["AcDbMatchProperties"] as RXClass; if( matchPropsClass == null ) { ed.WriteMessage( "\nAcDbMatchProperites Protocol Extension Runtime class not found!" ); return; } // Get the source TEXT object PromptEntityOptions peo = new PromptEntityOptions( "Select source TEXT entity: " ); 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 entity: "; PromptEntityResult perDest = ed.GetEntity( peo ); if( perDest.Status != PromptStatus.OK ) return; // Get the MatchProps protocol extension from the source object. // This requires acmatch.arx to be loaded, which we'll attempt // to do if not already loaded: IntPtr p = perSource.ObjectId.ObjectClass.QueryX( matchPropsClass ); if( p == IntPtr.Zero ) { SystemObjects.DynamicLinker.LoadModule( "acmatch.arx", false, false ); p = perSource.ObjectId.ObjectClass.QueryX( matchPropsClass ); if( p == IntPtr.Zero ) { ed.WriteMessage( "\nFailed to load acmatch.arx" ); return; } } // Create an instance of the MatchProperties managed wrapper: MatchProperties matcher = DisposableWrapper.Create( typeof( MatchProperties ), p, false ) as MatchProperties; if( matcher == null ) { ed.WriteMessage( "\nFailed to create instance of MatchProperties class" ); return; } // 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 ); matcher.CopyProperties( source, destination, flags ); tr.Commit(); } } } public enum MatchPropFlags { Color = 0x1, Layer = 0x2, Linetype = 0x4, Thickness = 0x8, LinetypeScale = 0x10, Entity = Color | Layer | Linetype | Thickness | LinetypeScale | Material | Lineweight | PlotStyleName, Text = 0x20, Dimension = 0x40, Hatching = 0x80, Lineweight = 0x100, PlotStyleName = 0x200, Polyline = 0x400, Viewport = 0x800, Table = 0x1000, Material = 0x2000, ShadowDisplay = 0x4000, Multileader = 0x8000, All = 0xFFFF }; }
TT:
Here's a somewhat more sophisticated version of the above sample,
that makes some of the code reusable in the form of extension methods:
[Revised after discovreing a few issues surrounding the MatchProperties
protocol extension]
[Revised again to better support getting the protocol extension through
an Entity in addition to ObjectId and RXClass, added support for module
unloading, and fixed some locked layer issues]
--- Code - C#: --- // 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 ) ); if( typeof( T ) == typeof( DBObject ) ) { 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; } }}
Jeff H:
So thats how its done.
Thanks!
Matus:
Hi Andrey,
I know this post is an old one, but I found it through google. I used your code and I found there's a problem with multiline attributes.
You have a placeholder for the condition, but it's not used. I solved it with the following code:
--- Code: --- If attref.IsMTextAttribute Then
Dim value As String = attref.MTextAttribute.Text
Dim newValue() As String = value.Split(New Char() {Chr(13)}, StringSplitOptions.RemoveEmptyEntries)
attref.SetAttributeFromBlock(ad, br.BlockTransform)
attref.TextString = String.Join(vbCrLf, newValue)
Else
Dim value As String = attref.TextString
attref.SetAttributeFromBlock(ad, br.BlockTransform)
attref.TextString = value
End If
--- End code ---
Sorry it's VB, but i guess you can understand it.
Also there's a thing in VB, you get a warning, that iteration variables should not be used in lambda expressions, because you can get unexpected results.
And I can confirm that the results are unexpected, occasional and unpredictable.
Maybe C# doesn't have this problems, but i modified this part too:
--- Code: ---
Dim attref1 As AttributeReference = attref
Dim ad As AttributeDefinition = attdefs.First(Function(n) n.Tag = attref1.Tag)
--- End code ---
I also wanted to say thank you for this very useful utility.
Matus
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version