Skip to content
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

Get the model from event streams #331

Closed
Evangelink opened this issue Feb 5, 2021 · 15 comments
Closed

Get the model from event streams #331

Evangelink opened this issue Feb 5, 2021 · 15 comments

Comments

@Evangelink
Copy link
Contributor

I have started to implement keyboard shortcuts in my application which I am handling through the dispatch mechanism:

let paste dispatch (obs: IObservable<KeyEventArgs>) =
	obs
    |> Observable.filter (...)
    |> Observable.add (fun _ -> MyMessage |> dispatch)

window.KeyDown |> paste MyDispatch

(note that I am using observables to make it easy to test the behavior) and I also have a button for the same command. The button is bound using Binding.cmdIf which prevents the action under some context but the shortcut can't do the same.

So far, I am handling the "invalid" case in the update and return model, [] in case the state is not good but I would prefer to be able to create some kind of observable state of my model which I could plug into my observable streams.

Any suggestion?

@TysonMN
Copy link
Member

TysonMN commented Feb 5, 2021

Can you share a link to a GitHub repo that demonstrates what you are currently doing and maybe includes some comments about how you would like it to be different?

@Evangelink
Copy link
Contributor Author

I have created a dummy project to showcase the behavior. Let me know your thoughts.

Here is the link https://github.com/Evangelink/Elmish.Wpf.Experiments

@TysonMN
Copy link
Member

TysonMN commented Feb 8, 2021

I understand now. Thanks for creating a project that I can run.

I think you could achieve your desired behavior given the ability to dispatch a list of messages instead of just a single message. Of course a list can be empty, so that would give you the ability to dispatch no messages. See #321. I also expect the message-mapping feature in v4 would be useful here. See #263.

Your subscription to the KeyDown event is "global". Suppose you had two counters but only one visible at a time. Could the pressing Ctrl+D be configured so that only the visible counter gets incremented? Of course this could be done in the F# code with filtering like you have done. I am wondering if binding to this event in the XAML would narrow the scope of the subscription.

@Evangelink
Copy link
Contributor Author

In my real app, I am trying to mimic a bookmark feature like in web browsers so I really want the Ctrl + D to work from anywhere within the app and not only when a specific part of the app is focused. Of course, it needs to work only when the page is "active".

Regarding the access to an IObservable<Model>, I don't know why I didn't think about using a Subject<Model> earlier... See Evangelink/Elmish.Wpf.Experiments#2

I think you could achieve your desired behavior given the ability to dispatch a list of messages instead of just a single message. Of course a list can be empty, so that would give you the ability to dispatch no messages. See #321.

I must be missing something but I fail to see how the ability to dispatch multiple messages will help in this case?

@TysonMN
Copy link
Member

TysonMN commented Feb 9, 2021

Regarding the access to an IObservable<Model>, I don't know why I didn't think about using a Subject<Model> earlier... See Evangelink/Elmish.Wpf.Experiments#2

I don't recommend calling modelObs.OnNext inside init or update. The design MVU is that these functions are pure.

I think you could achieve your desired behavior given the ability to dispatch a list of messages instead of just a single message. Of course a list can be empty, so that would give you the ability to dispatch no messages. See #321.

I must be missing something but I fail to see how the ability to dispatch multiple messages will help in this case?

Because "multiple" here really means "zero or more". Your binding code could look something like this.

"AddOne" |> Binding.cmd (fun m obj -> if shouldAddOne m obj then [ AddOne ] else [])

@Evangelink
Copy link
Contributor Author

Because "multiple" here really means "zero or more". Your binding code could look something like this.

As far as I understand this is just changing the cmdIf to a cmd and not at all having an impact on shortcuts.

How are you or how would you you do something similar? Let's say you want to have undo (ctrl + z) or redo (ctrl + y) or save (ctrl + s) or whatever else and you need both a button (with a cmdIf) and a keyboard shortcut that will use the same validation function.

@TysonMN
Copy link
Member

TysonMN commented Feb 9, 2021

Oh, two mistakes on my part. I know think that this problem is much simpler than I first thought.

First mistake is thinking that we needed support for multi-message bindings in order to dispatch zero messages. The family of Cmd bindings already support dispatching zero messages. Those are the ones that end with If'.

Your binding code could look something like this.

"AddOne" |> Binding.cmd (fun m obj -> if shouldAddOne m obj then [ AddOne ] else [])

Second mistake is using cmd there instead of cmdParam. (I did say "something like" ;) )


Anyway, I think what you want is already possible. See this branch.

The commit on that branch turns the event into a command and then creates a binding via cmdParamIf that matches the behavior you have in your first sample.

Is that the behavior you want?

@TysonMN
Copy link
Member

TysonMN commented Feb 9, 2021

As far as I understand this is just changing the cmdIf to a cmd and not at all having an impact on shortcuts.

How are you or how would you you do something similar? Let's say you want to have undo (ctrl + z) or redo (ctrl + y) or save (ctrl + s) or whatever else and you need both a button (with a cmdIf) and a keyboard shortcut that will use the same validation function.

Your response here makes me think that you don't know it is possible to turn an event into a command. In addition to the branch linked to in my previous comment, see the EventBindingsAndBehaviors sample.

@TysonMN
Copy link
Member

TysonMN commented Feb 9, 2021

Your subscription to the KeyDown event is "global". Suppose you had two counters but only one visible at a time. Could the pressing Ctrl+D be configured so that only the visible counter gets incremented? Of course this could be done in the F# code with filtering like you have done. I am wondering if binding to this event in the XAML would narrow the scope of the subscription.

In my real app, I am trying to mimic a bookmark feature like in web browsers so I really want the Ctrl + D to work from anywhere within the app and not only when a specific part of the app is focused. Of course, it needs to work only when the page is "active".

I have a guess for the answer to my question. In the branch I created for you, I turned the KeyDown event into a command for the whole Window, but I could have put that conversion on some other element: something "smaller". Then I think the behavior would be that the event is only raised when that element (or some child?) has focus.

@Evangelink
Copy link
Contributor Author

🤦 I saw the event to command example but totally forgot about it and so went with a complex approach... I will look in details the example, your comments and example and let you know if I have some question.

@Evangelink
Copy link
Contributor Author

This is definitely better than what I was doing... Also with my WPF skills getting back to memory, I think for this exact case of having a view global handler it's even easier if I go with InputBindings:

<UserControl.InputBindings>
    <KeyBinding Command="{Binding Undo}" Gesture="Ctrl+Z" />
    <KeyBinding Command="{Binding Redo}" Gesture="Ctrl+Y" />
</UserControl.InputBindings>

where Undo and Redo are my commands.

@TysonMN
Copy link
Member

TysonMN commented Feb 9, 2021

I didn't know about this InputBindings/KeyBinding. It looks great!

@Evangelink
Copy link
Contributor Author

I am going to close the issue as I think we sorted it out. Out of curiosity, would you like me to open a PR updating the https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/EventBindingsAndBehaviors sample project or adding a new one with an example of InputBindings?

@TysonMN
Copy link
Member

TysonMN commented Feb 9, 2021

Out of curiosity, would you like me to open a PR updating the https://github.com/elmish/Elmish.WPF/tree/master/src/Samples/EventBindingsAndBehaviors sample project or adding a new one with an example of InputBindings?

I am up for it. What do you think @cmeeren? This is a really just a feature of WPF, so nothing novel, but I didn't know of it.

@cmeeren
Copy link
Member

cmeeren commented Feb 10, 2021

Sure! 👍 Try updating the existing EventBindingsAndBehaviors sample. As I understand it, this will just be another demonstration of that. Keep it simple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants