问题描述
我有一个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
保留一组节点的坐标,这样就无需处理INotifyPropertyChanged
和Link
属性更新,因为它们由X
本身处理。
我会保持简单。 Node
具有Link
个项目的集合。 Link
连接两个Node
对象,因此具有两个端点:源节点和目标节点。您总是将Link
添加到源Node
的链接集合中。按照此规则,Link
仅需要将对目的地Node
的引用存储在属性Destination
中(可能还需要存储其他数据,例如描述等)。
绘制链接时,只需要遍历每个Node
并遍历链接集合即可。然后从当前Node
到当前Link.Destination
画一条线。
您会看到视角已发生变化:Node
具有零个或多个指向单个邻居的Link
对象。 Link
是关系的“被动”部分。无需从Link
的角度查看图形,而是从Node
的角度查看图形,然后从Node
移至Node
。 Link
不再关心Node
,但是Node
关心Link
,即Node
具有Link
。
这将产生一个有向图,可以通过访问Link
集合中的Node.Links
个项目来遍历。对于无向图,您必须维护对两个Node´ endpoints in the
Link`对象的引用。
Link
数据本身没有明确的位置。例如,当拖动Node
时,连接点图可以在节点边界周围浮动。跨Canvas
。 Link
由所有者(源的Node.Position
)隐式定位。
和端点(Link.Destination.Position
)。
换句话说,您必须区分数据模型及其渲染的视图元素。 Node
有位置。 Link
不是,但是连接两个Node
对象,即连接两个位置。在视图/渲染级别,例如,映射到Link
的元素Line
与Node
元素的位置不同,例如Rectangle
。
您需要一个通过遍历图形来绘制元素的引擎:
- 获取一个
Node
(Link
的来源)并创建一个Rectangle
。 - 将
Rectangle
的位置绑定到当前Node.Position
的{{1}}上。 - 遍历
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
对象的引用这一事实来指示链接。