views:

626

answers:

1

Hi, 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!

+4  A: 

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

Robert Fraser
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
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.
Ray Burns
Thanks a bunch for your detailed answer. I'll give it a try.
Scooby
@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)
Robert Fraser
+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.
slugster