An example of using WxPython to create a node interface. Part 4: Implementing Drag & Drop
In a small series of articles will be described the use of WxPython to solve a very specific task of developing a user interface, and even how to make this solution universal. This tutorial is designed for those who have already begun to study this library and want to see something more complex and holistic than the simplest examples (although everything will start from relatively simple things).
Drag & Drop support is a useful and popular thing, but here we will use it not quite for its intended purpose. We will thus create new nodes. This business works quite simply. It is required to create an object of class “wx.TextDropTarget” and pass it to the method “SetDropTarget” of class “wx.Window” from which our canvas is inherited. Accordingly, at the moment of the drop, the method “wx.TextDropTarget.OnDropText” will be called, which we will need to implement. For the test, the implementation of this class will look like this:
classTextDropTarget(wx.TextDropTarget):def__init__(self): wx.TextDropTarget.__init__(self) defOnDropText(self, x, y, data):print x, y, data
Now, when throwing text into the 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 the class “wx.PyDropTarget”, which can take everything and everything.
10. Create nodes with Drag & Drop
Simply typing the text is not very useful, so we use the resulting text to create nodes with it. But first you need to slightly change the architecture and add a node factory (this will be useful for the future). At the moment - it will be a very simple factory:
which in turn renders the specified text on top of the rectangular node. It remains to add to the canvas a method that allows you to add new nodes from the description:
And modernize TextDropTarget a bit so that it calls this method when the text arrives:
classTextDropTarget(wx.TextDropTarget):def__init__(self, canvas): wx.TextDropTarget.__init__(self) self._canvas = canvas defOnDropText(self, x, y, data):print x, y, data self._canvas.CreateNodeFromDescriptionAtPosition(data, [x, y])
And now we can create new text nodes by simply throwing text fragments onto the canvas. It looks like this:
The code, as always, can be found in the appropriate commit on GitHub .
11. Clone nodes with Drag & Drop
But besides creating nodes, using Drag & Drop, you can also organize copying of nodes, and it is very easy. If the user at the moment of the start of dragging the node holds the Ctrl key, we just need to initiate the beginning of Drag & Drop and give the 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 mouse 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 we received from the node. It remains to implement the “GetCloningNodeDescription” method of the node and everything will be ready. But first we implement the interface:
classClonableObject(CanvasObject):def__init__(self, **kwargs): super(ClonableObject, self).__init__(**kwargs) self.clonable = TruedefGetCloningNodeDescription(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 in the node:
which just gives up its text. The current version of the code lives here .
12. Scalable nodes
Well, before completing the fourth part, add the last little thingies. Let's make the nodes scale to the size of the text. To do this, we will slightly change the text node rendering method:
The “GetTextExtent” method in this case returns the size of a rectangle that occupies text. Accordingly, before rendering the node, we update its dimensions so that it is 10 pixels larger than the text on each side. This is how the whole process now looks like: