Страницы

Поиск по вопросам

четверг, 29 ноября 2018 г.

Передача значения из View в Model и последующий вызов метода - как?

Следуя паттерну MVVM имеем View, ViewModel и некие классы для Model, в частности ExcelImporter для импорта и парсинга экселевского файла.
Во вьюхе есть поле ввода адреса файла. Биндится к соответствующему свойству в VM:
public string ExcelPath { get { return excelPath; }
set { excelPath = value; OnPropertyChanged("ExcelPath"); //? } }
Вопрос 1: если значение ExcelPath меняется только из вьюхи пользователем через диалоговое окно выбора файла, то во ViewModel в сеттере ему ведь не нужно делать OnPropertyChanged("ExcelPath")? И, следовательно, нужно в биндинге оставить Mode=Default (он же OneWay)?
Также в VM есть экземпляр класса ExcelImporter, который отвечает за импорт файла, со свойством, опять же, ExcelPath
Вопрос 2: где будет корректным передавать значение из VM.ExcelPath в ExcelImporter.ExcelPath? В сеттере VM.ExcelPath? Или ещё была шальная мысль сделать событие ExcelPathChanged, на которое подписать саму же VM, и в обработчике устанавливать ExcelImporter.ExcelPath = ExcelPath. Или подписать на это событие ExcelImporter, но это по MVVM вроде как совсем неправильно.
public string ExcelPath { get { return excelPath; }
set { excelPath = value; ExcelImporter.ExcelPath = ExcelPath; //? } }
Вопрос 2.1: а можно вообще вот так сделать свойство в VM?
public string ExcelPath { get { return ExcelImporter.ExcelPath; }
set { ExcelImporter.ExcelPath = value; } }
Вопрос 3: если в сеттере, то что именно присваивать: value, excelPath или, как в вопросе 2, ExcelPath?
public string ExcelPath { get { return excelPath; }
set { excelPath = value; ExcelImporter.ExcelPath = value; //? ExcelImporter.ExcelPath = excelPath; //? ExcelImporter.ExcelPath = ExcelPath; //? } }
Вопрос 4: чтобы ExcelImporter сразу же после получения ExcelPath выполнял метод Import() - т.е. чтобы с точки зрения пользователя всё автоматически происходило после выбора файла из обычного OpenFileDialog, без всяких лишних нажатий кнопки типа "Импортировать" - этот метод должен вызываться где? В сеттере VM.ExcelPath, в сеттере ExcelImporter.ExcelPath или через событие в ExcelImporter, какой-нибудь ExcelPathChanged, на которое подписан... сам же ExcelImporter или VM, и уже там в обработчике вызывать ExcelImporter.Import()? Больше ничего в голову не приходит, а такое активное использование сеттера для кучи дополнительных действий вызывает сомнение.
Вообще задача простая: получить из View адрес файла, передать его в ExcelImporter и вызвать метод Import(). Может, вообще все вопросы неправильные, и это делается как-то по-другому? И суть вопросов не в том как это сделать вообще, а как сделать правильно, религиозно верно, так сказать, а не мартышкиным методом "абы работало" :)
PS Прошу прощения за некоторое нарушение правил SO, за несколько вопросов сразу, но как видите они взаимосвязаны, и создавать несколько отдельных тем показалось лишним.
UPD1
@andrey-k
Мне сложно было представить такую ситуацию, что импорт происходит сразу после ввода имени файла, минуя нажатие кнопки
Ну поле, оно же TextBox, существует постольку-поскольку, чтобы была возможность скопипастить адрес файла, но в осноном конечно же выбор через винапишный OpenFileDialog. Хотя всё это сводится к одному - по сути что ввод ручками пользователя, что OpenFileDialog возвращает строкой путь к файлу. Разумеется, его нужно проверить. Но не вижу никакого смысла вынуждать пользователя тыкаться лишний раз ещё в какие-то кнопки после указания файла. Выбор документа и выуживание из него определённых данных - это первые 50% функционала.


Ответ

По вопросу 1: View не должно знать, кто и как меняет свойства в VM. Завтра вы поменяете VM, и при этом вы не должны менять ещё и View. Пусть каждый из уровней заботится только о себе.
По поводу OnPropertyChanged("ExcelPath"); //?: лучше писать, конечно, так:
void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
и пользоваться просто NotifyPropertyChanged()
string excelPath; public string ExcelPath { get { return excelPath; } set { if (excelPath != value) { excelPath = value; NotifyPropertyChanged(); } } }
По поводу того, когда обновлять модель — это решать вам и только вам. Может быть, обновлять модель сразу неправильно, а нужно подождать, пока пользователь скомандует «вот сейчас давайте». А может быть, нужно обновлять её как можно скорее. Вы как архитектор вашего приложения должны решать такие вопросы сами.
В любом случае, обновление модели — дело VM, а не наоборот.
По вопросу 2.1 — технически так делать можно, да (но не забудьте NotifyPropertyChanged()). Вопрос в том, правильно ли это для вашего случая. Например, если модель бежит не в главном потоке, то доступ к ней из UI-потока может быть неверным.
По вопросу 3 — не имеет ровно никакого значения. Все три значат одно и то же. (Я всё же не использовал бы ExcelPath, чтобы не идти лишний раз через getter, но это вопрос личных предпочтений.)
По поводу вопроса 4 — опять-таки это не диктуется паттерном MVVM. Всё определяется лично вами. Я бы делал так: при выставлении setter'а проверял значение на правильность, проверял, можно ли запускать импорт, и при этом условии запускал бы:
string excelPath; public string ExcelPath { get { return excelPath; } set { if (excelPath == value) return; excelPath = value; NotifyPropertyChanged(); OnPathChanged(); } }
async void OnPathChanged() { if (!IsValidPath(excelPath)) { SetInvalidFlag(); // so UI can pick it up return; }
await BookNewImport(); }
ExcelImporter currentImporter, pendingImporter; async Task BookNewImport() { var importer = new ExcelImporter(ExcelPath); if (currentImporter != null) { pendingImporter = importer; await currentImporter.CancelAsync(); if (pendingImporter != importer) // new import booked return; // so not run ours } currentImporter = importer; await currentImporter.ImportAsync(); if (currentImporter == importer) currentImporter = null; // else there's other importer running }
Это лучше тем, что модель дёргается только после всех проверок. Плюс, поскольку импорт — процесс по сути длительный, я бы сделал к нему async-интерфейс, и не блокировал вызывающий поток (как это сделано в сниппете выше).

Комментариев нет:

Отправить комментарий