Open manuel-mauky opened 9 years ago
Hi @lestard unfortunately you did not find the time to document this, so I'll just ask my question here: how would you suggest opening a FileChooser
using MVVM? I want to implement a "Open File" dialog and my initial idea was to use a Command
in the ViewModel, that gets executed when the user clicks on a MenuItem. Unfortunately, the FileChooser.showOpenDialog()
Method requires a Window
object as parameter. How would I pass the Window
object from the View down to the Command
inside the ViewModel?
I could of course move the FileChooser logic to the View but as I understood this would somehow break the MVVM pattern, doesn't it?
val loadImageCommand: Command = DelegateCommand {
dirtyCheckAction {
val window: Window = ???
val image = FileChooser().apply {
title = resourceBundle.getString("load.image.filechooser.title")
}.showOpenDialog(window)
}
}
Hi @pbauerochse,
I use this with Java as follow: Note: I have a toolbar, which manages the clicks and and so on ... but you can use your own implementation.
In the ViewModel
I provide the ToolbarScope
(you can use your default scope for this view) @ScopeProvider({ToolbarScope.class})
and then in the public void initialize()
method the scope subscribes to the events with
toolbarScope.subscribe(ToolbarConstants.FILE_OPEN, (s, objects) -> openFile());
toolbarScope.subscribe(ToolbarConstants.FILE_ADD, (s, objects) -> addFile());
toolbarScope.subscribe(ToolbarConstants.FILE_DELETE, (s, objects) -> deleteFile());
The methods openFile, ... are just events for the View
:
private void openFile() {
publish(ToolbarConstants.SPECIFICVIEW_FILE_OPEN);
}
private void addFile() {
publish(ToolbarConstants.SPECIFICVIEW_FILE_ADD);
}
private void deleteFile() {
publish(ToolbarConstants.SPECIFICVIEW_FILE_DELETE);
}
And in the View
I implement it as follows:
Subscribing to the ViewModel
events:
viewModel.subscribe(ToolbarConstants.SPECIFICVIEW_FILE_OPEN, (s, objects) -> openFile());
viewModel.subscribe(ToolbarConstants.SPECIFICVIEW_FILE_ADD, (s, objects) -> addFile());
viewModel.subscribe(ToolbarConstants.SPECIFICVIEW_FILE_DELETE, (s, objects) -> deleteFile());
Then just call the methods:
public void addFile() {
FileChooser fileChooser = new FileChooser();
List<File> files = fileChooser.showOpenMultipleDialog(stage);
addFiles(files);
}
private void addFiles(List<Files> files){
viewModel.doSomethingWithFiles(files);
}
Therefore you have a clear separation between the View
and ViewModel
.
Hi, the solution from @svedie is a good way of doing this, however maybe you don't need the scopes. It's also possible with simple View/ViewModel. Using the publish/subscribe mechanism from the ViewModel to trigger an action in the View is a good solution.
Some additional clarification on how to decide which logic gets to the viewModel and which code belongs to the view:
In MVVM you have two basic ideas:
Sometimes these ideas are mutually exclusive so you have to find a compromise. Also keep in mind that MVVM is heavily driven by the question of how to test things.
So a basic rule of thumb is: "If you need the JavaFX UI thread, it belongs to the View - otherwise it belongs to the ViewModel".
I'm sure that fileChooser.showOpenDialog()
is only possible when there is a JavaFX UI thread available. For this reason this statement shouldn't be in the ViewModel. Ideally a ViewModel can be tested in a plain JUnit test without a running JavaFX application in the background (even though we have testing utils to do exactly this).
With all this in mind we try to design the code so that as much logic as possible is put in the ViewModel and only those parts that need a UI thread (or are heavily depending on UI controls) are put in the View. The code from @svedie is a good example for the interaction between View and ViewModel: The ViewModel tells the View that it needs some files. The View opens the dialog and directly gives the resulting list of files that the User has selected back to the ViewModel. This way the logic in the View is kept to a minimum.
Thank you @svedie and @lestard for the input. It was a great help. I am using the publish/subscribe machanism now (for now without Scope) and it's working like a charm.
Happy new year in advance to both of you
This projects has 4 examples https://github.com/BionicCodeStackoverflow/MVVM-Open-FileDialog-Example
Another solution I found here https://stackoverflow.com/questions/3801681/good-or-bad-practice-for-dialogs-in-wpf-with-mvvm So, we have View, ViewModel, and DialogService.
In ViewModel:
var result = this.dialogService.showDialog("Dialogwindow Title", dialogwindowVM);
// Do anything with the dialog result
in View:
viewModel.setDialogService(new DialogServiceImpl(this));
Currently I find this solution the best. @manuel-mauky , @svedie what do you think about it?
It's up to you, there is no right or false, only different ways of implementation.
Create a small example that shows the proper handling of dialogs with mvvmfx.