Removed original answer because it was long and doesn't really help out much
Edit:
Ok so disregard the above - it looks like you are trying to load two different views over two different viewmodels, which as far as I know is not what Context
is designed for. The Context
property loads two different views over the same viewmodel e.g. in your XAML:
<ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left"
cal:View.Context="GroupDetail"
cal:View.Model="{Binding ActiveItem}"/>
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"
cal:View.Context="RelayEdit"
cal:View.Model="{Binding ActiveItem}"/>
Given a VM with a name of RelayEditViewModel
activated via ActivateItem()
CM will try to load the following views:
RelayEdit.GroupDetail
for the content control
RelayEdit.RelayEdit
for the tab control
See:
...
If you try to load another ViewModel, the same conventions will apply to find the view
GroupDetailViewModel
results in
GroupDetail.GroupDetail
for the content control
GroupDetail.RelayEdit
for the tab control
It sounds like this isn't what you want (and I'm not sure why anything is loading at all - what namespace are your views in? Have you customised the view locator?)
I'm still trying to get my head round the lifecycle support you require but it sounds like you want the edit view to be lifecycle managed (since you want the load/save/guard type support) but the detail view is to be read-only and doesn't care if it's closed without being guarded
In that case you probably want to add a property to your ViewModel which will hold a reference to the details viewmodel but don't activate it ... just set the property without calling ActivateItem(vm)
example:
[Export(typeof(RelayListViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class RelayListViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<Group> {
....
// Backing field + prop to hold the details view - the content control will bind to this
private IScreen _details;
public IViewAware Details { get { } set { } } // Implement standard NotifyOfPropertyChange here for this property
public void Edit() { //Requested Edit
RelayEditViewModel viewModel = TryAndLocateViewModel(SelectedRelay.Group.Rack.Id, SelectedRelay.Group.Id);
ActivateItem(viewModel);
}
....
public void ViewGroupDetail(Relay relay) { //Requested View
GroupDetailViewModel viewModel = container.GetExportedValue<GroupDetailViewModel>();
// Instead of activating, just assign the VM to the property and make sure Details calls NotifyOfPropertyChange to let CM know to start the binding logic
Details = viewModel;
}
Then in your XAML
<!-- Just bind the details view to the Details property -->
<ContentControl x:Name="Details" HorizontalContentAlignment="Left" />
<!-- Leave this as-is, as it's working ok -->
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0" />
(I've assumed that you are using the TabControl
s default conventions above, but tweak if neccessary)
You can use the same VM for both the details and the edit view as long as you set the Context
property accordingly.
Let me know if that helps
Edit:
Just to answer the question about MVVM and coupling etc...
All you are doing is composing a more complex viewmodel from several simpler viewmodels (and therefore a more complex view from several simpler views). As long as your reference to the details VM is not a concrete type, there is very loose coupling between the VMs. You could assign ANY viewmodel type that implements that interface into the Detail
property on the main VM and CM will try to locate the view for it and build the interface. This is perfectly fine (you can use your IoC to get the type for the details window if needed)
If your details view needs lifecycle you should inherit from Screen
, but I doubt that your details view needs activation (since it's just a details view and is ready only) so just implementing IViewAware
and inheriting from PropertyChangedBase
will do. The edit view, however, needs to have lifecycle and therefore should inherit from Screen.
Conductor
already contains an ActiveItem
property, and provides management of lifecycle for child items activated via ActivateItem()
, all you are doing is creating an extra 'bolt-on' property for your conductor which references the additional vm (i.e. you need ActiveItem
and Details
)