I am working on a Python application that plots data from a large file containing records from lots and lots of sources. One of the options I am trying to give the user is the option to only plot for a subset of these sources if so desired. I accomplish this by first reading the files, finding out how many unique things there are, and then creating a QCheckBox() for each, named after its source (each source has a unique name). In this particular case, the data file is parsed into a giant dictionary where the keys are the unique source. I want to connect to the stateChange() event for each checkbox and then disable plotting for that source when the box is unchecked. Which in this case would be adding/removing the source from a list of sources when the box is checked/unchecked. The problem I am running into is that all of my checkboxes end up connecting to the final source in my list.
Initially, the window that is created looks correct, each button is named appropriately. Every time a button gets pressed, the btnstate() is supposed to simply print the text associated with that button. The method works if you can explicitly define each button, as shown by the radio buttons in the example. If you click either, you will get the correct name of the button printed, but when unchecking/rechecking ANY of the check boxes, btnstate prints "test4".
What am I doing wrong?
Here is the code (sources changed to dummy values):
import sys
from PyQt4.QtGui import *
def btnstate(b):
print b.text()
def main():
app = QApplication([])
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
radio_layout = QHBoxLayout()
checkbox_layout = QHBoxLayout()
#setup radio buttons for config pop-up window
r1 = QRadioButton("Page Count")
r2 = QRadioButton("Date")
r1.toggled.connect(lambda:btnstate(r1))
r2.toggled.connect(lambda:btnstate(r2))
radio_layout.addWidget(r1)
radio_layout.addWidget(r2)
cbs = []
for idx, serial in enumerate(["test1", "test2", "test3", "test4"]):
temp = QCheckBox(serial)
temp.setText(serial)
temp.setChecked(True)
checkbox_layout.addWidget(temp)
temp.stateChanged.connect(lambda:btnstate(temp))
cbs.append(temp)
layout.addLayout(radio_layout)
layout.addLayout(checkbox_layout)
widget.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I think the reason that this is happening has to do with how Python binds and unbinds references inside the loop. Because temp is being redefined on each iteration, the slot is also being updated, so that it effectively calls the same lambda for every button. That's sort of hand-wavy, because my understanding of the details of Python referencing is not super deep. But I know that the Python bindings to Qt have a lot of problems with Python's referencing and garbage collection, e.g. when deleting widgets, because the Qt object hierarchy doesn't totally work with Python.
Anyway, more practically, there is a pretty easy fix. You can use the functools.partial method to define a partial function as the slot, rather than a lambda. Bind the button as the first object, leaving the button state (emitted as the signal argument) unbound. Like so:
import functools
def btnstate(button, state):
print button.text()
Then in the loop:
for idx, serial in enumerate(['test1', 'test2', 'test3', 'test4']):
temp = QCheckBox(serial)
checkbox_layout.addWidget(temp)
temp.stateChanged.connect(functools.partial(btnstate, serial))
Running this, I now get the correct labels printed when each box is check/unchecked.
Edit:
See this post for another example of Python's reference counting interacting with strange ways with Qt's object hierarchy.
Related
If, e.g, a script contains button or a list it seems that the user is unable to edit other objects (e.g like figures) while the button or list box is actively being displayed on the screen. Therefore I would like to ask if I can "unfocus" the button so that I can freely edit the desired object (e.g zoom in/out, add manuallly a legend etc.)?
E.g:
while indx == 1
list = {[...
'Choose_option',...
'The data file will be exported (with a total of_____'...
num2str(height(EXPORT))'_____datapoints)'],...
};
[indx] = listdlg('SelectionMode','single','ListString',list,'ListSize', [600 300]);
switch indx
case 1
% placeholder
case 2
source_1 ='D:\MyFile\Programm_alpha\Test.xls';
destination_1 ='D:\TargetEXPO\Programm_beta'
copyfile(source_1 , destination_1);
xlswrite(source_1,{'Begin_reading'},'Sheet_1','A1');
end
end
listdlg creates a modal dialog box, which means it disables interaction with everything else in MATLAB until the dialog box is closed. The same is true for inputdlg and questdlg.
If you want to have a non-modal window where the user can select things, you will have to build this yourself. You will need uicontrol for this. A good place to start is by looking at the code for listdlg (it used to be a plain M-file back in the day, not sure if it still is though).
I've made a block of RadioButton widgets for my tkd interface, which work exactly as expected. However, the documentation for tkd's RadioButton doesn't show how one determines which of the buttons is pressed. Instead, the example code simply shows how to create an array of buttons that are linked together (i.e. so that only one can be selected at a time)
// The radio button group parent.
auto frame = new Frame()
.pack();
auto option1 = new RadioButton(frame, "Foo")
.setSelectedValue("foo")
.select()
.pack();
auto option2 = new RadioButton(frame, "Bar")
.setSelectedValue("bar")
.pack();
The CheckButton widget is a similar, and can be polled withe its associated isChecked() method, but nothing similar seems to be present for the RadioButton.
How do I check whether a given RadioButton has been selected?
Additionally, is there an easy way to check which member of an array of RadioButtons is selected, without iterating over the entire set myself?
After reading more documentation, it appears tkd implements this feature with a series of mixins.
The RadioButton class implements the Value interface described here.
Calling any of the associated methods will allow access to the state variable that is shared by the entire button array. E.g.
option1.getValue(); // Returns "Foo" if option 1 is selected, "Bar" if option 2 is.
option2.getValue(); // Same as above, since the value is shared.
option2.setValue("bar"); //Change value, without changing the selected button.
I have a small python project that requires data to be pulled from a network and displayed every second (which is how often it changes) for a scientific application. 3 of these things are simply numbers, while another is a 128x128 camera image, which is brought in as an ndarray and drawn using matplotlib's imshow to a tkinter canvas.
I've tried two methods - using aniamtion.FuncAnimation() and after(interval, function), and both have the same result, which is that while the frame updates, the window can't be moved. and it feels jerky.
I assume that's something that can't be overcome (and probably doesn't matter)? I thought that perhaps multi-threading might help so the main window is on one thread, while the updated data can be on another?
Thanks!
Below is the basic code which now includes threading, and the error when closing the window is fixed by adding an event to the window close function, and also a few break commands while getting data that checks if the window is closed before it tries to interact with a GUI item that doesn't exist.
import blah, blah, blah
global safe_shutdown, window_status, my_thread
safe_shutdown = False
window_status = True
window = tk.Tk()
def widow_close():
window_status=False
while True:
if safe_shutdown == True:
window.destroy
return False
def get_updates():
while True:
#code to go get data from network
if window_status == False:
safe_shutdown == True:
break
#more code to place data on the GUI
if window_status == False:
safe_shutdown == True:
break
#only get updates once a second
time.sleep(1)
print "thread complete"
my_thread= threading.Thread(target=get_updates, args=()).start()
window.protocol("WM_DELETE_WINDOW", window_close)
window.mainloop()
I'm not an expert, so I'm not sure if there's a better solution. But I've had success with two separate solutions:
1: While it is not recomended to have the tkinter loop in a thread, you are allowed to have the data updating the tkinter app in a thread. This has worked pretty good for me in the past simply using the threading package. The thread will then just set the various stuff in need of a refresh.
2: Call the update_idletasks() on the window to force it to update. This can then be added at various places in you code where it would make sense to update the view.
Solution 1 whould take care of all stuttering, while solution 2 might just make it a bit better. I suppose it depends on your implementation.
app=Tk()
age=IntVar()
name=StringVar()
id=IntVar()
def add_user():
app1=Tk()
L1 = Message(app1,text="Name")
L1.pack( side = LEFT)
E1 = Entry(app1,textvariable=name)
E1.pack()
L2 = Message(app1,text="\nAge")
L2.pack( side = LEFT)
E2 = Spinbox(app1,from_=1,to_=100,textvariable=age)
E2.pack()
l3=Message(app1,text="\nId")
l3.pack()
e3=Spinbox(app1,from_=1,to_=100,textvariable=id)
e3.pack()
b5=Button(app1,text="submit",command=app1.destroy)
b5.pack()
app1.mainloop()
print age.get(),name.get(),id.get()
return
b1=Button(app,command=add_user,relief=RIDGE,text="add patient details")
b1.pack(side=BOTTOM)
app.mainloop()
the print statement doesn't print the correct values,it always prints the default values.I don't understand where I made a mistake
The reason you can't get the values is that the widgets have been destroyed once mainloop has exited.
The bigger problem in your code is that you are creating two instances of Tk. Tkinter isn't designed to work that way. A proper Tkinter program creates exactly one instance of Tk, and exits when that one instance is destroyed. If you want to create more than one window, the second and subsequent windows need to be instances of Toplevel.
You might find the answer to this question useful: Correct way to implement a custom popup tkinter dialog box
I'm trying to design a Qt GUI application with user customize-able hotkeys. The main issue I'm running into is how to synchronize the hotkeys across the application since a particular hotkey (for example, copy) may be used by multiple widgets/components.
My current strategy is to use a reference class which holds a list of QKeySequence objects for each different hotkey. Each widget would have to have a way to reference this master list and have custom implementations of low-level the keyPressEvent which would compare inputted keys vs. the hotkeys. I don't particularly like this strategy, though, as it requires significant re-implimentation in each widget and feels like I'm trying to re-invent the wheel.
I also tried using QAction objects which can hold QKeySequence shortcuts internally, then use these to trigger higher-level events which I can handle using slots & signals. However, the main issue I have here is how to manage which slots signals get routed to.
For example, say I have 2 open widgets which can both receive a copy action signal. I can connect a slot for both of these to the same signal and take advantage of the single update point for shortcuts, but then things get messy since only the active widget should act on the copy signal, not both widgets. I can re-implement the focusOutEvent and focusInEvent handlers to connect/disconnect slots manually, but this also seems to run into the same issue above where I'm trying to re-invent the wheel and doing more work than is necessary.
Is there an easier way around this problem?
I don't think there is a particularly easy/non-tedious solution to this problem, but when I needed to add user-customizable hotkeys to my application, here is how I did it:
1) Start with your application that has hard-coded key shortcuts, e.g. code like this:
QMenu * editMenu = new QMenu;
QAction * copyItem = menu->addAction(tr("Copy"), this, SLOT(CopyData()));
copyItem->setShortcut(tr("Ctrl+C"));
2) Create a GetKeySequence() function that looks something like this:
static QHash<QString, QKeySequence> _usersKeyPreferences;
static bool _usersKeyPreferencesLoaded = false;
QKeySequence GetKeySequence(const QString & keySequence, const QString & contextStr)
{
if (_usersKeyPreferencesLoaded == false)
{
// Oops, time to load in the user's saved custom-key settings from a file somewhere
_usersKeyPreferences = LoadUsersKeyPreferencesFromFile();
_usersKeyPreferencesLoaded = true; // so we'll only try to load the file once
}
if (_usersKeyPreferences.contains(contextStr))
{
return _usersKeyPreferences[contextStr];
}
else
{
// No user preference specified? Okay, fall back to using the
// hard-coded default key sequence instead.
return QKeySequence(qApp->translate(contextStr, keySequence));
}
}
3) Now the tedious part: grovel over all of your code, and anywhere you've specified a key-sequence explicitly (like in the third line of the code shown for step 1), wrap it with a call to GetKeySequence(), like this:
copyItem->setShortcut(GetKeySequence(tr("Ctrl+C"), tr("Edit_Menu|Copy")));
4) At this point, your program's key-sequences will be customizable; just make sure that the key-settings-file is present on disk before GUI-creation code runs. Here's an excerpt from my program's key-mappings file (which I store as a simple ASCII text file):
Edit_Menu|Copy = Ctrl+C
Edit_Menu|Cut = Ctrl+X
Edit_Menu|Paste = Ctrl+V
[... and so on for all other menu items, etc...]
... of course one downside to this approach is that once the GUI is created, the key-bindings can't be modified "on the fly" (at least, not without a lot of additional coding). My program gets around this simply by closing and then re-creating all windows after the user clicks "Save and Apply" in the Edit Key Bindings dialog.
5) An optional further step (which is some extra work up front but saves time in the long run) is to write a program (or script) that greps all the .cpp files in your program's codebase looking for calls GetKeySequence() in the code. When it finds a GetKeySequence() call, it parses out the two arguments to the call and prints them as a line in a key-bindings file with the default settings. This is useful because you can make this script part of your autobuild, and thereafter you'll never have to remember to manually update the default key-settings-file whenever you add a new menu item (or other key-sequence specifier) to your program.
This worked well for me, anyway. The advantage is that you don't have to refactor your existing program at all; you can just go through it inserting GetKeySequence() as necessary while leaving the larger logic/structure of the program intact.