3

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;
}
Bish25
  • 606
  • 1
  • 10
  • 35
  • Please show how those DataTemplates are used in the XAML. You don't need to explicitly set the DataContext on the view inside the template; it will inherit the datatemplate's DataContext. What does `DataContext="{Binding VM:CustomerVM}"` mean? Are you trying to give it a type? – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '17 at 14:11
  • Thanks for the update. Where does the ContentControl get its `Content` from? – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '17 at 14:15
  • I have updated the question on how the view is loaded in. The `CustomerVM` is the ViewModel for the Customer User Control View. – Bish25 Jun 05 '17 at 14:15
  • The content isn't set, could that be the issue? When i tried setting the content the user control didnt show. – Bish25 Jun 05 '17 at 14:16
  • What did you try setting the content to? Also, you talk about a listview, but I see no sign of anything involving a listview, so I'm concerned that you've got a lot going on here that I can't even guess at. – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '17 at 14:17
  • 1
    i will update the question with more detail – Bish25 Jun 05 '17 at 14:18
  • @edPlunkett When i change `` to `` the listview populates with the one record i have in the load customers command. – Bish25 Jun 05 '17 at 14:22
  • Is `VM:Customers` the view \defined in `Customer.xaml`? Do you ever create an instance of `CustomerVM`? – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '17 at 14:28
  • @edplunkett am i understanding MVVM wrong? [link](https://stackoverflow.com/a/35395128/6371663) – Bish25 Jun 05 '17 at 14:28
  • Within `Customer.xaml` i am creating an instance with `d:DataContext="{d:DesignInstance ViewModel:CustomerVM}` – Bish25 Jun 05 '17 at 14:30
  • A design instance is an instance created at design time so the designer can provide intellisense help on the properties of your viewmodel. At runtime it doesn't exist. I'll update my answer momentarily to address this matter. – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '17 at 14:32
  • @edplunkett i feel sooo embarrassed right now, i add ` ` to the user control and it works now. it was because i only declare it within the design instance. – Bish25 Jun 05 '17 at 14:40
  • 1
    The learning curve on this stuff is pretty steep. See my update: What you've done will work, as far as it goes, but how can `MainVM` interact with the children? It can't even find them. My update demonstrates a simple design that solves that problem. – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '17 at 14:44

1 Answers1

4

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.

  • 1
    I have implemented this now a works a dream and helped me understand MVVM a bit more, thanks again. If you have any blogs or tutorials online i would be interested as your approach and explaining is clear. – Bish25 Jun 05 '17 at 15:18