Search code examples
c#wpfdata-binding

Data binding when data model depends on another data model's properties


I have a Node class (represented by rectangles on-screen) as well as a Link class (represented by arrows between the rectangles).

In terms of data, nodes and links have their own data properties. But a Link's coordinates are equal to its two constituting nodes coordinates; i.e.

  • link.x1 = node1.x
  • link.y1 = node1.y
  • link.x2 = node2.x
  • link.y2 = node2.y

Ideally, whenever the position of a node changes, the position of all its in and out Links should change as well. I would like to access it like so: linkInstance.x1 = nodeInstance1.x.

Here's what I've tried:

class Link : ReactiveBase
{
    private int x1;
    private int y1;
    private int x2;
    private int y2;

    private Node fromNode;
    private Node toNode;

    //Just copies value, no updates.
    public int X1 { get {return this.fromNode.X;} }
    public int Y1 { get {return this.fromNode.Y;} }
    public int X2 { get {return this.toNode.X;} }
    public int Y2 { get {return this.toNode.Y;} }

    //Binding to From.X in xaml works; but don't like accessing node from  link
    public Node From { get { return this.fromNode; } }
    //...

    public Link(Node from, Node to)
    {
        this.fromNode = from;
        this.toNode = to;

        //works; but quite a lot of work
        from.PropertyChanged += test;
        //...
    }

    private void test(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "X")
        {
            this.NotifyPropertyChanged("X1");
        }
        else if (e.PropertyName == "Y")
        {
            this.NotifyPropertyChanged("Y1");
        }
    }
    //...
}

Is there are a simpler way of achieving this?


Solution

  • I think this is a simpler solution:

    class Node: ViewModelBase
    {    
        private int _X;
        public int X
        {
            get => _X;
            set
            {
                _X = value;
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(X)));
            }
        }
    
        private int _Y;
        public int Y
        {
            get => _Y;
            set
            {
                _Y = value;
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(Y)));
            }
        }
    }
    
    class Link: ViewModelBase
    {
        public Link(Node from, Node to)
        {
            FromNode = from;
            ToNode = to;
        }
        
        private Node _FromNode;
        public Node FromNode
        {
            get => _FromNode;
            set
            {
                _FromNode = value;
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(FromNode)));
            }
        }
        private Node _ToNode;
        public Node ToNode
        {
            get => _ToNode;
            set
            {
                _ToNode = value;
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(ToNode)));
            }
        }
    }
    

    Where ViewModelBase is a base class that implements the INotifyPropertyChanged interface. Instead of having your Link hold the coordinates of a set of nodes, it can hold a reference to the nodes themselves, that way you don't need to handle the X and Y property updates since they are handled by the Node itself.