Removing the selected items from streamlet's multiselect - python

I am trying to delete objects from a list by choosing them via streamlits multiselect widget, having the list entries as entries of the widget. Thus, the list also decides what options are in the multiselect box.
However, after the app reruns—once I deleted some options—I get the error: streamlit.errors.StreamlitAPIException: Every Multiselect default value must exist in options
Here is some minimal code example.
import streamline as st
if st.button("Refill") or "options" not in st.session_state:
st.session_state.options=["a","b","c"]
def submit():
for item in st.session_state.selected:
st.session_state.options.remove(item)
form=st.form("My form")
form.multiselect("Select", st.session_state.options, key="selected")
form.form_submit_button("Submit", on_click=submit)
I tried to add the line
st.session_state.selected=[]
to the submit function so that the multiselect-box is cleared and does not reference deleted items, but it did not solve the issue.
Thanks for any help in advance! :)

Add the following to the top of your code:
if "selected" in st.session_state:
del st.session_state.selected
Explanation:
The streamlit multi-select widget maintains the last selection in its internal state (st.session_state.selected in your case), so if you delete an item from your st.session_state.options list, it will error out, as it can't find the current selection in the list you passed to it.
To fix this, simply delete the session_state.selected prior to running the rest of the code; this is done with the suggested if-Statement.
You might also want to add the kwarg clear_on_submit=True on your st.form definition, as that clears the input of the form as well.

Related

Modify Checkboxes based on changes in Streamlit

I wanted to create a checkbox which checks all other checkboxes if checked and if the checkbox is unchecked that all other checkboxes also get unchecked.
For example:
Clicking on “all” should be also checking “option1, option2, etc.”. Same if I uncheck.
I tried to use session_states but could not come up with a solution.
Is there a possibility to modify checkboxes?
You can use this way.
import streamlit as st
isall = st.checkbox(label="All")
st.checkbox(label="option1", value=isall)
st.checkbox(label="option2", value=isall)

Tkinter get OptionMenu Option List

I'm using Python 3, Tkinter module. I've looked at ttk library and one of the widgets there is an Option Menu. It's great, but I was wondering if there is a way to retrieve the list of options that are currently in use by the menu.
In this example:
Options_List=["option1","option2"]
My_Menu = OptionMenu(master, variable, *Options_List))
I'm aware that it may seem trivial. Just retrieve the Options_List variable. But now let's assume I'm making loads of options (using the same, or different lists):
Options_List=["option1","option2","option3"]
Menu_List = []
for Option in range(3):
My_Menu = OptionMenu(master, variable, *Options_List))
Menu_List.append(My_Menu)
Options_List.del(-1) #removes last item
I just took advantage of the fact that when the Option Menu is assigned, the options are the copy of the Options_List variable, not a reference to it, so when the code is executed, they all refer to their own version of Options_List.
Option output would yield:
Menu_List[0] -> ["option1","option2","option3"]
Menu_List[1] -> ["option1","option2"]
Menu_List[2] -> ["option1"]
Now you can see that I can't just retrieve Options_List, because each Option Menu has its own list to work off.
So, any ideas? Is there any way I can get hold of the list of options that my nth Option Menu is using?
The option menu is nothing more than a standard button with a menu attached to it. So, to get the values in the option menu you simply need to get the menu associated with the optionmenu, and use the methods available on the menu to get the items on the menu.
For example, let's assume that om represents the optionmenu. To get the menu you can do this:
menu = om['menu']
menu now is a reference to a Menu object. You can find out the index of the last item with the index method:
last_index = menu.index("end")
With that, you can iterate over the items in the menu. If you want the label, you can use entrycget to get the value of that attribute:
values = []
for i in range(last_index+1):
values.append(menu.entrycget(i, "label"))
With that, values will contain the values that appear on the menu.

How do I update the data associated with a QListWidgetItem from the QListWidget in PyQt?

