/// <summary> /// Configures custom port handling with the help of <see cref="ILookup"/>. /// </summary> /// <remarks> /// When a user interacts with edges and their endpoints, /// node.Lookup(IPortCandidateProvider) is called for the nodes in that graph, /// and the framework returns the implementation of IPortCandidateProvider which /// supplies the list of available ports. /// /// Instead of the default, we'll register a custom lookup for type IPortCandidateProvider. /// /// Note: we'll update this method in a future tutorial step to work with folding. /// </remarks> private void CustomizePortHandling() { // Sets auto cleanup to false, since we don't want to remove unoccupied ports. Graph.NodeDefaults.Ports.AutoCleanUp = false; // First we create a GraphDecorator from the IGraph. // GraphDecorator is a utility class that aids in decorating model items from a graph instance. // Here, we call NodeDecorator.PortCandidateProviderDecorator // to access the lookup decorator for ports - the thing we want to change. // One way to decorate the graph is to use the factory design pattern. // We set the factory to a lambda expression which // returns instances that implement the IPortCandidateProvider interface. // Here we can create a CompositePortCandidateProvider that combines various port candidate providers. // The ExistingPortsCandidateProvider provides port candidates at the locations of the already existing ports. // The NodeCenterPortCandidateProvider provides a single port candidate at the center of the node. // The ShapeGeometryPortCandidateProvider provides several port candidates based on the shape of the node. Graph.GetDecorator().NodeDecorator.PortCandidateProviderDecorator.SetFactory( node => PortCandidateProviders.Combine( PortCandidateProviders.FromExistingPorts(node), PortCandidateProviders.FromNodeCenter(node), PortCandidateProviders.FromShapeGeometry(node))); // To modify the existing lookup for a graph element, typically we decorate it with the help // of one of graph's Get...Decorator() extension methods, // which allows to dynamically insert custom implementations for the specified types. // Doing this can be seen as dynamically subclassing // the class in question (the INode implementation in this case), but only // for the node instances that live in the graph in question and then // overriding just their Lookup(Type) method. The only difference to traditional // subclassing is that you get the "this" passed in as a parameter. // Doing this more than once is like subclassing more and more, so the order matters. }
internal CustomPortCandidateProvider(INode node) { this.node = node; geometryPortCandidateProvider = PortCandidateProviders.Combine( PortCandidateProviders.FromNodeCenter(node), PortCandidateProviders.FromShapeGeometry(node)); }
/// <summary> /// Creates an <see cref="IPortCandidateProvider"/> that considers the node's shape and rotation. /// </summary> private static IPortCandidateProvider CreatePortCandidateProvider(INode node) { var rotatedPortModel = RotatablePortLocationModelDecorator.Instance; var freeModel = FreeNodePortLocationModel.Instance; var rnsd = (RotatableNodeStyleDecorator)node.Style; var wrapped = rnsd.Wrapped; var sns = wrapped as ShapeNodeStyle; if (wrapped is ShinyPlateNodeStyle || wrapped is BevelNodeStyle || sns != null && sns.Shape == ShapeNodeShape.RoundRectangle) { return(PortCandidateProviders.Combine( //Take all existing ports - these are assumed to have the correct port location model PortCandidateProviders.FromUnoccupiedPorts(node), //Provide explicit candidates - these are all backed by a rotatable port location model PortCandidateProviders.FromCandidates( //Port candidates at the corners that are slightly inset new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(freeModel.CreateParameter(new PointD(0, 0), new PointD(5, 5)))), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(freeModel.CreateParameter(new PointD(0, 1), new PointD(5, -5)))), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(freeModel.CreateParameter(new PointD(1, 0), new PointD(-5, 5)))), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(freeModel.CreateParameter(new PointD(1, 1), new PointD(-5, -5)))), //Port candidates at the sides and the center new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeLeftAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeBottomAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeCenterAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeTopAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeRightAnchored)) ))); } if (sns != null && sns.Shape == ShapeNodeShape.Rectangle) { return(PortCandidateProviders.Combine( PortCandidateProviders.FromUnoccupiedPorts(node), PortCandidateProviders.FromCandidates( //Port candidates at the corners new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeTopLeftAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeTopRightAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeBottomLeftAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeBottomRightAnchored)), //Port candidates at the sides and the center new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeLeftAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeBottomAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeCenterAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeTopAnchored)), new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(FreeNodePortLocationModel.NodeRightAnchored)) ))); } if (sns != null) { // Can be an arbitrary shape. First create a dummy node that is not rotated var dummyNode = new SimpleNode { Style = sns, Layout = node.Layout }; var shapeProvider = PortCandidateProviders.FromShapeGeometry(dummyNode, 0); var shapeCandidates = shapeProvider.GetTargetPortCandidates(null); var rotatingCandidates = shapeCandidates.Select(c => new DefaultPortCandidate(node, rotatedPortModel.CreateWrappingParameter(c.LocationParameter))); return(PortCandidateProviders.Combine( PortCandidateProviders.FromUnoccupiedPorts(node), PortCandidateProviders.FromCandidates(rotatingCandidates))); } return(null); }