WPF window loses control when Revit API displays an error message

Anonymous

WPF window loses control when Revit API displays an error message

Anonymous
Not applicable

Hello,

 

I have created a testapp with a WPF window, with a button, I create the Window from the command class as follows

This is tested in Revit 2014, but will also occur in other versions I am sure...

 

public Result Execute(ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements)
{
Window1 t = new Window1(commandData);
t.ShowDialog();
return Result.Succeeded;
}

 

and which contains the following code in a button click event handler

 

Autodesk.Revit.DB.Family f;
string p1 = @"C:\temp\testje\family.rfa";
Autodesk.Revit.DB.Transaction t = new Autodesk.Revit.DB.Transaction(_comm.Application.ActiveUIDocument.Document);
t.Start("loadf");
_comm.Application.ActiveUIDocument.Document.LoadFamily(p1, out f);
t.Commit();

 

if the family loads correctly this is no problem: the plugin stays in control

 

but if for instance we change the family to a family from a later version of Revit, Revit will tell us that this is the case (which is fine by me, allthough I would prefer no dialogues in my API code)

 

after this, all of a sudden, the Revit UI is back in control, even though the WPF Window is still active??

In Winforms this would not be the case, a modal dialogue is always modal

 

Anyone has an idea what is the issue here and how to deal with it?

 

 

 

0 Likes
Reply
2,584 Views
8 Replies
Replies (8)

ollikat
Collaborator
Collaborator
Hi

I would suspect that exception was thrown somewhere. Usually when .NET exception occurs during the Revit add-in, Revit catches it and tells it in the UI (I'm sure you are familiar with that message dialog). But sometimes for some reason (that at least I'm not aware) a thrown exception is somehow totally ignored and forgotten. In other words you wont get any kind of message about the exception. Your code just has stops executing and Revit receives the control again.

Fortunately Visual Studio provides an easy way of observing exceptions during run time. Just press Ctrl + Alt + E and you will see the dialog called "Exceptions". You need to check the "Thrown" check box for the "Common Language Runtime Exceptions". Now when you have attached the visual studio debugger into Revit process you will be notified for every single .NET exception that happens, even it would be handled by try-catch block. This might help you to trace the problem.
0 Likes

Anonymous
Not applicable
Hello,

thanks for getting back to me.I already had the CLR exceptions box ticke in
the debug environment, and noticed there was none.

In this case I have written a very small client that simply loads a family
which is created in a newer version of Revit than the version that is
running. This causes a messagebox from Revit that displays this mistake.

My point is: the behaviour is different in a Winforms plugin, compared to a
WPF plugin. The 'code behind' is exactly the same between the two clients.

This can easily be reproduced by creating an IExternalCommand derived class
which opens a window with a single button and a click handler. In the click
handler do the loadfamily call that causes Revit to display the message and
you will see the difference.

best regards
Jeroen
0 Likes

ollikat
Collaborator
Collaborator

Hi

Yes...indeed I was also able to reproduce the exact behavior you reported.

Documentation of ShowDialog() says:

"A window that is opened by calling the ShowDialog method does not automatically have a relationship with the window that opened it"

 

So I though maybe assigning a owner window would solve the case. I tried the following:

 

var wnd = new Window1(commandData.Application.ActiveUIDocument.Document);

Process[] processes = Process.GetProcessesByName("Revit");

if (0 < processes.Length)
{
  IntPtr h = processes[0].MainWindowHandle;
  var wih = new System.Windows.Interop.WindowInteropHelper(wnd);
  wih.Owner = h;
}

wnd.ShowDialog();

 

But it didn't help.

 

So...I'm also a bit out of ideas. I agree that the window should not behave like that...it should keep it's modality.

 

0 Likes

Anonymous
Not applicable

Hi thanks again

 

 

I consider this as quite a big issue. What might work (as a workaround) is creating a winforms window, redefining the WPF form to a WPF control and then host this control from within the Winform window

 

it is a ****ty solution, which makes me wonder why nobody before me has reported this

 

Someone should be able to answer this.... and find a solutionn

0 Likes

Anonymous
Not applicable

http://tech.pro/tutorial/799/wpf-tutorial-using-wpf-in-winforms

 

describes a technique on how to host a WPF usercontrol from a Winforms form

I have used that as a workaround. The workaround works, but it is not a great solution. It will have to do for the time being I am afraid. Hope Autodesk will come up with a better solution. Will ask through ADN next week

 

 

0 Likes

Anonymous
Not applicable

I think you need to set the Window.Owner property so it sticks to Revits ApplicationWindow.

 

I've written a class that implements System.Windows.Window for helping me to host WPF UserControl in my API Add-ins for Revit.

 

I use this code to get Revit ApplicationWindow and set it to my class Owner property.

/// <summary>
/// Set this windows owner to Revit ApplicationWindow
/// </summary>
private void SetWindowOwner()
{
      WindowInteropHelper helper = new WindowInteropHelper(this);
      helper.Owner = Autodesk.Windows.ComponentManager.ApplicationWindow;
}

My complete class:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;

namespace MyNamespace
{
    public class UserControlHostWindow : Window
    {

        public string FocusedControlName { get; set; }
        public string WindowPrefixTitle { get; set; }

