简体   繁体   中英

auto-update calculated property in WPF datagrid

I have a class TestModel that has a FirstName and a LastName property plus a "computed property" that has no setter but just returns the FullName in the format "Lastname, Firstname".

public class TestModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName
    {
        get
        {
            return $"{LastName}, {FirstName}";
        } 
    }

}

I store multiple objects of this class in an ObservableCollection which is the ItemsSource for a datagrid in a WPF app. When I add items through the GUI, they immediately appear in the grid and everthing is fine.

However, when I change for instance the FirstName of one specific object in the grid, FullName does not change automatically (it does change, actually, but the change is not reflected in the GUI). Do I have to implement INotifyPropertyChanged on this class? If so, how would I do this? I read some examples but I must admit that I was unable to transfer them to my case...

You'll have to impelement INotifyPropertyChanged and make sure that the event is invoked whenever your FullName property changes. This is the case when either your FirstName or your LastName property changes.

public class TestModel : INotifyPropertyChanged
    {
    public event PropertyChangedEventHandler PropertyChanged;
    private string _firstName;
    public string FirstName { get => _firstName;
    set
    {
        _firstName = value;
        InvokePropertyChanged();
        InvokePropertyChanged(nameof(FullName));
    }

    private _string _lastName;    
    public string LastName
    {
       get => _lastName;
       set
       {
          _lastName= value;
          InvokePropertyChanged();
          InvokePropertyChanged(nameof(FullName));
       }
    }

   public string FullName
   {
       get
       {
          return $"{LastName}, {FirstName}";
       } 
    }

    private void InvokePropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Yes, you need to implement INotifyPropertyChanged in your view model.

example:

public class ViewModelBase: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }
    protected void RaisePropertyChanged(String propertyName)
    {
        VerifyPropertyName(propertyName);
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
}

add it to your programm code like this:

public class Testmodel : ViewModelBase
{
    private string _FistName;
    public string FirstName
    {
        get
        {
            return _FistName;
        }
        set
        {
            if(_FistName == value)
            {
                return;
            }
            _FistName = value;
            RaisePropertyChanged("FirstName");
            RaisePropertyChanged("FullName");
        }
    }
    private string _LastName;
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            if (_LastName == value)
            {
                return;
            }
            _LastName = value;
            RaisePropertyChanged("LastName");
            RaisePropertyChanged("FullName");
        }
    }
    public string FullName
    {
        get
        {
            return $"{LastName}, {FirstName}";
        }
    }
}

If you now set a value to FirstName or LastName, it should raise a PropertyChanged event that updates the gui. try it out:)

In order to receive updates by datagrid your class needs to implement INotifyPropertyChanged interface. And each property that is bound to the datagrid should raise the PropertyChanged event. In the case of the calculated property, you can raise the event manually in particular places as it is advised in other answers. But, another approach is to delegate that work to DependenciesTracking lib:

 public class TestModel : INotifyPropertyChanged
    {
        private readonly IDependenciesMap<TestModel> _dependenciesMap = new DependenciesMap<TestModel>()
            .AddDependency(i => i.FullName, i => $"{i.LastName}, {i.FirstName}", i => i.FirstName, i => i.LastName);

        private string? _firstName;
        
        private string? _lastName;
        
        private string _fullName;

        public string? FirstName
        {
            get => _firstName;
            set
            {
                if (value == _firstName) return;
                _firstName = value;
                OnPropertyChanged();
            }
        }

        public string? LastName
        {
            get => _lastName;
            set
            {
                if (value == _lastName) return;
                _lastName = value;
                OnPropertyChanged();
            }
        }

        public string FullName
        {
            get => _fullName;
            private set
            {
                if (value == _fullName) return;
                _fullName = value;
                OnPropertyChanged();
            }
        }

        public TestModel() => _dependenciesMap.StartTracking(this);

        public event PropertyChangedEventHandler? PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class Tests_SO_68712154
    {            
        [Test]
        public void Test_SO_68712154()
        {
            var sut = new TestModel();

            var raisedEventsCount = 0;
            sut.PropertyChanged += (_, args) =>
            {
                if (args.PropertyName == nameof(TestModel.FullName))
                    ++raisedEventsCount;
            };
            Assert.Multiple(() =>
            {
                Assert.That(sut.FullName, Is.EqualTo(", "));
                Assert.That(raisedEventsCount, Is.EqualTo(0));
            });

            sut.FirstName = "John";
            Assert.Multiple(() =>
            {
                Assert.That(sut.FullName, Is.EqualTo(", John"));
                Assert.That(raisedEventsCount, Is.EqualTo(1));
            });
            sut.LastName = "Smith";
            Assert.Multiple(() =>
            {
                Assert.That(sut.FullName, Is.EqualTo("Smith, John"));
                Assert.That(raisedEventsCount, Is.EqualTo(2));
            });
            sut.FirstName = null;
            Assert.Multiple(() =>
            {
                Assert.That(sut.FullName, Is.EqualTo("Smith, "));
                Assert.That(raisedEventsCount, Is.EqualTo(3));
            });
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM