#c_sharp #wpf #mvvm #wpf_commands
Подскажите, каким правильно пойти путем.
На форме WPF есть кнопка "Обзор", которая должна вызвать OpenFileDialog и после выбора
файла передать его имя в textbox.
Используя паттерн MVVM, я должен не менять значение textbox, а изменить соответствующие
значение в VM. И вот мой вопрос. Вызов OpenFileDialog я должен сделать в VM как ICommand,
или из своей формы на которой кнопка?
Ответы
Ответ 1
Диалоги-краеугольный камень MVVM ибо предполагается, что VM о View ничего не знает, но должен получать от неё данные. Получаем, что с одной стороны View должна вызывать команды VM с параметрами и VM ну никак не должна работать с отображением. Т.е. диалоги относятся к View. А с другой: View не должна содержать логики/кода, а вызов диалогового окна и получение результата это какая никакая а логика/код. Самый простой вариант: в коде View повесьте обработчик события на то событие по которому у вас должен отображаться диалог выбора файла в котором создаёте и отображаете диалог пользователю, а по факту успешного закрытия события вызываете команду VM из кода (в этом случае можно обойтись и интерфейсным методом, без объявления команды). В код на стороне VM Должна приходить строка пути к открываемому файлу(лам) и, возможно, тип доступа (чтение/чтение-запись и тп.) PS: Если вы придерживаетесь MVVM при разработке, имхо, главное, что следует всегда помнить: вы должны сохранять тестируемость Unit-тестами вашей VM. (т.е. если вместо M подсунуть тестовые данные, а вместо View напрямую вызвать команды у вас все публичные методы и команды должны тестироваться без танцев с бубном) Создание в VM диалогов ломает эту схему. Вариант посложнее: Иногда случается так, что диалог нужен кастомный. С кучей параметров которые должны задаваться на уровне VM. И вообще возможно так, что въюха должна контролировать диалог (т.е. например сначала отобразить его, а потом начать перемещать его по экрану, а потом ещё что-то эдакое) В этом случае возможно воспользоваться делегатами которые вы определяете на уровне VM. Входные и выходные параметры вы определяете так же в VM. Присвоение метода делегату осуществляется в коде который относится ко View. В нем реализуется отображение диалога (или чего бы то ни было ещё) и логика поведения/управления отображением. На выход вы, в принципе, можете передавать всё что угодно, начиная от результата диалога и заканчивая объектом реализующим объявленный вами интерфейс через который вы контролируете окно. Этот вариант по коду выходит чуть компактнее варианта с интерфейсами. Тестируемость не страдает. PSS: Имхо, "правильного" варианта вне глобального контекста задачи не существует. Помните, что "Hello World" написанный по всем канонам и стандартам это овер 100 строк кода. Варианты с интерфейсами/делегатами и пр. танцами с бубном и десятком сопутствующих классов специфичны, сложны в понимании, сопровождении и отладке (!) и нужны только в случае если у вас этих диалогов (разных видов) больше 3 и ожидаются ещё в будущем к тому же они все нестандартные и со сложным поведением. В общем "Не усложняйте" (с)Ответ 2
Самый правильный способ использовать интерфейс. Создаете интерфейс public interface IMessageShow { //Метод для показа диалога открытия файла string RequestFileNameForOpen(); // Событие изменения свойства связанного с последним открытым/сохраненным файлом event ActionLastActiveFileNameChanged; // Путь к последнему открытому/сохраненному файлу string LastActiveFileName { get; set; } // Стартовая папка для открытия/сохранения файла string InitialDirectoryFileDialogue { get; set; } // Заголовок окна открытия файла string TitleOpenFileDialogue { get; set; } // Фильтр файлов для окна открытия файла string FilterOpenFileDialogue { get; set; } } Класс реализующий интерфейс привожу не полностью, только чтоб была понятна идея using FileDialogue = System.Windows.Forms; //для файловых диалогов добавить в References проекта!!! public class MessageShow : IMessageShow { /// /// Событие изменения свойства связанного с последним открытым/сохраненным файлом /// public event ActionLastActiveFileNameChanged = delegate { }; //ctor public MessageShow() { //для диалога открытия файла InitialDirectoryFileDialogue = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer); TitleOpenFileDialogue = "Открыть текстовой файл"; FilterOpenFileDialogue = "Текстовой файл (*.txt)|*.txt|Все файлы (*.*)|*.*"; //для диалога сохранения файла TitleSaveFileDialogue = "Сохранить текстовой файл"; FilterSaveFileDialogue = FilterOpenFileDialogue; } /// /// Диалог открытия файла /// ///путь к файлу 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; } } Далее ViewModel в конструктор нужно передавать параметр типа 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(); } .... } Как-то так.Ответ 3
Если значение для TextBox берется из VM, то однозначно: Вызов OpenFileDialog я должен сделать в VM как ICommand так как результат OpenFileDialog нужно присвоить свойству, к которому привязывается TextBox: я должен не менять значение textbox, а изменить соответствующие значение в VM
Комментариев нет:
Отправить комментарий