        /// <summary>
        /// A window for hosting UserControl objects
        /// </summary>
        /// <param name="windowContent">UserControl instance for the windows content</param>
        /// <param name="title">Set the windows title</param>
        public UserControlHostWindow(UserControl windowContent, string title)
        {
            SetWindowOwner();
            WindowStartupLocation = WindowStartupLocation.CenterOwner;

            Content = windowContent;
            SizeToContent = SizeToContent.WidthAndHeight;
            
            WindowPrefixTitle = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
            Title = WindowPrefixTitle + " - " + title;

            Loaded += new RoutedEventHandler(UserControlHostWindow_Loaded);
        }
        
        private void UserControlHostWindow_Loaded(object sender, RoutedEventArgs e)
        {
            if (FocusedControlName != null)
            {
                object c = FindName(FocusedControlName);
                Control cn = c as Control;
                cn.Focus();
            }

            MinWindowSize();

        }
        
        /// <summary>
        /// Set this windows owner to Revit ApplicationWindow
        /// </summary>
        private void SetWindowOwner()
        {
            WindowInteropHelper helper = new WindowInteropHelper(this);
            helper.Owner = Autodesk.Windows.ComponentManager.ApplicationWindow;
        }

        /// <summary>
        /// Set min height and width based on content
        /// </summary>
        private void MinWindowSize()
        {
            if (Content is UserControl)
            {
                UserControl content = Content as UserControl;

                double Border = (ActualWidth - content.MinWidth) / 2;
                double TitlebarHeight = ActualHeight - content.MinHeight - 2 * Border;

                MinHeight = Border + TitlebarHeight + content.MinHeight + Border;
                MinWidth = Border + content.MinWidth + Border;
            }
        }

    }
}

I'm still developing this class so if anyone know of anything else a class like this needs please leave feedback 🙂

 

0 Likes

minet.axel
Enthusiast
Enthusiast

I tried with an ElementHost in a wpf window but I still have the problem of loss of focus

 

FormElementHost : (lose focus)

 

 - Command :

 

 [Transaction(TransactionMode.Manual)]
    public class FormElementHostCommand : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            FormElementHost form = new FormElementHost(commandData);

            form.ShowDialog();

            return Result.Succeeded;
        }
    }

 - Form (System.Windows.Forms.Form) :

 

  public partial class FormElementHost : Forms.Form
    {
        ExternalCommandData _commandData;

        public FormElementHost(ExternalCommandData commandData)
            : this()
        {
            _commandData = commandData;
        }

        public FormElementHost()
        {
            InitializeComponent();
            this.userControl11.ClickEvent += UserControl11_ClickEvent;
        }

        private void UserControl11_ClickEvent(object sender, EventArgs e)
        {
            Family family;
            string message;
            UtilsFamily.LoadFamily(_commandData.Application.ActiveUIDocument.Document, out family, out message);
        }
    }

 

- UserControl WPF (System.Windows.Controls.UserControl) :

 

public partial class UserControl1 : UserControl
    {
        public event EventHandler ClickEvent;

        private void RaisesEventClick()
        {
            if (this.ClickEvent != null)
                this.ClickEvent(this, new EventArgs());
        }

        public UserControl1()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            RaisesEventClick();
        }
    }

 

 

 

 

FormNoElementHost : (not lose focus)

 - Command :

 

 [Transaction(TransactionMode.Manual)]
    public class FormNoElementHostCommand : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            FormNoElementHost form = new FormNoElementHost(commandData);

            form.ShowDialog();

            return Result.Succeeded;
        }
    }

 - Form (System.Windows.Forms.Form) :

 

public partial class FormNoElementHost : Forms.Form
    {
        ExternalCommandData _commandData;

        public FormNoElementHost(ExternalCommandData commandData)
            : this()
        {
            _commandData = commandData;
        }

        public FormNoElementHost()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Family family;
            string message;
            UtilsFamily.LoadFamily(_commandData.Application.ActiveUIDocument.Document, out family, out message);
        }
    }

 

WindowWPF: (lose focus)

 - Command :

    [Transaction(TransactionMode.Manual)]
    public class WindowWPFCommand : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            WindowWPF  window = new WindowWPF(commandData);

            window.ShowDialog();

            return Result.Succeeded;
        }
    }

 - Window (System.Windows.Window) :

 

 

    public partial class WindowWPF : Window
    {
        ExternalCommandData _commandData;

        public WindowWPF(ExternalCommandData commandData)
            : this()
        {
            _commandData = commandData;
        }

        public WindowWPF()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Family family;
            string message;
            UtilsFamily.LoadFamily(_commandData.Application.ActiveUIDocument.Document, out family, out message);
        }
    }

 

 

 

 

 

UtilsFamily :

 

 

    public class UtilsFamily
    {
        public static bool LoadFamily(Document doc, out Family family, out string message)
        {
            family = null;
            message = null;

            // ask user to select a family file
            System.Windows.Forms.OpenFileDialog fileDlg = new System.Windows.Forms.OpenFileDialog();
            fileDlg.Filter = "Revit family (*.rfa)|*.rfa";
            fileDlg.Multiselect = false;
            if (fileDlg.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
                return false;

            using (Transaction tr = new Transaction(doc))
            {
                tr.Start("Load family");

                if (!doc.LoadFamily(fileDlg.FileName, new FamilyOption(), out family))
                {
                    message = "Could not load family !";
                    tr.RollBack();
                    return false;
                }

                tr.Commit();
            }
            return true;
        }
    }

 

 

 

 

 

0 Likes

minet.axel
Enthusiast
Enthusiast

Link of the video with the 3 examples: Link

0 Likes