I have a QListWidget that has 10 QListWidgetItems. When each of those QListWidgetItems is created I do something like this:
item = QtGui.QListWidgetItem("Text to Show")
item.setData(36, "A specific value that is used later")
self.ui.my_list.addItem(item)
Now, later in the application, after a user clicks a button, I want to update the text "A specific value that is used later", for the item that is selected. I have attempted to do this
ndx = self.ui.my_list.currentRow()
self.ui.my_list.item(ndx).setData(36, "Updated!")
The problem is, this doesn't work. It doesn't throw any errors, but the data is just gone. In my button press signal I have this code to see the value before and after the reassignment:
ndx = self.ui.my_list.currentRow()
print "Before:", self.ui.my_list.item(ndx).data(36).toPyObject()
self.ui.my_list.item(ndx).setData(36, "Updated!")
print "After:", self.ui.my_list.item(ndx).data(36).toPyObject()
This outputs:
Before: A specific value that is used later
After:
How can I properly change the data so that it is saved back to the QListWidgetItem?
You may want to check that the role value your using is a valid user role, otherwise it may be altered internally. I write in c++, but I use the QListWidget and QListWidgetItem(s) frequently, and this was a problem I encountered early on. I imagen that the functionality is similar regardless of the language. According to the documentation here, the first user role that can be used is 0x0010. So to tie it to your example, you might try the following:
item.setData(0x0010, "A specific value that is used later")
For additional item roles you would use 0x0011, 0x0012, 0x0013, etc.

PyQt - How to connect multiple signals to the same widget

[ ]All1 [ ]All2
[ ]checkbox1A [ ]checkbox1B
[ ]checkbox2A [ ]checkbox2B
Based on the chart above, a few things need to happen:
The All checkboxes only affect the on/off of the column it resides in, and checks on/off all the checkboxes in that column.
All checkboxes work in pairs, so if checkbox1A is on/off, checkbox1B needs to be on/off
If an All checkbox is checked on, and then the user proceeds to check off one or more checkbox in the column, the All checkbox should be unchecked, but all the checkboxes that are already checked should remain checked.
So really this is more like a chain reaction setup. If checkbox All1 is on, then chieckbox1A and 2A will be on, and because they are on, checkbox1B and 2B are also on, but checkbox All2 remains off. I tried hooking up the signals based on this logic, but only the paired logic works 100%. The All checkbox logic only works 50% of the time, and not accurately, and there's no way for me to turn off the All checkbox without turning all already checked checkboxes off.
Really really need help ... T-T
Sample code:
cbPairKeys = cbPairs.keys()
for key in cbPairKeys:
cbOne = cbPairs[key][0][0]
cbTwo = cbPairs[key][1][0]
cbOne.stateChanged.connect(self.syncCB)
cbTwo.stateChanged.connect(self.syncCB)
def syncCB(self):
pairKeys = cbPairs.keys()
for keys in pairKeys:
cbOne = cbPairs[keys][0][0]
cbOneAllCB = cbPairs[keys][0][4]
cbTwo = cbPairs[keys][1][0]
cbTwoAllCB = cbPairs[keys][1][4]
if self.sender() == cbOne:
if cbOne.isChecked() or cbTwoAllCB.isChecked():
cbTwo.setChecked(True)
else:
cbTwo.setChecked(False)
else:
if cbTwo.isChecked() or cbOneAllCB.isChecked():
cbOne.setChecked(True)
else:
cbOne.setChecked(False)
EDIT
Thanks to user Avaris's help and patience, I was able to reduce the code down to something much cleaner and works 100% of the time on the 1st and 2nd desired behavior:
#Connect checkbox pairs
cbPairKeys = cbPairs.keys()
for key in cbPairKeys:
cbOne = cbPairs[key][0][0]
cbTwo = cbPairs[key][1][0]
cbOne.toggled.connect(cbTwo.setChecked)
cbTwo.toggled.connect(cbOne.setChecked)
#Connect allCB and allRO signals
cbsKeys = allCBList.keys()
for keys in cbsKeys:
for checkbox in allCBList[keys]:
keys.toggled.connect(checkbox.setChecked)
Only need help on turning off the All checkbox when the user selectively turns off the modular checkboxes now
If I'm understanding your data structure, I have a solution. Correct me if I'm wrong: allCBList is a dict (confusing name! :) ). Its keys are the all* checkboxes. And a value allCBList[key] is a list with checkboxes associated with that all checkbox. For your example structure it'll be something like this:
{ All1 : [checkbox1A, checkbox1B],
All2 : [checkbox2A, checkbox2B]}
Then what you need to is this: when a checkbox is toggled and it is in checked state, then you need to check the All* checkbox if all the other checkboxes are in checked state. Otherwise it will be unchecked.
for key, checkboxes in allCBList.iteritems():
for checkbox in checkboxes:
checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))
I guess, this statement requires a bit of explanation:
lambda checked, checkboxes=checkboxes, key=key:
lambda creates the callable that is connected to the signal. toggled passes checkbox status, and it will be passed to checked variable. checkboxes=checkboxes and key=key parts pass the current values to checkboxes and key parameters of the lambda. (You need this because of the closure in lambdas)
Next comes:
key.setChecked(...)
We are setting the checked state of key which is the appropriate All* checkbox. And inside this:
checked and all(checkbox.isChecked() for checkbox in checkboxes)
all is True if everything inside is True, where we check every checkboxs state. And this will return True if all are checked (i.e. isChecked() returns True).
checked and ... part is there to short-circuit the all. If the current checkbox turns unchecked, then we don't need to check others. All* would be unchecked.
(PS: By the way, you don't need to get .keys() of a dict to iterate over keys. You can just iterate over the dict and it will iterate over its keys.)
Edit: Just to avoid chain reaction with All* checkboxes toggled by clicking any sub-checkboxes, it's necessary to change the signal for All* checkboxes to clicked, instead of toggled. So, the All* check boxes will affect other below them only in the case of user interaction.
In the end, your modified code will be:
# Connect checkbox pairs
# you just use the values
# change 'itervalues' to 'values' if you are on Python 3.x
for cbPair in cbPairs.itervalues():
cbOne = cbPair[0][0]
cbTwo = cbPair[1][0]
cbOne.toggled.connect(cbTwo.setChecked)
cbTwo.toggled.connect(cbOne.setChecked)
# Connect allCB and allRO signals
# change 'iteritems' to 'items' if you are on Python 3.x
for key, checkboxes in allCBList.iteritems():
for checkbox in checkboxes:
key.clicked.connect(checkbox.setChecked)
checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))
Your problem is that your checkboxes are connecting the toggled signal and toggling their state in your connected slots so the signal is emitted again (so the slots are executed again...) and you get unpredictable results. Obviously that is not your wanted behavior. You can fix it in several ways:
by disconnecting the signals at the beginning of the slots and connecting them again at the end
by using some clever code that controls the re-emission of signals (I think this is what Avari's code does in a very compact way, but I'm not completely sure)
by using a clicked signal because it is not re-emitted when the checkbox state changes
Which approach you follow is up to you. The following code uses the third approach:
self.cbPair = {}
self.cbPair['0'] = (QtGui.QCheckBox('all1', parent),
QtGui.QCheckBox('all2', parent))
self.cbPair['1'] = (QtGui.QCheckBox('1a', parent),
QtGui.QCheckBox('1b', parent))
self.cbPair['2'] = (QtGui.QCheckBox('2a', parent),
QtGui.QCheckBox('2b', parent))
for v in self.cbPair.values():
for cb in v:
cb.clicked.connect(self.updateCB)
def updateCB(self):
cb = self.sender()
is_checked = cb.isChecked()
id = str(cb.text())
try:
# Update a whole column
column = int(id[-1]) - 1
rows = ('1', '2')
except ValueError:
# Update a row and the headers row
rows = (id[0], )
column = {'a': 1, 'b': 0}.get(id[-1])
if not is_checked:
for c in (0, 1):
self.cbPair['0'][c].setChecked(is_checked)
for r in rows:
self.cbPair[r][column].setChecked(is_checked)
Note that I'm using the checkboxes text as a UID from wich row and colum values are calculated. If you want to use different text labels for your checkboxes you may need to set the UIDs as attributes to every checkbox.

