
An example of using WxPython to create a node interface. Part 4: Implementing Drag & Drop
In a short series of articles we will describe the use of WxPython to solve a very specific task of developing a user interface, and how to make this solution universal. This tutorial is intended for those who have already begun to study this library and want to see something more complex and holistic than the simplest examples (although it starts with relatively simple things).

In this part, we will add Drag & Drop support to our application and teach him how to create new nodes in this way.
Part 1: Learning to draw
Part 2: Handling mouse events
Part 3: Continuing to add features + keyboard processing
Part 4: Implementing Drag & Drop
Part 5: Connecting nodes
Who cares, welcome to the ...
Drag & Drop support is a useful and popular thing, but here we will use it not for its intended purpose. In this way we will create new nodes. This business works quite simply. It is required to create an object of class "wx.TextDropTarget" and pass it to the "SetDropTarget" method of class "wx.Window" from which our canvas is inherited. Accordingly, at the time of the drop, the "wx.TextDropTarget.OnDropText" method will be called, which we will need to implement. For the test, the implementation of this class will look like this:
Now, when throwing text in a window, a message with the position and the text itself will be printed in the console. As you might have guessed, for accepting files, there is a similar class “wx.FileDropTarget”, which works in the same way, well, or a class “wx.PyDropTarget”, which can accept everything and everything.
Just typing text is not very useful, so we use the resulting text to create nodes with it. But first you need to change the architecture a bit and add a node factory (this will come in handy for the future). At the moment - it will be a very simple factory:
which simply creates an instance of "SimpleTextBoxNode", which is always an advanced descendant of "SimpleBoxNode":
which in turn renders the given text on top of the rectangular node.
It remains to add a method to the canvas, allowing you to add new nodes from the description:
And slightly modernize TextDropTarget so that it calls this method when the text arrives:
And now we can create new text nodes simply by throwing text fragments onto the canvas.
It looks like this:
The code, as always, can be found in the corresponding commit on GitHub .
But in addition to creating nodes, using Drag & Drop, you can also organize copying nodes, and it’s very easy. If the user at the moment you start dragging the node holds Ctrl, we just need to initiate the beginning of Drag & Drop and give a description of the node. And the node creation code will do the rest of the work for us. To initiate Drag & Drop, we will add the following code to the left-click handler:
Here we create the source of Drag & Drop and give it the description that we received from the node. It remains to implement the GetCloningNodeDescription method on the node and everything will be ready. But first, we implement the interface:
And now the implementation of the method for the node:
which just gives her text.
The current version of the code lives here .
Well, before completing the fourth part, add the last little thing. Let's make the nodes scale to fit the text. To do this, we will slightly change the method of rendering a text node:
The GetTextExtent method in this case returns the size of the rectangle that the text occupies. Accordingly, before rendering a node, we update its dimensions so that it is 10 pixels larger than the text on each side. This is how this whole process now looks:
The code lives in this commit on GitHub .
PS: About typos write in a personal.

