This might be related to Issue #210.
https://github.com/ScottHutchinson/MyCSharpExe
Run the MyExistingCSharpExe startup project, click the button:
Exception thrown: 'System.InvalidOperationException' in PresentationFramework.dll
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll
Additional information: Application is already running the Dispatcher.
In Elmish.WPF, that exception is thrown on the line
https://github.com/elmish/Elmish.WPF/blob/b4decde84695fca769c394d23a99a53f33087088/src/Elmish.WPF/Program.fs#L51
@cmeeren, Elmish.WPF supports the case that Application has already been instantiated.
https://github.com/elmish/Elmish.WPF/blob/b4decde84695fca769c394d23a99a53f33087088/src/Elmish.WPF/Program.fs#L36-L41
Should it also support the case that the Application has also had its Dispatcher started?
Maybe Application.Current.MainWindow <- window is not the correct thing if we just want to call window.ShowDialog. And the SubModelWin approach seems a roundabout route if you just need one modal dialog to reuse over and over.
Setting Application.Current.MainWindow is not an issue. It is only set if Application.Current is null, so no data is being overwritten. It is only done for convenience.
Should it also support the case that the
Applicationhas also had itsDispatcherstarted?
Sorry, I don't even know what it means for the Dispatcher to "have been started" or why it causes problems.
It means that, it addition having already instantiated Application, the
blocking function Application.Current.Run has already been called as well.
Application cannot be instantiated twice, and the Dispatcher cannot be started twice.
Is it then at all possible for Elmish.WPF to handle this case? How would that look? What would that entail? (Not rhetorical questions; I'm genuinely confused.)
I just want to make clear that Elmish.WPF is a framework (not library) intended for "owning" a complete WPF app, not function as part of an existing non-Elmish.WPF app, which seems to be what is happening here and in #210. If there are simple changes we can do to make it work that 1) do not cause significantly added maintenance burden, and 2) does not complicate anything for existing users, then I'm open to it. Otherwise, based on my current understanding of this issue and #210, this is not really a supported use-case.
Hmmm... I had no idea that showing a dialog using Elmish.WPF would not be supported. It seems like such a simple use case. However, I suppose I could meet my requirements by launching a new application/process instead of a modal dialog, but I think that would be much more complicated since I can't prevent the user from going back to the main application (C++MFC) and attempting to launch another Elmish.WPF application while the first one is still running. It could be worked out, but might not work quite as the user might expect. Thank you for considering supporting this new use case.
I had no idea that showing a dialog using Elmish.WPF would not be supported. It seems like such a simple use case.
Elmish.WPF supports dialogs. One approach is show the dialog in a new window as demonstrated in the NewWindow sample (or file-specific dialogs as demonstrated in the FileDialogs sample), and another approach is to show the dialog within an existing window using something like Material Design In XAML.
In contrast, your example code tries to show a dialog in a new window by running Elmish.WPF. That is what is not currently supported, but I think it is possible. I will investigate this soon.
It looks like the Win2 binding in the NewWindow sample is close to what I need, but I'd like to separate the control of the window from the model definition if possible. In the sample, the model is all about the window state, not the domain model. It seems to me that the view in MVU has leaked into the model in that sample.
In contrast, your example code tries to show a dialog in a new window by running Elmish.WPF. That is what is not currently supported
Yes, this is what I meant. Calling Program.run... in Elmish.WPF is similar to calling Application.Run in WPF: It should only be done once per application. You run Elmish.WPF for your "root window" as it were, so that your whole app is using Elmish.WPF. Once you are running an application with Elmish.WPF, dialogs are supported, as documented.
If you @bender2k14 can find a simple way to make it possible to run Elmish.WPF (with a separate dispatch loop I guess) for arbitrary new windows in a non-Elmish.WPF app, then that's great! However, as said, I don't think this is a use-case that warrants significantly increased complexity in the Elmish.WPF code-base, nor any inconveniences for existing users.
...with a separate dispatch loop...
I am not completely sure what you mean here by "loop".
I think that each thread has (at most) one Dispatcher instance associated with it and each Window is assigned the Dispatcher instance corresponding to the thread that instantiated it.
Sorry, I meant elmish dispatch loop.
It seems to me that the view in MVU has leaked into the model in that sample.
The model, through the bindings, completely controls if the second window is open, closed, or hidden. If you want, you can define your own DU with those states and then map to the required state in the associated binding. However, that would make the sample more complicated, and simplicty is a primary goal of the samples.
If you @bender2k14 can find a simple way to make it possible to run Elmish.WPF (with a separate dispatch loop I guess) for arbitrary new windows in a non-Elmish.WPF app, then that's great! However, as said, I don't think this is a use-case that warrants significantly increased complexity in the Elmish.WPF code-base, nor any inconveniences for existing users.
I don't see a (good) way to check if a Dispatcher is already running. Here is the closest answer I found.
Of course we could just call Run and learn if the Dispatcher is already running by catching the appropriate exception, but I dislike this EAFP-style. I prefer for an exception to mean that something particularly bad happened. That makes subscribing to the first-chance exceptions event more useful and makes debugging easier because there is no need to adjust the debug settings that control on which exceptions the debugger should break.
This issue turned up while googling problems I had while working on https://github.com/elmish/Elmish.WPF/pull/320#issuecomment-761209461. I haven't re-read the entire thread, but have you tried calling Program.startElmishLoop instead of Program.runWindow?
@cmeeren Sorry for the delayed response. I didn't realize your question was for me. Yes, I think Program.startElmishLoop is how we solved it. See our complete function below for future reference. I was planning to add a sample demonstrating this approach, but so far I've been distracted.
module PublicAPI =
let loadWindow (msgTypeID: int) (msgTypeName: string) (parentStructName: string) (configFolder: string) =
try
App.configFolderPath <- App.getConfigFolder configFolder
let mutable timeMeasurementString = ""
let watch = Stopwatch.StartNew()
let measureElapsedTime label =
let elapsed = watch.Elapsed // <-- explicit copy prevents level 5 warning FS0052.
timeMeasurementString <- sprintf "%s | %s Elapsed Time: %.1f seconds"
timeMeasurementString label elapsed.TotalSeconds
watch.Restart()
let waitWindow = WaitWindow()
App.mkNewWindow().Activated.Add (fun _ ->
measureElapsedTime "TreeView loading"
waitWindow.Close()
)
let init = App.init msgTypeID msgTypeName parentStructName measureElapsedTime
let showDialogWithConfig config (window: Window) program =
waitWindow.Show()
Program.startElmishLoop config window program
window.ShowDialog ()
Program.mkSimpleWpf init App.update App.rootBindings
|> showDialogWithConfig ElmConfig.Default (App.window)
|> ignore
// Return diagnostic information in a string.
sprintf "StructFilters Tree View (MT 0x%04X)%s"
msgTypeID timeMeasurementString
with ex ->
sprintf "StructFilters loadWindow Exception: %s" ex.Message
FYI, we are revamping sample startup code in #320.