39

私は「よろしいですか?」をどこに置くかをいじっています。MVVMWPFアプリでプロンプトを入力します。

私はこれらが純粋にビューの一部であると考えることに傾いています。ViewModelがを公開している場合、DeleteCommandそのコマンドはすぐに削除されると思います。

このようなプロンプトをViewModelに統合するには、プロンプトをバインドするためのプロパティである別RequestDeleteCommandのプロパティを公開する必要DeletePromptItemがあります。これは、プロンプトを表示するためのトリガーとしても機能します。

これでも、引数として指定された項目と一致することDeleteCommandを要求する特定のロジックをViewModelに配置しない限り、単体テストの呼び出しを直接停止することはできません。DeletePromptItemDeleteCommand

ただし、これはすべて、ViewModelのノイズのように見えます。プロンプトは、ミスクリックなどを防ぐためのユーザーインターフェイスの問題です。これは、DeleteCommandを呼び出す確認済みのプロンプトが表示されている必要があることを示しています。

何かご意見は?

4

11 に答える 11

16

プロンプトは絶対に ViewModelの一部であってはなりませんが、これはビューでそれらをハードコーディングすることが最善の解決策であることを必ずしも意味しません (それは非常に合理的な最初のアプローチですが)。

View と ViewModel の間の結合を減らすことができる方法として、私が知っている 2 つの方法があります。対話サービスを使用する方法と、対話要求を開始する方法です。どちらもここで非常によく説明されています。あなたは見てみたいかもしれません。

一般的な考え方は、非同期の対話がどのように行われるかを抽象化し、イベントベースのロジックに似たものを操作すると同時に、操作の一部としてユーザーと対話したいことを ViewModel が表現できるようにすることです。最終的な結果として、このやり取りを文書化し、単体テストを行うことができます。

編集:プロトタイプ プロジェクトでPrism 4 をインタラクション リクエストと共に使用して調査したことを付け加えておく必要があります。 XAML!)。

于 2012-04-19T11:32:52.380 に答える
8

ただし、これはすべて、ViewModelのノイズのように見えます。プロンプトは、ミスクリックなどを防ぐためのユーザーインターフェイスの問題です。これは、DeleteCommandを呼び出す確認済みのプロンプトが表示されている必要があることを示しています。

同意します; このようなプロンプトはビューで処理する必要があります。最終的には、ビューはユーザーが表示および操作しているものであり、ビューモデルではないためです。ビューが呼び出される必要があるというユーザーからの確認を取得したら、DeleteCommand先に進んでビューモデルでそれを呼び出します。

私の見方では、ビュー自体をテストしない限り、単体テストはユーザーの操作とは実際には何の関係もありません。

于 2012-04-19T11:27:59.707 に答える
6

私の意見では、ユーザーへのプロンプトは2つの部分で構成されています。

  1. プロンプトを表示するかどうか、および結果をどのように処理するかを決定するロジック
  2. プロンプトを実際に表示するコード

パート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
    }
}

この回答で説明されているように、このアプローチは、メッセージボックスを表示するだけでなく、他のウィンドウを表示するためにも簡単に使用できます。

于 2012-04-19T11:43:23.943 に答える
3

モーダルウィンドウを管理するサービスを介してこれを行うことをお勧めします。私もかなり前にこの問題に直面しました。このブログ投稿は私を大いに助けてくれました。

これはSilverlightの投稿ですが、wpfと比較してそれほど違いはありません。

于 2012-04-19T11:28:24.173 に答える
1

プロンプトに依存すると思いますが、一般的に言えば、ユーザーにプロンプ​​トを表示する必要があるコードロジックは、ビューモデルにあることが多いです。ロジックが実行され、これが別のエンティティに影響を与える可能性があることは明らかです。ユーザーは次に何をしたいかを選択する必要があります。この時点で、ビューにユーザーにプロンプ​​トを表示するように要求することはできません。それを VM で。これは私がいつも不安に思っていたことですが、プロンプトを表示するダイアログ サービスを呼び出して true または false を返す Confirm メソッドをベース VM に記述しただけです。

    /// <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 で確固たる答えを得られないことがある灰色のクロスオーバー領域の 1 つであり、他の人々のアプローチに興味があります。

于 2012-04-19T11:36:41.077 に答える
1

これを見てください:

MVVM と確認ダイアログ

ビューモデルで同様の手法を使用しているのは、ビューモデルの一部であり、削除を続行するかどうかを尋ねることであり、視覚的なオブジェクトやビューではないと考えているためです。説明されている手法を使用すると、モデルは、私が好きではない視覚的な参照を参照するのではなく、確認ダイアログやメッセージ ボックスなどを呼び出すある種のサービスを参照します。

于 2012-04-19T11:39:03.870 に答える
1

私が過去にそれを処理した方法は、ダイアログを表示する必要があるときに発生する ViewModel にイベントを配置することでした。ビューはイベントにフックし、確認ダイアログの表示を処理し、その EventArgs を介して呼び出し元に結果を返します。

于 2012-04-19T12:15:43.087 に答える
1

私は「よろしいですか?」と思います。アプリケーションロジックであり、アニメーションなどの純粋なUIのものではないため、プロンプトはビューモデルに属します。

したがって、最良のオプションは、deletecommand execute メソッドで「よろしいですか」サービス ダイアログを呼び出すことです。

編集: 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
     } 

ダイアログ サービスは何でもかまいませんが、削除する前にチェックするアプリケーション ロジックはビューモデルにあります。

           

于 2012-04-19T12:35:48.193 に答える
0

個人的には、データがないので、ビューの一部にすぎないと思います

于 2012-04-19T11:28:26.517 に答える
0

EventAggregatorパターンを使ってこのような問題を解決します。

ここで説明されているのを見ることができます

于 2012-04-19T11:33:32.170 に答える
0

古い W​​inForms アプリを 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;
}

これらの静的メソッドでダイアログ ボックスを一元化すると、後でカスタム ダイアログに簡単に交換できます。

于 2018-12-19T16:52:12.260 に答える