Which event do I need when user switch between Documents

My Add-in crashes when user switch to another opened document, while WPF-Window stays opened. I started the window in modeles mode and assigned an ExternalEvent. What do I need to do when CurrentUIDocument changes?

Thank you!

I’m not 100% sure if it works, but maybe try detecting a view activated event, then compare the document before/after the event fires. Not sure if switching documents counts as a new view activation, but i’m assuming it does. There is a ViewActivating event as well before the other event occurs, so maybe you coule catch that and then wait for the next ViewActivated event to check for the document?

I’ll try it in pyRevit later on today hopefully as I can check for that event using their hooks system, but my C# is too basic.

Update: Got it working in pyRevit using a temporarily stored file (using its store/load data). I’m sure C# can do a similar thing by holding onto the document title and updating it whenever you leave a view. The code I used below in two hooks triggered by ViewActivated and ViewActivating are below:

# import libraries
from pyrevit import revit,script

# get document
doc = revit.doc
data = doc.Title

# try to write data
try:
	script.store_data("Prior doc", data, this_project=False)
except:
	pass
# import libraries
from pyrevit import revit,script

# get document
doc = revit.doc
data = doc.Title

# try to write data
try:
	priorTitle = script.load_data("Prior doc", this_project=False)
	if priorTitle != data:
		# Refresh your tool's UI here
		print("Changed document")
except:
	pass
2 Likes

Why don’t you just subscribe to the DocumentChanged event and automatically close down your application? The user can then re-open it if they open another document. It might seem counter intuitive, but the user will get used to it and realize that your apps lifetime is constrained to the lifetime of a Revit document. Its going to avoid a lot of complications and keeps it simple on the dev side.

2 Likes

Isn’t the DocumentChanged event for transactions in the current documents? I mean, if an element is being deleted or updated?

 This event is raised whenever a Revit transaction is either committed, undone or redone. This is a readonly event, designed to allow you to keep external data in synch with the state of the Revit database. To update the Revit database in response to changes in elements, use the IUpdater framework. 

@GavinCrump thank you very much Gavin! I also tought it could be ViewActivated event but I am not sure…

@Thomas_Mahon how can I pass my window to that event to close it when DocumentChanged?

I meant DocumentClosing event. In respect to where you would subscribe it ultimately depends on your software architecture. Typically, I subscribe in an application services class, but if your app doesn’t need it then I guess what you’ve done works just as well. To pass the window to your event handler is dead simple; just store it in a private field, then call the close method in your event handler.

EDIT: You don’t need EventRegisterHandler class, just declare the event handler privately in your class implementing IExternalCommand / with the Execute method. Then you can access the private field.

1 Like

I need EventRegisterHandler because of the fact that I am using the modeless WPF window. The command is like that:

User selects a curtain wall
and every time the selection in UI changes, it selects grid lines based on criterias. Without that external event I could not select the element in Revit.
grafik

It works like automatic mode in Dynamo. I integrated INotifiyPropertyChanges interface when something changes. In that window I passed uiDoc as property and OnPropertyChanged(“uiDoc”) but it does not the job.

public class ElementToSelect: OberservableObject
    {
        private UIDocument _uiDoc;
        public UIDocument uiDoc { get { return _uiDoc; } set { _uiDoc = value; OnPropertyChanged("uiDoc"); } }
        private Wall _element;
        public Wall element { get { return _element; } set { _element = value; OnPropertyChanged("element"); } }
        private string _elementName = "Fassade auswählen";
        public string elementName { get { return string.IsNullOrEmpty(_elementName) ? _elementName : _element.Name; }}
        private CurtainGrid _curtainGrid  { get { return _element.CurtainGrid; } }
        
        // Versatz und Jede N. Element
        private int _slider=1;
        public int slider { get { return _slider; } set { _slider = value; OnPropertyChanged("slider"); } }

        private int _versatzSlider=0;
        public int versatzSlider { get { return _versatzSlider; } set { _versatzSlider = value; OnPropertyChanged("versatzSlider"); } }

        // Richtung
        private bool _reverse;
        public bool reverse { get { return _reverse; } set { _reverse = value; OnPropertyChanged("reverse"); } }

        // Vertikales Raster
        private bool _vertikal;
        public bool vertikal { get { return _vertikal; } set { _vertikal = value; OnPropertyChanged("vertikal"); } }

        //Horizontales Raster
        private bool _horizontal;
        public bool horizontal { get { return _horizontal; } set { _horizontal = value; OnPropertyChanged("horizontal"); } }

        //Rasterlinien
        public IList<ElementId> GridIds
        {
            get
            {
                if (vertikal == true)
                {
                    IList<ElementId> elemendIds = _curtainGrid.GetVGridLineIds().ToList();
                    if (reverse == true)
                    {
                        IList<ElementId> reversedList = Enumerable.Reverse(elemendIds).ToList();
                        return reversedList.Cast<ElementId>().Where((_, i) => (i + 1 - versatzSlider) % slider == 0).ToList();
                    }
                    else return elemendIds.Cast<ElementId>().Where((_, i) => (i + 1 - versatzSlider) % slider == 0).ToList();

                }
                else
                {
                    IList<ElementId> elemendIds = _curtainGrid.GetUGridLineIds().ToList();
                    if (reverse == true)
                    {
                        IList<ElementId> reversedList = Enumerable.Reverse(elemendIds).ToList();
                        return reversedList.Cast<ElementId>().Where((_, i) => (i + 1 - versatzSlider) % slider == 0).ToList();
                    }
                    else return elemendIds.Cast<ElementId>().Where((_, i) => (i + 1 - versatzSlider) % slider == 0).ToList();
                }

            }
        }
    }

