web-dev-qa-db-ja.com

別のビューモデルのコレクションにモデルを追加する

セットアップ

ですから、MainViewModelクラスが存在するプロジェクトに取り組んでいます。このMainViewModelには、監視可能なコレクションによるSoldiersのリストが含まれています。 MainViewに新しいAddSoldierViewを表示するボタンがあります。 AddSoldierViewAddSoldierViewModelの要素にバインドしています。 AddSoldierViewは基本的に、ユーザーがその兵士のすべてのデータを入力するフォームです。

問題

Soldierに関するAddSoldierViewModelの情報を取得したので、これをMainViewModelのObservableCollectionに追加できるようにしたいと思います。 AddSoldierViewのボタン(Add Soldier)にコマンドをバインドしましたが、その情報をMainViewModelに戻す方法がわかりません。

私が試したこと

AddSoldierViewModelがEventArgとして渡されるSoldierModelにイベントハンドラーを既に設定しています。しかし、イベント自体をトリガーすることができません。

助言がありますか?私はMVVMの精神に忠実であり続けようと努力してきましたが、まだ整理しようとしているいくつかのねじれがあります。いくつかのコードスニペット、UML図などを表示したい場合はお知らせください。

AddSoldierViewModel.cs

public class AddSoldierViewModel : ViewModelBase
{
    public event EventHandler<AddSoldierEventArgs> AddSoldierClicked;

    public ICommand AddSoldierCommand;

    private Soldier _soldier;

    public Soldier Soldier
    {
        get => _soldier;
        set
        {
            _soldier = value;
            RaisePropertyChanged(nameof(Soldier));
        }
    }

    public AddSoldierViewModel() 
    {
        AddSoldierCommand = new RelayCommand(AddSoldier);
    }

    private void AddSoldier()
    {
        OnAddSoldierClicked(new AddSoldierEventArgs()
        {
           Soldier = Soldier
        });
    }

    protected virtual void OnAddSoldierClicked(AddSoldierEventArgs e)
    {
        var handler = AddSoldierClicked;
        handler?.Invoke(this, e);
    }
}

MainViewModel.cs

public class MainViewModel : ViewModelBase
{
    #region - Private Properties -
    private Team _selectedTeam;
    private Soldier _selectedSoldier;
    #endregion // - Private Properties -

    #region // - Public Properties -
    public ObservableCollection<Soldier> Soldiers { get; set; }
    public ObservableCollection<Team> Teams { get; }
    public Team SelectedTeam
    {
        get => _selectedTeam;
        set
        {
            _selectedTeam = value;
            RaisePropertyChanged(nameof(SelectedTeam));
        }
    }
    public Soldier SelectedSoldier
    {
        get => _selectedSoldier;
        set
        {
            _selectedSoldier = value;
            RaisePropertyChanged(nameof(SelectedSoldier));
        }
    }
    #endregion // - Public Properties -

    #region // - Commands -
    public ICommand DeleteTeamCommand { get; private set; }
    public ICommand AddSoldierDialogCommand { get; private set; }
    #endregion // - Commands -

    #region  - Services -
    public IDialogService AddSoldierDialogService { get; private set; }
    #endregion // - Services -

    #region - Constructors -
    public MainViewModel()
    {
        Soldiers = new ObservableCollection<Soldier>();
        Teams = new ObservableCollection<Team>();

        Soldiers.CollectionChanged += Soldiers_CollectionChanged;
        Teams.CollectionChanged += Teams_CollectionChanged;

        DeleteTeamCommand = new RelayCommand(DeleteTeam);
        AddSoldierDialogCommand = new RelayCommand(AddSoldierDialog);

        AddSoldierDialogService = new AddSoldierDialogService();
    }

    #endregion // - Constructors -

    #region - Methods -
    private void AddSoldierDialog()
    {
        AddSoldierViewModel addSoldierViewModel = new AddSoldierViewModel();
        addSoldierViewModel.AddSoldierClicked += AddSoldierViewModel_AddSoldierClicked;
        AddSoldierDialogService.ShowDialog(addSoldierViewModel);
    }

    private void AddSoldierViewModel_AddSoldierClicked(object sender, AddSoldierEventArgs e)
    {
        Soldiers.Add(new Soldier(e.Soldier));
    }

    private void Teams_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        foreach (var item in e.NewItems)
        {
        }
        foreach (var item in e.OldItems)
        {
        }
        RaisePropertyChanged(nameof(Teams));
    }

    private void Soldiers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        foreach (var item in e.NewItems)
        {
        }
        foreach (var item in e.OldItems)
        {
        }
        RaisePropertyChanged(nameof(Soldiers));
    }
    #endregion // - Methods -
}
3
Matthew S

AddSoldierViewModelはダイアログボックス用なので、物事をもっと簡単にできると思います。変更する必要があるのは、AddSoldierDialog()メソッドだけです。

_private void AddSoldierDialog()
{
    AddSoldierViewModel addSoldierViewModel = new AddSoldierViewModel();

    if(AddSoldierDialogService.ShowDialog(addSoldierViewModel) == true)
    {
        Soldiers.Add(addSoldierViewModel.Soldier);
    }
}
_

ShowDialog()の戻り値は_bool?_である必要があります。ここで、trueはユーザーが良好な状態でダイアログを閉じたことを意味します。 falseは不正な状態でダイアログを閉じ、nullはウィンドウ自体からダイアログを閉じます。ユーザーがその兵士を追加するつもりであることがわかったら、それを直接リストに追加できます。

これにより、複雑なイベントルートが回避され、デバッグが簡単になります。


もちろんif「Add Soldier Dialog」の使用例はstay openであり、ユーザーが閉じる前に複数の新しい兵士を追加できるようにする場合、イベントが必要になります。不要になった場合は、必ず登録を解除してください。

最後のコメントは、イベントを保持することを選択した場合、送信する前にSoldierインスタンスのコピーを処理することです。そうすれば、どこかの誰かがイベントの兵士はユニークだと思ったので、長引くバグがなくなります。

その場合、クリックハンドラは次のようになります。

_private void RaiseAddSoldierClicked()
{
    AddSoldierClicked?.Invoke(this, new AddSoldierEventArgs
    {
        Soldier = new Soldier(Soldier)
    });
}
_

すべてのリスナーの代わりにイベントソースでそれを処理すると、予期しないバグを減らすのに役立ちます。

1
Berin Loritsch

だから昨夜はもう少しいじってみましたが、MVVMの良い解決策を見つけたかもしれません。イベントのアイデアを再検討し、EventHandler AddSoldierClickAddSoldierVMに追加しました。選択したSoldierを保持するカスタムEventArgsクラスも作成しました。

AddSoldierVMMainVMをインスタンス化するときに、AddSoldierClickイベントをサブスクライブし、EventArgのSoldierオブジェクトをMainVM

その後、RaisePropertyChanged(nameof(Soldiers))イベントでSoldiers_CollectionChanged()を実行します。


家に帰ったら、コードスニペットを投稿します。しかし、私はあなたたちとこの答えを共有することにしました。他の方法はありますか?私はこれはかなりきれいだと思います(イベントにサブスクライブし、MainVMが完了したときにサブスクライブを解除することを忘れると、潜在的なメモリリークに注意する必要があります)。

0
Matthew S