wpfwpf-controlsgridlinesconnector

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


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!


Solution

  • 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