none
Linking WPF Objects together with a simple line (ala flowchart)

    Question

  • (wpf newbie here)
    What approach would be best to create a link between two WPF-rendered boxes and represent that link with a line.  The result I am looking for is what one would expect in a flowcharting program... move one box and the line follows along, bound to specific points on both boxes.

    Thanks for any suggestions.
    Thursday, September 27, 2007 11:59 PM

Answers

  • Hello, actually this is not quite a newbie question… But it can be even tougher if you use another technology. So let’s see how to do this in WPF.

     

    First, you can use a Canvas to do the layout. Most drawing, chart programs use Canvas because Canvas uses absolute position. So let’s say you have a Canvas named canvasRoot.

     

    Now we can create an AttachedProperty for FrameworkElement, to store a series of points that can be used by the connection lines. We hope those points to be relative to each FrameworkElement. Let’s create it in a helper class:

        public class LinkPointHelper

        {

            public static readonly DependencyProperty LinkPointsProperty = DependencyProperty.RegisterAttached("LinkPoints", typeof(List<Point>), typeof(FrameworkElement), new PropertyMetadata(new List<Point>()));

     

            public static List<Point> GetLinkPoints(FrameworkElement element)

            {

                return (List<Point>)element.GetValue(LinkPointsProperty);

            }

     

            public static void SetLinkPoints(FrameworkElement element, List<Point> value)

            {

                element.SetValue(LinkPointsProperty, value);

            }

     

            public static void AddLinkPoint(FrameworkElement element, Point p)

            {

                GetLinkPoints(element).Add(p);

            }

            //You can add other methods such as RemoveLinkPoint...

             }

     

    Now we’re ready to create a Line between two FrameworkElements with a method named CreateLinkLine. This method, at a minimum, will have 5 parameters, the 2 FrameworkElements that the line should link, 2 integer values indicates which LinkPoint on each FrameworkElement the line should use, and the Canvas containing the two FrameworkElements. You can add additional parameters to control the Line’s look:

            public static void CreateLinkLine(FrameworkElement element1, int index1, FrameworkElement element2, int index2, Canvas container)

            {

                       }

     

    Monday, October 01, 2007 7:42 AM

