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

Example for using Dialogs with mvvmfx #171

Open
manuel-mauky opened this issue Jan 27, 2015 · 4 comments
Open

Example for using Dialogs with mvvmfx #171

manuel-mauky opened this issue Jan 27, 2015 · 4 comments

Comments

@manuel-mauky
Copy link
Collaborator

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

  • FileChooser dialog
  • ControlsFX dialogs
  • Openjfx dialogs
@pbauerochse
Copy link

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
Copy link

svedie commented Dec 30, 2019

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
Copy link
Collaborator Author

manuel-mauky commented Dec 30, 2019

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:

  • all logic should be in the ViewModel
  • the ViewModel should not have UI specific code

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
Copy link

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

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

No branches or pull requests

3 participants