Kevin Avignon
Kevin Avignon

Reputation: 2903

PropertyChanged is null only on the first event

I'm in the process of getting myself ready to code in WPF. So I came up with some basic mock code in order to get myself going. What I have is a simple WPF interface in which i have a date picker, a few text boxes and check boxes.

What I wanted to do with the INotifyPropertyChanged interface was that when I click on the button, my text box would simply show "Hello Kevin". To test it, I have started by saying that my string would start with "Initially" and when my method return, it would return "Hello Kevin".

I should also specify that I did a mock asynchronous code which would call a server and get the name in the database. This code works just fine. Thing is, PropretyChanged is null a first when I click on the button for the first time. But then, when I click it for a second time, it then shows the message I wanted to see.

It got me wondering if the method truly work as I intended it to do, so I did some basic testing with Console.WriteLine method which would just show me the result I'm currently looking for.

So below, you can find all the classes I'm using for this little stud software :

public class ViewModelCommonBase : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
 }

public class SimpleViewModel : ViewModelCommonBase
{
    #region fields
    private bool _isHealthCareEnabled=false;

    private bool _isBenefits=false;

    private bool _is401kEnabled=false;

    private DateTime _birth;

    #endregion

    #region Properties

    public int Age
    {
        get { return  (int)((DateTime.Now - Birth).TotalDays)/365 ;}

    }
    public bool IsHealthCareEnabled
    {
        get { return _isHealthCareEnabled; }
        set { _isHealthCareEnabled = value; }
    }


    public bool IsBenefitsEnabled
    {
        get { return _isBenefits; }
        set
        {
            if (_isBenefits != value)
            {
                _isBenefits = value;
                OnPropertyChanged("IsBenefitsEnabled");
            }
        }
    }

    public bool Is401kEnabled
    {
        get { return _is401kEnabled; }
        set { _is401kEnabled = value; }
    }


    public DateTime Birth
    {
        get { return _birth; }
        set
        {
            if (_birth != value)
            {
                _birth = value;
                OnPropertyChanged("Birth");
                OnPropertyChanged("Age"); 
            }
        }
    }

    #endregion      
}

public class AsyncServerStud :ViewModelCommonBase
{
    private string _nameOfPerson="Initial";

    public string NameOfPerson
    {
        get { return _nameOfPerson; }
        set
        {
            if (_nameOfPerson != value)
            {
                _nameOfPerson = value;
                OnPropertyChanged("NameOfPerson");

            }
        }
    }


    public async void FindNameOfPerson()
    {
        var result = await FindNameOfPersonAsync("Kevin");
        NameOfPerson = result;
        Console.WriteLine(NameOfPerson);
    }

    private Task<string> FindNameOfPersonAsync(string name)
    {
        return Task.Factory.StartNew(() => SearchNameOfPerson(name));
    }

    private string SearchNameOfPerson(string name)
    {
        Thread.Sleep(6000);
        return "Hello, " + name;
    }


}

    public class WPFViewModelPage
{
    public SimpleViewModel SimpleViewModel { get; set; }
    public AsyncServerStud MockServer { get; set; }


}

And here is my XAML that I used for all the data binding and the user interface

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
    <!--<vm:SimpleViewModel x:Key="viewModel"/>--> 
        <vm:WPFViewModelPage x:Key="viewModel"/>
    </Window.Resources>
    <Grid Name="myGrid" DataContext="{Binding Source={StaticResource viewModel}}">
        <CheckBox x:Name="chkBenefits"
                  Content="Has Benefits ?"
                  HorizontalAlignment="Left" 
                  Margin="62,45,0,0"
                  VerticalAlignment="Top"
                  IsChecked="{Binding SimpleViewModel.IsBenefitsEnabled}"/>
        <StackPanel IsEnabled="{Binding SimpleViewModel.IsBenefitsEnabled}">
            <CheckBox Content="Health care" 
                  HorizontalAlignment="Left" 
                  Margin="85,86,0,0"
                  VerticalAlignment="Top"
                  IsEnabled="{Binding SimpleViewModel.IsBenefitsEnabled}"
                  IsChecked="{Binding SimpleViewModel.IsHealthCareEnabled}"/>
            <DatePicker Name="mydatePicker" DisplayDate="{Binding SimpleViewModel.Birth}"/>
            <TextBlock TextWrapping="Wrap" Text="{Binding SimpleViewModel.Age}"/>
            <Button Content="Button" Click="Button_Click_1"/>
            <TextBox Name="mySecondTextBox" Height="23" TextWrapping="Wrap" Text="{Binding MockServer.NameOfPerson }"/>
        </StackPanel>


    </Grid>
</Window>

When I remove the datacontext reference in the xaml and put it in the code behind the page (I know there should not be any behind code in the UI but bear with me) it work. And that is some pretty weird stuff for me. How can one thing be bad but the other is good?

Here the new code behind

public WPFViewModelPage ViewModel { get; set; }

    public MainWindow()
    {
        InitializeComponent();


        ViewModel = new WPFViewModelPage()
        {
            SimpleViewModel = new SimpleViewModel(),
            MockServer = new AsyncServerStud()
        };

        this.DataContext = ViewModel;
    }

    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {

    }



    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        ViewModel.MockServer.FindNameOfPerson();
    }

Upvotes: 1

Views: 757

Answers (1)

Vimal CK
Vimal CK

Reputation: 3563

I have made below changes to your application and it is working for me. Please try below changes

Assign DataContext to Window instead of setting it to Grid

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:WpfApplication1"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Window.DataContext>
        <vm:WPFViewModelPage />
    </Window.DataContext>
    <Window.Resources>
        <vm:WPFViewModelPage x:Key="viewModel" />
    </Window.Resources>
    <Grid Name="myGrid">
        <CheckBox x:Name="chkBenefits"
                  Content="Has Benefits ?"
                  HorizontalAlignment="Left"
                  Margin="62,45,0,0"
                  VerticalAlignment="Top"
                  IsChecked="{Binding SimpleViewModel.IsBenefitsEnabled}" />
        <StackPanel IsEnabled="{Binding SimpleViewModel.IsBenefitsEnabled}">
            <CheckBox Content="Health care"
                      HorizontalAlignment="Left"
                      Margin="85,86,0,0"
                      VerticalAlignment="Top"
                      IsEnabled="{Binding SimpleViewModel.IsBenefitsEnabled}"
                      IsChecked="{Binding SimpleViewModel.IsHealthCareEnabled}" />
            <DatePicker Name="mydatePicker"
                        DisplayDate="{Binding SimpleViewModel.Birth}" />
            <TextBlock TextWrapping="Wrap"
                       Text="{Binding SimpleViewModel.Age}" />
            <Button Content="Button"
                    Click="Button_Click" />
            <TextBox Name="mySecondTextBox"
                     Height="23"
                     TextWrapping="Wrap"
                     Text="{Binding MockServer.NameOfPerson }" />
        </StackPanel>
    </Grid>
</Window>

Modify the WPFViewModelPage like below

    public class WPFViewModelPage
    {
        public SimpleViewModel SimpleViewModel { get; set; }
        public AsyncServerStud MockServer { get; set; }

        public WPFViewModelPage()
        {
            this.SimpleViewModel = new SimpleViewModel();
            this.MockServer = new AsyncServerStud();
        }
    }

Add below code in your Button_Click method

private void Button_Click(object sender, RoutedEventArgs e)
{
    WPFViewModelPage dataContext = this.DataContext as WPFViewModelPage;
    dataContext.MockServer.FindNameOfPerson();
}

Upvotes: 2

Related Questions