- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to open the WPF window a second time #210
Comments
Can you share a link to a branch that exhibits this problem? |
Haven't looked closely at this, but |
https://github.com/ScottHutchinson/MyExistingMFCApp Somehow this project does not reproduce the same exception as my production application, which I cannot share with you. But it still crashes when attempting to show the window a second time. Run the |
Understand, I have only one window to display, but the user can open and close it multiple times with different arguments each time that will be passed to the Maybe |
Also, I'm not thrilled with having the window state in the model like this:
|
It seems like maybe we need another function like |
This is a C++ project, right? Is C++ necessary to reproduce the issue you are facing? |
I think C++ has nothing to do with it. But that is the context in which I want to show a WPF window as a dialog. And I'm still trying to find the best way to do that. I need to be able to call the |
Quoting from #211 (comment)
Indeed. Issue #211 seems easier to me right now. I suggest we resolve that issue first and then reconsider this one. |
I'm finding it difficult to adapt the |
I wrote this function, but it doesn't solve the problem mentioned above. The let InitializeWpfApplication () =
if isNull Application.Current then
Application () |> ignore
Application.Current.MainWindow <- App.window
let init = App.init 0 "" "" measureElapsedTime
Program.mkSimpleWpf init App.update App.rootBindings
|> Program.startElmishLoop ElmConfig.Default App.window
(* Run without showing the main window, which is not needed
since the user will open a new dialog window by clicking
in an existing C++ MFC window
(in other apps, it could be an existing C# WPF window).
*)
Application.Current.Run App.window |
Hmmm...Maybe the simplest solution would be to just call EDIT: No, that didn't help. Maybe instead I could start the Elmish.WPF Application in its own AppDomain like this example: [Running multiple WPF applications in the same process using AppDomains] (https://eprystupa.wordpress.com/2008/07/31/running-multiple-wpf-applications-in-the-same-process-using-appdomains/). Not sure about .NET Core though. |
I might need to pass in an |
I don't really understand; AFAIK In any case, I think there are good Elmish-centric ways to achieve the behaviour you need, but I'm afraid I don't have the capacity to look into it now. In short, if you use Elmish.WPF for the whole app, then it shouldn't be a problem keeping a list of Elmish.WPF window states (or your preferred domain proxy) in the main model, and initializing these models however you want in the |
Is there a way to trigger an update by dispatching a message in code? Instead of binding to a WPF button, I just want the code to do it directly. Thanks |
You can use commands/subscriptions for this. See this section in the tutorial. |
One way to access the dispatcher is via a subscription. Here is one place in the samples where this is done. Elmish.WPF/src/Samples/SubModel/Program.fs Line 146 in 4452a1d
|
Thanks. I just found that. Also, this might be more direct: |
I still can't figure out how to trigger an update using Cmd.OfFunc.result or Cmd.OfMsg. I ran the line below, but it did not trigger the
|
Trying to use let bindings () : Binding<Model, Msg> list = [
"Dialog" |> Binding.subModelWin(
(fun m -> m.WinState), fst, id,
rootBindings,
(fun m dispatch ->
dispatch (ShowDialog (msgTypeID, msgTypeName, parentStructName))
MsgTypeFiltersWindow(Owner = Application.Current.MainWindow)
),
onCloseRequested = CloseDialog,
isModal = true
)
] |
EDIT: I think this is working. EDIT2: Sort of. Unfortunately, the type DialogOpeningEventArgs = {
MsgTypeID: int
MsgTypeName: string
ParentStructName: string
}
let dialogOpening = new Event<DialogOpeningEventArgs>()
let raiseDialogOpening (args : DialogOpeningEventArgs) = dialogOpening.Trigger(args)
let DialogOpening = dialogOpening.Publish
let dialogOpeningSubscriber dispatch =
DialogOpening.Add (fun args ->
dispatch (ShowDialog (args.MsgTypeID, args.MsgTypeName, args.ParentStructName))
)
...
|> Program.withSubscription (fun _ -> Cmd.ofSub App.dialogOpeningSubscriber)
|> Program.runWindowWithConfig...
...
App.raiseDialogOpening { MsgTypeID = msgTypeID; MsgTypeName = msgTypeName; ParentStructName = parentStructName} |
@ScottHutchinson, it seems you have some misconceptions regarding how the Elm architecture works.
This just creates a command; it does not execute it (which is done by the Elmish update loop). In order to dispatch messages in code, you need to set up a subscription. I think there is a sample that does this. You can use If you haven't already, I highly recommend you read the first parts of the Elmish.WPF tutorial, which explains the basics of the Elm architecture concepts. 🙂 And again: |
I have read all of your excellent tutorial, yet I still struggle with this use case. And I am trying everything you are saying to do, but still failing. If you read my previous post again, you'll see that am trying to do as you say. |
Not to worry. If you can explain in very simple terms the high-level functionality you are trying to accomplish, I may be able to create a sample that demonstrates how to do it. (The sample will use Elmish.WPF for the whole app and be in F# – if that should not work for your use-case, then I'm not sure Elmish.WPF is right for your use-case.) |
I really want Elmish.WPF to work for this. It just seems like too simple a problem to give up on it. I think there is not a single sample of init returning a command (using |
Instead of In any case, as I said above:
|
I want to show a modal dialog, with data initialized based on arguments. I want the user to be able to show that dialog over and over again, each time with different arguments. But I don't want the user to have to click a button on the main window to show the dialog. I want to show it from code. I don't really want the main window to ever be visible. The main window can just be an empty window. EDIT: Hang on. Maybe I should just be using the main window as the dialog (which is what I started out doing). I'll try that again with what I've learned today, maybe I can get it to work. |
Thanks. I assume only one such dialog can be open simultaneously? What is it that causes it to be shown? |
Yes, only one dialog at time, since it is modal. A function call causes it to be shown. If possible, I can just use the main window as the dialog, but I need to be able to hide it or close it between uses. |
What causes this triggering function to be called? (Just trying to understand the use-case better.) |
They are. |
The function is called when the user clicks on an item in a C++ MFC window. It is quite a complicated window with several tabs and a menu that I do not want to re-implement in WPF just so I can show one new dialog. |
@bender2k14 For reference, do you have a source for that? I remember an issue a while back, probably in the main Elmish repo, where this was discussed and where there may have been ever so slightly different (but that may have been a bug that is now fixed). At least it was not trivially true, as I remember it. |
@ScottHutchinson Thanks! So this is where I fall short. I have never used C++ nor MFC, and I have no idea how C++/MFC interfaces/interops with .NET/WPF, where the WPF |
I think maybe you should ignore the C++ aspect. Just focus on the idea of an F# function with parameters that shows an Elmish.WPF dialog window with data initialized based on those parameters. |
The function currently looks like below, but it only opens an empty main window and then triggers a ShowDialog case in the update function after the main window closes. Calling the function a second time triggers that case again. I'm still working out how to proceed next. module PublicAPI =
open NG_DART_WPF
let mutable isInitialized = false
let LoadWindow (msgTypeID: int) (msgTypeName: string) (parentStructName: string) =
if not isInitialized then
isInitialized <- true
let init = App.init msgTypeID msgTypeName parentStructName
Program.mkSimpleWpf init App.update App.bindings
|> Program.withSubscription (fun _ ->
Cmd.ofSub App.dialogOpeningSubscriber
)
|> Program.runWindowWithConfig
ElmConfig.Default
(Window())
|> ignore<int>
App.raiseDialogOpening { MsgTypeID = msgTypeID; MsgTypeName = msgTypeName; ParentStructName = parentStructName} |
It's getting late here but I'll try to have a look at it tomorrow, if no-one else does so in the meantime. In short, the main "error" above is calling |
I think maybe this is the WPF application's entry point in my case. I could call it in a separate function, but I'm not sure the result will be any different. The |
I tried adding an implicit entry point as shown below, but it blocked the initialization of the MFC application until I closed the WPF window, so I'm thinking there are only two ways I would be able to use Elmish.WPF: (1) Launch a new WPF application in a new process or AppDomain; or (2) if Elmish.WPF could be modified to support running in the context of module Main
open System
open System.Windows
[<STAThread()>]
do
let win = Window (WindowState = WindowState.Minimized)
//win.DataContext <- vm :> obj
let app = new Application() in
app.Run(win) |> ignore
let init = (do ()); true // from https://stackoverflow.com/a/18619285/5652483 |
I see, thanks for trying that out. Elmish.WPF is, from the ground up, intended to "be the whole app", as it were. If anyone wants to have a look at which changes would be needed to support running Elmish.WPF separately for separate windows, feel free, but that would also require someone to be willing to help maintain any increased complexity in Elmish.WPF going forward, because I'm not that keen on implementing and supporting it. (Since this is a hobby project, I need to prioritize my resources.) |
Yep.
|
I am willing to consider this. I just haven't had enough time yet to try things. My plan is to focus on #211 first. I might end up implementing several different approaches to help us focus on the implementation details. |
I'm going to try to implement a new |
Contributor guidelines are here: https://github.com/elmish/Elmish.WPF/blob/master/.github/CONTRIBUTING.md (they should show up when you create an issue/PR) |
Thanks. I should have searched. |
This is looking like it might require nothing more than adding the function below. I'll continue implementing the features of my dialog using that function, but so far it is working perfectly. If it works, then I'll write a sample before submitting a pull request. /// Starts the Elmish and WPF dispatch loops with the specified configuration.
/// Will show the specified window as a dialog, returning the dialog result.
/// This is a blocking function.
let showDialogWithConfig config (window: Window) program =
startElmishLoop config window program
window.ShowDialog () |
Is |
I would say so. |
I'm happy the fix seems so simple. 🙂
Thanks! As I understand it, this sample should not use Elmish for the main application, only for the dialog. That way we demonstrate how to use Elmish.WPF only for dialogs in an existing non-Elmish app. Do you agree? Also, we might consider not adding a Also, it would be great if someone can investigate what happens with the Elmish dispatch loop when the window is closed. Is it garbage collected, or does it leak? |
Yeah, I agree. For completeness, we might want one sample that does not use Elmish for the main application, and one that does use it. Also, since the let showDialogWithConfig config (window: Window) program =
Program.startElmishLoop config window program
window.ShowDialog ()
Program.mkSimpleWpf init App.update App.rootBindings
|> showDialogWithConfig
ElmConfig.Default
(MsgTypeFiltersWindow())
|> ignore |
Since |
Brilliant! Let's just focus on a sample, then. Personally I think it's sufficient to show a non-Elmish app using Elmish.WPF for sub-windows. Using separate Elmish dispatch loops for an app that already runs Elmish.WPF at the root seems unnecessary and, while possible, not something that anyone would really want to do. Feel free to enlighten me with counter-examples, though. |
In my application, another project calls the
LoadWindow
function shown below. But after the user closes theMsgTypeFiltersWindow
window and attempts to open it again by calling theLoadWindow
function again, I get'System.InvalidOperationException' in PresentationFramework.dll The Application object is being shut down.
.How can I write the
LoadWindow
function so it can be called over and over again?Thanks
The text was updated successfully, but these errors were encountered: