It's often desirable to show a progress meter during lengthy operations. Although there's currently no public API to make use of AutoCAD's progress meter from .NET, there are nevertheless a couple of approaches to doing so.
In this post I'll show how to do this using P/Invoke (using some code borrowed from Fenton Webb, from DevTech Americas) and in my next post I'll show how to use the "internal" AutoCAD managed assembly.
Here's the C# code that uses P/Invoke, which should work for AutoCAD 2007 and 2008:
using Autodesk.AutoCAD.Runtime;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ProgressMeterTest
{
public class Cmds
{
[DllImport(
"acad.exe",
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedSetStatusBarProgressMeter@@YAHPB_WHH@Z"
//This should work for AutoCAD 2006...
//EntryPoint = "?acedSetStatusBarProgressMeter@@YAHPBDHH@Z"
)]
private static extern int
acedSetStatusBarProgressMeter(
string label,
int minPos,
int maxPos
);
[DllImport(
"acad.exe",
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedSetStatusBarProgressMeterPos@@YAHH@Z"
)]
private static extern int
acedSetStatusBarProgressMeterPos(int pos);
[DllImport(
"acad.exe",
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedRestoreStatusBar@@YAXXZ"
)]
private static extern int acedRestoreStatusBar();
[CommandMethod("PB")]
public void ProgressBar()
{
acedSetStatusBarProgressMeter("Testing Progress Bar", 0, 100);
for (int i = 0; i <= 100; i++)
{
for (int j = 0; j <= 10; j++)
{
System.Threading.Thread.Sleep(1);
acedSetStatusBarProgressMeterPos(i);
// This allows AutoCAD to repaint
Application.DoEvents();
}
}
acedRestoreStatusBar();
}
}
}
And here's what you see when it runs:
Update:
Thanks to Chris Bray for pointing out the above technique (and the one I was about to show in Part 2) is unnecessary from AutoCAD 2007 onwards. A new class was introduced in AutoCAD 2007 called Autodesk.AutoCAD.Runtime.ProgressMeter.
Here's some C# code that demonstrates the use of this class:
using Autodesk.AutoCAD.Runtime;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ProgressMeterTest
{
public class Cmds
{
[CommandMethod("PB")]
public void ProgressBarManaged()
{
ProgressMeter pm = new ProgressMeter();
pm.Start("Testing Progress Bar");
pm.SetLimit(100);
// Now our lengthy operation
for (int i = 0; i <= 100; i++)
{
System.Threading.Thread.Sleep(5);
// Increment Progress Meter...
pm.MeterProgress();
// This allows AutoCAD to repaint
Application.DoEvents();
}
pm.Stop();
}
}
}
The original code is still the technique to use for AutoCAD 2005 & 2006, although you will need to uncomment the line in the DllImport attribute for acedSetStatusBarProgressMeter(), to make sure it uses the EntryPoint with the non-Unicode string argument ("?acedSetStatusBarProgressMeter@@YAHPBDHH@Z"). You'll clearly also need to comment out the current EntryPoint assignment, of course.
I'll forego the Part 2 post (and rename this one from Part 1), as there's really no need to look any other technique for this, at this stage.


Subscribe via RSS
Kean.
I've been using the following method to display a ProgressMeter in AutoCAD from .NET, avoiding P/Invoke. Should I change to use your method or is this the other internal method you speak of?
int numItems = 100; int counter = 0; // Create progress meter ProgressMeter pm = new ProgressMeter(); // Set it's caption pm.Start("Plotting Roads"); // Set it's maximum value pm.SetLimit(numItems); // Now our lengthy operation while (counter < numItems) { // // Do my process here // // Sleep a little bit System.Threading.Thread.Sleep(1); // Update Screen Application.UpdateScreen(); // Increment Progress Meter... pm.MeterProgress(); // ...and our loop counter counter++; } // We're done, clear the Progress Meter pm.Stop();Posted by: Chris Bray | May 25, 2007 at 04:27 PM
Hi Chris,
Oops - my research was inadequate on this one... I missed the inbuilt class (and took Fenton's word for this one not being available).
Please do what you're doing now - I'll check after the weekend what's what with the inbuilt class (perhaps it was introduced more recently, and Fenton was answering the question for an older release).
More soon...
Kean
Posted by: Kean | May 25, 2007 at 04:34 PM
Hi Chris,
OK - I see that Autodesk.AutoCAD.Runtime.ProgressMeter got exposed back in 2007. I'll update the post to reflect that.
Thanks again,
Kean
Posted by: Kean | May 29, 2007 at 05:36 PM
Hi Kean,
Why the need for Application.DoEvents. Also, where in the Autodesk documentation does it state that Application.DoEvents is supposed to be used in conjunction with the ProgressMeter Class?
Thanks,
Ron
Posted by: Ron Hanson | May 29, 2007 at 06:45 PM
Hi Ron,
It doesn't have to be used - it's just a choice of mine. I tend to use it to allow AutoCAD to still receive events (such as to repaint etc.) while undergoing long operations. But it's nothing to do with AutoCAD, it's a general Windows programming approach.
Try commenting that line out and then switching between AutoCAD and another app while running the "PB" command. Even if you add some code to repaint the display you get some imperfect behaviour, so I tend for this option.
If anyone has a better approach or a conflicting opinion, I'd be happy to hear it. :-)
Cheers,
Kean
Posted by: Kean | May 29, 2007 at 06:56 PM
Hi Kean,
If you don't mind I have one more question. Is there a reason that you're using System.Threading.Thread.Sleep()?
Please excuse my question if it seems basic, but I am trying very hard to learn AutoCAD with .NET and when I see something that warrants asking a question I just have to ask.
Ron
Posted by: Ron Hanson | May 29, 2007 at 07:13 PM
Hi Ron,
Not at all - it's a very valid question.
That line is only there to make the operation take long enough for you to watch the progress meter move slowly (or somewhat slowly) across. If it isn't there you just see a flash.
Feel free to keep the (post-related) questions coming - I'm sure there are others out there with similar thoughts.
Kean
Posted by: Kean | May 29, 2007 at 07:18 PM
Hi Kean,
Just one more thing.
Depending on how heavy is the process behind the progress bar if you leave AutoCAD window and get back the screen and the progress bar could appear frozen.
As you have suggested inside a comment at my Blog´s article, we need do yield AutoCAD during the process. At that time, you have pointed to use:
System.Windows.Forms.Application.DoEvents();
In this case, we can call DoEvents() over AutoCAD application during the loop or is there a better way to do that?
Regards,
Fernando.
Posted by: Fernando Malard | May 29, 2007 at 10:27 PM
Hi Fernando,
Yes - from my perspective DoEvents is the way to go, just as the above two code fragments have shown.
It's probably also worth thinking about trapping the escape key, to allow the task to be cancelled. I wrote this post showing how to do this, but I haven't tried to plug the two together, as yet.
Cheers,
Kean
Posted by: Kean | May 30, 2007 at 03:49 AM