How to draw connecting lines between two controls on a grid WPF

27,741

I'm doing something similar; here's a quick summary of what I did:

Drag & Drop

For handling the drag-and-drop between controls there's quite a bit of literature on the web (just search WPF drag-and-drop). The default drag-and-drop implementation is overly complex, IMO, and we ended up using some attached DPs to make it easier (similar to these). Basically, you want a drag method that looks something like this:

private void onMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    UIElement element = sender as UIElement;
    if (element == null)
        return;
    DragDrop.DoDragDrop(element, new DataObject(this), DragDropEffects.Move);
}

On the target, set AllowDrop to true, then add an event to 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 -----
}

Drawing the Line

Now for the tricky part! Each control exposes an AnchorPoint DependencyProperty. When the LayoutUpdated event is raised (i.e. when the control moves/resizes/etc), the control recalculates its AnchorPoint. When a connecting line is added, it binds to the DependencyProperties of both the source and destination's AnchorPoints. [EDIT: As Ray Burns pointed out in the comments the Canvas and grid just need to be in the same place; they don't need to be int the same hierarchy (though they may be)]

For updating the position 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);
}

For creating the line class (can be done in XAML, too):

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
        };
    }
}

If you want to get a lot fancier, you can use a MultiValueBinding on source and destination and add a converter which creates the PathGeometry. Here's an example from GraphSharp. Using this method, you could add arrows to the end of the line, use Bezier curves to make it look more natural, route the line around other controls (though this could be harder than it sounds), etc., etc.


See also

Share:
27,741
Scooby
Author by

Scooby

Updated on July 09, 2022

Comments

  • Scooby
    Scooby almost 2 years

    I am creating controls (say button) on a grid. I want to create a connecting line between controls. Say you you do mousedown on one button and release mouse over another button. This should draw a line between these two buttons.

    Can some one help me or give me some ideas on how to do this?

    Thanks in advance!

  • Ray Burns
    Ray Burns about 14 years
    This is a very good answer. Note that you can relax your requirement that all your objects be on the same Canvas if: 1. You put your lines on a separate canvas, either above or below your object layer (this is how adorner layers work), and 2. Have your LayoutUpdated handler use TransformToVisual(lineCanvas) to convert the coordinates to be relative to this canvas.
  • Ray Burns
    Ray Burns about 14 years
    By the way, I'm curious whether you intentionally marked this answer as community wiki, or if StackOverflow did that for you automatically because of the number of edits.
  • Scooby
    Scooby about 14 years
    Thanks a bunch for your detailed answer. I'll give it a try.
  • Robert Fraser
    Robert Fraser about 14 years
    @Ray Burns -- Thanks; I didn't know that! No; I didn't mark it CW, it must have done so automatically (or maybe I just accidentally)
  • slugster
    slugster about 14 years
    +1, great answer. It automatically becomes CW if you make too many edits. See if you can flag a moderator and have them change the ownership of the answer back to you.
  • Merlyn Morgan-Graham
    Merlyn Morgan-Graham about 13 years
    Where is this AnchorPoint that you talk about? Is this a custom dependency property that needs to be created on the controls that you want to connect lines between?
  • Robert Fraser
    Robert Fraser about 13 years
    @Merlyn Morgan-Graham - Yes, AnchorPoint is acustom DependencyProperty of type Point.