Search code examples
c#wpfdatagrid

Invalidate/Recalculate all visible items of a DataGrid column


I have a Datagrid containing addresses, their geo-coordinate and the distance to a reference point. The reference point is in a global variable and the distance is calculated on the fly.

The following is the data model of an item which is placed in an ObservableCollection:

public class Address : INotifyPropertyChanged
{
    // like "175 5th Ave, New York, NY 10010"
    public string location { 
        get { 
                return _location; 
        }            
        set { 
                if( value != _location ) { 
                        _location = value; 
                        NotifyPropertyChanged(); 
                } 
        } 
    }

    // encapsulates latitude and longitude
    public GeoCoordinate? Coordinate { 
        get { 
                return _coordinate; 
        }         
        set { 
                if( !value.Equals( _coordinate )) { 
                        _coordinate = value; 
                        NotifyPropertyChanged(); 
                        NotifyPropertyChanged( "Distance" ); 
                } 
        } 
    }

    // Calculates the distance between the reference point and this item
    [JsonIgnore]
    public int? Distance {
        get {
            if( Coordinate == null || Globals.Instance.SelectedReferencePoint == null )
                return null;
            var rp = Globals.Instance.SelectedReferencePoint;
            return ( int ) ( Math.Round( rp.Coordinate.DistanceTo( ( GeoCoordinate ) Coordinate ) / 1000.0 ) + 0.1 );
        }
    }

    private string _location;
    private GeoCoordinate? _coordinate;
    
    public event PropertyChangedEventHandler? PropertyChanged;
    private void NotifyPropertyChanged( [CallerMemberName] String propertyName = "" )
    {
        if( PropertyChanged != null ) {
            PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
        }
    }
}    

This works well when I change the coordinate of an item. The distance in the DataGrid will be updated accordingly.

However, when I change the SelectedReferencePoint, the distance won't be updated, obviously. I am wondering what would be the best way to do it. Of course I can loop through all items in the collection and call NotifyPropertyChanged( "Distance" ) manually. However, is there a better way to tell the DataGrid to recalculate the contents of the Distance column?


Solution

  • One solution as commented would be to subscribe to change notifications from within the Address when Globals.SelectedReferencePoint changes value. Then fire the Address.PropertyChanged on the Distance property when that happens:

    public class Globals : INotifyPropertyChanged
    {
        public static Globals Instance = /* ... */;
    
        public GeoCoordinate _refPoint;
        public GeoCoordinate SelectedReferencePoint
        {
            get
            {
                return _refPoint;
            }
            set
            {
                if(!value.Equals(_refPoint))
                {
                    _refPoint = value;
                    NotifyPropertyChanged();
                }
            }
        }
    
        public event PropertyChangedEventHandler? PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
    public class Address : INotifyPropertyChanged
    {
        // Take Globals as input (= code is easier to test)
        // or just access Globals.Instance instead
        public Address(Globals globals)
        {
            globals.PropertyChanged += HandleGlobalsPropertyChanged;
        }
    
        private void HandleGlobalsPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(Globals.SelectedReferencePoint))
            {
                NotifyPropertyChanged(nameof(Distance));
            }
        }
    
        // ...
    }
    

    Note: remember to unsubscribe from Globals.PropertyChanged again if an Address has a shorter lifespan than Globals.