Search code examples
c#wpf.net-coremvvmcrossopenfiledialog

OpenFileDialog using MvvmCross in a Wpf Core application


I recently started a small project to freshen up the little wpf knowledge i got and to try out new stuff. I got interested in MvvmCross but I wanted to keep it small for the time being. My goal is a simple image manipulator; image to ascii, recolor and whatnot ...

My setup: Mvx Core Library (.NET Standard 2.0)

  • MainViewModel - has method OpenFileDialog_Clicked which is fed into openFileDialogCommand

Wpf application (.NET Core 3.1)

  • MainView - has a menu with a button that's bound to the openFileDialogCommand on MainViewModel

The wall I ran into: Trying to get a hold of OpenFileDialog (namespace: Microsoft.Win32) to do this:

public void OpenFile_Clicked()
    {
        Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
        Nullable<bool> result = dlg.ShowDialog();
        if(result) {
            // big cash prize
        }
    }

I specified the namespace because I found several articles talking about missing references on OpenFileDialog. In my case the awnser was .NET Standard. OpenFileDialog just doesn't exist there and that's exactly my problem. How am I supposed to call OpenFileDialog in my ViewModel when the Core library has to be of type .NET Standard? Wouldn't a dependency on the wpf project ruin the entire mvvm pattern?! To be quite honest: I have read the entire mvx documentation and found a few leads but my wpf/mvvm/xaml understanding extends to maybe a third of it. The MvvmCross Inversion of Control documentation looked useful to me but I still don't get how this would work in my case. The given examples https://www.mvvmcross.com/documentation/fundamentals/inversion-of-control-ioc?scroll=43 don't show where these interfaces are located and to me it all breaks down to the fact that I somehow need to get OpenFileDialog out of the wpf project into the core project.


Solution

  • "@BionicCode excuse my french but what are you talking about? "The click handler should be in the code-behind of the view"? I think you had a little switcheroo there. My MainViewModel has the method and command while my MainView binds to it. I think I did everything exactly right. edit: I also clarified that i did NOT intend to make my core dependant on the wpf project."

    No problem. I think because you are French, you misunderstood me completely.

    I will start again: it looks like you are using the MVVM pattern.

    To implement MVVM correctly, you must follow some rules (the pattern).
    One rule is not to mix responsibilities of the view component with those of the view model component. Per definition, the view component only displays data to the user and handles user input e.g., collect data or interact with the user.

    User input is always part of the user interface (UI). A dialog is a control (application interface) intended to interact with the user. In your case the dialog collects user input (a file path).
    That's why this logic belongs to the view => instead of having OpenFile_Clicked in your MainViewModel only to show the OpenFileDialog to the user, OpenFile_Clicked should be an event handler in the code-behind of the UI e.g., MainWindow.xaml.cs.

    This event handler shows the dialog and passes the result (the picked file path) to the MainViewModel (using data binding, command pattern or via direct reference).

    "Wouldn't a dependency on the wpf project ruin the entire mvvm pattern?!"

    No, not the reference to a WPF project would ruin the MVVM pattern, but the reference to the OpenFileDialog class.
    If you would follow the previously explained principle and remove the dependency to OpenFileDialog from your MainViewModel, you will get back the advantages of MVVM.
    In other words: don't use OpenFileDialog or any other view component in MainViewModel at all.

    "How am I supposed to call OpenFileDialog in my ViewModel when the Core library has to be of type .NET Standard?"

    Again: don't use OpenFileDialog or any other view component in MainViewModel at all:

    MainWindow.xaml.cs (WPF .NET Core assembly)

    partial class MainWindow
    {
      // Event handler for Button.Click
      private void OnOpenDialogButton_Click(object sender, EventArgs e)
      {
        var dialog = new System.Windows.Forms.OpenFileDialog();
        dialog.Show();
        string selectedFilePath = dialog.FileName;
    
        this.ViewModel.HandleSelectedFile(selectedFilePath);
      }
    }
    

    MainViewModel.cs (.NET Standard library assembly)

    public void HandleSelectedFile(string filePath)
    {
      // TODO::Handle file e.g. open and read
    }
    

    Using OpenFileDialog forces the referencing assembly to give up the .NET Standard compliance. Because OpenFileDialog is a class defined in System.Windows.Forms, the referencing assembly requires to use either the .NET Framework or the .NET Core library.
    Following the above example will remove this dependency from MainViewModel and makes your library to comply to .NET Standard again.


    "The MvvmCross Inversion of Control documentation looked useful to me but I still don't get how this would work in my case."

    Inversion of Control tackles a different problem and has nothing to do with MVVM or showing dialogs from your view model (don't do this). It enables an application to be extensible by exchanging parts.

    Since you are using .NET Core you can use the build in IoC container: Overview of dependency injection