Its ok I miss read your code; I thought it was a class just for the event handler then I noticed you were implementing IExternalEventHandler after I posted so ignore what I said re IExternalCommand (I always use IExternalCommand even for modeless applications and implement IExternalEventHandler in a dedicated async task runner class to invoke transactions).

You can still follow all the steps I suggested in any case, just shift the subscription to DocumentClosing to your Execute method of your IExternalCommand implementation.

1 Like

I hope, I have done it correctly :slight_smile:

[Transaction(TransactionMode.Manual)]
    public class FassadenRaster : IExternalCommand
    {
        private UIDocument uiDoc = null;
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            try
            {
                UIDocument doc = commandData.Application.ActiveUIDocument;
                uiDoc = doc;
                var fenster = new FassadenRasterModelView(uiDoc);
                fenster.rasterAuswahl.Tag = commandData.Application.ActiveUIDocument;
                ExternalEvent _exEvent;
                EventRegisterHandler _exeventHander = new EventRegisterHandler();
                _exeventHander.fenster = fenster;
                _exEvent = ExternalEvent.Create(_exeventHander);
                fenster.ExEvent = _exEvent;
                fenster.Show();
                return Result.Succeeded;
            }
            catch
            {
                return Result.Failed;
            }
        }

        public class EventRegisterHandler : IExternalEventHandler
        {
            public bool EventRegistered { get; set; }
            public FassadenRasterModelView fenster { get; set; }
            public void Execute(UIApplication app)
            {
                if (EventRegistered)
                {
                    EventRegistered = false;
                    app.Application.DocumentClosing -= Application_DocumentChanged;
                }
                else
                {
                    EventRegistered = true;
                    app.Application.DocumentClosing += Application_DocumentChanged;
                }
            }

            void Application_DocumentChanged(object sender,
                Autodesk.Revit.DB.Events.DocumentClosingEventArgs e)
            {
                fenster.Close();

            }

            public string GetName()
            {
                return "EventRegisterHandler";
            }
        }
    }

Unfortunately it did not close the window :frowning:

here seem to be the problem. uiDoc stays same when I open another document. If I click on a radio button, it crashes Revit.

    public partial class FassadenRasterModelView : Window
    {
        readonly UIDocument document;
        public ExternalEvent ExEvent { get; set; }
        private string[] oben_unten = { "Oben -> Unten", "Unten -> Oben" };
        private string[] rechts_links = { "Rechts -> Links", "Links -> Rechts" };


        MainViewModel main = new MainViewModel();

        public FassadenRasterModelView(UIDocument doc)
        {
            document = doc;
            
            if (ExEvent != null)
            {
                ExEvent.Raise();
            }
                InitializeComponent();
            DataContext = main;
            
        }}

It looks like that: FassadenRasterModelView creates a class of MainViewModel which also use ElementToSelect Class. I binded properties with ObersableObject class when properties change.

I’ve not tested this but you want to do something like this:

