Page Index Toggle Pages: [1] 2  Send TopicPrint
Hot Topic (More than 10 Replies) custom nodes (Read 9673 times)
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
custom nodes
Oct 21st, 2013 at 12:43pm
Print Post  
Hi Stoyan,

I have a custom shape with the following code:

Code
Select All
class BROperator : ContainerNode
    {
        public string operatorSign { get; private set; }
        public int Priority { get; set; }
        object leftVariable;
        object rightVariable;

        public BROperator()
        {
            initialize();
        }

        public BROperator(string operatorSign)
        {
            this.operatorSign = operatorSign;
            initialize();
        }

        public BROperator(BROperator prototype)
            : base(prototype)
        {
            this.Priority = prototype.Priority;
            initialize();
        }

        private void initialize()
        {
            leftVariable = null;
            rightVariable = null;

            this.Brush = new LinearGradientBrush(Colors.LawnGreen, Colors.LawnGreen, 20);

            Rect nodeSize = new Rect();
            nodeSize.Width = 55;
            nodeSize.Height = 15;
            this.Bounds = nodeSize;

            this.MinimumSize = new Size(Bounds.Width, Bounds.Height);
            this.CaptionHeight = 0;
            this.Margin = 2;
            this.AutoShrink = true;
            this.AllowAddChildren = true;

            HighlightPen = new Pen(Brushes.White, 100);
        }

        public override void Draw(System.Windows.Media.DrawingContext graphics, MindFusion.Diagramming.Wpf.RenderOptions options)
        {
            double height = Bounds.Height;
            double width = Bounds.Width;
            double midHeight = height / 2;

            var shape = new PathGeometry();
            shape.Figures.Add(new PathFigure(new Point(0, midHeight), new[]
            {
                new LineSegment(new Point(5, 0), true),
                new LineSegment(new Point(width-5, 0), true),
                new LineSegment(new Point(width, midHeight), true),
                new LineSegment(new Point(width-5, height), true),
                new LineSegment(new Point(5, height), true),
                new LineSegment(new Point(0, midHeight), true)
            }, true));

            graphics.DrawGeometry(Brush, new Pen(Stroke, StrokeThickness), shape);
        }

        /// <summary>
        /// Add a BROperator node to the left side of this operator.
        /// </summary>
        /// <param name="nwOperator"></param>
        public void addLeftVariable(BROperator nwOperator)
        {
            this.leftVariable = nwOperator;
        }

        /// <summary>
        /// Add a BRVariable to the left side of this operator.
        /// </summary>
        /// <param name="nwVariable"></param>
        public void addLeftVariable(BRVariable nwVariable)
        {
            this.leftVariable = nwVariable;
        }

        /// <summary>
        /// Add a BROperator node to the right side of this operator.
        /// </summary>
        /// <param name="nwOperator"></param>
        public void addRightVariable(BROperator nwOperator)
        {
            this.rightVariable = nwOperator;
        }

        /// <summary>
        /// Add a BRVariable to the right side of this operator.
        /// </summary>
        /// <param name="nwVariable"></param>
        public void addRightVariable(BRVariable nwVariable)
        {
            this.rightVariable = nwVariable;
        }
    }
 



What the node looks like can be seen on the left side of the image below. And what I want it to look like is on the right side.



My questions are:
1) How can I make the shape look like that? In the middle I want to be able to place a bit of text. In the image it is "<"

2) As seen in the image the node on the right side has 2 open spots. How can I add nodes to those 2 spots? (so the node fits in there and either the property leftVariable or rightVariable is set) Only 1 node can be there at a time.

Thanks in advance!

Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #1 - Oct 21st, 2013 at 12:54pm
Print Post  
Hi,

1. You could draw the slots using a scaled down version of the same PathGeometry. Call the DrawingContext.DrawText method to draw the sign string.

2. Override the DiagramNode.OnDropOver method to be notified when another node is dropped over this one. From the override, you could set your variables depending on the drop position, and possibly call AttachTo to make child nodes follow the parent node when the user moves it.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #2 - Oct 21st, 2013 at 2:25pm
Print Post  
Hi Stoyan,

I have the shape now.

