External Events in a loop

External Events in a loop

Anonymous
Not applicable
3,166 Views
14 Replies
Message 1 of 15

External Events in a loop

Anonymous
Not applicable

Can an external event be raised and its Execute() method be executed inside a while(true) loop?

 

The code below works IF the while(true) loop is removed.

When the while(true) statement remains,

the external event RevitElementSelectionSingleElementCompleted's Execute() is only called when ESC is pressed.

 

I want user to pick an element, the app then updates some element parameter; the user picks another element, followed by another database update, and so on and so forth......until ESC is pressed.

 

Can this concept be implemented? If so, any hints/solutions I can further research is much appreciated. Thanks.

 

Shirley

 

 

private void ProcessIndividualSelection(ExternalCommandData commandData)

{

            UIDocument uidoc = commandData.Application.ActiveUIDocument;

 

            while (true)

            {

                try

                {

                    ArgsSingle.SelectedElement = uidoc.Selection.PickObject(ObjectType.Element, new GenericSelectionFilter(SelectedCategory), "Select an element to renumber. Press ESC to exit renumbering.");

                    ArgsSingle.SelectedPrefix = SelectedPrefix;

                    ArgsSingle.SequenceStartValue = SequenceStartValue;

                    ArgsSingle.SelectedCategory = SelectedCategory;

 

                    if (RevitElementSelectionSingleElementCompleted != null)

                        RevitElementSelectionSingleElementCompleted.Raise();

                }

                catch (Exception ex)

                {

                    Debug.Print(ex.Message);

                }

            }

        }

0 Likes
Accepted solutions (1)
3,167 Views
14 Replies
Replies (14)
Message 2 of 15

matthew_taylor
Advisor
Advisor

Hi Shirley,

I think 'PickObject' fires an exception if ESC is pressed. (I think it is a Autodesk.Revit.Exceptions.OperationCanceledException)

Your try-catch statement is catching that and then once again trapping you in the loop.

As Jeremy would say, exceptions should be exceptional.

So, your blanket catch-all try statement is the issue, and will likely continue to cause looping issues in one way or another.

Add this sort of try statement to limit your loop:

'sorry, in vb.net
dim proceed as Boolean = true While proceed try ArgsSingle.SelectedElement = uidoc.Selection.PickObject(ObjectType.Element, new GenericSelectionFilter(SelectedCategory), "Select an element to renumber. Press ESC to exit renumbering.") catch ex as OperationCanceledException proceed = false 'or use 'exit while' end try end while

 

I don't think it makes sense for this sort of thing to be within an event (if I understood you correctly?). A standalone macro would make much more sense.

 

Cheers,

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
0 Likes
Message 3 of 15

Anonymous
Not applicable

Yes, it makes sense. I shall give it a try. Thanks.

 

Shirley

Message 4 of 15

Anonymous
Not applicable

Hi Matt,

 

I try...catch(OperationCanceledException ex) but the externalevent raised was not executed immediately within the loop, before it returns to the beginning of another iteration of the loop itself.

 

It appears the external event requests are batched and when ESC is pressed, then all changes are made at once, despite of the fact that the event is raised inside the loop.

 

Is there no way to use an external event to update commandData.Application.ActiveUIDocument.Document within a loop, so that any database updates are instantaneous on screen? instead of batch updates after all selection is made or when ESC is pressed.

 

Just want to confirm before I give up on instantaneous db updates within a loop.

 

Thanks a bunch,

 

Shirley

 

0 Likes
Message 5 of 15

matthew_taylor
Advisor
Advisor
Hi Shirley,
There are ways of doing these things. Sometimes it's about finding the right or best way. Hopefully they're the same thing!
How about you tell us your process, best case scenario, in words/code/images, and we'll help you find a workable approach?

Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
0 Likes
Message 6 of 15

Anonymous
Not applicable

Hi Matt,

 

