@Anonymous wrote:
Sorry for the late response, I've spent a couple days with my family while the kids are on Fall break. This is an interesting approach and one that I didn't even realize existed. Let me play around with this and see where it goes. Thanks for the direction!
Just the other day, I came across this somewhat old code that does almost exactly what I described, except that it doesn't use an Idle handler, it uses a Timer instead. It isn't the optimal approach because of its reliance on a timer, but it does work.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace Namespace1
{
/// <summary>
/// A class that monitors the state of AutoCAD from another
/// process, signals when that state changes, and provides
/// a means to get and use the AcadApplication object.
///
/// The IsAcadReady property can be bound to the Enabled
/// property of user-interface elements to automatically
/// control their Enabled state based on AutoCAD's state.
///
/// When the Enabled property of a Control is bound to the
/// IsAcadReady property of this class, the bound Control
/// will be enabled only when AutoCAD is running, and in a
/// state that allows automation calls to be serviced.
///
/// See the usage example below for more details.
/// </summary>
[DefaultBindingProperty("IsAcadReady")]
public class AcadConnector : IDisposable, INotifyPropertyChanged
{
bool disposed;
dynamic acadObject = null;
bool ready = false;
System.Windows.Forms.Timer timer;
string progId = "AutoCAD.Application";
Type acadType;
public AcadConnector(string progId = "AutoCAD.Application", int pollingInterval = 500)
{
if(string.IsNullOrEmpty(progId))
throw new ArgumentException("progId cannot be null or empty");
this.progId = progId;
acadType = Type.GetTypeFromProgID(progId);
if(acadType == null)
throw new ArgumentException(string.Format("Type for {0} not found", progId));
timer = new System.Windows.Forms.Timer();
timer.Interval = Math.Max(pollingInterval, 100);
timer.Enabled = false;
timer.Tick += timerTick;
}
void timerTick(object sender, EventArgs e)
{
/// Disable the timer if nobody is listening:
UpdateTimerState();
/// update the current AutoCAD state:
queryAcadState();
}
/// <summary>
/// Determines if AutoCAD is running, and in a state
/// that allows automation calls to be serviced.
/// </summary>
void queryAcadState()
{
if(acadObject == null)
{
try
{
acadObject = Marshal.GetActiveObject(progId);
}
catch(COMException ex)
{
LastError = ex;
setIsReady(false);
if(ex.ErrorCode != -2147221021)
throw;
return;
}
}
try
{
setIsReady((bool) acadObject.GetAcadState().IsQuiescent);
}
catch(System.Exception ex)
{
LastError = ex;
acadObject = null;
setIsReady(false);
}
}
/// <summary>
/// This method updates the IsAcadReady property and
/// fires both events when the property's value changes.
/// </summary>
void setIsReady(bool value)
{
if(ready ^ value)
{
ready = value;
OnIsReadyChanged(value);
if(propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs("IsAcadReady"));
}
}
/// <summary>
/// A virtual method that can be overridden by a
/// derived type for more specialized purposes.
///
/// This method also fires the IsReadyChanged event.
/// </summary>
protected virtual void OnIsReadyChanged(bool value)
{
if(isReadyChanged != null)
isReadyChanged(this, new IsReadyChangedEventArgs(value));
}
/// <summary>
/// The property that indicates if an instance of
/// AutoCAD is running and is currently not busy.
///
/// This property can be bound to the Enabled
/// property of UI elements to synchronize their
/// enabled state with AutoCAD's quiescent state.
/// </summary>
public bool IsAcadReady
{
get
{
if(!timer.Enabled)
queryAcadState();
return ready;
}
}
/// <summary>
/// The AcadApplication instance as a dynamic object
/// </summary>
public dynamic Application
{
get
{
return acadObject;
}
}
/// <summary>
/// Indicates if there is a running instance of AutoCAD,
/// regardless of whether it is in a quiescent state, or
/// not.
///
/// Note: this property is not elegible for data binding.
/// </summary>
public bool IsAcadActive
{
get
{
if(!timer.Enabled)
queryAcadState();
if(acadObject != null)
{
try
{
bool value = (bool) acadObject.GetAcadState().IsQuiescent;
return true;
}
catch
{
acadObject = null;
}
}
return false;
}
}
/// <summary>
/// The last exception that was caught while polling.
/// </summary>
public System.Exception LastError
{
get;
private set;
}
/// <summary>
/// Starts a new instance of AutoCAD. After calling
/// this, use the Application property to get the
/// AcadApplication object of the instance that was
/// started.
/// </summary>
public bool Start()
{
try
{
acadObject = Activator.CreateInstance(acadType);
if(acadObject != null)
{
int i = 0;
while(true)
{
try
{
acadObject.Visible = true;
setIsReady(true);
return true;
}
catch
{
Thread.Sleep(100);
if(++i > 1000)
return false;
}
}
}
}
catch(System.Exception ex)
{
LastError = ex;
acadObject = null;
}
return false;
}
public void Dispose()
{
if(!disposed)
{
disposed = true;
this.timer.Dispose();
}
}
/// <summary>
/// The design calls for the Timer to be enabled only when
/// necessary, which is when there is at least one handler
/// for either the PropertyChanged or IsReadyChanged events.
/// Otherwise, there is no purpose to having a timer running
/// or routinely polling AutoCAD, so the timer is disabled if
/// there are no handlers for either of the two events.
///
/// When the timer's tick event fires, the Tick handler checks
/// to see if either of the aforementioned events has at least
/// one handler, and if not, it disables the timer.
///
/// When a handler is added to either of the two events, the
/// timer is enabled.
/// </summary>
/// <summary>
/// Disable the timer when there are no handlers for
/// either of the two events exposed by this class.
/// This method is called from the timer's Tick handler.
/// </summary>
void UpdateTimerState()
{
timer.Enabled = isReadyChanged != null || propertyChanged != null;
}
/// <summary>
/// Enables the timer when a handler is added to the event.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged
{
add
{
propertyChanged += value;
timer.Enabled = true;
}
remove
{
propertyChanged -= value;
}
}
/// <summary>
/// Enables the timer when a handler is added to the event.
/// </summary>
public event EventHandler<IsReadyChangedEventArgs> IsReadyChanged
{
add
{
isReadyChanged += value;
timer.Enabled = true;
}
remove
{
isReadyChanged -= value;
}
}
event EventHandler<IsReadyChangedEventArgs> isReadyChanged = null;
event PropertyChangedEventHandler propertyChanged = null;
}
public class IsReadyChangedEventArgs : System.EventArgs
{
public IsReadyChangedEventArgs(bool isReady)
{
this.IsReady = isReady;
}
public bool IsReady
{
get;
private set;
}
}
/// Basic Usage Example:
///
/// <summary>
/// An example WinForm that connects to AutoCAD via COM,
/// and uses data binding to automatically enable/disable
/// relevant UI elements when AutoCAD is not available or
/// is busy.
///
/// The form contains two buttons. One draws a Circle in
/// the model space of the active document, and the other
/// starts an instance of AutoCAD.
///
/// The code uses the AcadConnector helper class to
/// monitor the state of AutoCAD and signal when it has
/// changed, and automatically update the enabled state
/// of the 'Draw a Circle' button.
///
/// The 'Draw a Circle' button's Enabled property is bound
/// to the AcadConnector's 'IsAcadReady' property, which
/// returns true if AutoCAD is running and able to service
/// automation calls.
///
/// Whenever AutoCAD's state changes from 'ready' to
/// 'not ready' the 'Draw A Circle' Button's Enabled
/// state changes accordingly.
///
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
Button button1;
Button button2;
AcadConnector connector;
public Form1()
{
this.button1 = new Button();
this.SuspendLayout();
this.button1.AutoSize = true;
this.button1.Text = "Draw a Circle";
this.button1.Click += new EventHandler(this.button1_Click);
button2 = new Button();
button2.AutoSize = true;
button2.Location = new Point(12, 12);
this.button1.Location = new Point(12, button2.Top + button2.Height + 10);
button2.Text = "Start AutoCAD";
button2.Click += new EventHandler(button2_Click);
this.ClientSize = new Size(325, 122);
this.Controls.AddRange(new[] { button1, button2 });
this.Text = "Form1";
/// Create the AcadConnector instance:
connector = new AcadConnector();
/// Bind the 'Draw a Circle' Button's Enabled property
/// to the AcadConnector's IsAcadReady property:
this.button1.DataBindings.Add("Enabled", connector, "IsAcadReady",
false, DataSourceUpdateMode.OnPropertyChanged);
this.ResumeLayout(false);
this.PerformLayout();
}
private void button1_Click(object sender, EventArgs e)
{
connector.Application.ActiveDocument.ModelSpace.AddCircle(new[] { 10.0, 8.0, 0.0 }, 5.0);
}
void button2_Click(object sender, EventArgs e)
{
connector.Start();
}
/// <summary>
/// The instance of the connector should be Disposed with the Form:
/// </summary>
protected override void Dispose(bool disposing)
{
if(disposing && connector != null)
connector.Dispose();
connector = null;
base.Dispose(disposing);
}
}
}