The OLE way of doing drag&drop in wxPython - python

I have wxPython app which is running on MS Windows and I'd like it to support drag&drop between its instances (so the user opens my app 3 times and drags data from one instance to another).
The simple drag&drop in wxPython works that way:
User initiates drag: The source window packs necessary data in wx.DataObject(), creates new wx.DropSource, sets its data and calls dropSource.DoDragDrop()
User drops data onto target window: The drop target calls library function GetData() which transfers actual data to its wx.DataObject instance and finally - dataObject.GetData() unpacks the actual data.
I'd like to have some more sophisticated drag&drop which would allow user to choose what data is dragged after he drops.
Scenario of my dreams:
User initiates drag: Only some pointer to the source window is packed (some function or object).
User drops data onto target window: Nice dialog is displayed which asks user which drag&drop mode he chooses (like - dragging only song title, or song title and the artists name or whole album of the dragged artist).
Users chooses drag&drop mode: Drop target calls some function on the dragged data object, which then retrieves data from the drag source and transfers it to the drop target.
The scenario of my dreams seems doable in MS Windows, but the docs for wxWidgets and wxPython are pretty complex and ambigious. Not all wx.DataObject classes are available in wxPython (only wx.PySimpleDataObject), so I'd like someone to share his experience with such approach. Can such behaviour be implemented in wxPython without having to code it directly in winAPI?
EDIT:
Toni Ruža gave an answer with working drag&drop example, but that's not exactly the scenario of my dreams. His code manipulates data when it's dropped (the HandleDrop() shows popup menu), but data is prepared when drag is initiated (in On_ElementDrag()). In my application there should be three different drag&drop modes, and some of them require time-consuming data preparation. That's why I want to postpone data retrieval to the moment user drops data and chooses (potentially costly) d&d mode.
And for memory protection issue - I want to use OLE mechanisms for inter-process communication, like MS Office does. You can copy Excel diagram and paste it into MS-Word where it will behave like an image (well, sort of). Since it works I believe it can be done in winAPI. I just don't know if I can code it in wxPython.

Since you can't use one of the standard data formats to store references to python objects I would recommend you use a text data format for storing the parameters you need for your method calls rather than making a new data format. And anyway, it would be no good to pass a reference to an object from one app to another as the object in question would not be accessible (remember memory protection?).
Here is a simple example for your requirements:
import wx
class TestDropTarget(wx.TextDropTarget):
def OnDropText(self, x, y, text):
wx.GetApp().TopWindow.HandleDrop(text)
def OnDragOver(self, x, y, d):
return wx.DragCopy
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.numbers = wx.ListCtrl(self, style = wx.LC_ICON | wx.LC_AUTOARRANGE)
self.field = wx.TextCtrl(self)
sizer = wx.FlexGridSizer(2, 2, 5, 5)
sizer.AddGrowableCol(1)
sizer.AddGrowableRow(0)
self.SetSizer(sizer)
sizer.Add(wx.StaticText(self, label="Drag from:"))
sizer.Add(self.numbers, flag=wx.EXPAND)
sizer.Add(wx.StaticText(self, label="Drag to:"), flag=wx.ALIGN_CENTER_VERTICAL)
sizer.Add(self.field)
for i in range(100):
self.numbers.InsertStringItem(self.numbers.GetItemCount(), str(i))
self.numbers.Bind(wx.EVT_LIST_BEGIN_DRAG, self.On_ElementDrag)
self.field.SetDropTarget(TestDropTarget())
menu_id1 = wx.NewId()
menu_id2 = wx.NewId()
self.menu = wx.Menu()
self.menu.AppendItem(wx.MenuItem(self.menu, menu_id1, "Simple copy"))
self.menu.AppendItem(wx.MenuItem(self.menu, menu_id2, "Mess with it"))
self.Bind(wx.EVT_MENU, self.On_SimpleCopy, id=menu_id1)
self.Bind(wx.EVT_MENU, self.On_MessWithIt, id=menu_id2)
def On_ElementDrag(self, event):
data = wx.TextDataObject(self.numbers.GetItemText(event.Index))
source = wx.DropSource(self.numbers)
source.SetData(data)
source.DoDragDrop()
def HandleDrop(self, text):
self._text = text
self.PopupMenu(self.menu)
def On_SimpleCopy(self, event):
self.field.Value = self._text
def On_MessWithIt(self, event):
self.field.Value = "<-%s->" % "".join([int(c)*c for c in self._text])
app = wx.PySimpleApp()
app.TopWindow = Test()
app.TopWindow.Show()
app.MainLoop()
Methods like On_SimpleCopy and On_MessWithIt get executed after the drop so any lengthy operations you might want to do you can do there based on the textual or some other standard type of data you transfered with the drag (self._text in my case), and look... no OLE :)

Ok, it seems that it can't be done the way I wanted it.
Possible solutions are:
Pass some parameters in d&d and do some inter-process communication on your own, after user drops data in target processes window.
Use DataObjectComposite to support multiple drag&drop formats and keyboard modifiers to choose current format. Scenario:
User initiates drag. State of CTRL, ALT and SHIFT is checked, and depending on it the d&d format is selected. DataObjectComposite is created, and has set data in chosen format.
User drops data in target window. Drop target asks dropped DataObject for supported format and retrieves data, knowing what format it is in.
I'm choosing the solution 2., because it doesn't require hand crafting communication between processes and it allows me to avoid unnecessary data retrieval when user wants to drag only the simplest data.
Anyway - Toni, thanks for your answer! Played with it a little and it made me think of d&d and of changing my approach to the problem.

Related

Change certain data in QTreeView

I am trying to make a virtual file system for a part of a project (since I couldn't find any libraries for this — all the ones I found were for accessing your actual file system).
I decided to store it as an XML, and then to display it by generating a QStandardItemModel, and generating a QTreeView based on that model.
I am currently trying to add the rename, move, etc options. I can perform these changes in the XML fairly easily, but I've spent hours trying to figure it out with the treeview and haven't gotten anywhere. The best thing I've been able to do is to regenerate the model from the xml file, and then make that the treeview model. However, this will probably be too slow for my end program (which will have a few thousand "files"), and this also collapses all the nodes in the treeview which seems pretty annoying to fix. Overall, this just doesn't seem like the best way to do it, especially since I know which nodes are being changed, so it would be much simpler to just edit those individual nodes in the treeview (or in the model).
The way I would want this to work, is that the user selects an element of the treeview to rename, I get the selectedItem from the treeview, then somehow lookup the corresponding item in the model and modify that.
I've also considered traversing the model to find the item I want to move/rename and then doing that within the model, but I couldn't find any good documentation for how to traverse the model (it doesn't even seem to have a method that returns the number of children of a node).
Is there a "nice" way of doing this?
Example:
def clicked():
index = list(treeView.selectedIndexes())[0]
# TODO: change text of index to "changed text"
app = QApplication([])
window = QWidget()
layout = QVBoxLayout()
treeView = QTreeView()
model = QStandardItemModel()
model.invisibleRootItem().appendRow(QStandardItem('a'))
child_node = QStandardItem('b')
child_node.appendRow(QStandardItem('c'))
model.invisibleRootItem().appendRow(child_node)
treeView.setModel(model)
treeView.clicked.connect(clicked)
layout.addWidget(treeView)
window.setLayout(layout)
window.show()
app.exec_()
The clicked signal sends the QModelIndex associated with the clicked item so it is not necessary to use selectedIndexes().
Going to the bottom problem, the logic is to use the model to get the item given the QModelIndex and then use the setText() method, another more general option is to use the setData() method:
def clicked(index):
model = index.model()
item = model.itemFromIndex(index)
item.setText("changed text")
# or
# model.setData(index, "changed text", Qt.DisplayRole)

What is the simplest way of monitoring when a wxPython frame has been resized?

I want to know when a frame has been resized, so I can save the size and remember it the next time the application launches. Here is my on_resize method:
def on_resize(self, event):
logic.config_set('main_frame_size',
(event.Size.width, event.Size.height))
event.Skip()
And it's bound like this:
self.Bind(wx.EVT_SIZE, self.on_resize)
The problem is performance. For safety, my logic module saves the config file every time a setting changes, and writing the config file every time the resize event fires is way too performance taxing.
What would be the best/easiest way of monitoring for when the user is done resizing the frame?
Update
My config_set function:
def config_set(key, value):
"""Set a value to the config file."""
vprint(2, 'Setting config value: "{}": "{}"'.format(key, value))
config[key] = value
# Save the config file.
with open(config_file_path, 'w') as f:
pickle.dump(config, f)
You could handle EVT_IDLE which is triggered when the event queue is empty:
wx.IdleEvent: This class is used for EVT_IDLE events, which are generated and sent when the application becomes idle. In other words, the when the event queue becomes empty then idle events are sent to all windows (by default) and as long as none of them call RequestMore then there are no more idle events until after the system event queue has some normal events and then becomes empty again.
The process of resizing or moving a window should keep the event queue jammed so it won't become empty (and trigger the idle event) until the resizing/moving is done.
Set a dirty flag in EVT_SIZE and check it in the EVT_IDLE handler. If the flag is set, save the new size and reset the flag:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.resized = False # the dirty flag
self.Bind(wx.EVT_SIZE,self.OnSize)
self.Bind(wx.EVT_IDLE,self.OnIdle)
def OnSize(self,event):
self.resized = True # set dirty
def OnIdle(self,event):
if self.resized:
# take action if the dirty flag is set
print "New size:", self.GetSize()
self.resized = False # reset the flag
app = wx.PySimpleApp()
frame = Frame().Show()
app.MainLoop()
EVT_SIZE may also be triggered when restoring a minimized window (the window size remains the same). If you want to cut down on unnecessary saves, you may want to check if the size is actually different before you save it to the config (you could keep track of it in a variable containing the last saved size).
You may want to add EVT_MOVE to keep track of the window position.
You could start a timer and have it check for changes every so often, kind of like the auto-save in Microsoft Word. Or you could set some kind of flag when EVT_SIZING or EVT_SIZE occurs and then bind to EVT_LEAVE_WINDOW as you'll almost certainly leave the window when you're done resizing. Thus when that event fires, you check the flag that was set and if it is set, you save and reset the flag.
On windows, you can save the configuration in the registry, which results in no performance hit when the window is resized.
On other OS's, where there is no registry, I guess you need to use a file. However, I am surprised that even this gives the kind of performance penalty that you would notice.
Are you sure that whatever poor performance you are seeing is due to this? ( Perhaps your redrawing code is slow? )
I would think that any modern OS would look after such a small file write without getting in your way. Perhaps it is Python problem?
I urge you to look into the above questions first. However, to answer your actual question:
The way to do this is to save the window size in a variable, and only write it to a file when your application quits.
Took a look at the code you just posted. I am not a python expert, but it looks like you are re-opening the file on every update. If so, no wonder it is slow!
Keep the file open all the time.
Only write the file when your app quits.
You might also take a look at the wxWidget wxConfig class.
You definitely shouldn't be saving the window geometry on every resize, it should be normally only done when the frame is closed. If you want extra safety (but, honestly, how do you manage to crash in Python?), you can also call the same function you call on frame close from a EVT_TIMER handler. But window geometry is hardly a critical resource so I don't think there is ever any real need to do this.

What's a good method to make a table full of labels editable/selectable?

I've got a GtkTable that contains several GtkLabel's of data that it displays. These labels can have formatting done to them( i.e. alignment, padding, foreground color ).
An example of what a table might look like:
Each line of information in this table is displayed in its own GtkLabel attached to table you see. Now, I'm attempting to have the data be selectable and possibly editable. This doesn't seem very easy to do, considering the data is spread across several widgets( each in its own GtkLabel) and there doesn't seem to be any way to make text selectable across multiple widgets. I have come up with my own solution to this problem:
Essentially,I'm packing the GtkTable into a GtkEventBox so that I can register events on the table, and also, packed within the eventbox( well, its child VBox) is a GtkTextView that contains the same text( unformatted) as what's in the GtkTable. This textview will serve as the editable and selectable region I want from the table. To achieve this functionality, I keep the visibility of the table and textview opposite of one another...so, being packed on top of eachother in the box, they appear to be the same widget that switches 'modes'.
The textview:
So, with this visibility toggling, I can attach some signals to the event box, say, button-press/release-event, and focus-in/out type events to get the desired mode of the table.
Some sample code to get a better understanding:
eventbox = gtk.EventBox()
vbox = gtk.VBox()
table = gtk.Table() #Create the table, and fill it with labels
textview = gtk.TextView() #Create the textview, and set its textbuffer to the same data as the table
eventbox.add(vbox)
vbox.pack_start( table )
vbox.pack_start( textview )
eventbox.connect( "button-release-event", toggle_mode )
textview.connect( 'focus-out-event', toggle_mode )
def toggle_mode( widget, event ):
if table.get_visible():
table.set_visible( False )
textview.set_visible( True )
else:
table.set_visible( True )
textview.set_visible( False )
So, my question is: Is this a good method for what I want to achieve? Does anyone have any other experiences or a better method for doing this? Ultimately I'd like to have nicely formatted text, much like how a GtkTable filled with formatted GtkLabel's look, but with the ability to select a blob of text as one piece. How can this be done?
I recommend just using GtkTextView for everything, but if you insist on using GtkLabel for the look, then your method is as good as any (another popular method for this sort of thing is to use a GtkNotebook with the tabs hidden).
You still need a visual indication to tell the user that they can edit the text, though. Perhaps a toggle button.

Simple GUI Windows Drag&Drop

I'd like to make a simple GUI that offers buttons which I can drag and drop into other Windows applications such that this other applications receives a certain string depending on the button chosen.
What would be the easiest GUI framework for Python that allows this drag and drop?
Any of the UI Libraries are likely to have support for this in some fashion. In wxPython we allow List Items to be moved between various lists. Things might look like this:
class JobList(VirtualList):
def __init__(self, parent, colref = 'job_columns'):
VirtualList.__init__(self, parent, colref)
def _bind(self):
VirtualList._bind(self)
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self._startDrag)
def _startDrag(self, evt):
# Create a data object to pass around.
data = wx.CustomDataObject('JobIdList')
data.SetData(str([self.data[idx]['Id'] for idx in self.selected]))
# Create the dropSource and begin the drag-and-drop.
dropSource = wx.DropSource(self)
dropSource.SetData(data)
dropSource.DoDragDrop(flags = wx.Drag_DefaultMove)
Then, there's a ListDrop class that facilitates the dropping of things onto the lists:
class ListDrop(wx.PyDropTarget):
"""
Utility class - Required to support List Drag & Drop.
Using example code from http://wiki.wxpython.org/ListControls.
"""
def __init__(self, setFn, dataType, acceptFiles = False):
wx.PyDropTarget.__init__(self)
self.setFn = setFn
# Data type to accept.
self.data = wx.CustomDataObject(dataType)
self.comp = wx.DataObjectComposite()
self.comp.Add(self.data)
if acceptFiles:
self.data2 = wx.FileDataObject()
self.comp.Add(self.data2)
self.SetDataObject(self.comp)
def OnData(self, x, y, d):
if self.GetData():
if self.comp.GetReceivedFormat().GetType() == wx.DF_FILENAME:
self.setFn(x, y, self.data2.GetFilenames())
else:
self.setFn(x, y, self.data.GetData())
return d
And finally, a List where things can be dropped:
class QueueList(VirtualList):
def __init__(self, parent, colref = 'queue_columns'):
VirtualList.__init__(self, parent, colref)
self.SetDropTarget(ListDrop(self.onJobDrop, 'JobIdList', True))
def onJobDrop(self, x, y, data):
idx, flags = self.HitTest((x, y)) ##UnusedVariable
if idx == -1: # Not dropped on a list item in the target list.
return
# Code here to handle incoming data.
PyQt to the rescue.
Theoretically, you can use any library for which a graphical drag-and-drop designer exists. Such tools often generate markup language which the library parses, and sometimes they generate code directly. The latter is language-dependent whilst the former shouldn't be. Either way, you'll find a way of doing it with Python.
Practically, some libraries have better visual designing tools that others. WinForms designer was really elegant and seamless when I used it, so maybe IronPython? How about PyGTK, with Glade, or the aforementioned PyQt? Maybe Jython using Swing designed in NetBeans?
EDIT: Whoops, I didn't read the question properly. What you're looking for is a framework with drag-and-drop capabilities, i.e. most of them. There's lots of things to consider though, for example are the destination and source windows going to be from the same process? Will they be written with the same framework? These things could be relevant or not, depending on how it's all written.

wxpython - Binding events in external files

I am trying to bind events from a GUI file to use code from another file (effectively a "front end" and a "back end"). I can get the back end and front end working within the same file, but when I try to move them into separate files, I have issues getting the back end to see parts (labels, buttons, etc.) of the front end.
I. E. I need the back end code to change labels and do math and such, and it would need to affect the GUI.
I have provided a simple version of my program. Everything works with the exception of the error I get when I try to make the back end see the parts of the GUI.
mainfile.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import label_changer
class foopanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
box = wx.BoxSizer()
btn = wx.Button(self,1,"Press")
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
box.Add(btn)
self.lbl = wx.StaticText(self,1,"Foobar")
box.Add(self.lbl)
self.SetSizerAndFit(box)
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer()
self.p = foopanel(self)
sizer.Add(self.p,1)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,)
app.MainLoop()
label_changer.py
def change_label(self):
self.p.lbl.SetLabel("barfoo")
All I want it to do is change the label of the GUI, but use an external file.
I am doing this mostly to keep my code separate and just as a learning experience.
Thanks in advance!
One solution is to modify change_label to accept an argument that identifies the label to change. For example:
def change_label(event, label):
label.SetLabel("barfoo")
Then, use lambda to create a callback that passes that argument in:
btn.Bind(wx.EVT_BUTTON, label_changer,
lambda event, label=self.p.lbl: label_changer.change_label(event, label))
Make sure you define self.lbl before you do the binding.
For more on passing arguments to callbacks see Passing Arguments to Callbacks on WxPyWiki
A common way to do this is the MVC Pattern and pubsub. See this Example.
This
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
needs to be
btn.Bind(wx.EVT_BUTTON,label_changer.change_label)
and this
def change_label(self):
self.p.lbl.SetLabel("barfoo")
needs to be
def change_label(event):
panel = event.GetEventObject().GetParent()
panel.lbl.SetLabel("barfoo")
To clarify, you need to pass a reference to a function to Bind that is to be called when the event occurs. wx will always pass one argument to these functions - the event. The self that you usually see in the callbacks is a byproduct of them being bound methods. Every bound method (to oversimplify, a function defined in a class) gets implicitly passed a first argument when called that is a reference to a class instance. So since you can't get to this instance the traditional way in an "external" function you have to get to it through the event object.
One more thing, you are not realy separating the gui from the logic this way. This is because the logic (label_changer in this case) needs to know about the gui and to manipulate it directly. There are ways to achieve much stronger separation (st2053 hinted at one of them) but for a relatively small program you don't need to bother if you don't want to right now, simply splitting the code in multiple files and focusing on getting the thing done is fine. You can worry about architecture later.

Categories

Resources