sialcasa / mvvmFX

an Application Framework for implementing the MVVM Pattern with JavaFX
Apache License 2.0
489 stars 104 forks source link

Example for using Dialogs with mvvmfx #171

Open manuel-mauky opened 9 years ago

manuel-mauky commented 9 years ago

Create a small example that shows the proper handling of dialogs with mvvmfx.

pbauerochse commented 4 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)
    }
}
svedie commented 4 years ago

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.

manuel-mauky commented 4 years ago

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.

pbauerochse commented 4 years ago

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

PavelTurk commented 3 months ago

This projects has 4 examples https://github.com/BionicCodeStackoverflow/MVVM-Open-FileDialog-Example

PavelTurk commented 3 months ago

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?

svedie commented 3 months ago

It's up to you, there is no right or false, only different ways of implementation.