|
发表于 2012-7-15 18:50:24
|
显示全部楼层
Great work.
Just one small comment (and some code), which is that just like we've been doing in LISP since the beginning, we need to not allow a changed system variable's value to remain changed when our command exits. Using your code as an example, if an exception caused the code to exit before the BACKGROUNDPLOT system variable's value was restored, it will remain set to the temporary value you assign it to. While in many cases it may not be an issue, you might note that there are quite a few posts by users asking why the values of certain system variables mysteriously change while using AutoCAD, and of course, it is almost always caused by scripting that fails to take the needed precautions.
Here's a solution I've been using for quite some time, that I've recently updated to support System.Dynamic.
[ol]
// 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
{
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 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 e )
{
if( e.MoveNext() )
{
try
{
e.Current.Dispose();
}
finally
{
DisposeNext( e );
}
}
}
public IEnumerator GetEnumerator()
{
return items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
class Collection : KeyedCollection
{
public Collection() : base( StringComparer.OrdinalIgnoreCase )
{
}
protected override string GetKeyForItem( LocalSystemVariable value )
{
return value.Name.ToUpper();
}
}
}
}
[/ol]
kdub:edit code=csharp |
|