// LocalSystemVariables.cs copyright (c) 2007-2012 Tony Tanzillo
using System;
using System.Linq;
using System.Dynamic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Autodesk.AutoCAD.ApplicationServices;
namespace Autodesk.AutoCAD.ApplicationServices
{
// A common problem that affects AutoCAD scripting
// using LISP, is the need to temporarily change
// the values of AutoCAD system variables, and save
// and restore their previous values once the code
// that requires the values changed has finished.
//
// At first this seems like a simple problem because
// the programmer assumes that they only need to save
// the previous values of system variables in local or
// global program variables, change the system variable
// values, and just before their code has finished its
// job, restore the system variables to their previous,
// saved values.
//
// What complicates this seemingly-simple task, and causes
// significant problems for users, is that when an error
// causes a script to terminate prematurely, unless there
// are special precautions taken by an error handler, the
// values of changed system variables will not be restored
// to their previous, user-assigned values. In fact, this
// is a well-known problem in the world of LISP scripting,
// and many strategies have been devised and published to
// deal with it.
//
// In the .NET world, the problem is no less prominent, and
// failure to address it can result in the very same problem
// for users (the values of system variables changed after a
// .NET program or custom command has exited, usually as a
// result of an error).
//
// Because error/exception handling is far more robust in
// the .NET world (in contrast to LISP), the means we have
// to deal with the problem can also be more robust.
//
// LocalSystemVariable class.
//
// Encapsulates an AutoCAD system variable and an
// associated value that has the same scope as the
// life of the LocalSystemVariable instance.
//
// This class can be used to temporarily change the
// value of a system variable, save the previous value,
// and ensure the saved value is properly restored
// when the LocalSystemVariable instance is disposed.
// To use this class, create an instance of it under
// control of a using() directive, and supply the name
// and the new, temporary value for the system variable.
//
// within the using() block, the system variable's value
// will be set to the value passed to the constructor.
// When control leaves the using() block, the previous
// saved value of the system variable (the value it had
// when the constructor was called) will be restored.
//
// This very simple example shows how to temporarily
// set the value of the OSMODE system variable to 0
// for the duration of a registered command handler:
public static class LocalSystemVariableExample
{
[CommandMethod("MYCOMMAND")]
public static void MyCommand()
{
using( var osmode
= new LocalSystemVariable
( "OSMODE",
0 ) ) {
// here, the value of the OSMODE system variable
// will be set to 0. When the using() block is
// exited (even if it happens as a result of an
// exception), the value of OSMODE is restored
// to the value it had prior to entering this
// using() block.
// Show the temporary value of OSMODE:
WriteMessage("In MyCommand(): The value of OSMODE is now {0}", osmode.Value );
}
// Show the restored value of OSMODE:
WriteMessage("Exiting MyCommand(): the value of OSMODE is now {0}",
Application.GetSystemVariable("OSMODE") );
}
// Example usage of LocalSystemVariables collection
// to manage multiple system varibles with temporary
// values within the same scope.
// Just as when driving AutoCAD from LISP using the
// (command) function, When doing the same in .NET
// via the acedCmd() method, it is often necessary
// to save, change, and then restore the values of
// several system variables. This example shows how
// that can be easily achieved through the use of
// the LocalSystemVariables class:
[CommandMethod("MYCOMMAND2")]
public static void MyCommand2()
{
// Prompt for user input here
using( var sysvars
= new LocalSystemVariables
() ) {
sysvars["CMDECHO"] = 0;
sysvars["HIGHLIGHT"] = 0;
sysvars["BLIPMODE"] = 0;
sysvars["OSMODE"] = 0;
// Make calls to acedCmd() here to perform
// operations using AutoCAD commands.
} // At this point the previous, saved values of all
// system variables changed above are automatically
// restored, even if an exception causes control flow
// to exit the using() block prematurely.
}
// This example is functionally identical to the above
// one, except that it uses System.Dynamic to specify
// the names of the system variable as code identifiers,
// as if they were properties of the LocalSystemVariables
// class (note that identifiers are case-insensitive):
[CommandMethod("MYCOMMAND3")]
public static void MyCommand3()
{
// Prompt for user input here
using( dynamic sysvars
= new LocalSystemVariables
() ) {
sysvars.CmdEcho = 0;
sysvars.Highlight = 0;
sysvars.BlipMode = 0;
sysvars.OSMode = 0;
// Make calls to acedCmd() here to perform
// operations using AutoCAD commands.
} // Saved system variable values restored here
}
public static void WriteMessage( string fmt, params object[] args )
{
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( fmt, args );
}
}
// LocalSystemVariable class
public class LocalSystemVariable : IDisposable
{
object oldval = null;
string name = string.Empty;
public LocalSystemVariable( string name, object newval )
{
this.name = name;
this.oldval = SetValue( name, newval );
}
public string Name {get { return this.name; } }
internal static object GetValue( string name, bool validate = false )
{
if( string.IsNullOrEmpty( name ) )
throw new ArgumentException
("name"); 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;
}
}
// This was revised to deal with incompatible types
// (e.g., passing an int when a short is required),
// and will try to convert the new value to the type
// of the existing value, although I suspect that the
// managed runtime is already converting int to short
// in all cases.
internal static object SetValue( string name, object value )
{
if( value == null )
throw new ArgumentNullException
( "value" ); object oldval = GetValue( name, true );
object newval = value;
if( oldval.GetType() != value.GetType() )
newval = Convert.ChangeType( value, oldval.GetType() );
if( ! object.Equals( newval, oldval ) )
Application.SetSystemVariable( name, newval );
return oldval;
}
// Get or set the current value of the system variable.
// Note that setting this property does not change the
// initial saved value that will be restored when the
// instance is disposed:
public object Value
{
get
{
return GetValue( this.name, true );
}
set
{
SetValue( this.name, value );
}
}
public void Dispose()
{
if( oldval != null )
{
try
{
SetValue( this.name, this.oldval );
}
finally
{
this.oldval = null;
}
}
}
}
// A collection of LocalSystemVariables that automatically
// disposes its elements when the collection is disposed.
//
// This class can be used to manage multiple LocalSystemVariable
// instances without the complexity that would otherwise result
// from using multiple instances of LocalSystemVariables directly.
//
// The contained system variables can be accessed and added via
// the default indexer, or by declaring the instance as 'dynamic',
// and writing the system variable names as identifiers (as if
// they were properties of this class).
//
// Refer to the included examples for both forms of usage.
public class LocalSystemVariables : DynamicObject, IDisposable, IEnumerable<LocalSystemVariable>
{
Collection items
= new Collection
();
public LocalSystemVariables()
{
}
public bool Contains( string name )
{
return items.Contains( name );
}
public override bool TryGetMember( GetMemberBinder binder, out object result )
{
result = LocalSystemVariable.GetValue( binder.Name );
return result != null;
}
public override bool TrySetMember( SetMemberBinder binder, object value )
{
this[binder.Name] = value;
return true;
}
public object this[string name]
{
get
{
return LocalSystemVariable.GetValue( name, true );
}
set
{
if( ! items.Contains( name ) )
items
.Add( new LocalSystemVariable
( name,
value ) ); else
LocalSystemVariable.SetValue( name, value );
}
}
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 = items.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()
{
return items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
class Collection : KeyedCollection<string, LocalSystemVariable>
{
public Collection() : base( StringComparer.OrdinalIgnoreCase )
{
}
protected override string GetKeyForItem( LocalSystemVariable value )
{
return value.Name.ToUpper();
}
}
}
}