In this part, we will add Drag & Drop support to our application and teach him how to create new nodes in this way.
Part 1: Learning to draw
Part 2: Handling mouse events
Part 3: Continuing to add features + keyboard processing
Part 4: Implementing Drag & Drop
Part 5: Connecting nodes
Who cares, welcome to the ...
9. Add support for Drag & Drop
Drag & Drop support is a useful and popular thing, but here we will use it not for its intended purpose. In this way we will create new nodes. This business works quite simply. It is required to create an object of class "wx.TextDropTarget" and pass it to the "SetDropTarget" method of class "wx.Window" from which our canvas is inherited. Accordingly, at the time of the drop, the "wx.TextDropTarget.OnDropText" method will be called, which we will need to implement. For the test, the implementation of this class will look like this:
class TextDropTarget(wx.TextDropTarget):
def __init__(self):
wx.TextDropTarget.__init__(self)
def OnDropText(self, x, y, data):
print x, y, data
Now, when throwing text in a window, a message with the position and the text itself will be printed in the console. As you might have guessed, for accepting files, there is a similar class “wx.FileDropTarget”, which works in the same way, well, or a class “wx.PyDropTarget”, which can accept everything and everything.
10. Creating Nodes with Drag & Drop
Just typing text is not very useful, so we use the resulting text to create nodes with it. But first you need to change the architecture a bit and add a node factory (this will come in handy for the future). At the moment - it will be a very simple factory:
class NodesFactory(object):
def __init__(self):
pass
def CreateNodeFromDescription(self, nodeDescription):
return SimpleTextBoxNode(text=nodeDescription)
which simply creates an instance of "SimpleTextBoxNode", which is always an advanced descendant of "SimpleBoxNode":
class SimpleTextBoxNode(SimpleBoxNode):
def __init__(self, **kwargs):
super(SimpleTextBoxNode, self).__init__(**kwargs)
self.text = kwargs.get("text", "No text")
def Render(self, gc):
super(SimpleTextBoxNode, self).Render(gc)
gc.DrawText(self.text, self.position[0]+10, self.position[1]+10)
which in turn renders the given text on top of the rectangular node.
It remains to add a method to the canvas, allowing you to add new nodes from the description:
def CreateNodeFromDescriptionAtPosition(self, nodeDescription, pos):
node = self._nodesFactory.CreateNodeFromDescription(nodeDescription)
if node:
node.position = pos
self._canvasObjects.append(node)
self.Render()
And slightly modernize TextDropTarget so that it calls this method when the text arrives:
class TextDropTarget(wx.TextDropTarget):
def __init__(self, canvas):
wx.TextDropTarget.__init__(self)
self._canvas = canvas
def OnDropText(self, x, y, data):
print x, y, data
self._canvas.CreateNodeFromDescriptionAtPosition(data, [x, y])
And now we can create new text nodes simply by throwing text fragments onto the canvas.
It looks like this:
The code, as always, can be found in the corresponding commit on GitHub .
11. Clone nodes with Drag & Drop
But in addition to creating nodes, using Drag & Drop, you can also organize copying nodes, and it’s very easy. If the user at the moment you start dragging the node holds Ctrl, we just need to initiate the beginning of Drag & Drop and give a description of the node. And the node creation code will do the rest of the work for us. To initiate Drag & Drop, we will add the following code to the left-click handler:
if evt.ControlDown() and self._objectUnderCursor.clonable:
text = self._objectUnderCursor.GetCloningNodeDescription()
data = wx.TextDataObject(text)
dropSource = wx.DropSource(self)
dropSource.SetData(data)
dropSource.DoDragDrop(wx.Drag_AllowMove)
Here we create the source of Drag & Drop and give it the description that we received from the node. It remains to implement the GetCloningNodeDescription method on the node and everything will be ready. But first, we implement the interface:
class ClonableObject(CanvasObject):
def __init__(self, **kwargs):
super(ClonableObject, self).__init__(**kwargs)
self.clonable = True
def GetCloningNodeDescription(self):
"""
GetNodeDescription should return a dictionary that contains
all information required for cloning this node at another position
"""
raise NotImplementedError()
And now the implementation of the method for the node:
def GetCloningNodeDescription(self):
return self.text
which just gives her text.
The current version of the code lives here .
12. Scalable nodes
Well, before completing the fourth part, add the last little thing. Let's make the nodes scale to fit the text. To do this, we will slightly change the method of rendering a text node:
def Render(self, gc):
textDimensions = gc.GetTextExtent(self.text)
self.boundingBoxDimensions = [textDimensions[0]+20, textDimensions[1]+20]
super(SimpleTextBoxNode, self).Render(gc)
gc.DrawText(self.text, self.position[0]+10, self.position[1]+10)
The GetTextExtent method in this case returns the size of the rectangle that the text occupies. Accordingly, before rendering a node, we update its dimensions so that it is 10 pixels larger than the text on each side. This is how this whole process now looks:
The code lives in this commit on GitHub .
PS: About typos write in a personal.