How to add event listener to dynamic QTableWidgetItem in PyQt4?

I am a Python and Qt newbie...
I am working on a GUI program that shows a list of data using QTableWidget at the beginning.
The rows are inserted into the table using the setItem() method. They are QTableWidgetItem objects.
Now I want to allow users to click-select a certain row (the QTableWidgetItem), and my program will populate a secondary QTableWidget.
I have learnt that there is a thing called Signal and Slot. Am I going to use that?
There are also examples of using installEventFilter(), but it is not appropiate for the QTableWidgetItem.
The easiest way for this would just be to use the itemClicked-signal of the QTableWidget:
def handle_item_clicked(tableItem):
#the callback, called when an item is clicked, with the item as an argument
#populate the secondary table in some way
#register the callback with the QTableWidget
yourTableWidget.itemClicked.connect(handle_item_clicked)
You could also use the currentItemChanged signal which gives you the current and previous selection (e.g. so you can clear or hide your secondary widget if the user deselects an item, in which case the current item will be None).
Qt doesn't have "event listeners". As you mentioned, you do such things by connecting signals to slots.
For example:
QObject.connect(myTableWidget, QtCore.SIGNAL("clicked()"), self.foobar)
This will cause the method self.foobar() to be called when you click on myTableWidget. The foobar function could retrieve which item is selected and populate the second widget based on that.
Take note that in the last argument of connect, you don't use the parentheses (foobar()), just the method name!

Categories

Resources