The Setup
So I'm working on a project in which there exists a MainViewModel
class. This MainViewModel
contains a list of Soldiers
through an observable collection. I have a button in the MainView
which displays a new AddSoldierView
. The AddSoldierView
is binding to elements from the AddSoldierViewModel
. The AddSoldierView
is basically a form where the user inputs all of the data for that soldier.
The Problem
Now that I have the Soldier
's information on the AddSoldierViewModel
, I want to be able to add that back into the ObservableCollection of the MainViewModel
. I have binded a command to the button (Add Soldier) on the AddSoldierView
, but I'm not sure how to get that information back into the MainViewModel
.
What I've tried
I've already setting up an event handler on the AddSoldierViewModel
in which the SoldierModel
is passed as an EventArg. But I can't get the event itself to trigger.
Any suggestions? I've been trying to stay true to the MVVM spirit, but there are still some kinks I'm trying to sort out. Let me know if you want to see some code snippets, UML diagrams, or whatever.
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 -
}
Best Answer
Since the
AddSoldierViewModel
is for a dialog box, I think you can make things a lot simpler. The only thing that needs to change is theAddSoldierDialog()
method:The return value of
ShowDialog()
should bebool?
, wheretrue
means the user closed the dialog in a good state.false
would be closing the dialog in a bad state andnull
would be closing the dialog from the window itself. Once you know the user intended to add that soldier, you can add it to your list directly.This avoids complicated event routes and simplifies debugging.
Of course, if the use case of the "Add Soldier Dialog" were to stay open and allow the user to add multiple new soldiers before closing, then your event would be necessary. Just make sure to unregister it when you don't need it any more.
Last comment would be if you do choose to keep the event, you handle copying the Soldier instance before you send it. That way you don't have lingering bugs because somewhere someone thought the soldier in the event was unique.
In that case your click handler would look something like this:
Handling that on the event source instead of all the listeners will help reduce unexpected bugs.