Author Topic: Passing arguments to command  (Read 4911 times)

0 Members and 1 Guest are viewing this topic.

WILL HATCH

  • Bull Frog
  • Posts: 450
Passing arguments to command
« on: March 18, 2013, 03:56:21 PM »
It is possible to pass arguments to a command  in a way similar to pushing them onto the callstack before calling the command?

I've got existing commands that use the OpenFileDialog to get a list of drawings to process.  I'm using my new palette to send this list to commands.  I've modified my old command as follows:
Code - C#: [Select]
  1. [CommandMethod("MyCommand")]
  2. public void MyCommand()
  3. {
  4.      Blah Blah Blah...
  5.      do open file dialog
  6.      foreach (file in filenames)
  7.      {
  8.           Blah Blah Blah....
  9.      }
  10. }
  11.  
  12. changed to:
  13.  
  14. [CommandMethod("MyCommand")]
  15. public void MyCommand()
  16. {
  17.      MySubCommand(new List<string>());
  18. }
  19.  
  20. public void MySubCommand(List<string> filenames)
  21. {
  22.      using (DocumentLock docLock = Application.DocumentManager.MDIActiveDocument.LockDocument())
  23.      {
  24.           Blah Blah Blah...
  25.           if (filenames.Count == 0)
  26.           {
  27.                do open file dialog
  28.                filenames = file dialog filenames
  29.           }
  30.           foreach (file in filenames)
  31.           {
  32.                Blah Blah Blah....
  33.           }
  34.      }
  35. }

I know we can pass a selection set via UsePickSet flag, but not sure what is possible here.  Can see it could be done using the stack, but not sure what stack interactions occur during command invocation so that method would very easily become a tragic failure.

Thanks for any input!

TheMaster

  • Guest
Re: Passing arguments to command
« Reply #1 on: March 18, 2013, 11:05:59 PM »
It is possible to pass arguments to a command  in a way similar to pushing them onto the callstack before calling the command?

I've got existing commands that use the OpenFileDialog to get a list of drawings to process.  I'm using my new palette to send this list to commands.  I've modified my old command as follows:
Code - C#: [Select]
  1. [CommandMethod("MyCommand")]
  2. public void MyCommand()
  3. {
  4.      Blah Blah Blah...
  5.      do open file dialog
  6.      foreach (file in filenames)
  7.      {
  8.           Blah Blah Blah....
  9.      }
  10. }
  11.  
  12. changed to:
  13.  
  14. [CommandMethod("MyCommand")]
  15. public void MyCommand()
  16. {
  17.      MySubCommand(new List<string>());
  18. }
  19.  
  20. public void MySubCommand(List<string> filenames)
  21. {
  22.      using (DocumentLock docLock = Application.DocumentManager.MDIActiveDocument.LockDocument())
  23.      {
  24.           Blah Blah Blah...
  25.           if (filenames.Count == 0)
  26.           {
  27.                do open file dialog
  28.                filenames = file dialog filenames
  29.           }
  30.           foreach (file in filenames)
  31.           {
  32.                Blah Blah Blah....
  33.           }
  34.      }
  35. }

I know we can pass a selection set via UsePickSet flag, but not sure what is possible here.  Can see it could be done using the stack, but not sure what stack interactions occur during command invocation so that method would very easily become a tragic failure.

Thanks for any input!

Not sure what 'stack' you're referring to. I don't know of any stack that AutoCAD takes command input from.

You can use Editor.GetString() to prompt for input, and supply the input via a call to the Command() wrapper for RunCommand(), acedCmd(), acedCommand(), etc.,

But that really doesn't make sense if you are sending the input yourself. I'm really not sure I understand what it is you're trying to do, but if the problem is that you're trying to control a batch process from code in a PaletteSet, you can use the Application's Idle event to send command input via SendStringToExecute().

Search this group for 'Application.Idle' and you should find some code I posted some time back showing how the event is used in a transient way.

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Passing arguments to command
« Reply #2 on: March 19, 2013, 12:27:14 AM »
Hi Will,

