I am trying to configure my text file viewer to open files by dragging them onto it. I've looked at several tutorials and tried to imitate them, but my widget never seems to be receiving the "drag_data_received" signal. Here, self.topLevel is a gtk.Window widget, the root of my application, and this is the last bit of the code for setting it up. I've confirmed that dragging text files onto it doesn't call OnDrop at all.
def OnDrop(widget, context, x, y, sel, targetType, timestamp):
print context.actions
print context.targets
return True
self.topLevel.connect("drag_data_received", OnDrop)
self.topLevel.drag_dest_set(gtk.DEST_DEFAULT_DROP |
gtk.DEST_DEFAULT_MOTION |
gtk.DEST_DEFAULT_HIGHLIGHT, [("text/*", 0, 0)], gtk.gdk.ACTION_COPY)
self.topLevel.show_all()
I realized that my application was getting the signal, but the TextView widget that I was dropping everything onto (because it occupied most of its window) was absorbing these signals and not calling my callback function; dropping the file onto any other part of the application fixed it. I got it to work by calling the TextView's drop_dest_unset function.
Related
I know this is a super vague question, but I'm just getting into GUI development using wxPython and could use some guidance. I have a program that:
opens a modal dialog box where the user is to select a .csv file containing data to be analyzed
stores the data as a pandas DataFrame object
does some formatting, cleaning up, and calculation on the data
generates a new dataframe with the results of the calculations
plots the results (linear regressions) and displays the results tables, as well as saving both the plots and new tables to .png and .csv files, respectively.
I want a GUI such that, when launched, a simple window appears with some text and a single button in the middle "import csv to begin" or something (I was able to create this first window by subclassing wx.Frame, but the button currently doesn't do anything). On clicking the button, the modal dialog will open so the user can select the .csv data file. On clicking OK/Open/whatever the button is (long day, memory no work), the window/frame will change to a different layout (again, was able to piece together a class for this frame). My question is mainly how I should go about getting the data between frames WHILE ALSO changing the frame.
The method for switching between frames I found was to include, in the class definition, the method
def _NextFrame(self, event):
self.frame.Show()
self.Hide()
and then in the body of main() call it as
app = wx.App(redirect=True)
f1 = Frame("Frame1")
f2 = Frame("Frame2")
f1.frame = f2
f2.frame = f1
f1.Show()
app.MainLoop()
But this was for just switching between two instances of the same frame, not two different frames with different functions. Additionally, I think that this way will instantiate the frames all before running the app, so if I have the self.getcsv() function called in the __init__() of my second frame, the user will be prompted to open a file before they even click the button on the first frame (even though the second frame is as-yet invisible).
Can I use the code for the CLI version, build in the classes for the GUI, and handle all the calculations etc. outside of wxPython, using wx only to display what I want to display? I'm just pretty lost in general. Again, sorry for the vague question, but I didn't know where else to turn.
Finished the app. For other green GUI programmers, the way I handled this was to instantiate the next frame in an event handler bound to a logical button/control (such as a "Start" button, "Analyze" button, etc.). For example, after creating all the classes for the different frames, data handlers, and so on, I start the app with
def main():
app = wx.App()
frm = StartFrame(None)
frm.Show()
app.MainLoop()
if __name__ == "__main__":
main()
Within the StartFrame instance, there's a "Start" button bound to the handler:
def _OnStart(self,event):
frm2 = ParaFrame(None)
frm2.Show()
self.Destroy()
The ParaFrame frame has an "analyze" button which is a little more complex: it instantiates a (non-wx, custom) class DataHandler, sets various attributes according to user input in the ParaFrame instance, calls a DataHandler method which analyzes the data, then instantiates the result frame (which takes some of the results from DataHandler's analysis as __init__() parameters), shows it, deletes the DataHandler, and destroys itself:
def _analyze(self, event):
dhandler = DataHandler(self)
dhandler.path = self.path
dhandler.logconv = self.logbtn.GetValue()
dhandler.numTar = int(self.inputNum.GetValue())
dhandler.conc = self.inputcb.GetValue()
for idx, tar in enumerate(self.tarcbs):
dhandler.targets.append(self.tarcbs[idx].GetValue())
dhandler._analyzeData()
resfrm = ResultFrame(None, dhandler.targets, dhandler.dftables)
resfrm.Show()
del dhandler
self.Destroy()
From the ResultFrame instance, aside from just displaying the results, there are controls to either exit the app (bound to _OnExit, below) or restart the app from the beginning to run a new analysis (bound to _OnRestart):
def _OnExit(self, event):
"""Close frame & terminate app"""
self.Close(True)
def _OnRestart(self, event):
frm = StartFrame(None)
frm.Show()
self.Destroy()
This method also helped get around the problem with the example of switching frames I found; that example was suited to switching back and forth between two persistent frames, whereas I wanted a linear A --> B --> C approach, where once a frame was displayed, the previous frame should be destroyed.
Hopefully this will help someone in the future :)
I am currently creating a GUI in Python 3.7, using PyQt5 and Qt Designer in the Spyder environment. The GUI has many different windows. Basically I am starting with the UI_Start window and then open the next window when a button is pressed. The GUI is working kind of fine, however after approximately 50 windows the program suddenly doesn't show the next window anymore but also doesn't stop the execution. The weird thing about this issue is that:
the exact same window class has been called a lot of times beforehand and there have never been any issues
the problem does not only occur for one window but it can also occur for another window class (but after the same amount of windows being shown)
I tried to figure out why the .show() command is suddenly not working anymore. I used print statements to see where the program "breaks down". I saw that even the print statements after the .show() command are working but then as the window isn't shown I can't press any button to trigger the next event. So basically the program is hanging.
I am relatively new to programming in Python and creating GUIs but I thought that maybe the problem occurs due to memory leak. This is why I am now trying to open memory space when closing a window by using self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True). However, now I am facing the problem that the next window doesn't show up anymore. So how can I use DeleteOnClose if I want to show a new window afterwards?
Also if anyone has a suggestion for the original problem, please let me know. I am trying to figure out the problem since like a week but have not come any further.
Thank you already!
Some part of my code to work with:
class UI_Start(QtWidgets.QMainWindow):
def __init__(self):
super(UI_Start, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('Screen_Start.ui', self) # Load the .ui file
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) # added newly
self.Start_pushButton_Start.clicked.connect(self.openKommiScreen)
def openKommiScreen(self):
self.close()
self.KommiScreen = UI_Kommi(self)
class UI_Kommi(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(UI_Kommi, self).__init__(parent)
uic.loadUi('Screen_Kommi.ui', self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
global sheetNo
sheetNo = 1
self.WeiterButton = self.findChild(QtWidgets.QPushButton,'pushButton_Weiter')
self.WeiterButton.clicked.connect(self.openScanScreen)
self.show()
def openScanScreen(self):
self.close()
self.ScanScreen = UI_Scan(self)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = UI_Start()
window.show()
sys.exit(app.exec_())
At first I would guess it's a garbage collection problem. The only reference to your new window is stored in your previous one. Which is deleted, so there is no more reference to your window object and python may delete it automatically.
In these cases I often goes for a global variable to store the current windows references.
I have a program that at some point opens a new window (filled with buttons and gizmo's for the user to select and play around with) that is defined as follows:
def window(self,master):
def close(self):
# change some variables
self.destroy()
top = self.top = Toplevel()
# Several lines of buttons
top.lift()
top.protocol("WM_DELETE_WINDOW",close(self))
I initially had a close button there that would wrap everything up nicely but I noticed that if the user used the standard 'X' in the corner of the window, this function obviously would not be called and that would give a lot of problems later on. I found out about the 'WM_DELETE_WINDOW' suggestion from some other questions on this website but it gives me a rather strange error:
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1630, in wm_protocol
'wm', 'protocol', self._w, name, command)
TclError: bad window path name ".33862072"
I assume that it somehow has gotten the wrong window ID and is unable to catch the event. My question is thus, is that true or not and secondly how should I continue to deal with this issue.
Let's examine this line of code:
top.protocol("WM_DELETE_WINDOW",close(self))
This line of code is saying "immediately call the function close(self), and assign the result to the protocol handler. See the problem? It's immediately calling close, likely before self has been fully constructed. You don't want the function to be called, you want to pass in a reference to the function.
Make close be a method of self (rather than an embedded function) and change the call to top.protocol to look like this (note the lack of trailing parenthesis):
top.protocol("WM_DELETE_WINDOW", self.close)
If you prefer to keep the nested function, you can use lambda:
top.protocol("WM_DELETE_WINDOW", lambda window=self: close(window))
I'm writing a 'wizard' type Python Tkinter GUI that collects information from the user and then performs several actions based on the user's entries: file copying, DB updates, etc. The processing normally takes 30-60 seconds and during that time, I want to:
Provide the user with text updates on the activity and progress
Prevent the user from closing the app until it's finished what it's doing
I started on the route of having the text updates appear in a child window that's configured to be trainsient and using wait_window to pause the main loop until the activities are done. This worked fine for other custom dialog boxes I created which have OK/cancel buttons that call the window's destroy method. The basic approach is:
def myCustomDialog(parent,*args):
winCDLG = _cdlgWin(parent,*args)
winCDLG.showWin()
winCDLG.dlgWin.focus_set()
winCDLG.dlgWin.grab_set()
winCDLG.dlgWin.transient(parent)
winCDLG.dlgWin.wait_window(winCDLG.dlgWin)
return winCDLG.userResponse
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.userResponse = ''
def showWin(self):
#Tkinter widgets and geometry defined here
def _btnOKClick(self):
#self.userResponse assigned from user entry/entries on dialog
self.dlgWin.destroy()
def _btnCancelClick(self):
self.dlgWin.destroy()
However this approach isn't working for the new monitor-and-update dialog I want to create.
First, because there's no user-initiated action to trigger the copy/update activities and then the destroy, I have to put them either in showWin, or in another method. I've tried both ways but I'm stuck between a race condition (the code completes the copy/update stuff but then tries to destroy the window before it's there), and never executing the copy/update stuff in the first place because it hits the wait_window before I can activate the other method.
If I could figure out a way past that, then the secondary problem (preventing the user from closing the child window before the work's done) is covered by the answers below.
So... is there any kind of bandaid I could apply to make this approach work the way I want? Or do I need to just scrap this because it can't work? (And if it's the latter, is there any way I can accomplish the original goal?)
self.dlgWin.overrideredirect(1) will remove all of the buttons (make a borderless window). Is that what you're looking for?
As far as I know, window control buttons are implemented by the window manager, so I think it is not possible to just remove one of them with Tkinter (I am not 100% sure though). The common solution for this problem is to set a callback to the protocol WM_DELETE_WINDOW and use it to control the behaviour of the window:
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.dlgWin.protocol('WM_DELETE_WINDOW', self.close)
self.userResponse = ''
def close(self):
tkMessageBox.showwarning('Warning!',
'The pending action has not finished yet')
# ...
My app has a GtkFileChooserButton that you can use to open a chooser widget and pick a single file .. and then perform operations on that file. This works.
I've added drag & drop functionality to the button as well. It works, but it's buggy.
In short, the first dnd to the FileChooserButton triggers the file-set signal and appears to have worked, -- the name changes from "( None )" to the file's name and if you then click on the button, it pops up a chooser widget showing the proper file selected; HOWEVER, as you can see from the debug output below, get_filename() does not return the filename! Not the first time, anyway.
The second time we dnd a file onto the button (whether it's a different file or the same file again), all goes well and get_filename() returns the file's name. Why?
Here's the debug output from my program when I drag three files on to the button, one at a time:
[on_file-set] FileChooserButton.get_filename() output:
None
[on_file-set] FileChooserButton.get_filename() output:
'/home/ryran/Desktop/priv.txt'
[on_file-set] FileChooserButton.get_filename() output:
'/home/ryran/Desktop/kinamppost'
PS: When I did this, the 1st and 2nd dnd were actually the same file.
If you want to see the full app in action, it's at http://github.com/ryran/pyrite, and I'd love to post code, but there's not really anything to post!! I'm not doing drag_dest_set() because FileChooserButton already supports dnd. So ALL I'm doing is defining a cb for the FileChooserButton's file-set signal. So uhh.. here's that:
def action_chooserbtn_file_set(self, widget):
print "[on_file-set] FileChooserButton.get_filename() output:\n{!r}\n".format(widget.get_filename())
For the record I also tried doing all this in concert with defining drag_dest_set, but came up with the same results.
What else to say? I'm starting to think this is a bug.
Turns out this really is a GTK+ bug. Talked to a developer on IRC. He helped me realize that and then encouraged me to post a bug report, which I did -- https://bugzilla.gnome.org/show_bug.cgi?id=669718