All replies

  • Hello, actually this is not quite a newbie question… But it can be even tougher if you use another technology. So let’s see how to do this in WPF.

     

    First, you can use a Canvas to do the layout. Most drawing, chart programs use Canvas because Canvas uses absolute position. So let’s say you have a Canvas named canvasRoot.

     

    Now we can create an AttachedProperty for FrameworkElement, to store a series of points that can be used by the connection lines. We hope those points to be relative to each FrameworkElement. Let’s create it in a helper class:

        public class LinkPointHelper

        {

            public static readonly DependencyProperty LinkPointsProperty = DependencyProperty.RegisterAttached("LinkPoints", typeof(List<Point>), typeof(FrameworkElement), new PropertyMetadata(new List<Point>()));

     

            public static List<Point> GetLinkPoints(FrameworkElement element)

            {

                return (List<Point>)element.GetValue(LinkPointsProperty);

            }

     

            public static void SetLinkPoints(FrameworkElement element, List<Point> value)

            {

                element.SetValue(LinkPointsProperty, value);

            }

     

            public static void AddLinkPoint(FrameworkElement element, Point p)

            {

                GetLinkPoints(element).Add(p);

            }

            //You can add other methods such as RemoveLinkPoint...

             }

     

    Now we’re ready to create a Line between two FrameworkElements with a method named CreateLinkLine. This method, at a minimum, will have 5 parameters, the 2 FrameworkElements that the line should link, 2 integer values indicates which LinkPoint on each FrameworkElement the line should use, and the Canvas containing the two FrameworkElements. You can add additional parameters to control the Line’s look:

            public static void CreateLinkLine(FrameworkElement element1, int index1, FrameworkElement element2, int index2, Canvas container)

            {

                       }

     

    Monday, October 01, 2007 7:42 AM
  • So how shall we create the line? It’s not difficult to just create a link Line since we can easily get the two points from the parameters. The difficult part is how to configure the line to move together with the FrameworkElements. In a traditional application, you’ll probably end up by create a base class named MyBaseShape, a class ConnectionLine, and a lot of sub classes such as MyRectangle of MyBaseShape. You can manipulate a relationship between a collection of MyBaseShapes and a collection of ConnectionLines, so that if a MyBaseShape moves, the corresponding ConnectionLines will also move. This will of course be a lot of work.

     

    Fortunately, in WPF, we have data binding to serve us. It’s very easy to imagine we can bind the built in Line’s X1, X2, Y1, Y2 property to two LinkPoints on two FrameworkElements. Since our LinkPoints are relative to each FrameworkElements, we’ll use a Converter to add the LinkPoint’s X, Y property with the FrameworkElement’s Canvas.Left, Top property. So a first approach will be bind the ConverterParameter to Canvas.Left, Top. Unfortunately, ConverterParameter is not a DependencyProperty, we cannot use data binding on it. So we’ll have to use a MultiConverter instead:

        public class OffsetConverter : IMultiValueConverter

        {

            public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)

            {

                //The first value is our LinkPoint.X, Y, and the second value is Canvas.Left, Top.

                double d = (double)values[1];

                if (double.IsNaN(d))

                {

                    d = 0.0;

                }

                return d + (double)values[0];

            }

     

            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)

            {

                throw new NotImplementedException();

            }

        }

     

    Also, to bind to properties on the FrameworkElements, they must have names. We can register a name in CreateLinkLine method, but remember to set FrameworkElement.Name property when you create one:

                //Remember to remove the name if you want to remove a FrameworkElement.

                if (container.FindName(element1.Name) == null)

                {

                    container.RegisterName(element1.Name, element1);

                }

                if (container.FindName(element2.Name) == null)

                {

                    container.RegisterName(element2.Name, element2);

                }

     

    Now we can create the binding (also in CreateLinkLine method):

                Line line = new Line();

     

                //Bind line.X1 to element1.LinkPoint[index1].X + element1.Canvas.Left:

                MultiBinding mbx1 = new MultiBinding();

                Binding bx11 = new Binding();

                bx11.ElementName = element1.Name;

                bx11.Path = new PropertyPath("(FrameworkElement.LinkPoints)[" + index1 + "].X");

                mbx1.Bindings.Add(bx11);

                Binding bx12 = new Binding();

                bx12.ElementName = element1.Name;

                bx12.Path = new PropertyPath("(Canvas.Left)");

                mbx1.Bindings.Add(bx12);

                OffsetConverter converterx1 = new OffsetConverter();

                mbx1.Converter = converterx1;

                line.SetBinding(Line.X1Property, mbx1);

     

    You can use the same way to create addtional bindings: Bind line.Y1 to element1.LinkPoint[index1] + element1.Canvas.Top, Bind line.X2 to element2.LinkPoint[index2] + element2.Canvas.Left, Bind line.Y2 to element2.LinkPoint[index2] + element2.Canvas.Top. After all bindings are ready, we just need to add the Line to the Canvas:

                line.Stroke = Brushes.Black;

                container.Children.Add(line);

     

    Monday, October 01, 2007 7:42 AM
  • Now let’s do some tests. FrameworkElement can be almost anything, such as all Shapes and all Controls and your own derived classes. So let’s create a ConnectLine between an Ellipse and a Button. Don’t forget we must assign a unique Name for each FrameworkElement:

            private Ellipse ellipse = new Ellipse();

            private Button button = new Button();

     

            ellipse.Name = "rectangle";

            ellipse.Width = 100;

            ellipse.Height = 100;

            ellipse.Fill = Brushes.Red;

            LinkPointHelper.SetLinkPoints(ellipse, new List<Point>());

            LinkPointHelper.AddLinkPoint(ellipse, new Point(50, 50));

            LinkPointHelper.AddLinkPoint(ellipse, new Point(0, 50));

            canvasRoot.Children.Add(ellipse);

            button.Name = "button";

            button.Width = 100;

            button.Height = 100;

            LinkPointHelper.SetLinkPoints(button, new List<Point>());

            LinkPointHelper.AddLinkPoint(button, new Point(50, 0));

            LinkPointHelper.AddLinkPoint(button, new Point(50, 50));

            button.SetValue(Canvas.LeftProperty, 200d);

            canvasRoot.Children.Add(button);

            LinkPointHelper.CreateLinkLine(ellipse, 0, button, 1, canvasRoot);

     

    Now you can see a line has linked the Ellipse and the Button’s center points, since we set both FrameworkElements to be of size 100,100, and the two LinkPoints are both 50, 50. In the runtime, let’s move the two FrameworkElements a bit:

                ellipse.SetValue(Canvas.TopProperty, 100d);

                ellipse.SetValue(Canvas.LeftProperty, 50d);

                button.SetValue(Canvas.TopProperty, -10d);

                button.SetValue(Canvas.LeftProperty, -20d);

     

    Without any further work to do on the Line, it still connects the Ellipse and the Button’s center points.

     

    To make the solution more mature, you still have some further work to do, but I think you can see how WPF makes our jobs easier. If you try to achieve the same without AttachedProperty and data binding, you’ll have the feeling. J

     

    Monday, October 01, 2007 7:43 AM
  • Why do you need ElementName for Binding?


                //Remember to remove the name if you want to remove a FrameworkElement.

                if (container.FindName(element1.Name) == null)

                {

                    container.RegisterName(element1.Name, element1);

                }

                if (container.FindName(element2.Name) == null)

                {

                    container.RegisterName(element2.Name, element2);

                }


    You can use binding.Source = element instead:


    Binding bx11 = new Binding();

    bx11.Source = element1;

    Thursday, November 08, 2007 12:16 AM
  • Check out my post towards the line binding at http://dvuyka.spaces.live.com/blog/cns!305B02907E9BE19A!195.entry

    If you are intrested in diagramming you might find other intresting stuff all over the blog.

    Last working sample can be found at http://dvuyka.spaces.live.com/blog/cns!305B02907E9BE19A!220.entry

     

    Hope this will help you

    Thursday, November 15, 2007 3:43 PM

  • Hi,

    I am working on a project, which include a small diagramming editor.
    i would like to know how to draw Rectangle, then Select , Move at runtime by click and drag with mouse.
    i need to do small UML diagraming component.

    I am thinking to create the whole app in WPF with either(C#,VB.Net),

    I hope someone will help me in this .

    Tin

    Monday, December 31, 2007 3:44 PM
  • Simply check out my blog entries.

    http://dvuyka.spaces.live.com

    As I'm fond of diagramming very much you'll find a lot of helpful materials all over the posts. Also you will find there sample diagramming application with sources provided. Also check out the links I've posted in the previous responce.

    Monday, December 31, 2007 5:14 PM
  • hi,
    there is a great codeproject article on the above issue http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx
    jay
    Tuesday, May 19, 2009 4:19 PM