views:

69

answers:

2

I am just wondering is this the way to show dialogs in MVVM?

public ICommand OpenFileCommand
{
    get
    {
        if (_openFileCommand == null) {
            _openFileCommand = new RelayCommand(delegate
            {
                var strArr = DialogsViewModel.GetOpenFileDialog("Open a file ...", "Text files|*.txt | All Files|*.*");
                foreach (string s in strArr) {
                    // do something with file
                }
            });
        }
        return _openFileCommand;
    }
}

public class DialogsViewModel {
    public static string[] GetOpenFileDialog(string title, string filter)
    {
        var dialog = new OpenFileDialog();
        dialog.Title = title;
        dialog.Filter = filter;
        dialog.CheckFileExists = true;
        dialog.CheckPathExists = true;
        dialog.Multiselect = true;
        if ((bool)dialog.ShowDialog()) {
            return dialog.SafeFileNames;
        }
        return new string[0];
    }
}

If so how should I allow myself to say modify the options on dialogs I am showing. Example, I want another dialog to have different dialog options dialog.something = something_else without adding alot of arguments to my method

+2  A: 

Showing the dialog itself should not happen in a ViewModel. I usually provide a dialog service interface IDialogServices, with appropriate Dialog method calls. I then have one of the View classes (typically the MainWindow) implement this interface and perform the actual Show logic. This isolates your ViewModel logic from the specific View and, for instance, allows you to unit test code that wants to open a dialog.

The main task then is injecting the service interface into the ViewModel that needs it. This is easiest if you have a dependency-injection framework, but you can also do this with a service-locator (a static class where you can register interface implementations) or pass the interface to the ViewModel through its constructor (depends on how your ViewModels are constructed.)

Dan Bryant
Hmm, maybe i should have named `DialogsViewModel` something like `IDialogService` but the idea is that showing of dialog is separate from my main view model. would it make a difference if i renamed it. I'll try to read up on DI too. Also, if my dialogs just common file dialogs eg. open/save file, then i will have just a service and no views implementing the interface right? also can you show me what will `IDialogService` look like?
jiewmeng
http://www.codeproject.com/KB/WPF/MessageBoxInMVVM.aspx
bjoshi
Maybe I am just lazy, or I want to keep things simple and lean. will a `DialogService` that implements `IDialogService` that exposes `GetOpenFileNames` that return `string[]` of files to open be good enough? (without the service locator). When unit testing i can just provide a `FakeDialogService` that gives a bunch of filenames right? I don't get why is a Locator required
jiewmeng
@jiewmeng, a locator just makes it easier for the ViewModel to find the service. You can also pass in the service when the ViewModel is instantiated and that works just fine.
Dan Bryant
A: 

I think using a DialogService is a heavy-weight approach. I like using Actions/Lambdas to handle this problem.

Your view model might have this as a declaration:

public Func<string, string, dynamic> OpenFileDialog { get; set; }

The caller then would create your view model like so:

var myViewModel = new MyViewModel();
myViewModel.OpenFileDialog = (title, filter) =>
{
    var dialog = new OpenFileDialog();
    dialog.Filter = filter;
    dialog.Title = title;

    dynamic result = new ExpandoObject();
    if (dialog.ShowDialog() == DialogResult.Ok) {
        result.Success = true;
        result.Files = dialog.SafeFileNames;
    }
    else {
        result.Success = false;
        result.Files = new string[0];
    }

    return result;
};

You could then call it like:

dynamic res = myViewModel.OpenFileDialog("Select a file", "All files (*.*)|*.*");
var wasSuccess = res.Success;

This type of approach really pays off for testing. Because your tests can define the return on your view model to be whatever they like:

 myViewModelToTest.OpenFileDialog = (title, filter) =>
{
    dynamic result = new ExpandoObject();
    result.Success = true;
    result.Files = new string[1];
    result.Files[0] = "myexpectedfile.txt";

    return result;
};

Personally, I find this approach to be the most simplistic. I would love other's thoughts.

JP
Hmm, but i am guessing this will have issues unit testing? I read that the point of separating showing of dialogs is largely separation on concerns and unit testing. So I am guessing your method will have that problem?
jiewmeng
No, not at all. That was my point is answering and in also showing how you can use it in a unit test. I think it's the best way for unit testing because your unit test won't have to deal with the dialog, you define the result of the function in advance. See my last snippet of code.
JP
@JP, An interface and a delegate are essentially the same wiring, although I think the Func on the ViewModel is a bit cryptic (no Intellisense when calling the Func from your ViewModel to know the naming of the parameters and you have to inject the Func separately to each ViewModel.) Interfaces play nicely with dependency injection and they can be easily Mocked for unit testing (with any decent mocking framework, you have the same flexibility for replacing behavior without having to statically create a class specifically for the test.)
Dan Bryant