Code
Select All
public override void Draw(System.Windows.Media.DrawingContext graphics, MindFusion.Diagramming.Wpf.RenderOptions options)
        {
            //base.Draw(graphics, options);
            double height = Bounds.Height;
            double width = Bounds.Width;
            double midHeight = height / 2;
            double midWidth = Bounds.Width / 2;

            var shape = new PathGeometry();
            shape.Figures.Add(new PathFigure(new Point(0, midHeight), new[]
            {
                new LineSegment(new Point(5, 0), true),
                new LineSegment(new Point(width - 5, 0), true),
                new LineSegment(new Point(width, midHeight), true),
                new LineSegment(new Point(width - 5, height), true),
                new LineSegment(new Point(5, height), true),
                new LineSegment(new Point(0, midHeight), true)
            }, true));
            shape.Figures.Add(new PathFigure(new Point(5, midHeight), new[]
                {
                    new LineSegment(new Point(10, 10), true),
                    new LineSegment(new Point(midWidth - 15, 10), true),
                    new LineSegment(new Point(midWidth - 10, midHeight), true),
                    new LineSegment(new Point(midWidth - 15, height - 10), true),
                    new LineSegment(new Point(10, height-10), true)
                }, true));
            shape.Figures.Add(new PathFigure(new Point(midWidth + 10, midHeight), new[]
                {
                    new LineSegment(new Point(midWidth + 15, 10), true),
                    new LineSegment(new Point(width - 10, 10), true),
                    new LineSegment(new Point(width - 5, midHeight), true),
                    new LineSegment(new Point(width - 10, height - 10), true),
                    new LineSegment(new Point(midWidth + 15, height - 10), true)
                }, true));
            FormattedText txt = new FormattedText(operatorSign, CultureInfo.GetCultureInfo("en-us"), FlowDirection, new Typeface("Verdana"), 15, Brushes.Black);
            Point txtPosition = new Point(Bounds.Width / 2 - 5.5, Bounds.Height / 2 - 10);

            graphics.DrawGeometry(Brush, new Pen(Stroke, StrokeThickness), shape);
            graphics.DrawText(txt, txtPosition);
        }
 



Could you give an example of the override of ondropover?

Thanks in advance!

Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #3 - Oct 21st, 2013 at 4:47pm
Print Post  
Now I see that you derive that class from ContainerNode; you shouldn't have to override OnDropOver since ContainerNode already implements it. You could just handle the ContainerChildAdded event to align the added child to one of the slot positions.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #4 - Oct 22nd, 2013 at 7:53am
Print Post  
Hi Stoyan,

I have the following code for the connection logic:

This is in the diagram class:
Code
Select All
private void OperatorNodeAdded(object sender, ContainerChildEventArgs e)
        {
            //throw new NotImplementedException();
            var addedNode = e.Node;
            var nearestNode = e.Container;
            if (nearestNode != null)
            {
                if (nearestNode is BROperator && addedNode is BROperator)
                {
                    double minDistance = Double.MaxValue;
                    BROperator parentOperator = nearestNode as BROperator;
                    BROperator addedOperator = addedNode as BROperator;
                    Point[] targetPoints = new Point[2]
                    {
                        parentOperator.rightVariablePosition,
                        parentOperator.leftVariablePosition
                    };
                    Point pointToSnap = addedOperator.connectionPoint;
                    Point targetPoint = new Point();

                    // Check the distance of each targetpoint and the pointToSnap for the closest point
                    foreach (var s in targetPoints)
                    {
                        // Calculate the distance between the points
                        double distance_squared = (pointToSnap.X - s.X) * (pointToSnap.X - s.X) + (pointToSnap.Y - s.Y) * (pointToSnap.Y - s.Y);
                        double distance = Math.Sqrt(distance_squared); //Utilities.Distance(pointToSnap, s);
                        if (distance < minDistance)
                        {
                            minDistance = distance;
                            targetPoint = s;
                        }
                    }

                    // Move this node to position
                    addedOperator.Move(targetPoint.X, targetPoint.Y);
                    parentOperator.resizeNode();
                }
            }
        }
 



