Binding view model from locator to view

Nov 25, 2014 at 1:04 PM
I have a view setup like so:
<UserControl x:Class="CallTracker.WPF.Views.CallUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 

             DataContext="{Binding Source={StaticResource Locator}, Path=CallViewModel}">

    <StackPanel>
        <TextBlock Text="{Binding GreetingText}" />
    </StackPanel>
</UserControl>
And my locator defines the CallViewModel as:
        // Create CallViewModel on demand
        public CallViewModel CallViewModel
        {
            get
            {
                return _CallViewModel;
            }
            set
            {
                if (_CallViewModel != value)
                {
                    _CallViewModel = value;
                }
            }
        }
        private CallViewModel _CallViewModel;
During execution I am trying to create a new CallUserControl to use but, using Snoops, I can see that the datacontext of CallUserControl is not being bound to the viewmodel (the datacontext in Snoops says null).

Stepping thru the code I can see that the CallViewModel is getting created and assigned to the object within the locator but the UserControl's datacontext is not binding to this newly created object.

I am creating the new CallViewModel as such:
        private void NewCall()
        {
            MessageBox.Show("NewCallCommand executed.");

            // Only start a new call if one doesn't currently exist
            if((App.Current.Resources["Locator"] as ViewModelLocator).CallViewModel == null)
            {
                CurrentCallViewModel = new CallViewModel();
            }
        }
Do you know why this new ViewModel would not bind to the new Model? Is there some mechanism similar to NotifyPropertyChanged that I have to call from the locator to get the objects to bind?
Coordinator
Nov 25, 2014 at 2:10 PM
You may need to define the static Locator resource in App.xaml.
<Application x:Class="SimpleMvvm_Wpf.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <vm:ViewModelLocator xmlns:vm="clr-namespace:SimpleMvvm_Wpf"
                                   x:Key="Locator" />
    </Application.Resources>
</Application>

Cheers,
Tony
Nov 25, 2014 at 3:49 PM
I should have mentioned, the Locator is working correclty with other Views & ViewModels, just not this one. Can you confirm what I am doing is valid as far as setting the ViewModel value in the Locator class?

I don't see any kind of NotifyProperty method in Locator. Is there something that I can call that would force the binding?

Thanks for your help!
Coordinator
Nov 25, 2014 at 4:44 PM
Ah, this is easy. I don't see where you are creating a new CallViewModel in the Locator. Try something like this:
public class ViewModelLocator
{
    public CallViewModel CallViewModel
    {
        get { return new CallViewModel(); }
    }
}
There's no need for a NotifyPropertyMethod, as the data binding is one-way. You might want to download SimpleMvvmToolkit.VS2013.Samples-5.5.0.0 and have a look at how it's done there.

Cheers,
Tony
Nov 25, 2014 at 7:14 PM
I just realized I didn't post my CurrentCallViewModel property getter/setter which is where I am trying to create the view model. I then also realized I was creating the property then never really using so let's start fresh.

What I am trying to accomplish is hiding/showing UserControls within a StackPanel based on the existence of a ViewModel (vm != null). I am displaying CompanyInfoUserControl and hiding CallUserControl when Locator.CallViewModel is null (and vice-versa when it is not null) like so:
        <StackPanel Name="MainContentStackPanel">

            <v:CompanyInfoUserControl Visibility="{Binding CallViewModel, Source={StaticResource Locator},
                Converter={StaticResource NullToVisibilityConverter},
                ConverterParameter=CallViewModel}" />

            <v:CallUserControl Visibility="{Binding CallViewModel, Source={StaticResource Locator},
                Converter={StaticResource NotNullToVisibilityConverter},
                ConverterParameter=CallViewModel}" />
            
        </StackPanel>
When starting my program I can see via Snoops that CallViewModel is null and the StackPanel correctly shows the CompanyInfoUserControl and hides CallUserControl.

I have a button/keybinding that when triggered calls the NewCall() method and does successfully create the CallViewModel (I can verify via Snoops that the CallViewModel object does exist after this method call).
        private void NewCall()
        {
            MessageBox.Show("NewCallCommand executed.");

            // Only start a new call if one doesn't currently exist
            if((App.Current.Resources["Locator"] as ViewModelLocator).CallViewModel == null)
            {
                (App.Current.Resources["Locator"] as ViewModelLocator).CallViewModel = new CallViewModel();
            }
        }
and setting the CallViewModel in the Locator like so:
        // Create CallViewModel on demand
        public CallViewModel CallViewModel
        {
            get
            {
                return _CallViewModel;  
            }
            set
            {
                if (_CallViewModel != value)
                {
                    _CallViewModel = value;
                }
            }
        }
        private CallViewModel _CallViewModel;
Everything appears to execute correctly when I step through the code EXCEPT that the CallUserControl's data context is not updated to be bound to the newly created CallViewModel.

I do not want to simply return a new CallViewModel anytime the getter is called as that will always return an object and never be null.

Hopefully this is clear enough to understand what I am trying to do. If you know of a better way to do this I would also be interested in that.

Thanks again for your help, I really like the SimpleMVVM package!
Coordinator
Nov 26, 2014 at 8:27 AM
I could advise against hiding/showing UserControls within a StackPanel based on the existence of a ViewModel. Instead, add a property to the "parent" ViewModel of type Visibility, then bind the Visibility property of the View to it. That will work, and save you from the difficulties of creating and destroying the ViewModel and possible resource leakage.

Cheers,
Tony
Nov 26, 2014 at 12:51 PM
Thanks. I have updated my code to do this but I am still having an issue with my CallViewModel not binding to my CallUserControl.

My program starts with CallViewModel being null and then using a button I want to start a new call thus creating a new CallViewModel object. This all works correctly as far as creating the CallViewModel except that the data context of CallUserControl never gets updated to use the newly created CallViewModel. The data context remains NULL.
Coordinator
Nov 26, 2014 at 1:54 PM
Is there a reason why you don't want to create a new CallViewModel in the getter of the CallViewModel property in the Locator? That is what your View's DataContext is bound to, which is probably why it is null.

The other possibility is an error in the data binding expression in the View, but that would show up in the Debug output window at runtime.

Hope this helps.

Cheers,
Tony