Я делаю нечто подобное; вот краткое изложение того, что я сделал:
Drag & Drop
Для обработки перетаскивания между элементами управления в Интернете довольно много литературы ( просто выполните поиск в WPF drag-and-drop ). Реализация перетаскивания по умолчанию является слишком сложной, IMO, и мы в конечном итоге использовали несколько подключенных DP, чтобы упростить ее ( аналогично этим ). По сути, вам нужен метод перетаскивания, который выглядит примерно так:
private void onMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if (element == null)
return;
DragDrop.DoDragDrop(element, new DataObject(this), DragDropEffects.Move);
}
На цели установите AllowDrop в true, затем добавьте событие в Drop:
private void onDrop(object sender, DragEventArgs args)
{
FrameworkElement elem = sender as FrameworkElement;
if (null == elem)
return;
IDataObject data = args.Data;
if (!data.GetDataPresent(typeof(GraphNode))
return;
GraphNode node = data.GetData(typeof(GraphNode)) as GraphNode;
if(null == node)
return;
// ----- Actually do your stuff here -----
}
Рисование линии
Теперь о хитрой части! Каждый элемент управления предоставляет AnchorPoint DependencyProperty . Когда возникает событие LayoutUpdated (то есть, когда элемент управления перемещается / изменяется / и т. Д.), Элемент управления пересчитывает свою точку привязки. При добавлении соединительной линии она привязывается к свойствам DependencyProperties AnchorPoints источника и назначения. [ EDIT : Как указал Рэй Бернс в комментариях, холст и сетка просто должны быть в одном месте; они не должны быть внутри одной иерархии (хотя они могут быть)]
Для обновления позиции DP:
private void onLayoutUpdated(object sender, EventArgs e)
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, isInput ? 0 : size.Height);
AnchorPoint = TransformToVisual(node.canvas).Transform(ofs);
}
Для создания класса строки (также может быть сделано в XAML):
public sealed class GraphEdge : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Source { get { return (Point) this.GetValue(SourceProperty); } set { this.SetValue(SourceProperty, value); } }
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Destination { get { return (Point) this.GetValue(DestinationProperty); } set { this.SetValue(DestinationProperty, value); } }
public GraphEdge()
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding {Source = this, Path = new PropertyPath(SourceProperty)};
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
Content = new Path
{
Data = geometry,
StrokeThickness = 5,
Stroke = Brushes.White,
MinWidth = 1,
MinHeight = 1
};
}
}
Если вы хотите сделать что-то интереснее, вы можете использовать MultiValueBinding для источника и получателя и добавить конвертер, который создает PathGeometry. Вот пример из GraphSharp. Используя этот метод, вы можете добавить стрелки в конец линии, использовать кривые Безье, чтобы сделать его более естественным, обвести линию вокруг других элементов управления (, хотя это может будь жестче, чем кажется ) и т. д. и т. п.
Смотри также