Author Topic: TheMaster's LocalSystemVariables  (Read 5364 times)

0 Members and 1 Guest are viewing this topic.

gile

  • Gator
  • Posts: 2507
  • Marseille, France
TheMaster's LocalSystemVariables
« on: July 16, 2012, 06:27:45 AM »
Following this reply (intersing subject, IMO).

Trying to change the LocalSystemVariables class to target Framework < 4.0 (avoiding DynamicObject inheritance), I wonder if it was a good way to use a Dictionary<string, LocalSystemVariable> rather than implementing a 'Collection' class.

Code - C#: [Select]
  1.     // From Tony T
  2.     // http://www.theswamp.org/index.php?topic=31897.msg474083#msg474083
  3.     public class LocalSystemVariables : IDisposable, IEnumerable<LocalSystemVariable>
  4.     {
  5.         private Dictionary<string, LocalSystemVariable> items = new Dictionary<string, LocalSystemVariable>();
  6.  
  7.         public LocalSystemVariables()
  8.         {
  9.         }
  10.  
  11.         public object this[string key]
  12.         {
  13.             get
  14.             {
  15.                 return GetValue(key, true);
  16.             }
  17.             set
  18.             {
  19.                 GetValue(key, true);
  20.                 if (!items.ContainsKey(key))
  21.                     this.Add(new LocalSystemVariable(key, value));
  22.                 else
  23.                     Application.SetSystemVariable(key, value);
  24.             }
  25.         }
  26.  
  27.         public void Add(LocalSystemVariable lsv)
  28.         {
  29.             items.Add(lsv.Name, lsv);
  30.         }
  31.  
  32.         public bool Contains(string name)
  33.         {
  34.             return items.ContainsKey(name);
  35.         }
  36.  
  37.         static object GetValue(string name)
  38.         {
  39.             return GetValue(name, false);
  40.         }
  41.  
  42.         static object GetValue(string name, bool validate)
  43.         {
  44.             try
  45.             {
  46.                 object result = Application.GetSystemVariable(name);
  47.                 if (result == null)
  48.                     throw new ArgumentException("name");
  49.                 return result;
  50.             }
  51.             catch
  52.             {
  53.                 if (validate)
  54.                     throw new ArgumentException(
  55.                       string.Format("Invalid System Variable Name: {0}", name));
  56.                 else
  57.                     return null;
  58.             }
  59.         }
  60.  
  61.         public void Remove(string name)
  62.         {
  63.             items.Remove(name);
  64.         }
  65.  
  66.         public void Clear()
  67.         {
  68.             items.Clear();
  69.         }
  70.  
  71.         public int Count
  72.         {
  73.             get
  74.             {
  75.                 return items.Count;
  76.             }
  77.         }
  78.  
  79.         public void Dispose()
  80.         {
  81.             if (items != null)
  82.             {
  83.                 try
  84.                 {
  85.                     using (IEnumerator<LocalSystemVariable> e = this.Reverse().GetEnumerator())
  86.                     {
  87.                         DisposeNext(e);
  88.                     }
  89.                 }
  90.                 finally
  91.                 {
  92.                     items = null;
  93.                 }
  94.             }
  95.         }
  96.  
  97.         // Recursively dispose each item in a try/finally block
  98.         // to ensure that all items are disposed. This could be
  99.         // viewed as being very dangerous, but because the number
  100.         // of elements is always small, the risk of overflowing
  101.         // the stack would be virtually impossible.
  102.  
  103.         void DisposeNext(IEnumerator<LocalSystemVariable> e)
  104.         {
  105.             if (e.MoveNext())
  106.             {
  107.                 try
  108.                 {
  109.                     e.Current.Dispose();
  110.                 }
  111.                 finally
  112.                 {
  113.                     DisposeNext(e);
  114.                 }
  115.             }
  116.         }
  117.  
  118.         public IEnumerator<LocalSystemVariable> GetEnumerator()
  119.         {
  120.             foreach (KeyValuePair<string, LocalSystemVariable> item in items)
  121.             {
  122.                 yield return item.Value;
  123.             }
  124.         }
  125.  
  126.         IEnumerator IEnumerable.GetEnumerator()
  127.         {
  128.             return this.GetEnumerator();
  129.         }
  130.     }


