"Ты уверен?" подсказки. Часть ViewModel или чисто представление?
Я играл с тем, где поставить "ты уверен?" типа в моем MVVM приложении WPF.
Я склоняюсь к мысли, что они являются чисто частью представления. Если ViewModel предоставляет DeleteCommand
, я бы ожидал, что эта команда будет немедленно удалена.
Чтобы интегрировать такие приглашения в ViewModel, ему нужно было бы открыть отдельное свойство RequestDeleteCommand
, a DeletePromptItem
для привязки приглашения к нему и которое также может быть двойным в качестве триггера для отображения приглашения.
Даже при этом ничего не останавливает unit test вызов DeleteCommand
напрямую, если только я не помещаю определенную логику в ViewModel, чтобы потребовать DeletePromptItem
совместить элемент, предоставленный в качестве аргумента DeleteCommand
.
Однако, это все просто кажется мне шум в ViewModel для меня. Запрос скорее является проблемой пользовательского интерфейса для защиты от misclicks и т.д. Мне кажется, что это должно быть в представлении с подтвержденным приглашением, вызывающим DeleteCommand.
Любые мысли?
Ответы
Ответ 1
Запросы определенно не должны быть частью ViewModel, но это не обязательно означает, что лучшее решение - это жестко закодировать их в View (даже если это очень разумный первый подход).
Я знаю две альтернативы, которые могут уменьшить связь между View и ViewModel: использование службы взаимодействия и запуск запросов взаимодействия. Оба объяснены очень хорошо здесь; Вы могли бы хотеть взглянуть.
Общая идея заключается в том, что вы абстрагируете, как выполняются асинхронные взаимодействия, и работаете с чем-то, более похожим на логику, основанную на событиях, и в то же время позволяете ViewModel выражать, что он хочет взаимодействовать с пользователем как часть операции; в результате вы можете задокументировать это взаимодействие и протестировать его.
Редактировать: я должен добавить, что я исследовал использование Prism 4 с запросами взаимодействия в проекте-прототипе, и я был очень доволен результатами (с небольшим количеством кода фреймворка вы можете даже указать, что произойдет с конкретным запросом взаимодействия полностью в XAML!).
Ответ 2
Однако, это все просто кажется мне шум в ViewModel для меня. Запрос скорее является проблемой пользовательского интерфейса для защиты от misclicks и т.д. Мне кажется, что это должно быть в представлении с подтвержденным приглашением, вызывающим DeleteCommand.
Я согласен; такие запросы должны обрабатываться в представлении, так как в конечном счете представление - это то, что пользователь видит и взаимодействует с, а не моделью представления. После того, как ваше мнение получило подтверждение от пользователя о том, что нужно вызвать DeleteCommand
, перейдите к нему и вызовите его в своей модели просмотра.
Как я вижу, модульные тесты действительно не имеют никакого отношения к взаимодействию с пользователем, если вы не проверяете само представление.
Ответ 3
По моему мнению, запрос пользователя состоит из двух частей:
- Логика, определяющая, следует ли показывать подсказку и что должно быть сделано с результатом
- Код, на самом деле показывающий приглашение
Часть 2 явно не принадлежит ViewModel.
Но часть 1 действительно принадлежит.
Чтобы сделать это разделение возможным, я использую службу, которая может использоваться ViewModel и для которой я могу предоставить реализацию, специфичную для среды, в которой я находилась (WPF, Silverlight, WP7).
Это приводит к следующему коду:
interface IMessageBoxManager
{
MessageBoxResult ShowMessageBox(string text, string title,
MessageBoxButtons buttons);
}
class MyViewModel
{
IMessageBoxManager _messageBoxManager;
// ...
public void Close()
{
if(HasUnsavedChanges)
{
var result = _messageBoxManager.ShowMessageBox(
"Unsaved changes, save them before close?",
"Confirmation", MessageBoxButtons.YesNoCancel);
if(result == MessageBoxResult.Yes)
Save();
else if(result == MessageBoxResult.Cancel)
return; // <- Don't close window
else if(result == MessageBoxResult.No)
RevertUnsavedChanges();
}
TryClose(); // <- Infrastructure method from Caliburn Micro
}
}
Этот подход можно легко использовать не только для отображения окна сообщения, но и для отображения других окон, как описано в этом ответе.
Ответ 4
Я бы предложил сделать это через службу, которая управляет модальными окнами. Некоторое время назад я столкнулся с этой проблемой. Это сообщение в блоге мне очень помогло.
Несмотря на то, что это сообщение silverlight, оно не должно сильно отличаться по сравнению с wpf.
Ответ 5
Я решаю эту проблему, используя шаблон EventAggregator
.
Вы можете увидеть, как это объясняется здесь
Ответ 6
Я думаю, что это зависит от подсказки, но в целом логика кода, которая должна запрашивать пользователя, часто присутствует в модели представления, например, пользователь нажал кнопку, чтобы удалить элемент списка, команда запускается в виртуальная машина, логика запущена, и очевидно, что это может повлиять на другой объект, пользователь должен выбрать то, что они хотят сделать, на этом этапе вы не сможете попросить View запросить пользователя, чтобы я не мог найти другого выбора но обрабатывать его в виртуальной машине. Это то, с чем мне всегда было непросто, но я просто написал метод Confirm в моей базовой виртуальной машине, который вызывает диалоговое окно для запроса dsiplay и возвращает true или false:
/// <summary>
/// A method to ask a confirmation question.
/// </summary>
/// <param name="messageText">The text to you the user.</param>
/// <param name="showAreYouSureText">Optional Parameter which determines whether to prefix the message
/// text with "Are you sure you want to {0}?".</param>
/// <returns>True if the user selected "Yes", otherwise false.</returns>
public Boolean Confirm(String messageText, Boolean? showAreYouSureText = false)
{
String message;
if (showAreYouSureText.HasValue && showAreYouSureText.Value)
message = String.Format(Resources.AreYouSureMessage, messageText);
else
message = messageText;
return DialogService.ShowMessageBox(this, message, MessageBoxType.Question) == MessageBoxResult.Yes;
}
Для меня это одна из тех серых кроссоверов, на которые я иногда не могу получить твердый ответ в MVVM, поэтому я заинтересован в том, чтобы подходы poeples.
Ответ 7
Посмотрите на это:
MVVM и диалоговые окна подтверждения
Я использую подобный метод в своих моделях просмотра, потому что я считаю, что это часть модели представления, чтобы спросить, будет ли она продолжена с удалением или нет, а не с каким-либо визуальным объектом или представлением. С помощью описанной методики ваша модель не ссылается на какие-либо визуальные ссылки, которые мне не нравятся, а на какой-то сервис, вызывающий диалоговое окно подтверждения или окно сообщения или что-то еще.
Ответ 8
Я думаю: "Ты уверен?" подсказки относятся к viewmodel, потому что его логика приложения, а не чистая ui, например анимация и т.д.
поэтому наилучшим вариантом будет метод deleteecommand execute, чтобы вызвать диалоговое окно "Вы уверены" .
EDIT: Код ViewModel
IMessageBox _dialogService;//come to the viewmodel with DI
public ICommand DeleteCommand
{
get
{
return this._cmdDelete ?? (this._cmdDelete = new DelegateCommand(this.DeleteCommandExecute, this.CanDeleteCommandExecute));
}
}
поместите логику в метод выполнения
private void DeleteCommandExecute()
{
if (!this.CanDeleteCommandExecute())
return;
var result = this.dialogService.ShowDialog("Are you sure prompt window?", YesNo);
//check result
//go on with delete when yes
}
служба диалога может быть любым, но логика приложения для проверки перед удалением находится в вашей модели просмотра.
Ответ 9
Лично я считаю, что это просто часть представления, поскольку нет данных
Ответ 10
Способ, которым я занимался в прошлом, - это помещать событие в ViewModel, которое запускалось, когда диалог должен отображаться. View перехватывает событие и обрабатывает отображение диалогового окна подтверждения и возвращает результат вызывающему через его EventArgs.
Ответ 11
С этим столкнулся при портировании старого приложения WinForms на WPF. Я думаю, что важно иметь в виду, что WPF выполняет на много, что он делает под капотом посредством сигнализации между модели представления и представления с событиями (т.е. INotifyPropertyChanged.PropertyChanged
, INotifyDataErrorInfo.ErrorsChanged
и т.д.). Мое решение проблемы состояло в том, чтобы взять этот пример и работать с ним. На мой взгляд модель:
/// <summary>
/// Occurs before the record is deleted
/// </summary>
public event CancelEventHandler DeletingRecord;
/// <summary>
/// Occurs before record changes are discarded (i.e. by a New or Close operation)
/// </summary>
public event DiscardingChangesEvent DiscardingChanges;
Затем представление может прослушивать эти события, запрашивать пользователя, если это необходимо, и отменять событие, если это необходимо.
Обратите внимание, что CancelEventHandler
определяется для вас фреймворком. Однако для DiscardingChanges
вам нужен результат с тремя состояниями, чтобы указать, как вы хотите обработать операцию (т.е. сохранить изменения, отменить изменения или отменить то, что вы делаете). Для этого я просто сделал свой собственный:
public delegate void DiscardingChangesEvent(object sender, DiscardingChangesEventArgs e);
public class DiscardingChangesEventArgs
{
public DiscardingChangesOperation Operation { get; set; } = DiscardingChangesOperation.Cancel;
}
public enum DiscardingChangesOperation
{
Save,
Discard,
Cancel
}
Я пытался придумать лучшее соглашение об именах, но это было лучшее, что я мог придумать.
Итак, приведение его в действие выглядит примерно так:
ViewModel (на самом деле это базовый класс для моих моделей представлений на основе CRUD):
protected virtual void New()
{
// handle case when model is dirty
if (ModelIsDirty)
{
var args = new DiscardingChangesEventArgs(); // defaults to cancel, so someone will need to handle the event to signal discard/save
DiscardingChanges?.Invoke(this, args);
switch (args.Operation)
{
case DiscardingChangesOperation.Save:
if (!SaveInternal())
return;
break;
case DiscardingChangesOperation.Cancel:
return;
}
}
// continue with New operation
}
protected virtual void Delete()
{
var args = new CancelEventArgs();
DeletingRecord?.Invoke(this, args);
if (args.Cancel)
return;
// continue delete operation
}
Посмотреть:
<UserControl.DataContext>
<vm:CompanyViewModel DeletingRecord="CompanyViewModel_DeletingRecord" DiscardingChanges="CompanyViewModel_DiscardingChanges"></vm:CompanyViewModel>
</UserControl.DataContext>
Посмотреть код позади:
private void CompanyViewModel_DeletingRecord(object sender, System.ComponentModel.CancelEventArgs e)
{
App.HandleRecordDeleting(sender, e);
}
private void CompanyViewModel_DiscardingChanges(object sender, DiscardingChangesEventArgs e)
{
App.HandleDiscardingChanges(sender, e);
}
И пара статических методов, являющихся частью класса App, которые может использовать каждое представление:
public static void HandleDiscardingChanges(object sender, DiscardingChangesEventArgs e)
{
switch (MessageBox.Show("Save changes?", "Save", MessageBoxButton.YesNoCancel))
{
case MessageBoxResult.Yes:
e.Operation = DiscardingChangesOperation.Save;
break;
case MessageBoxResult.No:
e.Operation = DiscardingChangesOperation.Discard;
break;
case MessageBoxResult.Cancel:
e.Operation = DiscardingChangesOperation.Cancel;
break;
default:
throw new InvalidEnumArgumentException("Invalid MessageBoxResult returned from MessageBox.Show");
}
}
public static void HandleRecordDeleting(object sender, CancelEventArgs e)
{
e.Cancel = MessageBox.Show("Delete current record?", "Delete", MessageBoxButton.YesNo) == MessageBoxResult.No;
}
Централизация диалогового окна в этих статических методах позволяет нам легко заменять их для пользовательских диалогов позже.