And this is the BROperator class:
Code
Select All
class BROperator : ContainerNode
    {
        public string operatorSign { get; private set; }
        public int Priority { get; set; }
        object leftVariable;
        object rightVariable;
        public Point leftVariablePosition { get; private set; }
        public Point rightVariablePosition { get; private set; }
        // The connection point of this node to connect to another node
        public Point connectionPoint { get; private set; }

        public BROperator()
        {
            initialize();
        }

        public BROperator(string operatorSign)
        {
            initialize();
            this.operatorSign = operatorSign;
        }

        public BROperator(BROperator prototype)
            : base(prototype)
        {
            this.Priority = prototype.Priority;
            operatorSign = prototype.operatorSign;
            initialize();
        }

        private void initialize()
        {
            leftVariable = null;
            rightVariable = null;

            this.MinimumSize = new Size(Bounds.Width, Bounds.Height);
            this.CaptionHeight = 0;
            this.Margin = 2;
            this.AutoShrink = true;
            this.AllowAddChildren = true;
            this.Brush = new LinearGradientBrush(Colors.LawnGreen, Colors.LawnGreen, 20);

            Rect nodeSize = new Rect();
            nodeSize.Width = 70;
            nodeSize.Height = 30;
            this.Bounds = nodeSize;
        }

        public override void Draw(System.Windows.Media.DrawingContext graphics, MindFusion.Diagramming.Wpf.RenderOptions options)
        {
            //base.Draw(graphics, options);
            double height = Bounds.Height;
            double width = Bounds.Width;
            double midHeight = height / 2;
            double midWidth = Bounds.Width / 2;

            connectionPoint = new Point(Bounds.X + midWidth, Bounds.Y);

            var shape = new PathGeometry();
            // Main outline of the shape
            shape.Figures.Add(new PathFigure(new Point(0, midHeight), new[]
                {
                    new LineSegment(new Point(5, 0), true),
                    new LineSegment(new Point(width - 5, 0), true),
                    new LineSegment(new Point(width, midHeight), true),
                    new LineSegment(new Point(width - 5, height), true),
                    new LineSegment(new Point(5, height), true),
                    new LineSegment(new Point(0, midHeight), true)
                }, true));

            // Scaled down outline for the leftVariable
            shape.Figures.Add(new PathFigure(new Point(5, midHeight), new[]
                {
                    new LineSegment(new Point(10, 10), true),
                    new LineSegment(new Point(midWidth - 15, 10), true),
                    new LineSegment(new Point(midWidth - 10, midHeight), true),
                    new LineSegment(new Point(midWidth - 15, height - 10), true),
                    new LineSegment(new Point(10, height-10), true)
                }, true));
            leftVariablePosition = new Point(Bounds.X + midWidth - 25, Bounds.Y + 10);

            // Scaled down outline for the rightVariable
            shape.Figures.Add(new PathFigure(new Point(midWidth + 10, midHeight), new[]
                {
                    new LineSegment(new Point(midWidth + 15, 10), true),
                    new LineSegment(new Point(width - 10, 10), true),
                    new LineSegment(new Point(width - 5, midHeight), true),
                    new LineSegment(new Point(width - 10, height - 10), true),
                    new LineSegment(new Point(midWidth + 15, height - 10), true)
                }, true));
            rightVariablePosition = new Point(Bounds.X + midWidth + 25, Bounds.Y + 10);

            // Add the operatorSign as text
            FormattedText txt = new FormattedText(operatorSign, CultureInfo.GetCultureInfo("en-us"), FlowDirection, new Typeface("Verdana"), 15, Brushes.Black);
            Point txtPosition = new Point(Bounds.Width / 2 - 5.5, Bounds.Height / 2 - 10);

            graphics.DrawGeometry(Brush, new Pen(Stroke, StrokeThickness), shape);
            graphics.DrawText(txt, txtPosition);
        }

        public void resizeNode()
        {
            // Sample resizing
            this.MinimumSize = new Size(Bounds.Width * 2, Bounds.Height);
        }
 



But it doesn't seem to be connecting to the right position. Help? Tongue

Thanks in advance!

Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #5 - Oct 22nd, 2013 at 11:00am
Print Post  
When this event is raised, the container has been already resized to wrap the dropped child node, but not yet redrawn, so your left/right position variables do not match the new container position and size. Instead of moving the child to these old slot positions, calculate new positions from current container.Bounds after deciding whether to align to the left or right slot, and move the child node there.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #6 - Oct 22nd, 2013 at 12:48pm
Print Post  
Do you mean to move the resize to the top, before the calculation of the points is done?

Actually, what I want to do with this node is:
1) To be able to add a node to either open slots
2) If a node is dragged over either slot, the slot highlights indicating it can be dropped there.
Note: There is a HighlightPen in ContainerNode but it doesn't seem to work on a custom defined shape.
3) If the drop is possible, then set the LeftVariable property and resize the node
4) If I remove the node from the slot, then the property is set to null
Is ContainerNode the right class to use? (if so, how?)

Thanks in advance!

Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #7 - Oct 22nd, 2013 at 2:09pm
Print Post  
Quote:
Do you mean to move the resize to the top