kdub:edit code=csharp
« Last Edit: September 02, 2012, 11:19:09 AM by Kerry »
Speaking English as a French Frog

TheMaster

  • Guest
Re: TheMaster's LocalSystemVariables
« Reply #1 on: July 16, 2012, 01:00:22 PM »
Following this reply (intersing subject, IMO).

Trying to change the LocalSystemVariables class to target Framework < 4.0 (avoiding DynamicObject inheritance), I wonder if it was a good way to use a Dictionary<string, LocalSystemVariable> rather than implementing a 'Collection' class.

Code: [Select]
    // From Tony T
    // http://www.theswamp.org/index.php?topic=31897.msg474083#msg474083
    public class LocalSystemVariables : IDisposable, IEnumerable<LocalSystemVariable>
    {
        private Dictionary<string, LocalSystemVariable> items = new Dictionary<string, LocalSystemVariable>();

        public LocalSystemVariables()
        {
        }

        public object this[string key]
        {
            get
            {
                return GetValue(key, true);
            }
            set
            {
                GetValue(key, true);
                if (!items.ContainsKey(key))
                    this.Add(new LocalSystemVariable(key, value));
                else
                    Application.SetSystemVariable(key, value);
            }
        }

        public void Add(LocalSystemVariable lsv)
        {
            items.Add(lsv.Name, lsv);
        }

        public bool Contains(string name)
        {
            return items.ContainsKey(name);
        }

        static object GetValue(string name)
        {
            return GetValue(name, false);
        }

        static object GetValue(string name, bool validate)
        {
            try
            {
                object result = Application.GetSystemVariable(name);
                if (result == null)
                    throw new ArgumentException("name");
                return result;
            }
            catch
            {
                if (validate)
                    throw new ArgumentException(
                      string.Format("Invalid System Variable Name: {0}", name));
                else
                    return null;
            }
        }

        public void Remove(string name)
        {
            items.Remove(name);
        }

        public void Clear()
        {
            items.Clear();
        }

        public int Count
        {
            get
            {
                return items.Count;
            }
        }

        public void Dispose()
        {
            if (items != null)
            {
                try
                {
                    using (IEnumerator<LocalSystemVariable> e = this.Reverse().GetEnumerator())
                    {
                        DisposeNext(e);
                    }
                }
                finally
                {
                    items = null;
                }
            }
        }

        // Recursively dispose each item in a try/finally block
        // to ensure that all items are disposed. This could be
        // viewed as being very dangerous, but because the number
        // of elements is always small, the risk of overflowing
        // the stack would be virtually impossible.

        void DisposeNext(IEnumerator<LocalSystemVariable> e)
        {
            if (e.MoveNext())
            {
                try
                {
                    e.Current.Dispose();
                }
                finally
                {
                    DisposeNext(e);
                }
            }
        }

        public IEnumerator<LocalSystemVariable> GetEnumerator()
        {
            foreach (KeyValuePair<string, LocalSystemVariable> item in items)
            {
                yield return item.Value;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }

I revised the code in that post today, to deal with type mismatches (e.g., passing an int for a value that requires a short).  The SetValue() method tries to convert the passed in value to the same type as the current value, avoiding the invalidcastexception that I used to see when I forget to cast an int to a short.

I used a KeyedCollection<> because I wanted the items to be ordered (in the order they were added), so I could restore their values in the reverse order.  I don't remember exactly why I thought I needed it, but otherwise I don't see any other problem with using a Dictionary<>.

kaefer

  • Guest
Re: TheMaster's LocalSystemVariables
« Reply #2 on: July 17, 2012, 05:42:44 AM »
Following this reply (intersing subject, IMO).
I used a KeyedCollection<> because I wanted the items to be ordered (in the order they were added), so I could restore their values in the reverse order.

Interesting subject, indeed; although the general use case is somehow shrouded in mystery. You are mutating the state of the application, then you want - come what may - its original state restored. You're doing this in a way that necessitates the management of an IDisposable collection. How complex is your control flow that you can't just be using the using (C#) statement?

I'd like to illustrate the preferable case of linearly nested try-finally blocks (a.k.a. use-binding in F# - the only drawback is that the variable has to be named), and transform them into a function that operates on an equivalent immutable collection, which ain't pretty anymore. We start with an object expression to generate the IDisposable and a closure to capture the old state.

Code - F#: [Select]
  1. module Disposable =
  2.     let create f = { new System.IDisposable with member me.Dispose() = f me }
  3.  
  4. let sysvar varName value =
  5.     match box value with
  6.     | null -> raise <| new System.ArgumentNullException()
  7.     | newValue ->
  8.         let oldValue = acadApp.GetSystemVariable varName
  9.         acadApp.SetSystemVariable(varName, newValue)
  10.         Disposable.create (fun _ -> acadApp.SetSystemVariable(varName, oldValue))
  11.  
  12. let test01() =
  13.     use d0 = sysvar "CMDECHO" 0
  14.     use d1 = sysvar "HIGHLIGHT" 0
  15.     use d2 = sysvar "BLIPMODE" 0
  16.     use d3 = sysvar "OSMODE" 0
  17.     ... (* inner code goes here *)

The use-bindings are defined in terms of the using (F#) function as:
Code - F#: [Select]
  1. let test02() =
  2.     using (sysvar "CMDECHO" 0) <| fun d0 ->
  3.     using (sysvar "HIGHLIGHT" 0) <| fun d1 ->
  4.     using (sysvar "BLIPMODE" 0) <| fun d2 ->
  5.     using (sysvar "OSMODE" 0) <| fun d3 ->
  6.     ... (* inner code goes here *)

Change the signature of each function from System.IDisposable->unit to unit->unit and wrap the whole thing in a lambda too:
Code - F#: [Select]
  1. let test03() =
  2.     (fun () ->
  3.         using (sysvar "CMDECHO" 0) (ignore >> fun() ->
  4.         using (sysvar "HIGHLIGHT" 0) (ignore >> fun() ->
  5.         using (sysvar "BLIPMODE" 0) (ignore >> fun() ->
  6.         using (sysvar "OSMODE" 0) (ignore >> fun() ->
  7.         ... (* inner code goes here *) ) ) ) ) )
  8.         ()

...which yields:
Code - F#: [Select]
  1. let test04() =
  2.     List.foldBack
  3.         (fun t s () -> using t (ignore >> s))
  4.         [   sysvar "CMDECHO" 0
  5.             sysvar "HIGHLIGHT" 0
  6.             sysvar "BLIPMODE" 0
  7.             sysvar "OSMODE" 0 ]
  8.         (fun () -> ... (* inner code goes here *) )
  9.         ()

TheMaster

  • Guest
Re: TheMaster's LocalSystemVariables
« Reply #3 on: July 17, 2012, 03:18:00 PM »
Following this reply (intersing subject, IMO).
I used a KeyedCollection<> because I wanted the items to be ordered (in the order they were added), so I could restore their values in the reverse order.

Interesting subject, indeed; although the general use case is somehow shrouded in mystery. You are mutating the state of the application, then you want - come what may - its original state restored. You're doing this in a way that necessitates the management of an IDisposable collection. How complex is your control flow that you can't just be using the using (C#) statement?


Before we start counting trees, let me show you the whole forest.

Your question has an easy answer.  In most high-level customization and scripting, we can identify a 'standard' or common set of system variables that are always saved, changed, and restored by numerous custom commands (namely those that use acedCmd() to script AutoCAD commands), so the underlying logic behind the design of LocalSystemVariables which I did not endeavour to illustrate up to this point, is that it can be specialized, and a specialization of it can handle that set of commonly-managed system variables internaly, rather than having to specify that same set of system variables explicitly in each and every custom command implementation that used the base type.

Code - C#: [Select]
  1.  
  2. public class CommandScriptingEditorState : LocalSystemVariables
  3. {
  4.   public CommandScriptingEditorState()
  5.   {
  6.     // This is just an example of some of the 'common'
  7.     // sysvars that are always saved/changed/restored
  8.     // when AutoCAD commands are scripted via acedCmd().
  9.    
  10.     base["CMDECHO"] = 0;
  11.     base["BLIPMODE"] = 0;
  12.     base["HIGHLIGHT"] = 0;
  13.     base["OSMODE"] = 0;
  14.   }
  15. }
  16.  
  17.  

With the above, whenever I need to write a command that itself scripts other AutoCAD commands (via acedCmd), instead of using a LocalSystemVariables instance, I would use a specialization of it, like the one shown above, that will internally manage the system variables that are always managed when scripting AutoCAD commands, and with that, my top-level application logic is vastly simplified, to a far greater extent than what you've presented.

Code - C#: [Select]
  1.  
  2. [CommandMethod("MYCOMMAND")]
  3. public static void MyCommand()
  4. {
  5.   using( new CommandScriptingEditorState() )
  6.   {
  7.     // script AutoCAD commands via acedCmd() here,
  8.     // all of the system variables that are managed
  9.     // internally by CommandScriptingEditorState()
  10.     // are saved/changed/restored for us.
  11.   }
  12. }
  13.  
  14.  

Here's another example of a more-complicated specialization of the above specialization that serves to simplify a few common tasks in consuming code, namely changing current editor entity properties to values that will be inherited by new objects created via command scripting:

Code - C#: [Select]
  1.  
  2. // Simplifies code logic of applications that script AutoCAD
  3. // commands and often need to control common properties
  4. // of entitites created by scripted AutoCAD commands:
  5.  
  6. public class CurrentEditorProperties : CommandScriptingEditorState
  7. {
  8.   public CurrentEditorProperties( string layer = null, string color = null, string linetype = null )
  9.     : base()
  10.   {
  11.     // The defaults are the current values:
  12.    
  13.     base["CLAYER"] = layer ?? base["CLAYER"];
  14.     base["CECOLOR"] = color ?? base["CECOLOR"];
  15.     base["CELTYPE"] = linetype ?? base["CELTYPE"];
  16.   }
  17.  
  18.   public string Layer
  19.   {
  20.     get
  21.     {
  22.       return base["CLAYER"];
  23.     }
  24.     set
  25.     {
  26.       base["CLAYER"] = value;
  27.     }
  28.   }
  29.  
  30.   public string Color
  31.   {
  32.     get
  33.     {
  34.       return base["CECOLOR"];
  35.     }
  36.     set
  37.     {
  38.       base["CECOLOR"] = value;
  39.     }
  40.   }
  41.  
  42.   public string Linetype
  43.   {
  44.     get
  45.     {
  46.       return base["CELTYPE"];
  47.     }
  48.     set
  49.     {
  50.       base["CELTYPE"] = value;
  51.     }
  52.   }
  53. }
  54.  
  55. // Example:
  56.  
  57. [CommandMethod("MYCOMMAND")]
  58. public static void MyCommand()
  59. {
  60.   using( var props = new CurrentEditorProperties( "WALLS", "BYLAYER", "BYLAYER" ) )
  61.   {
  62.     // script AutoCAD commands via acedCmd() here.
  63.     // In addition to the common system variables
  64.     // managed by its base class, the current layer,
  65.     // color, and linetype sysvars are managed by
  66.     // the CurrentEditorProperties instance.
  67.     //
  68.     // If you need to change the property assigned
  69.     // to objects subsequently created by scripted
  70.     // commands, it's just this easy:
  71.    
  72.     props.Layer = "DOORS";
  73.  
  74.     // Insert some doors...  
  75.     // and then change it again:
  76.    
  77.     props.Layer = "WINDOWS";
  78.    
  79.     // Insert some windows....
  80.  
  81.     // and when you're done, the original current
  82.     // layer, color, and linetype, along with all
  83.     // the system variables managed by the base type
  84.     // are restored to what they were before your
  85.     // command was started.
  86.   }
  87. }
  88.  
  89.  

Now, Is that application-level code logic simple enough?

[edit TT]: renamed CommandScriptSystemVariables to CommandScriptingEditorState


kdub:edit code=csharp
« Last Edit: September 02, 2012, 11:21:32 AM by Kerry »

dgorsman

  • Water Moccasin
  • Posts: 2437
Re: TheMaster's LocalSystemVariables
« Reply #4 on: July 18, 2012, 10:15:27 AM »
That isn't half bad.  I'm sure I'll be able to find a use for that technique, even if not for system variables.   8-)
If you are going to fly by the seat of your pants, expect friction burns.

try {GreatPower;}
   catch (notResponsible)
      {NextTime(PlanAhead);}
   finally
      {MasterBasics;}

TheMaster

  • Guest
Re: TheMaster's LocalSystemVariables
« Reply #5 on: July 19, 2012, 12:23:02 PM »
Quote
Interesting subject, indeed; although the general use case is somehow shrouded in mystery. You are mutating the state of the application, then you want - come what may - its original state restored. You're doing this in a way that necessitates the management of an IDisposable collection. How complex is your control flow that you can't just be using the using (C#) statement?


To further underscore the advantage of using a collection/dictionary to marhsal managed system variables (in contrast to the use of the C# using() statement):

A collection/dictionary coupled with a specialization that manages the system variables whose values are always saved/changed/restored when commands are scripted, allows additional system variables to also be managed on a case-by-case, per-usage basis, very easily.

For example:

Code - C#: [Select]
  1.  
  2. // Example custom command that scripts the AutoCAD FILLET
  3. // command, that needs to temporarily change the FILLETRAD sysvar:
  4.  
  5. [CommandMethod("MYFILLET")]
  6. public static void MyFillet()
  7. {
  8.   using( var state = new CommandScriptingEditorState() )
  9.   {
  10.      state["FILLETRAD"] = 0.0;  
  11.  
  12.      // Script the FILLET command here.
  13.  
  14.      // When the using() block is exited, all system variables
  15.      // managed internally by ComandScriptingEditorState,
  16.      // along with the FILLETRAD sysvar will have their
  17.      // original values restored.
  18.   }
  19. }
  20.  
  21.  


kdub:edit code=csharp
« Last Edit: September 02, 2012, 11:22:44 AM by Kerry »

weslleywang

  • Mosquito
  • Posts: 3
Re: TheMaster's LocalSystemVariables
« Reply #6 on: July 24, 2012, 09:51:44 AM »
Hi Tony:
  this is amazing.
  I have some a quesiton:
  I am working on customize Script Pro from Autodesk lab, it use a lot reflection to GetVariable/SetVariable like:

                        OnedataArray[0] = "LOGFILEMODE";
                        _lf =
                          ActiveDocument.GetType().InvokeMember(
                            "GetVariable",
                            BindingFlags.InvokeMethod,
                            null, ActiveDocument, OnedataArray
                          );

                        TwoVariable[0] = "RECOVERYMODE";
                        TwoVariable[1] = 0;
                        ActiveDocument.GetType().InvokeMember(
                          "SetVariable",
                          BindingFlags.InvokeMethod,
                          null, ActiveDocument, TwoVariable
                        );

  what's the best to change your code to make it work

Thank you so much
Wes

TheMaster

  • Guest
Re: TheMaster's LocalSystemVariables
« Reply #7 on: July 26, 2012, 02:57:36 AM »
You don't really need to invoke the COM GetVariable() and SetVariable() APIs using reflection unless your client is in another process and you don't want a dependence on the AutoCAD COM type libraries.

You can use the managed GetSystemVariable() and SetSystemVariable() methods, and there's no need to use them if you are going to use a wrapper like LocalSystemVariable.