When using PaletteSet as UI to allow user to interact with AutoCAD (i.e. letting AutoCAD do particular processing), it is common practice to use Document.SendStringToExecute() to call AutoCAD command, either built-in one, or custom-built one. PaletteSet is a modeless UI, floating on top of AutoCAD window and user can freely change the focus between AutoCAD window, or the PaletteSet window. This would cause issue when a command is active with AutoCAD (usually, it is in the middle of the command, waiting for user input) and user goes to the PaletteSet to trigger another command, as described in the question posted in the discussion forum, such as clicking a button to call a command to insert a block. In this case, the interaction with PaletteSet either results in AutoCAD command line showing error message; or nothing happens at AutoCAD command line - the active command is still waiting to be either completed, or cancelled.
One way to handle this issue is always test if there is active command in PaletteSet' user interaction event handler first, and only goes ahead when there is no active command.
The other approach is whenever PaletteSet's user interaction even is triggered, always cancel any active command first, just like we usually do with any menu/toolbar/ribbon item macro: prefixing it with "^C^C" to cancel possible active command before the macro is called.
Obviously the latter approach would be desirable in most cases and and more compliant with AutoCAD conventions.
So, here is my solution to the question posted in the forum aforementioned.
Firstly I created a custom PaletteSet. One should ALWAYS derive custom PaletteSet from Autodesk.AutoCAD.Windows.PaletteSet class. DO NOT directly use PaletteSet class without subclass it.
Following is the code of the Palette (System.Windows.Forms.UserControl), which simply has 2 buttons; each button's Tag property is given a valid block name; that means, clicking on each button would trigger a block inserting command. Since the UI is very simple, I only show it code behind here:
using System; using System.Windows.Forms; namespace SendCommandFromPaletteSet { public partial class BlockPalatte : UserControl { public BlockPalatte() { InitializeComponent(); } public event BeginBlockInsertingEventHandler BeginBlockInserting; // the 2 buttons' Click event is wired to this event handler private void ButtonClick(object sender, EventArgs e) { var tag = ((Control)sender).Tag; if (tag!=null) { var blkName = tag.ToString(); if (!string.IsNullOrEmpty(blkName)) { BeginBlockInserting?.Invoke( sender, new BeginBlockInsertingEventArgs(blkName)); } } } } }
Here is the class MyBlockPaletteSet:
using System; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Windows; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; namespace SendCommandFromPaletteSet { public class MyBlockPaletteSet : PaletteSet { private BlockPalatte _blkPalette; private bool _doInsertion = false; public string CurrentBlockName { private set; get; } public MyBlockPaletteSet() : base( "My Block PaletteSet", "BlkPs", new Guid("B33AE81D-0FBB-49EA-83D2-62D667EEDDCA")) { this.Style = PaletteSetStyles.ShowCloseButton | PaletteSetStyles.UsePaletteNameAsTitleForSingle | PaletteSetStyles.Snappable; this.MinimumSize = new System.Drawing.Size(400, 400); this.KeepFocus = true; _blkPalette = new BlockPalatte(); Add("Blocks", _blkPalette); _blkPalette.BeginBlockInserting += BlkPalette_BeginBlockInserting; } private void BlkPalette_BeginBlockInserting(object sender, BeginBlockInsertingEventArgs e) { if (!string.IsNullOrEmpty(e.BlockName)) { var dwg = CadApp.DocumentManager.MdiActiveDocument; CurrentBlockName = e.BlockName; var cmdActive = Convert.ToInt32(CadApp.GetSystemVariable("CMDACTIVE")); if (cmdActive>0) { dwg.CommandCancelled += Dwg_CommandCancelled; _doInsertion = true; dwg.SendStringToExecute("\x03\x03", false, true, false); } else { DoBlockInsert(dwg); } } } private void Dwg_CommandCancelled(object sender, CommandEventArgs e) { var dwg = CadApp.DocumentManager.MdiActiveDocument; if (_doInsertion) { dwg.CommandCancelled -= Dwg_CommandCancelled; _doInsertion = false; DoBlockInsert(dwg); } } private void DoBlockInsert(Document dwg) { CadApp.MainWindow.Focus(); dwg.SendStringToExecute("InsBlk ", true, false, false); } } public class BeginBlockInsertingEventArgs : EventArgs { public string BlockName { private set; get; } public BeginBlockInsertingEventArgs(string blkName) { BlockName = blkName; } } public delegate void BeginBlockInsertingEventHandler(object sender, BeginBlockInsertingEventArgs e); }
As the code shows, the user interaction event (clicking the buttons) in the Palette is bubbled to the custom PaletteSet, where the actual command execution is called (via SendStringToExecute()). Also, custom PaletteSet conveys the user input information (what block is to insert).
The trick of dealing the issue raised in aforementioned question is to test if there is active command with current active document by examine system variable "CMDACTIVE". If no, go ahead to send command to execution; if yes, hook up the CommandCancelled event of active drawing, and then send "^C^C" as command to cancel active command, what ever it is, and then set a flag to indicate new command is waiting to be executed. Therefore, the Command_Cancelled event handler would be triggered and pending command from user interaction with Palette is sent to execution.
Following is the command class that shows the custom PaletteSet and does the actual work: inserting multiple blocks in a loop whenever user clicks a button in the Palette. To simplify the code, I only have code to let user to pick insertion point in a loop until user either cancels the loop, or click the button in the Palette, which cancels the active point picking loop. Here is the code:
using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: CommandClass(typeof(SendCommandFromPaletteSet.Commands))] namespace SendCommandFromPaletteSet { public class Commands { private static MyBlockPaletteSet _blkPs = null; [CommandMethod("BlkPs", CommandFlags.Session)] public static void RunCommand() { if (_blkPs==null) { _blkPs = new MyBlockPaletteSet(); } _blkPs.Visible = true; } [CommandMethod("InsBlk", CommandFlags.NoHistory )] public static void InsertBlock() { if (_blkPs == null || !_blkPs.Visible) return; var blkName = _blkPs.CurrentBlockName; InsertBlock(blkName); } #region private methods private static void InsertBlock(string blkName) { var dwg = CadApp.DocumentManager.MdiActiveDocument; var ed = dwg.Editor; var count = 0; while(true) { if (!PickInsertionPoint(ed, out Point3d pt)) { break; } else { count++; ed.WriteMessage($"Inserting block \"{blkName}\" #{count}..."); } } } private static bool PickInsertionPoint(Editor ed, out Point3d pt) { pt = Point3d.Origin; var res = ed.GetPoint("\nSelect block position:"); if (res.Status== PromptStatus.OK) { pt = res.Value; return true; } else { return false; } } #endregion } }
Watch this video clip for the code in action. As the video clip shows, when there is active command waiting for user input, be it AutoCAD built-in command, or a custom command, or the command started by the PaletteSet, whenever user clicks a button in the PaletteSet, the active command is cancelled, and whatever command tied to the button-click starts.