SpaceSteak
SpaceSteak

Reputation: 199

WPF: UI not being updated with INotifyPropertyChanged

I'm currently learning WPF, DataContexts & DataBinding. My goal is to have a Taskbar task (using NotifyIconWpf) that has a continuous thread running the background to monitor a network.

I've managed to get a UI element (shown in screenshot) bound to the ProgramClock class, but it does not update when the ProgramClock changes, most likely because something in the INotifyPropertyChanged parameters are wrong.

The closest similar problem I've found is UI not being updated INotifyPropertyChanged however I haven't been able to figure out what to change the DataPath in the XAML, or how to make INotifyPropertyChanged work properly.

Note that the BackgroundWorker thread successfully updates the App's static ProgramClock (checked with a separate WinForm) and that time is initially loaded in the WPF, so it's probably the PropertyChanged not being called properly.

enter image description here

ProgramClock

public class ProgramClock : INotifyPropertyChanged
    {
        private DateTime _myTime;
        public event PropertyChangedEventHandler PropertyChanged;
        private ClockController clockController;

        public ProgramClock()
        {

            this._myTime = DateTime.Now;
            clockController = new ClockController();

            MessageBox.Show("created new clock");
        }

        public DateTime MyTime
        {
            get
            {
                return this._myTime;
            }

            set
            {
                if (_myTime == value) return;
                _myTime = value;

                //System.Windows.Forms.MessageBox.Show(PropertyChanged.ToString());
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));
            }
        }

        public string MyTimeString
        {
            get { return this._myTime.ToString(); }
        }

        public void UpdateTime()
        {
            this.MyTime = DateTime.Now;
        }
    }

Bubble CS

public partial class InfoBubble : System.Windows.Controls.UserControl
{

    public InfoBubble()
    {
        InitializeComponent();
        this.DataContext = App.ClockBindingContainer;
    }
}

Bubble XAML

<UserControl x:Class="FileWatcher.Controls.InfoBubble"
             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">
    <Border
        Background="White"
        BorderBrush="Orange"
        BorderThickness="2"
        CornerRadius="4"
        Opacity="1"
        Width="160"
        Height="40">
            <TextBlock
          Text="{Binding Path=MyTimeString}"
          HorizontalAlignment="Center"
          VerticalAlignment="Center" />
    </Border>
</UserControl>

Main app

public partial class App : System.Windows.Application
{

    private TaskbarIcon tb;
    private ResourceDictionary _myResourceDictionary;
    public static ProgramClock _programClock = new ProgramClock();

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        NotifIconStarter();
    }

    public static ProgramClock ClockBindingContainer
    {
        get { return _programClock; }
    }
}

Upvotes: 0

Views: 3368

Answers (3)

MasterMastic
MasterMastic

Reputation: 21306

You are not notifying the change in the property you are binding to (which is MyTimeString), so WPF knows MyTime changes, but does MyTimeString change too? was never notified.

Try to change this:

            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));

To this:

            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("MyTimeString")); // Not MyTime!

Upvotes: 1

fex
fex

Reputation: 3558

   if (PropertyChanged != null)
       PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));

You should pass property name:

   if (PropertyChanged != null)
       PropertyChanged(this, new PropertyChangedEventArgs("MyTime");

However I suggest you to get PostSharp library - it has nice features that enable you to write normal properties and "decorate it by attribute" with automatic raising of PropertyChanged. If you do not want to use PostSharp at least create some method like:

public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
     if (PropertyChanged != null)
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}

and call it in your setter. ([CallerMemberName] is C# 5.0 feature which automatically pass "caller" name (in setter it will pass property name )

Upvotes: 2

Daniel Kelley
Daniel Kelley

Reputation: 7747

One problem is in your invocation of the PropertyChanged event. You need to pass the name of the property that is changing to the PropertyChangedEventArgs not the new value.

So use:

if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("MyTime"));

instead of:

if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs(_myTime.ToString()));

However, you are actually binding to another property - MyTimeString.

Ultimately the property you are binding to needs to raise the event.

Upvotes: 5

Related Questions