Please see both attachments (description in .docx and zip file in Visual Studio 2013 using C#).

Any suggestions to point me to the right direction is appreciated.

Thanks for your time.

 

Shirley

 

 

 

0 Likes
Message 7 of 15

matthew_taylor
Advisor
Advisor

Hi Shirley,

I think you're making the task a lot more complex than it needs to be.

If you were placing families using promptforfamilyinstanceplacement, I'd say sure, use an event so that you can trap the added families, but an event handler is unnecessary for this operation.

 

The key is, if you want anything to change 'in Revit', it needs to be included in a committed transaction. So, when you're renumbering a series, and want to see your progress, each update needs to be committed.

 

This is what I would do:

1. Show dialog to get settings from the user.

2. Close the dialog.

3. Prompt the user to select an element etc:

dim proceed as Boolean = true
using transGroup as new TransactionGroup(doc,"Renumber Elements")
Transgroup.start
While proceed
dim ref as Reference=nothing
  try
        ref = uidoc.Selection.PickObject(ObjectType.Element, new GenericSelectionFilter(SelectedCategory), "Select an element to renumber. Press ESC to exit renumbering.")
  catch ex as OperationCanceledException
       exit while
  end try
try
  dim element as Element = doc.GetElement(ref)
  using trans as new Transaction (doc,"Update Element Mark")
    trans.Start
    'set parameter value here.
'increment counter trans.commit end using catch ex as exception proceed=false end try end while transGroup.assimilate End Using 'always return succeeded. return result.succeeded

 

4. Always returning succeeded is important.

5. Done.

 

Let me know if you need me to clarify anything....it's just quasi-code!

 

Cheers,

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
0 Likes
Message 8 of 15

Anonymous
Not applicable

Hi Matt,

 

I think we are getting very close to making it work.

 

Here is my updated code:

 

namespace RevitElementRenumber2017
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
 
    public class RevitElementRenumber2017 : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            try
            {
                var menu = new ElementRenumberMenu(commandData) {StartPosition = FormStartPosition.CenterScreen};//Publisher
                menu.Show(); //menu.Show would be modaless; modaless form starts a separate thread and transaction.manual will be errored out!
            }
            catch (Exception)
            {
                return Result.Failed;
            }
            return Result.Succeeded;
        }
    }
}

  menu.Show() displays the form and when user clicks the PickElement button on the form, the btn_click event does this:

 