I'm not sure what does "move the resize to the top" mean. Here is a sample scenario showing what happens:

1. the container's top-left corner is at (0,0), the right slot alignment point you keep in the rightPosition field is something like (75, 10)
2. you move a child node to the right slot
3. with AutoShrink enabled, the container's Bounds becomes equal to the child's Bounds inflated by container.Margin. E.g. the container's origin might be located at (50, 0) now rather than (0,0)
4. the ContainerChildAdded event is raised
5. you align the child to the saved rightPosition.

That rightPosition in step 5 was correct for the (0,0) position of the container. Now the container is placed at (50,0), so you should calculate a new rightPosition based on current container.Bounds before moving the child there. You should still use the saved positions when deciding whether to align to the left or right slot, since they correspond to what the user sees on screen.

Perhaps that will be easier if you derive from DiagramNode or ShapeNode indeed. Then the control will not resize the parent BROperator automatically, so your saved slot positions will still be valid. You will not be aligning the nodes in ContainerChildAdded, but from an OnDropOver override. You can call the AttachTo method of child nodes to make them follow your custom container when it moves.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #8 - Oct 22nd, 2013 at 3:02pm
Print Post  
Hi Stoyan,

I have the following code for the override of the OnDropOver but it doesn't move the node to the calculated spot. I changed the inheritance from a ContainerNode to a class which inherits from ShapeNode.

Code
Select All
public override bool OnDropOver(DiagramItem item)
        {
            if (item is BROperator)
            {
                BROperator toAdd = item as BROperator;
                Point[] targetPoints = new Point[2]
                {
                    this.rightVariablePosition,
                    this.leftVariablePosition
                };

                Point pointToSnap = toAdd.connectionPoint;
                Point targetPoint = new Point();

                // Check the distance of each targetpoint and the pointToSnap for the closest point
                double minDistance = Double.MaxValue;
                foreach (var s in targetPoints)
                {
                    // Calculate the distance between the points
                    double distance_squared = (pointToSnap.X - s.X) * (pointToSnap.X - s.X) + (pointToSnap.Y - s.Y) * (pointToSnap.Y - s.Y);
                    double distance = Math.Sqrt(distance_squared); //Utilities.Distance(pointToSnap, s);
                    if (distance < minDistance)
                    {
                        minDistance = distance;
                        targetPoint = s;
                    }
                }

                // Calculate the distance of the two points
                double dx = targetPoint.X - pointToSnap.X;
                double dy = targetPoint.Y - pointToSnap.Y;
                Debug.WriteLine("X distance to move: " + dx);
                Debug.WriteLine("Y distance to move: " + dy);

                // Move the dragged node to position
                toAdd.Bounds.Offset(dx, dy);
                toAdd.SetBounds(toAdd.Bounds, true, true);

                // Attach the dragged node to this node
                toAdd.AttachTo(this, AttachToNode.MiddleLeft);

                resizeNode();
            }
            return base.OnDropOver(item);
        }

        public void resizeNode()
        {
            //this.MinimumSize = new Size(Bounds.Width * 2, Bounds.Height);
            this.Bounds = new Rect(Bounds.X, Bounds.Y, Bounds.Width * 2, Bounds.Height);
        }
 



Greets,
Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #9 - Oct 23rd, 2013 at 6:27am
Print Post  
Rect is a value type, and toAdd.Bounds.Offset() modifies a temporary Rect instance on the stack rather than the node's field. Replace that code with following:

Code
Select All
var childBounds = toAdd.Bounds;
childBounds.Offset(dx, dy);
toAdd.SetBounds(childBounds, true, true); 



I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #10 - Oct 23rd, 2013 at 9:31am
Print Post  
Hi Stoyan,

Thanks, it moves the node now Smiley

I have another question: how can I highlight either slots of the node when another node hovers over a particular slot?

Thanks in advance!

Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #11 - Oct 23rd, 2013 at 10:22am
Print Post  
Hi,

Override OnDragOver too, update some field that indicates current target slot, and draw the shape path in a different color for that slot:

Code
Select All
int highlightSlot;
public override bool OnDragOver(DiagramItem item)
{
	int nearestSlot = ...;
	if (nearestSlot != highlightSlot)
	{
		highlightSlot = nearestSlot;
		Repaint();
	}
	return base.OnDragOver(item);
} 



You will have to create and draw a new PathGeometry in Draw() for the highlighted slot.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #12 - Oct 23rd, 2013 at 2:59pm
Print Post  
Much appreciated, it works!! Cheesy

