Bish25
Bish25

Reputation: 616

How to add mutiple ViewModels to user controls in main window

This my first attempt at implementing the MVVM model so apologies in advance. I have a main window which has its own ViewModel. The Main Window then has three user controls which i which loads depending on what selected in the navigation menu. However the data context is not changing to the correct view model.

MainWindow.xaml

<Window.DataContext>
    <VM:MainVM></VM:MainVM>
</Window.DataContext> 

<Window.Resources>
    <DataTemplate x:Key="View1Template" DataType="{x:Type VM:CustomerVM}">
        <View:Customers  DataContext="{Binding VM:CustomerVM}" />
    </DataTemplate>

    <DataTemplate x:Key="View2Template" DataType="{x:Type VM:SuppliersVM}">
        <View:Suppliers DataContext="{Binding VM:Suppliers}"/>
    </DataTemplate>
</Window.Resources>

<ContentControl  Margin="0,135,0,10" Grid.Column="1">
    <ContentControl.Style>
        <Style TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate"  Value="{StaticResource View1Template}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding SwitchView}" Value="0">
                    <Setter Property="ContentTemplate" Value="{StaticResource View1Template}" />
                </DataTrigger>
                <DataTrigger Binding="{Binding SwitchView}" Value="1">
                    <Setter Property="ContentTemplate" Value="{StaticResource View2Template}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

this has its own ViewModel and navigation loads the view correctly, however i have a command which runs on load and its not performing. This just populates a listview. I know it works because if i remove the datacontext of the window and set it as CustomerVM the listview is populated however navigation no longer works because MainVM has been removed.

Customer.xaml

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding LoadCustomersCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
    <TextBox Tag="Search Customers" x:Name="searchTextBox" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" HorizontalAlignment="Left" Height="40" Margin="10,9,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="340" Padding="5,6,0,0" FontSize="16"/>
    <Label Content="{Binding Path=SearchText}" Margin="430,0,436,263" />
    <ListView ItemsSource="{Binding Customers}" x:Name="customerListBox" Margin="10,57,10,10"  AlternationCount="2"   >
        <ListView.View>
            <GridView>
                <GridViewColumn Header="ID" Width="Auto" DisplayMemberBinding="{Binding id}" />
                <GridViewColumn Header="NAME" Width="Auto" DisplayMemberBinding="{Binding name}"  />
                <GridViewColumn Header="ADDRESS" Width="Auto" DisplayMemberBinding="{Binding address1}" />
                <GridViewColumn Header="ADDRESS 2" Width="150" DisplayMemberBinding="{Binding address2}" />
                <GridViewColumn Header="TOWN" Width="150" DisplayMemberBinding="{Binding town}" />
                <GridViewColumn Header="COUNTY" Width="150" DisplayMemberBinding="{Binding county}" />
                <GridViewColumn Header="POSTCODE" Width="150" DisplayMemberBinding="{Binding postcode}" />
                <GridViewColumn Header="PHONE" Width="150" DisplayMemberBinding="{Binding phone}" />
                <GridViewColumn Header="EMAIL" Width="150" DisplayMemberBinding="{Binding email}" />
            </GridView>
        </ListView.View>   
    </ListView>
</Grid>

CustomerVM.cs

private string _searchText;
private readonly ObservableCollection<Customer> _customers = new ObservableCollection<Customer>();

public string Title = "Customers";

public string SearchText
{
    get { return _searchText; }
    set
    {
        _searchText = value;
        RaisePropertyChangedEvent("SearchText");
    }
}

public IEnumerable<Customer> Customers
{
    get { return _customers; }
}

public ICommand LoadCustomersCommand
{
    get { return new DelegateCommand(LoadCustomers); }
}

public void LoadCustomers()
{
    Customer cus = new Customer { id = 1, name = "sam" };
    _customers.Add(cus);

}

MainVM.cs

public ICommand NavigationClick
{
    get { return new DelegateCommand(Navigate); }
}

public void Navigate()
{        
    SwitchView = 1;
}

Upvotes: 3

Views: 243

Answers (1)

I think there are several things going on here. We'll begin with the one that's most clear to me at the outset.

A DataContext is an object -- generally an instance of a viewmodel class. A DataTemplate is used to display an instance of a viewmodel class. Its DataContext is inherited by the controls that it contains. This is probably what you want:

<Window.Resources>
    <DataTemplate DataType="{x:Type VM:CustomerVM}">
        <View:Customers />
    </DataTemplate>

    <DataTemplate DataType="{x:Type VM:SuppliersVM}">
        <View:Suppliers />
    </DataTemplate>
</Window.Resources>

Secondly, you have to populate your ContentControl with an actual instance of one child viewmodel or another.

One way you've found is to give each view a <DataContext> element in the XAML that creates a viewmodel instance:

<DataContext>
    <VM:CustomerVM />
</DataContext>

But then how does the main viewmodel interact with its children? One solution is to use an MVVM framework that adds a byzantine "locator" that halfway solves the problem with a mess of kludges and workarounds. Much effort is spent satisfying the framework's demands.

That design puts the views in charge of everything, but views have no logic. They have no business being in charge. The "brain" of your application is broken into a dozen little pieces that are hidden from each other.

The other way is "viewmodel centric design": Design your application as a set of viewmodels. Child viewmodels are properties of parent viewmodels. MainVM can easily find its child SupplierVM because MainVM created it and owns it. Instead of the viewmodels being isolated appendages dangling from the views, the views are isolated appendages of the viewmodels -- which is absolutely fine, because views pretty much just sit there anyway.

Everything with WPF is easier this way.

You've got three viewmodels here: A parent, MainVM, and two children, CustomerVM and SupplierVM. Within the main view, you want to display one child or the other at a time.

So we'll give MainVM an instance of each child:

public MainVM()
{
    Customer = new CustomerVM();
    Supplier = new SupplierVM();
}

public CustomerVM Customer {
    get { return _customerVM; }
    private set { _customerVM = value; }
}
public SupplierVM Supplier {
    get { return _supplierVM; }
    private set { _supplierVM = value; }
}

public INotifyPropertyChanged _selectedChild;
public INotifyPropertyChanged SelectedChild {
    get { return _selectedChild; }
    set {
        if (value != _selectedChild) {
            _selectedChild = value;
            //  I don't know how you raise PropertyChanged; if it doesn't look 
            //  like this, let me know. 
            OnPropertyChanged();
        }
    }
}

public void Navigate()
{        
    SelectedChild = Customer;
}

In the XAML, the content control is much simplified. Whatever you put in SelectedChild will be displayed in the content control. How does it know what template to use?

Simple: We took the x:Key attribute off the data templates above, but we left the DataType attributes. That makes them into what's called "implicit data templates": Wherever they are in scope, they will be used automatically by any ContentControl that needs to display an object of one of those types. Anywhere in this Window, if you tell XAML "here's a CustomerVM; show it to the user", XAML will use the first of the above two datatemplates to display it.

<ContentControl 
    Content="{Binding SelectedChild}"
    Margin="0,135,0,10" 
    Grid.Column="1"
    />

You can get rid of the SwitchView property. What you did certainly works (and as you've found, doing something that works in XAML isn't always easy), but this way is easier and more conventional.

Upvotes: 4

Related Questions