private void btnPickElement_Click(object sender, EventArgs e)
       {
           Visible = false;
           var UIErrorFound = ErrorChecking();
           if (UIErrorFound) return;
           SelectedPrefix = txtPrefix.Text;
           SequenceStartValue = txtSequenceStartingPoint.Text.Trim();
           SelectedCategory = cbCategory.SelectedItem.ToString().Trim();
 
           switch (SelectedCategory)
           {
               case "Plumbing Fixtures":
               case "Mechanical Equipment":
                   RenumberSingleElement(cmdData);
                   break;
               case "Viewports":

 

The RenumberSingleElement(cmdData) has been updated to what you suggested and it looks like this now:

 

private bool RenumberSingleElement(ExternalCommandData commandData)
        {
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            Document dbdoc = uidoc.Document;
 
            int startValue;
            int.TryParse(SequenceStartValue, out startValue);
            var counter = startValue;
            bool proceed = true;
 
            using (var t1 = new TransactionGroup(dbdoc, "Select Single Element"))
            {
                t1.Start();
                while (proceed)
                {
                    Reference SelectedElement;
                    try
                    {
                        SelectedElement = uidoc.Selection.PickObject(ObjectType.Element, new GenericSelectionFilter(SelectedCategory), "Select an element to renumber. Press ESC to exit renumbering.");
                    }
                    catch (OperationCanceledException ex)
                    {
                        break;
                    }
 
                    try
                    {
                        using (var t2 = new Transaction(dbdoc, "Renumber Single Element"))
                        {
                            t2.Start();
                            if (string.IsNullOrEmpty(SelectedPrefix))
                            {
                                Parameter p = GetParameterForReference(dbdoc, SelectedElement);
                                var newLabel = counter.ToString();
                                SetParameterToValue(dbdoc, SelectedElement, p, newLabel);
                                counter++;
                            }
                            else
                            {
                                Parameter p = GetParameterForReference(dbdoc, SelectedElement);
                                var newLabel = String.Format("{0}{1}", SelectedPrefix, counter);
                                SetParameterToValue(dbdoc, SelectedElement, p, newLabel);
                                counter++;
                            }
 
                            var TransactionResult = t2.Commit();
 
                            if (TransactionResult == TransactionStatus.Committed) return false;
                        }
                    }
                    catch (Exception ex)
                    {
                        proceed = false;
                    }
                }
                t1.Assimilate();
            }
            return true;
        }

The current issue is whenever it hits the inner transaction t2, it errored out on the t2.Start() showing error "Starting a transaction from an external application running outside of API context is not allowed, which was why I used the External Event but the External Event won't fire inside a while loop but worked great if the user selects ALL the elements at once, then External Event is used to do db update but that does not offer the instantaneous screen update.  Instantaneous updates to me means when "pressing ESC on the updated param values on screen" WILL NOT reset the parameter values back to before-change.

 

So may be I am NOT setting the transaction mode correctly or the transactions in proper sequence.  What did I miss?

Thanks again for your patience and expertise.

 

Shirley

 

0 Likes
Message 9 of 15

Anonymous
Not applicable

Also, the menu.Show(); being modaless, which always starts a separated thread, hence the external event solution; using external event to try to get back inside the API thread, but that does not work or I have not figured out how to make it work for instantaneous screen updates yet.

 

I tried menu.ShowDialog() which totally does not work, it just froze up the whole thing!

0 Likes
Message 10 of 15

matthew_taylor
Advisor
Advisor

Hi Shirley,

You're best to put all of your code in a simple external command that you test using the AddinManager.

Once you have it running there, start adding whatever else you need, or rearrange the code more palatably.

 

My previously stated step 2 is the key. By trying to keep the dialog modeless, you are complicating a relatively simple macro.

There's no need for the form to be modeless (as far as I can tell). If the user wants to carry on with different settings, they can just hit <Enter> and the command will repeat, showing the dialog again.

You could even arrange the macro so that hitting ESC the first time takes you back to the settings dialog, and a second ESC exits the command.

 

But first, before adding the bells and whistles, start with a small macro that works in a simple way, without an interface.

Once that works, everything else should fall into place.

 

As Jeremy likes to say. KISS - keep it simple!

 

Let us know how you get on.

 

Cheers,

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
0 Likes
Message 11 of 15

Anonymous
Not applicable

Hi Matt,

 

Well, using the KISS (Keep It Simple and _____) principle (HAHA), I have the following macro created at the Application Level. 

Well, it worked until I pressed ESC, then it crashed (please see attachment).

Any suggestions? or may be instantaneous screen update cannot be done via Revit API at this time?
I don't want to give up until I know for sure.

Sorry to bother you repeatedly.

 

 

using System;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using System.Collections.Generic;
using System.Linq;

namespace ElementRenumbering
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.DB.Macros.AddInId("EA480D88-6753-4169-8F7B-BB78740F6731")]
    public partial class ThisApplication        
    {        
        private void Module_Startup(object sender, EventArgs e)
        {
            
        }
        
        private void Module_Shutdown(object sender, EventArgs e)
        {

        }
        
        public void ProcessElementRenumbering()
        {
            UIDocument uidoc = ActiveUIDocument;
            Document dbdoc = uidoc.Document;        
            
            string SelectedPrefix = "SSR-";    
            string startSequence = "100";
            string SelectedCategory = "Mechanical Equipment";
            bool proceed = true;
            
            int counter = Convert.ToInt32(startSequence);            
            
            using (var t1 = new TransactionGroup(dbdoc, "Select Single Element"))
            {
                t1.Start();
                while (proceed)
                {
                    Reference SelectedElement;
                    try
                    {
                        SelectedElement = uidoc.Selection.PickObject(ObjectType.Element, new GenericSelectionFilter(SelectedCategory), "Select an element to renumber. Press ESC to exit renumbering.");
                    }
                    catch (OperationCanceledException)
                    {                        
                        break;
                    }

                    try
                    {
                        using (var t2 = new Transaction(dbdoc, "Renumber Single Element"))
                        {
                            t2.Start();
                            if (string.IsNullOrEmpty(SelectedPrefix))
                            {
                                Parameter p = GetParameterForReference(dbdoc, SelectedElement);
                                var newLabel = counter.ToString();
                                SetParameterToValue(dbdoc, SelectedElement, p, newLabel);
                                counter++;
                            }
                            else
                            {
                                Parameter p = GetParameterForReference(dbdoc, SelectedElement);
                                var newLabel = String.Format("{0}{1}", SelectedPrefix, counter);
                                SetParameterToValue(dbdoc, SelectedElement, p, newLabel);
                                counter++;
                            }

                            t2.Commit();                            
                        }
                    }
                    catch (Exception)
                    {
                        proceed = false;                      
                    }
                }
                t1.Assimilate();                                
            }                
        }
        
        private Parameter GetParameterForReference(Document dbdoc, Reference r)
        {
            Element e = dbdoc.GetElement(r);
            Parameter p = null;

            if (e is Viewport)
                p = e.get_Parameter(BuiltInParameter.VIEWPORT_DETAIL_NUMBER);
            else if (e is FamilyInstance)
            {
                switch (e.Category.Name)
                {
                    case "Mechanical Equipment":
                    case "Plumbing Fixtures":
                        p = e.GetParameters("SSR Tag Identity").FirstOrDefault();
                        break;
                }
            }
            else
            {
                TaskDialog.Show("Error""Unsupported element.");
                return null;
            }

            return p;
        }

        private void SetParameterToValue(Document dbdoc, Reference r, Parameter p, string newValue)
        {
            Element e = dbdoc.GetElement(r);
            try
            {
                switch (p.StorageType)
                {
                    case StorageType.String:
                        p.Set(newValue);
                        break;
                    case StorageType.Integer:
                        p.Set(Convert.ToInt32(newValue));
                        //p.SetValueString(newValue);  //use if type is Double
                        break;
                }

                #region Trick to Refresh UI by moving the elements a tiny bit and then move it back to its original location
                e.Location.Move(XYZ.BasisX);
                e.Location.Move(XYZ.BasisX.Negate());
                #endregion
            }
            catch (Exception)
            {
            }
        }

        #region Revit Macros generated code
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(Module_Startup);
            this.Shutdown += new System.EventHandler(Module_Shutdown);
        }
        #endregion
    }

