0

I have a List View in which there are two Child Views. One is the Display View and another is Edit View. Here is how I have defined the List (Parent) view. Note that I want the two child UserControl's to occupy different space in the Parent.

<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
    ....

    <ContentControl x:Name="GroupDetail" Grid.Row="2" />
    <TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"/>
</UserControl>

Then In my view model, I activate these items based on user responses in the following manner

**View Model **

[Export(typeof(RelayListViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class RelayListViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<Group> {
    ....
    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>();
        ActivateItem(viewModel);
    }

The above code works but the Detail View is loaded in the Tabs space (the space meant for the Edit View). Actually, the ActivateItem(viewModel) does pick up the correct type of view to display but it is loaded in the wrong place for the Display View, that is, the Display View is loaded in the Edit View's space on the screen. Surely I am missing some obvious stuff.

In summary, how do we get two UserControls defined in a Parent UserControl to activate in its own space?

Edit - 1:

Here are two Screen Shots which show where I need to load the Edit and Detail Views respectively.

Edit Screen Loaded

View Screen Loaded (Should not load in Edit Area)

As you can see in the second screenshot, the Detail View gets loaded in the Detail Area as well as the Edit Area(Tabs). I wan't the Detail View only to appear in the Detail Area. The Edit Area is only for the Edit View.

Here is the code that I have used to generate the screen shots.

The Main View that houses both views

<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
    <Grid>
         ....
                     <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}"/>
     </Grid>
</UserControl>

Edit 2: I think I am very close to get it working. As per your suggestions I modified the Main(Parent) container as below.

<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
            <ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left" />
                <TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0" />

The Edit Screen and Detail Screens now appear in their proper places. However, the Detail ViewModels OnActivate is not called upon so I get a blank Detail View with no variables populated. All loading of Details View field is done on the OnActivate() override. Here is how my GroupDetailViewModel is defined

[Export(typeof(GroupDetailViewModel))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class GroupDetailViewModel : Screen {
    ...
    protected override void OnActivate() {
        base.OnActivate();
    ..
    } 

So certainly, I am missing some attribute. Or will I have to call some method on the GroupDetailViewModel to load the details manually ?

Jatin
  • 4,023
  • 10
  • 60
  • 107

1 Answers1

1

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:

http://caliburnmicro.codeplex.com/wikipage?title=View%2fViewModel%20Naming%20Conventions&referringTitle=Documentation

...

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 TabControls 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)

Charleh
  • 13,749
  • 3
  • 37
  • 57
  • I have edited the question to include Screen Shots. I need to have two active item one for edit and one for detail, but they should appear in their respective areas (two different regions in parent view) as indicated in screen shot. – Jatin Nov 30 '12 at 12:15
  • Thanks for that - makes much more sense now I can see it, you are using the `Context` property to choose the view to be loaded – Charleh Nov 30 '12 at 13:39
  • Yes I actually modified my code based on what I found out on web. But I am still not getting the desired behavior that DetailView should load in Detail Area and Edit View(s) should load in EditArea(as Tabs). The Detail View still loads in the Edit Area and when I try to load Edit Screen, it wipes out Detail View in the Detail Area – Jatin Nov 30 '12 at 13:57
  • Have you customised the view locator? Are you trying to use Context? You are calling ActivateItem on both VMs so the edit screen or details screen will appear in both places. Check my edit (if you haven't already) - you don't want lifecycle for the view only screen so you want to bind that using a property on the VM instead of using `ActivateItem` (i.e. DONT activate the details VM via ActivateItem, just call Activate on it if it implements IActivatable) – Charleh Nov 30 '12 at 14:08
  • If we don't use ActivateItem, how do we ensure that OnActivate() is invoked on the Detail ViewModel. Please see my edit 2. Is IActivatable part of Caliburn.Micro. I don't see in anywere in my code for resolving the reference. – Jatin Nov 30 '12 at 14:38
  • Ok, so I didn't extend the Detail ViewModel from Screen and then called the Detail View Model's Activate method manually to load the detail record. But is this all allowed to do as per the MVVM concept ? I mean the very idea of having one view model reference in another seems to be a little off in MVVM? – Jatin Nov 30 '12 at 14:59
  • This is perfectly fine - it's composition, you are composing a complex view from several ViewModels. Of course if you insert a property with a concrete type into your VM then you are starting to couple the VMs, but if you keep the reference as an interface type (such as IViewAware or IScreen) then the viewmodels are not strongly coupled. Your `RelayListViewModel` *requires* a details view, it's the same as having `ActiveItem` on the `Conductor` impelementation, you need a property to hold the components that make up the view, just that in your case you have an `ActiveItem` **and** `Details` – Charleh Nov 30 '12 at 15:25
  • Yes, That makes sense. Thanks once again. – Jatin Nov 30 '12 at 15:29