Let's talk about doing events, from two radically different perspectives, one great big external one and lots of teeny-weeny little internal ones.
I'll share some pictures from the European DevDay conference and snow in Munich today, then discuss a WPF issue that came up last week:
DataContext
Today we held the one and only European DevDay conference in Munich:
Jaime Rosales Duque came all the way from New York to help, and Jim Quanci even further, from San Francisco.
We have participants from all over Europe and even some from India.
The next few days are dedicated to an abbreviated Cloud Accelerator here that I am looking forward to very much.
Meanwhile, here is Maciej Szlek's WPF issue and solution:
Exactly two months back, we discussed PickPoint with WPF and no threads attached.
Now another modeless WPF issue was raised and solved by Maciej 'Max' Szlek:
Question: I'm creating a WPF addin that performs operations which will take much time.
During this time the add-in dialogue don't respond until the operation ends, even when calling the external event Raise method by view-model command.
Do you have some workaround to achieve dialogue responsively during performing API operations?
Answer: Yes, I have heard about similar issues in the past involving blocking of modeless WPF forms.
Unfortunately, I cannot find the relevant thread any longer.
The workarounds involved stuff like setting the window focus, e.g. using GetForegroundWindow and SetForegroundWindow, and allowing the WPF form to access the Windows message queue.
I think Revit was somehow blocking the message queue, for some reason.
I think the solution involved calling the DoEvents method.
A couple of WPF issues came up in the forum in the past couple of years, e.g.:
On the other hand, you can tell from these discussions that some people are successfully using WPF forms, and the development team are not aware of any issues with them.
I recommend sticking with Windows Forms if you have a choice.
Here is another recent article on modeless WPF forms, PickPoint and multithreading, addressing other issues that also might be of interest to you, an older one on WPF, and a comment on triggering an event from Jon.
Later, one little addition; I searched for "Revit API WPF DoEvents" and found this article on multithreading throwing exceptions in Revit 2015.
Response: I grabbed this DoEvents implementation on StackOverflow to "start work on a method that runs on the Dispatcher thread, and it needs to block without blocking the UI Thread... implement a DoEvents based on the Dispatcher itself".
I don't like it, it's not too elegant, it forces to break mvvm pattern, but it works.
If you would like to see my ugly non-refactored test code, clone my ExternalEventTests sample on BitBucket, also saved locally here on The Building Coder.
I haven't tested it much yet but it seems to be stable.
You can run it from the add-in manager – lack of static references to IExternalApplication.
On the occasion it allows to check if raised external events are queued or running next to each other. They are queued which is gooood but I think safer would be adapting below solution to the one external event – we don't know how the API engine will change in the future... ;) pattern for semi-asynchronous Idling API access...
Anyways thank you for your very accurate advice!
What would be in your circle of interest is WpfCommand.
WpfWindow contains a WPF implementation of the DoEvents method (in the way mentioned before) which is injected to ViewModel to minimize dependencies.
ViewModel contains 3 commands.
The first picks doors (it use part of my little cross API version framework) – simply to check how modeless WPF window would behave during hiding, picking and showing again.
The second and third flip doors – both have their own external event with different handler implementation. ViewModel's setStatus method calls injected DoEvents method.
If you have some more questions let me know.
Oh, and you can feel free to publish this solution on the blog of course, I don't have any problem with that ;)
Answer: When you mention picking and flipping doors, that reminds me of the Revit SDK ModelessDialog ModelessForm_ExternalEvent
and ModelessForm_IdlingEvent
samples.
Are you aware of those?
Is there any similarity, or is your sample completely unrelated to those?
Response: Yes, I'm aware of those, but my approach is quite different.
Congratulations to Max on solving this, and many thanks for sharing it here with us!
Jeroen Van Vlierden adds an update:
Just to let you know: I noticed that you mentioned my thread on a WPF window losing control when Revit API displays an error above.
I can add to this that I managed to work around the issue by converting the wpf form to a user control and using this user control in a winforms window with a wpf element host.
That works fine.
It was however disappointing that I was forced to do this after I spent a lot of work on my application.
Converting the application to winforms is no longer an option, so I will stick to this for now.
DataContext
Hps Anave shares another nugget of WPF experience in the Revit API discussion forum thread on Revit API DLL preventing WPF window regeneration:
Well, after a very long time integrating WPF in my own programs, my recommendation is NOT to use any Revit API class inside the view model class where you assign to the WPF window's
DataContext
.If ever you want to pass or get any information coming from an element or a parameter, it is better to extract its element Id's
IntegerValue
, and, when you are done with the WPF window, just create anElementId
from the integer value you acquired from the WPF window.There may be other solutions out there but this is the solution I have so far.
Many thanks to Hps for sharing this!
In the same thread, Gonçalo Feio shared his WPF element id converter, saying, This works for me:
public class ElementIdConverter : IValueConverter { public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) { if( value is R.ElementId ) { return ( value as ElementId ).IntegerValue; } return -1; } public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) { if( value is string ) { int id; if( int.TryParse( value as string, out id ) ) { return new ElementId( id ); } } return ElementId.InvalidElementId; } }
In this case, I expose a ElementId property in the view model.
You can also add validation to give some feedback to the end user.
Many thanks to Gonçalo for sharing this!