0 Likes
Message 12 of 15

matthew_taylor
Advisor
Advisor
Accepted solution

Hi Shirley,

Errr, that's not simple....!!

Here's a  simple proof of concept that works fine (only small modifications from what I posted earlier, one of which was making sure that the Revit exception was caught, and not the system exception):

Imports Autodesk.Revit
Imports Autodesk.Revit.Attributes
<Transaction(TransactionMode.Manual)> _ <Regeneration(RegenerationOption.Manual)> _ <Journaling(JournalingMode.NoCommandData)> _ Public Class InternalPickRenumber Implements UI.IExternalCommand #Region "IExternalCommand Members Implementation" Public Function Execute(ByVal commandData As UI.ExternalCommandData, ByRef message As String, ByVal elements As DB.ElementSet) As UI.Result Implements UI.IExternalCommand.Execute Dim proceed As Boolean = True Dim doc As DB.Document = commandData.Application.ActiveUIDocument.Document Dim uiDoc As UI.UIDocument = commandData.Application.ActiveUIDocument Dim counter As Integer = 0 Using transGroup As New DB.TransactionGroup(doc, "Renumber Elements") transGroup.Start() While proceed Dim ref As DB.Reference = Nothing Try ref = uiDoc.Selection.PickObject(UI.Selection.ObjectType.Element, _ New WsprCategoryFilter(DB.BuiltInCategory.OST_StructuralColumns), _ "Select an element to renumber. Press ESC to exit renumbering.") Catch ex As Autodesk.Revit.Exceptions.OperationCanceledException Exit While End Try Try Dim element As DB.Element = doc.GetElement(ref) Using trans As New DB.Transaction(doc, "Update Element Mark") trans.Start() WsprParameters2.SetValue(element, _
DB.BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS, counter.ToString) counter = counter + 1 'set parameter value here. 'increment counter trans.Commit() End Using Catch ex As Exception proceed = False End Try End While transGroup.Assimilate() End Using 'always return succeeded. Return UI.Result.Succeeded End Function #End Region End Class

 

You can see it working here:

http://autode.sk/2iELgER

 

Just get your settings from your modal dialog, close that dialog, then open the transactionGroup.

*In an ExternalCommand*. By all means use the application level to create a ribbon item, but it's not necessary for this macro.

 

KISS!!!

 

Cheers,

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
0 Likes
Message 13 of 15

Anonymous
Not applicable

Thank you Matt.....I finally got it to work exactly the way I want it. Thanks for being so patient (particularly with this non-Revit user).

0 Likes
Message 14 of 15

matthew_taylor
Advisor
Advisor

Hi @Anonymous,

Great news! You're welcome. 🙂

 

Cheers,

 

-Matt


Cheers,

-Matt
_______________________________________________________________________________
Marking a post as a 'solution' helps the community. Giving a post 'Kudos' is as good as saying thanks. Why not do both?
Message 15 of 15

joshua.lumley
Advocate
Advocate

Yes it is very possible, but if your changing variables on every loop then your IExternalEventHandler must be ICloneable and called in a special method like this:

aPublicAsyncMethod(ExternalEvent.Create(((_934_PRLoogle_Command06_EE15_CreateNewTags)myWindow2.my__934_PRLoogle_Command06_EE15_CreateNewTags.Clone())));


public async void aPublicAsyncMethod(ExternalEvent myExternalEvent)
        {            myExternalEvent.Raise();

            while (myExternalEvent.IsPending)
            {                await System.Threading.Tasks.Task.Delay(200); 
            }
        }