Advanced Topics
This guide covers dynamic ports, node locking, loop detection, and other advanced features.
Dynamic Ports
Add or remove ports at runtime. This is useful for nodes with variable inputs like “Add N Numbers” or “Merge Arrays”.
The process:
Call
portsAboutToBeInserted()orportsAboutToBeDeleted()Modify your internal data
Call
portsInserted()orportsDeleted()
void MyModel::addPortToNode(NodeId nodeId)
{
// 1. Prepare: library caches affected connections
portsAboutToBeInserted(nodeId, PortType::In, newPortIndex, newPortIndex);
// 2. Update your data
_portCounts[nodeId]++;
// 3. Complete: library restores shifted connections
portsInserted();
}
void MyModel::removePortFromNode(NodeId nodeId, PortIndex portIndex)
{
// 1. Prepare: library removes affected connections
portsAboutToBeDeleted(nodeId, PortType::In, portIndex, portIndex);
// 2. Update your data
_portCounts[nodeId]--;
// 3. Complete
portsDeleted();
}
In NodeDelegateModel:
void MyDelegate::addPort()
{
emit portsAboutToBeInserted(PortType::In, newIndex, newIndex);
_inputCount++;
emit portsInserted();
}
Warning
Always use the two-phase approach. Modifying ports without the
portsAboutTo... / ports... calls will corrupt connection state.
Locked Nodes
Prevent nodes from being moved or selected:
NodeFlags MyModel::nodeFlags(NodeId nodeId) const override
{
NodeFlags flags = AbstractGraphModel::nodeFlags(nodeId);
if (shouldBeLocked(nodeId)) {
flags |= NodeFlag::Locked;
}
return flags;
}
Update at runtime:
void MyModel::lockNode(NodeId nodeId)
{
_lockedNodes.insert(nodeId);
emit nodeFlagsUpdated(nodeId); // Tell scene to update
}
Resizable Nodes
Allow users to resize nodes (useful for embedded widgets):
NodeFlags MyModel::nodeFlags(NodeId nodeId) const override
{
return NodeFlag::Resizable;
}
Handle size changes:
bool MyModel::setNodeData(NodeId nodeId, NodeRole role, QVariant value) override
{
if (role == NodeRole::Size) {
_nodeSizes[nodeId] = value.toSize();
emit nodeUpdated(nodeId);
return true;
}
// ...
}
Non-Detachable Connections
Prevent users from dragging connections away from certain nodes:
bool MyModel::detachPossible(ConnectionId conn) const override
{
// Don't allow detaching from "output" nodes
if (isOutputNode(conn.outNodeId)) {
return false;
}
return true;
}
Loop Detection
The AbstractGraphModel provides loopsEnabled() to control cyclic connections:
// Default: loops allowed
bool AbstractGraphModel::loopsEnabled() const { return true; }
// DataFlowGraphModel: loops disabled
bool DataFlowGraphModel::loopsEnabled() const override { return false; }
When loops are disabled, connectionPossible() automatically rejects
connections that would create cycles.
Custom loop policy:
class MyModel : public AbstractGraphModel
{
public:
bool loopsEnabled() const override
{
return _allowLoops; // User-configurable
}
void setLoopsAllowed(bool allowed)
{
_allowLoops = allowed;
}
private:
bool _allowLoops = false;
};
Connection Policies
Control how many connections a port accepts:
QVariant MyModel::portData(NodeId nodeId, PortType portType,
PortIndex portIndex, PortRole role) const override
{
if (role == PortRole::ConnectionPolicyRole) {
if (portType == PortType::In) {
// Inputs accept only one connection
return QVariant::fromValue(ConnectionPolicy::One);
} else {
// Outputs can connect to many inputs
return QVariant::fromValue(ConnectionPolicy::Many);
}
}
// ...
}
Custom Connection Validation
Implement complex connection rules:
bool MyModel::connectionPossible(ConnectionId conn) const override
{
// Basic checks
if (!nodeExists(conn.inNodeId) || !nodeExists(conn.outNodeId))
return false;
// No self-connections
if (conn.inNodeId == conn.outNodeId)
return false;
// Type compatibility
auto outType = getOutputType(conn.outNodeId, conn.outPortIndex);
auto inType = getInputType(conn.inNodeId, conn.inPortIndex);
if (!typesCompatible(outType, inType))
return false;
// Custom rule: max 3 connections to any input
if (connections(conn.inNodeId, PortType::In, conn.inPortIndex).size() >= 3)
return false;
// Cycle detection (if loops disabled)
if (!loopsEnabled() && wouldCreateCycle(conn))
return false;
return true;
}
Programmatic Graph Manipulation
Build graphs in code (useful for testing or loading custom formats):
void buildGraph(AbstractGraphModel& model)
{
// Create nodes
NodeId source = model.addNode("Source");
model.setNodeData(source, NodeRole::Position, QPointF(0, 0));
NodeId process = model.addNode("Process");
model.setNodeData(process, NodeRole::Position, QPointF(200, 0));
NodeId output = model.addNode("Output");
model.setNodeData(output, NodeRole::Position, QPointF(400, 0));
// Create connections
if (model.connectionPossible({source, 0, process, 0})) {
model.addConnection({source, 0, process, 0});
}
if (model.connectionPossible({process, 0, output, 0})) {
model.addConnection({process, 0, output, 0});
}
}
Node Groups (Future)
Note
Node grouping is planned but not yet implemented. See the GitHub issues for status.
See also
examples/dynamic_ports/– Dynamic port exampleexamples/lock_nodes_and_connections/– Locking example