Here is a bad example of what I think your asking.
I threw this together last week because we receive drawing from architects and they constantly send updates and it might just be a door swing changes or a wall moves, etc.....
Anyways a guy here opens each drawing received  explodes everything because he does not understand how to change the color the block if the definition contains entities where the color is set by a layer other than zero or explicitly set, and changes every layer color to what he already has set 30 times. Also another guy copies the new drawing and paste it next to the old drawing and tries to eyeball what the changes are.

So I gave them some code to change all entities in blocks to layer zero and set ByLayer, and the code below which basic idea is to delete everything in model keeping all block and layer settings, etc... and WBlocking all the entities in from the new drawing. Did it for other reasons and tried to explain to them to freeze layers instead of deleting them because the archs usually already have closed polylines used for getting area that can be reused for automating creating ceiling grid for lights, etc......

Anyways just slapped this together to keep from slapping them and until I have time come up with something better or different workflow, but passes a string for filename by means of SendStringToExecute and  Editor.GetString().

The main reason it is broken up into two methods is because it would crash and did not have time to figure out what was going on but a drawing with dynamic blocks seemed to crash every time.

Code - C#: [Select]
  1.  
  2.         [CommandMethod("UpdateFromNewDrawing")]
  3.         public void UpdateFromNewDrawing()
  4.         {
  5.             OpenFileDialog ofd = new OpenFileDialog("New File", Path.GetFileName(Db.Filename), "dwg", "New File", 0);
  6.  
  7.  
  8.             if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK)
  9.             {
  10.                 return;
  11.             }
  12.             string fileName = ofd.Filename;
  13.             ObjectId targetId;
  14.             using (Transaction trx = Db.TransactionManager.StartTransaction())
  15.             {
  16.                 BlockTableRecord modelSpace = Db.ModelSpace();
  17.                 targetId = modelSpace.ObjectId;
  18.                 foreach (Entity ent in modelSpace.GetEntities(OpenMode.ForWrite, false, true))
  19.                 {
  20.                     ent.Erase();
  21.                 }
  22.                 trx.Commit();
  23.             }
  24.  
  25.  
  26.             Doc.SendStringToExecute("*_WblockCloneObjectsIntoCurrentDrawing " + fileName + "\n", false, false, false);
  27.         }
  28.  
  29.  
  30.         [CommandMethod("*_WblockCloneObjectsIntoCurrentDrawing", CommandFlags.NoHistory)]
  31.         public void __WblockCloneObjectsIntoCurrentDrawing()
  32.         {
  33.             PromptStringOptions pso = new PromptStringOptions("\nEnter File Name");
  34.             pso.AllowSpaces = true;
  35.             PromptResult pr = Ed.GetString(pso);
  36.             if (pr.Status != PromptStatus.OK)
  37.             {
  38.                 return;
  39.             }
  40.             string fileName = pr.StringResult;
  41.  
  42.  
  43.             if (File.Exists(fileName))
  44.             {
  45.                 PromptKeywordOptions pko = new PromptKeywordOptions("\nUpdate Blocks");
  46.                 pko.Keywords.Add("Yes");
  47.                 pko.Keywords.Add("No");
  48.                 pko.Keywords.Default = "Yes";
  49.                 pr = Ed.GetKeywords(pko);
  50.                 if (pr.Status != PromptStatus.OK)
  51.                 {
  52.                     return;
  53.                 }
  54.  
  55.  
  56.                 DuplicateRecordCloning dupRecordCloning = pr.StringResult == "Yes" ? DuplicateRecordCloning.Replace : DuplicateRecordCloning.Ignore;
  57.  
  58.  
  59.                 ObjectId targetId;
  60.                 using (Transaction trx = Db.TransactionManager.StartTransaction())
  61.                 {
  62.                     BlockTableRecord modelSpace = Db.ModelSpace();
  63.                     targetId = modelSpace.ObjectId;
  64.                     trx.Commit();
  65.                 }
  66.                 using (Database sideDb = new Database(false, true))
  67.                 {
  68.                     ObjectIdCollection ids;
  69.                     sideDb.ReadDwgFile(fileName, System.IO.FileShare.ReadWrite, true, "");
  70.                     sideDb.CloseInput(true);
  71.                     using (Transaction sideTrx = sideDb.TransactionManager.StartTransaction())
  72.                     {
  73.                         BlockTableRecord sideModel = sideDb.ModelSpace();
  74.                         ids = new ObjectIdCollection(sideModel.GetObjectIds().ToArray());
  75.                         if (ids.Count > 0)
  76.                         {
  77.                             IdMapping idMap = new IdMapping();
  78.                             sideDb.WblockCloneObjects(ids, targetId, idMap, dupRecordCloning, false);
  79.                         }
  80.                         sideTrx.Commit();
  81.                     }
  82.  
  83.  
  84.  
  85.  
  86.                 }
  87.             }
  88.             else
  89.             {
  90.                 Ed.WriteLine(fileName + " Not found!!");
  91.             }
  92.  
  93.  
  94.         }
  95.  


WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Passing arguments to command
« Reply #3 on: March 19, 2013, 12:28:53 AM »
Not sure what 'stack' you're referring to. I don't know of any stack that AutoCAD takes command input from.
back in my embedded programming days it was fairly common to pass values to subroutines by explicitly pushing them onto the call stack.  I know that this is quite obscure from the high level perspective of .NET programming, but I suspect that when we are passing parameters to a subroutine this is being implicitly done.  To clarify I am not proposing that AutoCAD takes the command from the stack, but that I could possibly push data onto it and the command could check for the presence of data, if such data were present it could bypass the call to OpenFileDialog
Quote
You can use Editor.GetString() to prompt for input, and supply the input via a call to the Command() wrapper for RunCommand(), acedCmd(), acedCommand(), etc.,

But that really doesn't make sense if you are sending the input yourself. I'm really not sure I understand what it is you're trying to do, but if the problem is that you're trying to control a batch process from code in a PaletteSet, you can use the Application's Idle event to send command input via SendStringToExecute().
Passing filenames explicitly via command line breaks the old functionality of the tools.  I have a series of tools which operate on multiple drawings and those drawings are selected using the open file dialog.  One example is a tool that will extract attributes from a given block and print them to an excel file, where they can be changed and imported again.  Sort of like a batch ATTOUT/ATTIN.
The drawback to the classic operation is that the OpenFileDialog limits you to selecting files which are in the same folder.  I have developed a palette which is similar to explorer treeview except it shows .dwg files.  I use the file/folder icon as greyscale/color to visually show the user which files will be processed.  In this way I can use the attribute extraction program to compile a drawing index for an entire project simply by selecting the folders which directly contain drawings.  The code in the palette compiles the list of drawings from the selected items and passes it to the attribute extraction program, which upon receiving a list with count != 0 bypasses the open file dialog and processes the list.

This would have previously taken ~30 runs using the previous software as a result of the companies file structure.  Previously this task would have taken days for a drafter to open all of the drawings and manually enter data into the index.  It was quite prone to error.
Quote
Search this group for 'Application.Idle' and you should find some code I posted some time back showing how the event is used in a transient way.

Will do, thank you for the input!

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Passing arguments to command
« Reply #4 on: March 19, 2013, 12:45:14 AM »
Hi Will,

Here is a bad example of what I think your asking.
I threw this together last week because we receive drawing from architects and they constantly send updates and it might just be a door swing changes or a wall moves, etc.....
Anyways a guy here opens each drawing received  explodes everything because he does not understand how to change the color the block if the definition contains entities where the color is set by a layer other than zero or explicitly set, and changes every layer color to what he already has set 30 times. Also another guy copies the new drawing and paste it next to the old drawing and tries to eyeball what the changes are.

So I gave them some code to change all entities in blocks to layer zero and set ByLayer, and the code below which basic idea is to delete everything in model keeping all block and layer settings, etc... and WBlocking all the entities in from the new drawing. Did it for other reasons and tried to explain to them to freeze layers instead of deleting them because the archs usually already have closed polylines used for getting area that can be reused for automating creating ceiling grid for lights, etc......

Anyways just slapped this together to keep from slapping them and until I have time come up with something better or different workflow, but passes a string for filename by means of SendStringToExecute and  Editor.GetString().

The main reason it is broken up into two methods is because it would crash and did not have time to figure out what was going on but a drawing with dynamic blocks seemed to crash every time.

Thanks Jeff, if I can't find a better way to handle this situation that will be how I approach it.  I think I could get the same result by defining an alternate for my command that would accept file paths via command line instead of through a list.

Cheers

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Passing arguments to command
« Reply #5 on: March 19, 2013, 01:12:54 AM »
I guess you could do something similar to AutoCAD's search paths by joining a collection of strings with a character that is invalid to use in a file path then splitting it.

Code - C#: [Select]
  1. Hard-coded to test quickly.
  2.  
  3.   [CommandMethod("FileList")]
  4.         public void FileList()
  5.         {
  6.            List<string> files =
  7.                new List<string>( Directory.GetFiles(@"J:\Current Jobs\Jobs-12\1228\DrawingPackage\Drawings\Model\FloorPlans"));
  8.            string fileNames = String.Join("|", files);
  9.            Doc.SendStringToExecute("PrintFileList " + fileNames + "\n", false, false, false);
  10.         }
  11.  
  12.  
  13.         [CommandMethod("PrintFileList")]
  14.         public void PrintFileList()
  15.         {
  16.             PromptStringOptions pso = new PromptStringOptions("\nEnter File Names seperated by \"|\"");
  17.             pso.AllowSpaces = true;
  18.             PromptResult pr = Ed.GetString(pso);
  19.             if (pr.Status != PromptStatus.OK)
  20.             {
  21.                 return;
  22.             }
  23.             foreach (string filePath in pr.StringResult.Split(new char[] { '|' }))
  24.             {
  25.                 Ed.Write(filePath);
  26.                 if (File.Exists(filePath))
  27.                 {
  28.                     Ed.Write(" Exists");
  29.                 }
  30.                 else
  31.                 {
  32.                     Ed.Write(" Does Not Exists");
  33.                 }
  34.                 Ed.WriteLine();
  35.             }
  36.         }
  37.  

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Passing arguments to command
« Reply #6 on: March 19, 2013, 01:18:54 AM »
I guess you could do something similar to AutoCAD's search paths by joining a collection of strings with a character that is invalid to use in a file path then splitting it.

Kudos, that's a fairly clean way of passing a large list via command line

TheMaster

  • Guest
Re: Passing arguments to command
« Reply #7 on: March 19, 2013, 01:50:24 AM »
I also have a TreeView-based UI for selecting multiple files (too many dependencies to share that however).

It puts a checkbox next to every file and folder (showing only applicable files), similar to what many backup software apps use to select files for backup. The advantage is that you can check folders and that implicitly selects all files in all subfolders.

However, I'm still not sure I understand what the problem is. In my own modeless UI's, I store the data that will be used in a process somewhere that is accessible to a command (whose CommandMethod may be in the same class or another class), and then I just use SendStringToExecute() to start the command. The command doesn't need to prompt for input, instead it just gets the data that was selected in the modeless UI and does its thing without any hitches.

In the case of the WPF UI thread, you may have to use SendStringToExecute() from the handler of the Application's Idle event, which isn't a big deal.


WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Passing arguments to command
« Reply #8 on: March 19, 2013, 03:57:30 PM »
However, I'm still not sure I understand what the problem is. In my own modeless UI's, I store the data that will be used in a process somewhere that is accessible to a command (whose CommandMethod may be in the same class or another class), and then I just use SendStringToExecute() to start the command. The command doesn't need to prompt for input, instead it just gets the data that was selected in the modeless UI and does its thing without any hitches.
That's the trick, thank you!  Since I've put a deep separation between the code of my UI and my functions I was struggling for a way to pass the data.  I got the idea for the method I used from what I've seen with overloading functions.  I hadn't considered hosting my commands in the same class as the UI, partially because I wasn't aware of exactly how AutoCAD would handle the class creation when the new method is called (i.e. I wasn't sure if AutoCAD would create a new instance of the class to call the method from).

Something else I had considered was to create a static class which contained a stack which I could use to pass data to my functions.

The goal here is to maintain classic user interaction with these functions while adding new capabilities with the minimum of code duplication and rework.
Quote
In the case of the WPF UI thread, you may have to use SendStringToExecute() from the handler of the Application's Idle event, which isn't a big deal.
I don't think that I will have any limitations with that.  The cancel command issue you helped me solve successfully calls SendStringToExecute.

On that note, I think the reason that the error was still being thrown even after the escape characters were being sent and the drawings were opening had to do with document locking.

TheMaster

  • Guest
Re: Passing arguments to command
« Reply #9 on: March 20, 2013, 02:33:10 AM »
However, I'm still not sure I understand what the problem is. In my own modeless UI's, I store the data that will be used in a process somewhere that is accessible to a command (whose CommandMethod may be in the same class or another class), and then I just use SendStringToExecute() to start the command. The command doesn't need to prompt for input, instead it just gets the data that was selected in the modeless UI and does its thing without any hitches.
That's the trick, thank you!  Since I've put a deep separation between the code of my UI and my functions I was struggling for a way to pass the data.  I got the idea for the method I used from what I've seen with overloading functions.  I hadn't considered hosting my commands in the same class as the UI, partially because I wasn't aware of exactly how AutoCAD would handle the class creation when the new method is called (i.e. I wasn't sure if AutoCAD would create a new instance of the class to call the method from).

Something else I had considered was to create a static class which contained a stack which I could use to pass data to my functions.

The goal here is to maintain classic user interaction with these functions while adding new capabilities with the minimum of code duplication and rework.
Quote
In the case of the WPF UI thread, you may have to use SendStringToExecute() from the handler of the Application's Idle event, which isn't a big deal.
I don't think that I will have any limitations with that.  The cancel command issue you helped me solve successfully calls SendStringToExecute.

On that note, I think the reason that the error was still being thrown even after the escape characters were being sent and the drawings were opening had to do with document locking.

I'm not sure what you'd need a stack for. What I usually do is disable the modeless UI until the entire process is finished, so that it can't be reentered.

If you let the user continue to interact with your UI while the process it started is in-progress, that may be possible, but it would also make things more complicated, possibly requiring thread synchronization, and a Queue rather than a Stack.

The UI would add work items to the queue and start the command if it isn't already running, and the running command would  continuously check the queue to see if there's more work items. If there is, the work items are processed and removed from the queue, and that entire process would repeat until the queue is empty.

You only need to store your data in a static class that both your UI and your command implementation have access to, which is preferable to making the CommandMethod a member of your UI class.

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Passing arguments to command
« Reply #10 on: March 20, 2013, 11:51:43 AM »
I'm not sure what you'd need a stack for. What I usually do is disable the modeless UI until the entire process is finished, so that it can't be reentered.

If you let the user continue to interact with your UI while the process it started is in-progress, that may be possible, but it would also make things more complicated, possibly requiring thread synchronization, and a Queue rather than a Stack.

The UI would add work items to the queue and start the command if it isn't already running, and the running command would  continuously check the queue to see if there's more work items. If there is, the work items are processed and removed from the queue, and that entire process would repeat until the queue is empty.

You only need to store your data in a static class that both your UI and your command implementation have access to, which is preferable to making the CommandMethod a member of your UI class.

Thank you very much!  The queue is a better class for passing the data, I was just unaware of it. This is much cleaner than the overload technique I was attempting and will require less modifications to existing code.