postable command usage

postable command usage

Ning_Zhou
Advocate Advocate
4,743 Views
44 Replies
Message 1 of 45

postable command usage

Ning_Zhou
Advocate
Advocate

seems postable command usage is quite limited, for instance

1) can only send warning message instead of disable command?

2) OOTB duplicate view will name that duplicated view w/ suffix Copy 1, is it possible to name it differently?

0 Likes
Accepted solutions (1)
4,744 Views
44 Replies
Replies (44)
Message 21 of 45

Sean_Page
Collaborator
Collaborator

Can you tell me which CommandId your trying to catch and I'll test, but I have other bindings that are working with the code as anticipated.

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
0 Likes
Message 22 of 45

Ning_Zhou
Advocate
Advocate

Import CAD, "ID_FILE_IMPORT"

very appreciated for your help spage!

0 Likes
Message 23 of 45

Sean_Page
Collaborator
Collaborator
Accepted solution

So, I feel like this may be a little bit of a hack, but I added a DateTime variable and stored when the DiasableCommand wad called. Then, inside the Idling method I checked to make sure the current time was 3-4 seconds later before firing and creating the binding again.

 

The main reason for this is because the PostCommand is queued for execution not executed right away, and unfortunately the Idler is executed before the PostCommand is.

 

Here is my working solution:

 

    class App : IExternalApplication
    {
        Document doc;
        UIDocument uiDoc;
        DateTime dt;
        UIControlledApplication revitApplication;
public Result OnStartup(UIControlledApplication RevitApplication)
        {
            revitApplication = RevitApplication;
            AddCommandBindings(RevitApplication, "ID_FILE_IMPORT");
            return Result.Succeeded;
        }

private void FileImport_Idling(object sender, IdlingEventArgs args)
        {
            if (DateTime.Now > dt.AddSeconds(3))
            {
                RevitCommandId rCommandId = RevitCommandId.LookupCommandId("ID_FILE_IMPORT");
                AddCommandBindings(revitApplication, "ID_FILE_IMPORT");
                uiApp.Idling -= FileImport_Idling;
            }
        }

        private Result AddCommandBindings(UIControlledApplication uiApp, string name)
        {
            RevitCommandId rCommandId = RevitCommandId.LookupCommandId(name);
            if (rCommandId.CanHaveBinding)
            {
                uiApp.CreateAddInCommandBinding(rCommandId).Executed += new EventHandler<ExecutedEventArgs>(this.DisableCommand);
            }
            else
            {
                MessageBox.Show("Cannot Bind");
            }
            return Result.Succeeded;
        }

        private void DisableCommand(object sender, ExecutedEventArgs args)
        {
            if (MessageBox.Show("Click OK to Continue", "Are you Sure?", MessageBoxButtons.OKCancel) == DialogResult.OK)
            {
                doc = args.ActiveDocument;
                uiDoc = new UIDocument(doc);
                uiDoc.Application.RemoveAddInCommandBinding(args.CommandId);
                revitApplication.Idling += new EventHandler<IdlingEventArgs>(FileImport_Idling);
                dt = DateTime.Now;
                uiDoc.Application.PostCommand(args.CommandId);
            }
        }
    }
}
Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
Message 24 of 45

Ning_Zhou
Advocate
Advocate

good catch spage, works great! in fact, only need about 0.1 second difference, thanks again.

Message 25 of 45

BobbyC.Jones
Advocate
Advocate

I would be nervous relying on timers.  Just know that the Idling event is going to fire twice, and it's the second one you want to act on.  I wrote a CommandEndedMonitor to wrap this.

 

 

    public class CommandEndedMonitor
    {
        public event EventHandler CommandEnded;

        private readonly UIApplication _uiApplication;

        private bool _initializing;

        public CommandEndedMonitor(UIApplication uiApplication)
        {
            _uiApplication = uiApplication;

            _initializing = true;

            _uiApplication.Idling += OnIdling;
        }

        private void OnIdling(object sender, IdlingEventArgs e)
        {
            if (_initializing)
            {
                _initializing = false;
                return;
            }

            _uiApplication.Idling -= OnIdling;

            OnCommandEnded();
        }

        protected virtual void OnCommandEnded()
        {
            CommandEnded?.Invoke(this, EventArgs.Empty);
        }
    }

 

Use it like so:

public void DisableCommand(object sender, ExecutedEventArgs args)
{
    if (MessageBox.Show("Click OK to Continue", "Are you Sure?", MessageBoxButtons.OKCancel) == DialogResult.OK)
            {
                doc = args.ActiveDocument;
                uiDoc = new UIDocument(doc);

                uiDoc.Application.RemoveAddInCommandBinding(args.CommandId);
                
                var commandMonitor = new CommandEndedMonitor(uiDoc.Application);
                commandMonitor.CommandEnded += FileImport_Idling;
              
                uiDoc.Application.PostCommand(args.CommandId);
            }
}


