WPF: UI not being updated with INotifyPropertyChan

2019-08-16 07:35发布

问题:

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.

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; }
    }
}

回答1:

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.



回答2:

   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 )



回答3:

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!