数据模型取决于另一数据模型的属性时的数据绑定

问题描述

我有一个Node类(由屏幕上的矩形表示)和一个Link类(由矩形之间的箭头表示)。

就数据而言,节点和链接具有其自己的数据属性。但是Link的坐标等于其两个组成节点的坐标;即

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

理想地,每当节点的位置更改时,其所有Link的位置也应更改。我想这样访问它:linkInstance.x1 = nodeInstance1.x

这是我尝试过的:

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");
        }
    }
    //...
}

有没有更简单的方法来实现这一目标?

解决方法

我认为这是一个更简单的解决方案:

trait Agent
trait SubscriptionHandle
trait AgentService {
  def getAgents[T <: Agent]: ObservableCollection[T]
}
trait ObservableCollection[T] extends util.Collection[T] {
  def onElementAdded(onAdded: Consumer[T]): SubscriptionHandle
}

def test[T <: Agent](service: AgentService): Unit = {
  val onAdded: Consumer[T] = ???
  val agents: ObservableCollection[T] = service.getAgents
  agents.onElementAdded(onAdded)
}

其中@deprecated是实现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))); } } } 接口的基类。它可以保存对节点本身的引用,而不是让ViewModelBase保留一组节点的坐标,这样就无需处理INotifyPropertyChangedLink属性更新,因为它们由X本身处理。

,

我会保持简单。 Node具有Link个项目的集合。 Link连接两个Node对象,因此具有两个端点:源节点和目标节点。您总是将Link添加到源Node的链接集合中。按照此规则,Link仅需要将对目的地Node的引用存储在属性Destination中(可能还需要存储其他数据,例如描述等)。

绘制链接时,只需要遍历每个Node并遍历链接集合即可。然后从当前Node到当前Link.Destination画一条线。
您会看到视角已发生变化:Node具有零个或多个指向单个邻居的Link对象。 Link是关系的“被动”部分。无需从Link的角度查看图形,而是从Node的角度查看图形,然后从Node移至NodeLink不再关心Node,但是Node关心Link,即Node具有Link

这将产生一个有向图,可以通过访问Link集合中的Node.Links个项目来遍历。对于无向图,您必须维护对两个Node´ endpoints in the Link`对象的引用。

Link数据本身没有明确的位置。例如,当拖动Node时,连接点图可以在节点边界周围浮动。跨CanvasLink由所有者(源的Node.Position)隐式定位。 和端点(Link.Destination.Position)。

换句话说,您必须区分数据模型及其渲染的视图元素。 Node有位置。 Link不是,但是连接两个Node对象,即连接两个位置。在视图/渲染级别,例如,映射到Link的元素LineNode元素的位置不同,例如Rectangle

您需要一个通过遍历图形来绘制元素的引擎:

  1. 获取一个NodeLink的来源)并创建一个Rectangle
  2. Rectangle的位置绑定到当前Node.Position的{​​{1}}上。
  3. 遍历Node集合中的Link个项目: 为每个Node.Links创建一个Link并将其起点绑定到当前Line,并将其端点绑定到当前链接的Node.Position(确定Link.Destination.Position的边缘最接近目的地Rectangle,即应用布局算法,例如通过使用Node)。

由于在视图级别进行了数据绑定,因此代表IValueConverter的图形将跟随节点图形的位置。在数据级别,Link没有位置。

链接.cs

Link

节点Node.cs

// Minimal version that create a directed graph,// since only the destination node is referenced
class Link : INotifyPropertyChanged
{
  private Node destination;
  public Node Destination
  {
    get => this.destination;
    set
    {
      this.destination = value;
      OnPropertyChanged();
    }
  }

  // Some additional data associated with this Link
  private string description;   
  public string Description
  {
    get => this.description;
    set 
    { 
      this.description = value; 
      OnPropertyChanged();
    }
  }
}

示例

class Node : INotifyPropertyChanged
{
  public ObservableCollection<Link> Links { get; set; }

  private Point position;   
  public Point Position
  {
    get => this.position;
    set 
    { 
      this.position = value; 
      OnPropertyChanged();
    }
  }
}

备注

如果链接本身不打算承载目标节点(和可选的源节点)以外的数据,则可以将其删除并将目标节点直接添加到源节点的集合中,例如。 var sourceNode = new Node(); var destinationNode = new Node(); // Connect both nodes from source to destination var link = new Link() { Destination = destinationNode }; sourceNode.Links.Add(link);
然后,链接变成纯视图元素。在数据级别,将通过Node.Neighbors持有对相邻Node对象的引用这一事实来指示链接。