I want to paint the outline of the slot black again if I drag the node outside the custom container node. I tried doing it with the following code but it doesn't work:

Code
Select All
public override void OnDragOut(DiagramItem item)
        {
            if (nearestSlot == 1 || nearestSlot == 2) // 1 and 2 indicate that one of the slots is closer and should be highlighted
            {
                nearestSlot = 0;
                Repaint();
            }
            base.OnDragOut(item);
        }
 



Am I using the wrong method? I also tried OnDragLeave() but it doesn't work..

Thanks in advance!

Vincent
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: custom nodes
Reply #13 - Oct 24th, 2013 at 8:34am
Print Post  
OnDragOut is invoked only if previous OnDragOver returned true, so instead of returning base.OnDragOver(), try returning true.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
Vincent
Junior Member
**
Offline


I Love MindFusion!

Posts: 62
Joined: Oct 3rd, 2013
Re: custom nodes
Reply #14 - Oct 24th, 2013 at 10:33am
Print Post  
Yup, works like a charm!

I have another question about the alignment of nodes in either slots (in class BROperator). BROperator inherits from class BR_Shape which has the following code for the alignment of nodes:

Code
Select All
protected override void UpdateModify(Point current, InteractionState ist)
        {
            base.UpdateModify(current, ist);
            AlignToNearestPoint(current, ist);
        }

        protected override void CompleteModify(Point end, InteractionState ist)
        {
            base.CompleteModify(end, ist);
            AlignToNearestPoint(end, ist);
        }

        protected void AlignToNearestPoint(Point current, InteractionState ist)
        {
            if (ist.AdjustmentHandle == 8) // If the node is moved
            {
                var nearbyNode = Parent.GetNearestNode(current, 30, this);

                    if (nearbyNode != null)
                    {
                        var bounds_nearby_node = nearbyNode.Bounds;
                        double x_coord = 0;
                        double y_coord = 0;

                        var bounds_this_node = this.Bounds;
                        double x_coord2 = 0;
                        double y_coord2 = 0;

                        double dx = 0;
                        double dy = 0;

                        if (nearbyNode is If_Shape)
                        {
                            If_Shape nearby_Node = nearbyNode as If_Shape;
                            nearby_Node.AlignNodeToMe(this);
                        }
                        if (nearbyNode is BROperator)
                        {
                            BROperator nearby_Node = nearbyNode as BROperator;
                            nearby_Node.AlignNodeToMe(this);
                        }
                        if (nearbyNode is Action_Shape)
                        {
                            // Calculate the point of the nearby node to connect to
                            x_coord = bounds_nearby_node.X + bounds_nearby_node.Width / 10;
                            y_coord = bounds_nearby_node.Bottom - bounds_nearby_node.Height / 90;
                            var targetPoint = new Point(x_coord, y_coord);

                            // Calculate the point of this node to connect to the nearby node
                            x_coord2 = bounds_this_node.X + bounds_this_node.Width / 10;
                            y_coord2 = bounds_this_node.Y;
                            var pointToSnap = new Point(x_coord2, y_coord2);

                            // Calculate the distance of the two points
                            dx = targetPoint.X - pointToSnap.X;
                            dy = targetPoint.Y - pointToSnap.Y;

                            // Move this node by the calculated distance
                            bounds_this_node.Offset(dx, dy);
                            SetBounds(bounds_this_node, true, true);

                            // Lastly, set the connectednode of the nearest node to this node
                            Action_Shape nearby_Node = nearbyNode as Action_Shape;
                            this.parent_node = nearby_Node;
                            nearby_Node.connected_node = this;
                        }
                        if (nearbyNode is Start_Point_Shape)
                        {
                            Start_Point_Shape sp = nearbyNode as Start_Point_Shape;
                            sp.AlignNodeToMe(this);
                        }
                    }
            }
        }
 



It works for the other shapes but not BROperator. If I have a BROperator node in the diagram (op1) and drag another BROperator node (op2) close to op1, op1 resizes correctly but op2 doesn't move to the open slot. Instead, it goes into a state where I can draw op2 (make it bigger or something). But the handlestyle for all BR_Shapes if MoveOnly. Do you have any ideas? (I've added the project as an attachment)

Thanks in advance!

Vincent
  

DragDropExample_-_Copy_002.zip (Attachment deleted)
Back to top
 
IP Logged
 
Page Index Toggle Pages: [1] 2 
Send TopicPrint