ですから、MainViewModel
クラスが存在するプロジェクトに取り組んでいます。このMainViewModel
には、監視可能なコレクションによるSoldiers
のリストが含まれています。 MainView
に新しいAddSoldierView
を表示するボタンがあります。 AddSoldierView
はAddSoldierViewModel
の要素にバインドしています。 AddSoldierView
は基本的に、ユーザーがその兵士のすべてのデータを入力するフォームです。
Soldier
に関するAddSoldierViewModel
の情報を取得したので、これをMainViewModel
のObservableCollectionに追加できるようにしたいと思います。 AddSoldierView
のボタン(Add Soldier)にコマンドをバインドしましたが、その情報をMainViewModel
に戻す方法がわかりません。
AddSoldierViewModel
がEventArgとして渡されるSoldierModel
にイベントハンドラーを既に設定しています。しかし、イベント自体をトリガーすることができません。
助言がありますか?私はMVVMの精神に忠実であり続けようと努力してきましたが、まだ整理しようとしているいくつかのねじれがあります。いくつかのコードスニペット、UML図などを表示したい場合はお知らせください。
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);
}
}
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 -
}
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)
});
}
_
すべてのリスナーの代わりにイベントソースでそれを処理すると、予期しないバグを減らすのに役立ちます。
だから昨夜はもう少しいじってみましたが、MVVMの良い解決策を見つけたかもしれません。イベントのアイデアを再検討し、EventHandler AddSoldierClick
をAddSoldierVM
に追加しました。選択したSoldier
を保持するカスタムEventArgsクラスも作成しました。
AddSoldierVM
でMainVM
をインスタンス化するときに、AddSoldierClick
イベントをサブスクライブし、EventArgのSoldier
オブジェクトをMainVM
。
その後、RaisePropertyChanged(nameof(Soldiers))
イベントでSoldiers_CollectionChanged()
を実行します。
家に帰ったら、コードスニペットを投稿します。しかし、私はあなたたちとこの答えを共有することにしました。他の方法はありますか?私はこれはかなりきれいだと思います(イベントにサブスクライブし、MainVM
が完了したときにサブスクライブを解除することを忘れると、潜在的なメモリリークに注意する必要があります)。