2
votes

The following is a stripped back version of a problem I'm encountering. It's a fairly common issue, but I'm struggling to find the solution.

I've an instantiated class which I've bound to an item on my main window. This class contains a DispatcherTimer which is used to update a value. In the example given it's incrementing this value by 1 every second.

I'd expect the bound item on my form to reflect this change by updating its value accordingly, however it never updates.

From reading other responses to similar questions on StackOverflow I've a feeling this is due to the nature of the main UI thread running separately to the thread which is causing the increment.

I'm banging my head against a wall though trying to get this binding to update with each call of my DispatcherTimer.

The following is the form element I'm wanting to update every second:

<TextBox Text="{Binding val}" Width="100"/>

Next, this is the instantiation of the class containing the timer and my applications configuration:

BasicTimer basictimer;
public MainWindow()
{
  InitializeComponent();
  basictimer = new BasicTimer();
  DataContext = basictimer;
}

Lastly, here's the class I've created. When created it configures a timer which it uses to update a value every second. Each time this value is updated I'd expect the main UI to be notified of the change and update accordingly. However, this message doesn't seem to be getting through.

  class BasicTimer: INotifyPropertyChanged
  {
    DispatcherTimer _timer;

    uint _val = 10;
    public uint val
    {
      get
      {
        return _val;
      }
      set
      {
        if(_val!=value)
        {
          _val = value;
          OnPropertyChanged("Value");
        }
      }
    }

    public BasicTimer()
    {
      _timer = new DispatcherTimer();
      _timer.Tick += new EventHandler(TimerTick);
      _timer.Interval = new TimeSpan(0, 0, 1);
      _timer.Start();
    }


    private void TimerTick(object sender, EventArgs e)
    {
      val++;
      Console.WriteLine(val);
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

I think I've managed to avoid the usual pitfalls of forgetting INotifyPropertChanged, and other bound values from other models are working just fine. It's just this property which is being updated via a thread that I'm having trouble with. I've also tried creating a similar timer using a simple Timer but I'm having the same problem.

Any thoughts would be very much appreciated, thanks!

1

1 Answers

3
votes

I believe your problem is in the call to OnPropertyChanged:

uint _val = 10;
public uint val
{
  get
  {
    return _val;
  }
  set
  {
    if(_val!=value)
    {
      _val = value;
      OnPropertyChanged("Value");
    }
  }
}

That should be

OnPropertyChanged("val");

The string in the call to OnPropertyChanged has to match the name of the property.

EDIT

The reason you want the name passed to OnPropertyChanged to always match the name of the property is because the data binding subscribes to your object's PropertyChanged event and is watching for the value in that string in the parameter passed to its event handler. If the name passed doesn't match the name it is looking for, it ignores the notification. It only updates the value of the control bound to that property when the names match.

As Aron mentioned in the comments, you can use the CallerMemberAttribute in your OnPropertyChanged method to ensure the property name is always passed to the method properly. According to the answer to this StackOverflow question, your method would look like this:

protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
  }
}

You would then call it with no parameter from the property's setter and the name will always be correct.

As the answer to the linked question says, this code compiles into IL code that is identical to what is produced if you hard code the string in your call, so this trick will always work and will be just as fast.