private FassadenRasterModelView _fenster;
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            try
            {
            	var application = commandData.Application;

                UIDocument doc = application.ActiveUIDocument;
                uiDoc = doc;
                _fenster = new FassadenRasterModelView(uiDoc);
                _fenster.rasterAuswahl.Tag = commandData.Application.ActiveUIDocument;
                ExternalEvent _exEvent;
                EventRegisterHandler _exeventHander = new EventRegisterHandler();
                _exEvent = ExternalEvent.Create(_exeventHander);
                _fenster.ExEvent = _exEvent;

                application.DocumentClosing += OnDocumentCosing;

                _fenster.Show();
                return Result.Succeeded;
            }
            catch
            {
                return Result.Failed;
            }
        }

        void OnDocumentCosing(object sender,
                Autodesk.Revit.DB.Events.DocumentClosingEventArgs e)
            {
            	var application = e.Document.Application

            	application.DocumentClosing -= OnDocumentCosing;

                _fenster.Close();
            }
1 Like

Thank you very much Thomas! Unfortunately it does not close the window when I open another document. If I close current document, it kills the window perfectly! As @GavinCrump mentioned ViewActivated would do the job in my case:

            void OnViewActivated(object sender, Autodesk.Revit.UI.Events.ViewActivatedEventArgs e)
            {
                if(docName != e.Document.Title)
                {
                    _fenster.Close();
                }
            }

I also integrated you suggestions as well when user creates, closes or opens a new document:

        private UIDocument uiDoc = null;
        private FassadenRasterModelView _fenster;
        private string docName;
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            try
            {
                UIDocument doc = commandData.Application.ActiveUIDocument;
                uiDoc = doc;
                docName = doc.Document.Title;
                _fenster = new FassadenRasterModelView(uiDoc);
                ExternalEvent _exEvent;
                EventRegisterHandler _exeventHander = new EventRegisterHandler();
                _exeventHander.fenster = _fenster;
                _exEvent = ExternalEvent.Create(_exeventHander);
                _fenster.ExEvent = _exEvent;
                commandData.Application.Application.DocumentClosing += OnDocumentCosing;
                commandData.Application.Application.DocumentOpened += OnDocumentOpened;
                commandData.Application.Application.DocumentCreating += OnDocumentCreating;
                commandData.Application.Application.DocumentChanged += OnDocumentChanged;
                commandData.Application.ViewActivated += OnViewActivated;
                _fenster.Show();
                return Result.Succeeded;
            }
            catch
            {
                return Result.Failed;
            }
            void OnDocumentCosing(object sender,
                    Autodesk.Revit.DB.Events.DocumentClosingEventArgs e)
            {
                var application = e.Document.Application;


                application.DocumentClosing -= OnDocumentCosing;

                _fenster.Close();
            }

            void OnDocumentChanges(object sender, ViewActivatedEventArgs e)
            {

                _fenster.Close();
            }
            void OnDocumentOpened(object sender, Autodesk.Revit.DB.Events.DocumentOpenedEventArgs e)
            {
                _fenster.Close();
            }
            void OnDocumentCreating(object sender, Autodesk.Revit.DB.Events.DocumentCreatingEventArgs e)
            {
                _fenster.Close();
            }
            void OnDocumentChanged(object sender, Autodesk.Revit.DB.Events.DocumentChangedEventArgs e)
            {
                _fenster.Close();
            }
            void OnViewActivated(object sender, Autodesk.Revit.UI.Events.ViewActivatedEventArgs e)
            {
                if(docName != e.Document.Title)
                {
                    _fenster.Close();
                }
            }
        }

        public class EventRegisterHandler : IExternalEventHandler
        {
            public bool EventRegistered { get; set; }
            public FassadenRasterModelView fenster { get; set; }
            public void Execute(UIApplication app)
            {
                if (EventRegistered)
                {
                    EventRegistered = false;
                }
                else
                {
                    EventRegistered = true;
                }
            }

            

            public string GetName()
            {
                return "EventRegisterHandler";
            }
        }
    }

Now it works like a charm! Thank you very much guys!

It would be way cooler if we could re-assign currentUIDocument again without initialize the window again. I think the problem is here that I used uiDoc as parameter not as property.
here:

_fenster = new FassadenRasterModelView(uiDoc);

aaaaand it’s done:

            // Ereignis für den Wechsel zwischen den Dokumenten
            void OnViewActivated(object sender, Autodesk.Revit.UI.Events.ViewActivatedEventArgs e)
            {
                if(docName != e.Document.Title)
                {
                    //_fenster.Close();
                    UIApplication uiApp = sender as UIApplication;
                    uiDoc = uiApp.ActiveUIDocument;
                    _fenster.document = uiApp.ActiveUIDocument;
                    docName = e.Document.Title;
                }
            }
1 Like