views:

958

answers:

5

I want to simulate data changing in the model and having that data be reflected in XAML. As I understand I need to implement INotifyPropertyChanged.

However, in the following code example, XAML displays the data from the customer only once but the date and time never changes.

What do I have to change in the following example to make XAML continually display the current date and time as it changes in the model?

In particular, I don't understand how the Binding would know when to check the model, every second? 10 times a second? There is no event that it is responding to. What am I missing here?

XAML:

<Window x:Class="TestBinding99382.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestBinding99382"
    Title="Window1" Height="300" Width="300">

    <Window.Resources>
        <ObjectDataProvider x:Key="DataSourceCustomer" ObjectType="{x:Type local:ShowCustomerViewModel}" MethodName="GetCurrentCustomer"/>
    </Window.Resources>

    <DockPanel DataContext="{StaticResource DataSourceCustomer}">
        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
            <TextBlock Text="{Binding Path=FirstName}"/>
            <TextBlock Text=" "/>
            <TextBlock Text="{Binding Path=LastName}"/>
        </StackPanel>
        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
            <TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/>
        </StackPanel>

    </DockPanel>
</Window>

Code Behind:

using System.Windows;
using System.ComponentModel;
using System;

namespace TestBinding99382
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }

    //view model
    public class ShowCustomerViewModel : INotifyPropertyChanged
    {
        private Customer _currentCustomer;

        public Customer CurrentCustomer
        {
            get
            {
                return _currentCustomer;
            }

            set
            {
                _currentCustomer = value;
                this.RaisePropertyChanged("CurrentCustomer");
            }
        }

        public ShowCustomerViewModel()
        {
            _currentCustomer = Customer.GetCurrentCustomer();
        }

        public Customer GetCurrentCustomer()
        {
            return _currentCustomer;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }

    //model
    public class Customer : INotifyPropertyChanged
    {
        private string _firstName;
        private string _lastName;
        private DateTime _timeOfMostRecentActivity;

        public string FirstName
        {
            get
            {
                return _firstName;
            }
            set
            {
                _firstName = value;
                this.RaisePropertyChanged("FirstName");
            }
        }

        public string LastName
        {

            get
            {
                return _lastName;
            }
            set
            {
                _lastName = value;
                this.RaisePropertyChanged("LastName");
            }
        }

        public DateTime TimeOfMostRecentActivity
        {

            get
            {
                return _timeOfMostRecentActivity;
            }
            set
            {
                _timeOfMostRecentActivity = value;
                this.RaisePropertyChanged("TimeOfMostRecentActivity");
            }
        }

        public static Customer GetCurrentCustomer()
        {
            return new Customer { FirstName = "Jim", LastName = "Smith", TimeOfMostRecentActivity = DateTime.Now};
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}
A: 

You seem to be binding to Customer, and Customer doesn't implement INotifyPropertyChanged. Try implementing INotifyPropertyChanged on Customer, and see if that fixes it. This will require moving to manual properties (rather than automatically implemented properties) in order to raise the event.

Marc Gravell
Something like this, in fact: http://stackoverflow.com/questions/839147/wpf-data-binding-to-changing-object/839164#839164
Marc Gravell
Thanks, but I changed the code above so that the Customer class implements INotifyChanged as well but it still doesn't work. What else do I have to do?
Edward Tanguay
as far as I can tell my model class is implementing INotifyPropertyChanged the same as yours, are you perhaps binding it differently in XAML?
Edward Tanguay
And you are raising the events in the setters?
Marc Gravell
yes as in the above code: this.RaisePropertyChanged("TimeOfMostRecentActivity"); is that what you mean?
Edward Tanguay
And you are setting TimeOfMostRecentActivity? Hmm... that should work...
Marc Gravell
A: 

Your customer class needs to implement INotifyPropertyChanged and you need to invoke the event during the setter methods of the properties of Customers.

Edit: Have looked at you code again, I wonder whether your ShowCustomerViewModel class needs to listen to the _currentCustomer PropertyChanged event and forward it as its own PropertyChangedEvent. Worth a shot.

AnthonyWJones
Thanks, but I changed the code above so that the Customer class implements INotifyChanged as well but it still doesn't work. What else do I have to do?
Edward Tanguay
But how does the Binding object know that the "time as changed" in this example? Does it constantly reinstantiate and observe the model?
Edward Tanguay
A: 

Can you check how many times GetCurrentCustomer() gets called? Maybe it just re-creates new instances all the time.

Vlagged
I put a Console.Write in the GetCurrentCustomer() of the ViewModel and it gets called once. Which is what I would expect actually, but everywhere you read about XAML binding, MVVM, the great feature is that the XAML will update itself based on changes in the model, that is what I am trying to recreate here so I can build on it.
Edward Tanguay
+2  A: 

Your code is working correctly. The current date and time will not update automatically by magic. You have to implement some timer to update it.

For exemple you could add this to your Customer class:

 private Timer _timer;

 public Customer()
 {
  _timer = new Timer(UpdateDateTime, null, 0, 1000);
 }

 private void UpdateDateTime(object state)
 {
  TimeOfMostRecentActivity = DateTime.Now;
 }
sacha
Wouldn't you have to snychronize the property change with the Dispatcher since you are causing a UI change from a thread different than the UI thread?
flq
As far as i know, WPF Databinding gets the value on the UI thread anyway after PropertyChanged has been raised.
Botz3000
The business object is not aware that he is used on an WPF application. So no, we should not synch with the WPF Dispatcher.
sacha
Thanks, the timer is a cool addition to what I was trying to do, I posted a complete example of using it here: http://stackoverflow.com/questions/852441/fat-models-skinny-viewmodels-and-dumb-views-the-best-mvvm-approach
Edward Tanguay
+2  A: 

Maybe I'm not getting what you wish this code to do.

You have created a Customer Object which implements INotifyPropertyChanged. You have another class which is a factory for the XAML to get to the instance. Now you create a Customer instance with predefined properties. The View displays them. Unless you change the properties somehow, the View will not update.

I added a button to your WPF View

<Button DockPanel.Dock="Bottom" x:Name="UpdateTime" Click="UpdateTime_Click">Update Activity Timestamp</Button>

private void UpdateTime_Click(object sender, RoutedEventArgs e)
      {
         Customer.GetCurrentCustomer().TimeOfMostRecentActivity = DateTime.Now;
      }

I also changed Customer type to create a single instance for Current Customer

private static Customer _CurrentCustomer;
      public static Customer GetCurrentCustomer()
      {
         if (null == _CurrentCustomer)
         {
            _CurrentCustomer = new Customer { FirstName = "Jim", LastName = "Smith", TimeOfMostRecentActivity = DateTime.Now };
         }
         return _CurrentCustomer;
      }

Now each time I click the button, the DateTime Property is modified and the view auto-updates due to the INotifyPropertyChanged mechanism. The code seems to be working AFAISee.

Gishu
Also CustomerViewModel doesnt need to implement INotifyPropertyChanged.
Gishu
Thanks, this really helped me move forward, I posted the example I was able to create based on your suggestions here: http://stackoverflow.com/questions/852441/fat-models-skinny-viewmodels-and-dumb-views-the-best-mvvm-approach
Edward Tanguay