private void FileImport_Idling(object sender, EventArgs args)
{
    //Not sure this line is neccessary, but copied from original code
    var rCommandId = RevitCommandId.LookupCommandId("ID_FILE_IMPORT");

    AddCommandBindings(revitApplication, "ID_FILE_IMPORT");
}

 

--
Bobby C. Jones
0 Likes
Message 26 of 45

Sean_Page
Collaborator
Collaborator

Thanks @BobbyC.Jones for the approach. I had thought about a bool to track this, but I didn't pursue it at the time, but I think I have simplified your approach even more.

 

I think all you need to do is set the bool (idleCheck) in the DIsableCommand method, then check for it in the Idler as you did, but I don't need the other calls.

 

Disable:

 private void DisableCommand(object sender, ExecutedEventArgs args)
        {
            if (MessageBox.Show("Click OK to Continue", "Are you Sure?", MessageBoxButtons.OKCancel) == DialogResult.OK)
            {
                doc = args.ActiveDocument;
                uiDoc = new UIDocument(doc);
                uiDoc.Application.RemoveAddInCommandBinding(args.CommandId);
                Helpers.StaticVariables.revitApplication.Idling += new EventHandler<IdlingEventArgs>(FileImport_Idling);
                idleCheck = true;
                uiDoc.Application.PostCommand(args.CommandId);
            }
        }

 

Idler:

private void FileImport_Idling(object sender, IdlingEventArgs args)
        {
            if(!idleCheck)
            {
                AddCommandBindings(Helpers.StaticVariables.revitApplication, "ID_FILE_IMPORT");
                Helpers.StaticVariables.revitApplication.Idling -= FileImport_Idling;
                return;
            }
            idleCheck = false;
        }

 

 

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
0 Likes
Message 27 of 45

BobbyC.Jones
Advocate
Advocate

Ah, there you go.  Much better than the timer.  Now if you could just find a way to abstract away that pesky little bool, then you'd be cookin'.

--
Bobby C. Jones
Message 28 of 45

Ning_Zhou
Advocate
Advocate

thanks all, that's what i thought too previously, just need a bit logic to know if that command is already fired or not.

0 Likes
Message 29 of 45

Ning_Zhou
Advocate
Advocate

what if i want to continue the command and do something about it, for instance, duplicate view w/ suffix of username, need another event like document changed one? to my understanding, post command can only be used to disable command and/or display dialog message.

0 Likes
Message 30 of 45

Sean_Page
Collaborator
Collaborator

I think at that point you would need to integrate / user Dynamic Model updaters and rename views based on the criteria that needs to be met.

 

However, according to the documentation it doesn't appear there are limits or restrictions on what can be done during a binding method.

 

https://www.revitapidocs.com/2017/a9a2ddeb-f35c-de4f-55b2-83f6fdea7dae.htm 

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
0 Likes
Message 31 of 45

Ning_Zhou
Advocate
Advocate

thanks spage, i noticed that ViewActivated event can be used right after PostCommand so DMU won't be needed in this case, but for other postable commands like create ref plane w/ naming, etc., i may have to use DMU after all, i remembered that i tried DMU (or else?) long time ago (well, i'm not full time developer, more like coder on demand), any good DMU sample will be very much appreciated, have a great weekend!

0 Likes
Message 32 of 45

BobbyC.Jones
Advocate
Advocate

DMU would work, but you can also catch added elements in a DocumentChanged event and process them in the Idle event.  We have, for example, a number of commands on the ribbon that post the ArchitectualFloor command.  After the floors are added they are modified and then tagged.  To the end user it all appears to be a single command.

 

We use an ElementAddedMonitor, a wrapper around the DocumentChanged event, to gather the floors.  Together with the CommandEndedMonitor the entire thing looks similar to this:

 

    public class AddFloor : IExternalCommand
    {
        private ElementAddedMonitor _floorAddedMonitor;

        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            var ofTypeFloor = new ElementClassFilter(typeof(Floor));

            _floorAddedMonitor = 
                new ElementAddedMonitor(commandData.Application.ActiveUIDocument.Document, ofTypeFloor);
            _floorAddedMonitor.Start();

            var commandMonitor = new CommandEndedMonitor(commandData.Application);
            commandMonitor.CommandEnded += OnCommandEnded;

            commandData.Application.PostCommand(
                RevitCommandId.LookupPostableCommandId(PostableCommand.ArchitecturalFloor));

            return Result.Succeeded;
        }

        private void OnCommandEnded(object sender, EventArgs e)
        {
            _floorAddedMonitor.Stop();

            var addedFloorIds = _floorAddedMonitor.ElementIds;

            //'Continue' the command and process the newly added floors.
        }
    }

 

Here is the ElementAddedMonitor

   public class ElementAddedMonitor
    {
        private readonly Document _document;
        private readonly List<ElementId> _elementIds;
        private readonly ElementFilter _filter;

        public ElementAddedMonitor(Document document, ElementFilter filter)
        {
            _document = document;

            _filter = filter;

            _elementIds = new List<ElementId>();
        }

        public IEnumerable<ElementId> ElementIds => 
            _elementIds.Where(id => _filter.PassesFilter(_document, id));

        public void Start()
        {
            _document.Application.DocumentChanged += OnDocumentChanged;
        }

        public void Stop()
        {
            _document.Application.DocumentChanged -= OnDocumentChanged;
        }

        private void OnDocumentChanged(object sender, DocumentChangedEventArgs e)
        {
            var elements = e.GetAddedElementIds(_filter);
            _elementIds.AddRange(elements);
        }
    }

 

 

 

--
Bobby C. Jones
Message 33 of 45

Ning_Zhou
Advocate
Advocate

thanks Bobby, looks like your solution needs to be in cmd scope instead of app scope, means custom command is needed instead of OOTB one, is there a way or workaround?

0 Likes
Message 34 of 45

Ning_Zhou
Advocate
Advocate

recall last message, actually it works in app scope, the only problem is that DocumentChanged event is read-only so you can get added elements but you cannot modify it i.e. rename, well, seems DMU is the only way out

Message 35 of 45

Ning_Zhou
Advocate
Advocate

DMU works great.

Bobby, thanks for your posted codes, you can actually modify the newly added floors because you only used DocumentChanged event to get elements only but not modify them, isn't it? this brings out my question: is it doable to use OOTB command only? i mean without using add-in command, just create floors via OOTB menu, etc. then modify them.

0 Likes
Message 36 of 45

BobbyC.Jones
Advocate
Advocate

Correct, what I showed collects entity ids in the DocumentChanged event and allows modification to the document in the Idling event.  But I only recommend this approach if you need to limit the scope of your modifications around a command you are posting.  If you are not posting a command and need to monitor all entities that meet your criteria, no matter how or when they are added or modified, then DMU is indeed the way to go.

--
Bobby C. Jones
Message 37 of 45

Ning_Zhou
Advocate
Advocate

works great in idling event too, thanks Bobby!

0 Likes
Message 38 of 45

Ning_Zhou
Advocate
Advocate

i now try to override multiple PostableCommands in one Revit session, seems not work well, something like

public Result OnStartup(UIControlledApplication a)

{

UICA.a = a;
AddCommandBinding(a, PostableCommand.ImportCAD);
AddCommandBinding(a, PostableCommand.DuplicateView);
AddCommandBinding(a, PostableCommand.ReferencePlane);

return Result.Succeeded;
}

if i use it individually then it works, something like

public Result OnStartup(UIControlledApplication a)

{

UICA.a = a;
AddCommandBinding(a, PostableCommand.ImportCAD);
//AddCommandBinding(a, PostableCommand.DuplicateView);
//AddCommandBinding(a, PostableCommand.ReferencePlane);

return Result.Succeeded;
}

i must miss something yet to be figured out

0 Likes
Message 39 of 45

Sean_Page
Collaborator
Collaborator

This should work without any issues at all, and I have 4 commands bound in this same fashion. I would ask if  you have any hard string "ID_FILE_IMPORT" etc in your code anywhere? If you do, then you may not be binding / disabling anything but that one command.

 

I would also ask what is it doing or not doing? You say it doesn't work well, but what does that involve exactly?

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
0 Likes
Message 40 of 45

Ning_Zhou
Advocate
Advocate

thanks spage, no hard string "ID_FILE_IMPORT" or "PostableCommand.ImportCAD" etc in code anywhere, somehow the commented-out ViewActivated and DocumentChanged events affect the result even if i only run "import CAD" only, the dialog box won't display every time which is desired, big bump!

DialogResult result = MessageBox.Show("click OK to continue", "MTR BIM & DD", MessageBoxButtons.OKCancel);
if (result == DialogResult.OK)
{
uiApp.RemoveAddInCommandBinding(args.CommandId);
uiApp.PostCommand(args.CommandId);
//uiApp.ViewActivated += new EventHandler<ViewActivatedEventArgs>(OnViewActivated); // for view naming
//uiApp.Application.DocumentChanged += new EventHandler<DocumentChangedEventArgs>(OnDocumentChanged); // for ref plane naming
idleCheck = true;
uiApp.Idling += new EventHandler<IdlingEventArgs>(OnIdling);
}

0 Likes