OpenFileDialog and MVVM

Tell me what is the right way to go.

On the WPF form, there is a "Browse" button that should call OpenFileDialog and after selecting a file, pass its name to textbox.

Using the MVVM pattern, I should not change the value of the textbox, but change the corresponding value in the VM. And here's my question. Calling OpenFileDialog should I do in the VM as ICommand, or from my form on which button?

Author: MSDN.WhiteKnight, 2016-10-11

3 answers

Dialogs are the cornerstone of MVVM because it is assumed that the VM does not know anything about the View, but should receive data from it. We get that on the one hand, the View should call VM commands with parameters and the VM should not work with the display in any way. I.e., the dialogs belong to the View. And on the other hand: The View should not contain logic/code, and calling the dialog box and getting the result is what no logic/code is.

The simplest option: in the View code, hang the event handler on the the event for which you should display the file selection dialog in which you create and display the dialog to the user, and after the event is successfully closed, you call the VM command from the code (in this case, you can also use the interface method, without declaring the command).

The code on the VM side should contain the path string to the file being opened(lam) and, possibly, the access type (read/read-write, etc.)

PS: If you stick to MVVM when developing, imho, the main thing is that you should always remember: you must keep the testability of your VM's Unit tests. (i.e., if you put test data instead of M, and call commands directly instead of View, all public methods and commands should be tested without dancing with a tambourine) Creating dialogs in the VM breaks this pattern.

The option is more complicated: Sometimes it happens that you need a custom dialog. With a bunch of parameters that should be set at the VM level. And in general, it is possible that vzyukha should control the dialogue (i.e., for example first display it, and then start moving it around the screen, and then something else like that) In this case, it is possible to use the delegates that you define at the VM level. You define the input and output parameters in the same way in the VM. The method is assigned to the delegate in the code that refers to the View. It implements the display of the dialog (or whatever it is) and the logic of the behavior/control of the display. In principle, you can transmit anything to the output, starting from the result of the dialog and ending with an object that implements the interface you declared through which you control the window. This version of the code is slightly more compact than the version with interfaces. Testability does not suffer.

PSS: Imho ,there is no" correct " option outside the global context of the task. Remember that "Hello World" written according to all the canons and standards is over 100 lines of code. Options with interfaces/delegates, etc. dancing with a tambourine and a dozen related classes they are specific, difficult to understand, maintain, and debug (!) and are only needed if you have more than 3 of these dialogs (of different types) and are expected in the future, besides, they are all non-standard and with complex behavior. In general, "Do not complicate" (c)

 5
Author: Alexey, 2016-10-11 20:49:18

The most correct way to use the interface. Creating an interface

public interface IMessageShow
{
    //Метод для показа диалога открытия файла
    string RequestFileNameForOpen();
    // Событие изменения свойства связанного с последним открытым/сохраненным файлом
    event Action<string> LastActiveFileNameChanged;
    // Путь к последнему открытому/сохраненному файлу
    string LastActiveFileName { get; set; }
    // Стартовая папка для открытия/сохранения файла
    string InitialDirectoryFileDialogue { get; set; }
    // Заголовок окна открытия файла
    string TitleOpenFileDialogue { get; set; }
    // Фильтр файлов для окна открытия файла
    string FilterOpenFileDialogue { get; set; }

}

The class that implements the interface is not completely given, just so that the idea is clear

using FileDialogue = System.Windows.Forms; //для файловых диалогов добавить в References проекта!!!
public class MessageShow : IMessageShow
{
    /// <summary>
    /// Событие изменения свойства связанного с последним открытым/сохраненным файлом
    /// </summary>
    public event Action<string> LastActiveFileNameChanged = delegate { };

    //ctor
    public MessageShow()
    {
        //для диалога открытия файла
        InitialDirectoryFileDialogue = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer);
        TitleOpenFileDialogue = "Открыть текстовой файл";
        FilterOpenFileDialogue = "Текстовой файл (*.txt)|*.txt|Все файлы (*.*)|*.*";
        //для диалога сохранения файла
        TitleSaveFileDialogue = "Сохранить текстовой файл";
        FilterSaveFileDialogue = FilterOpenFileDialogue;
    }
     /// <summary>
    /// Диалог открытия файла
    /// </summary>
    /// <returns>путь к файлу</returns>
    public string RequestFileNameForOpen()
    {
        //не использовать System.Win32, а нужно using System.Windows.Forms;
        FileDialogue.OpenFileDialog openDialog = new FileDialogue.OpenFileDialog();
        openDialog.InitialDirectory = InitialDirectoryFileDialogue;
        openDialog.Title = TitleOpenFileDialogue;
        openDialog.Filter = FilterOpenFileDialogue;
        openDialog.AutoUpgradeEnabled = VistaUI;
        //показываем и проверяем,что имя файла получено
        if (openDialog.ShowDialog() == FileDialogue.DialogResult.Cancel)
        {
            return String.Empty;
        }

        //результат
        LastActiveFileName = openDialog.FileName;
        //событие
        LastActiveFileNameChanged(LastActiveFileName);
        return LastActiveFileName;
    }
}

Next, the ViewModel constructor must pass a parameter of the type IMessageShow

class SideBViewModel : BindableBase
{
    private IMessageShow message;

    //ctor
    public SideBViewModel(IMessageShow message)
    {
        this.message = message;
        message.LastActiveFileNameChanged += Message_LastActiveFileNameChanged;
    }
    //свойство куда будет сохранено имя и путь файла, кот.хочет открыть юзер
    private string _FromShowQuestion;
    public string FromShowQuestion
    {
        get { return _FromShowQuestion; }
        set { SetProperty(ref _FromShowQuestion, value); }
    }
    //обработка события
    private void Message_LastActiveFileNameChanged(string obj)
    {
        FromShowQuestion = message.LastActiveFileName;
    }

    //команда для кнопки открытия диалога
    private RelayCommand _FileOpenCommand;
    public RelayCommand FileOpenCommand
    {
        get { return _FileOpenCommand = _FileOpenCommand ?? new RelayCommand(OnFileOpen); }
    }
    //метод вызываемой командой
    private void OnFileOpen()
    {
        message.RequestFileNameForOpen();
    }
....
}

Something like that.

 3
Author: Bulson, 2016-10-11 19:01:39

If the value for TextBox is taken from VM, then unambiguously:

I should make the OpenFileDialog call in the VM as ICommand

Since the result OpenFileDialog must be assigned to the property to which {[0 is bound]}:

I should not change the value of the textbox, but change the corresponding value in the VM

 2
Author: Gardes, 2016-10-11 13:19:26