Подтвердить что ты не робот

"Ты уверен?" подсказки. Часть ViewModel или чисто представление?

Я играл с тем, где поставить "ты уверен?" типа в моем MVVM приложении WPF.

Я склоняюсь к мысли, что они являются чисто частью представления. Если ViewModel предоставляет DeleteCommand, я бы ожидал, что эта команда будет немедленно удалена.

Чтобы интегрировать такие приглашения в ViewModel, ему нужно было бы открыть отдельное свойство RequestDeleteCommand, a DeletePromptItem для привязки приглашения к нему и которое также может быть двойным в качестве триггера для отображения приглашения.

Даже при этом ничего не останавливает unit test вызов DeleteCommand напрямую, если только я не помещаю определенную логику в ViewModel, чтобы потребовать DeletePromptItem совместить элемент, предоставленный в качестве аргумента DeleteCommand.

Однако, это все просто кажется мне шум в ViewModel для меня. Запрос скорее является проблемой пользовательского интерфейса для защиты от misclicks и т.д. Мне кажется, что это должно быть в представлении с подтвержденным приглашением, вызывающим DeleteCommand.

Любые мысли?

4b9b3361

Ответ 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;
}

Централизация диалогового окна в этих статических методах позволяет нам легко заменять их для пользовательских диалогов позже.