I am working on a multi-tab dashboard with DASH and I have some callback problems.
1. Problem: Button enabled / disbaled in connection with a checkbox:
On my first tab there is a button that leads to the next tab and that should only be enabled if a checkbox is clicked. This is working. But if I unclick the checkbox, the button is still enabled even tho it should be disabled then again. Here my code:
#callback(
Output(component_id='start-button',component_property='disabled'),
Input(component_id='checklist-
conditions',component_property='value'),prevent_initial_call=True
)
def enable_start_button(conditions_read):
if conditions_read == "I herby comfirm that I read...":
disabled = False
if conditions_read != "I herby comfirm that I read...":
disabled = False
2. Problem: Button enabled if more pre-conditions are set:
A button should be enabled only if 4 conditions are fulfilled. However, the button is already enabled if only one of the 4 conditions is fulfilled. Here the code:
#callback(
Output(component_id='continue1-button',component_property='disabled'),
Input(component_id='contact-first-name',component_property='value'),
Input(component_id='contact-last-name',component_property='value'),
Input(component_id='contact-mail',component_property='value'),
Input(component_id='contact-company',component_property='value'),
prevent_initial_call=True
)
def
enable_start_button(first_name_checked,last_name_checked,mail_checked,company_checked):
if first_name_checked != None and last_name_checked != None and mail_checked !=
None and company_checked!= None:
disabled = False
3. Problem: 2 inputs should be enabled (here dropdowns) if certain conditions are fulfilled.
However, only one of the 2 inputs gets enabled, the other stays grey. For example, it should be possible to choose a new wind turbine type and rating if before the user selects in another dropdown as a turbine type = new. Here the code:
#callback(
Output(component_id='new-turbine',component_property='disabled'),
Output(component_id='new-turbine-rating',component_property='disabled'),
Input(component_id='turbine-type',component_property='value'),
prevent_inital_callback = True
)
def enable_new_turbine(new_turbine):
if new_turbine == "New":
disabled1 = False
disabled2 = False
I am already searching for a longer time for a solution since it seems not to be such a difficult problem. But I still have not found anything and I would be glad for any help!
Thanks a lot and best regards,
Lexy.
Edit:
The missing returns appear to be the problem.
Lets talk this through on the second Problem. By setting Output(component_id='continue1-button',component_property='disabled') you indicate that the Output of that callback will set the component_property of the button with the id 'continue1-button'. This is the value you need to return from the function. Has the condition occured that the button should not be disabled anymore end the function call with return False. Should the button still be disabled return True.
As for Problem 3 you would need to return 2 values because you have two outputs, for example return False, False if both dropdowns should be activated.
first of all I cant test the things I'm saying right now and I'm no expert either, but maybe my two cents will help you :)
Are you actually returning the values? I guess you do, but just to be certain.
Problem: I dont know if it fixes the problem, but your if's do the same thing and are always right. Either conditions_read is the string or not.
Problem: I GUESS that there is no else statement, therefore NONE is returned and NONE resolves to False, therefore it seems as if the if statement was true.
Problem: I'm not sure. You talk about 3 dropdowns, but in your code are only two. Maybe you are not returning the disabled1 and disabled2 and therefore again only NONE is returned and only one gets activated
Related
I have an algorithm in a game that simplified looks like this:
if click:
if a and not b and not c:
b = True
c = True
follow_mouse_pos()
if b and c:
b = False
c = False
stay_on_slot()
This is for an inventory system, and the first if statement picks up an item from the clicked slot, and the other one puts it on the clicked slot.
The problem I have is that I want to have the same mouse button for both actions, but if I do that, the item just flickers between the slot and the mouse position.
a, b and c are conditions I have set up to decide wether I pick up the item or I put it in a slot.
a is irrelevant here because it is just for visual stuff.
b is True if the mouse is holding an item
c is True if the clicked slot is empty
The problem is that meeting one of the statements requirements will lead to meeting the conditions of the other statement, and it will loop as fast as the main loop goes. This is a problem because then I have to time it every time I want to move something in the inventory, and that is not good.
I have tried if elif statements but since this is in a loop, the only difference is that on each loop only one of them will be checked, but one of them will always be met.
Is there any way I can avoid this problem?
The full project is on github
The file that contains this part of the code is here:
VoidShips/scripts/GUI/gui.py at line 203
One possibility is that instead of one event "click", you have one like "mouse button up" and another "mouse button down".
Like this you could maybe build something like a drag and drop if that is what you want.
just use if...elif, so if the 'if' condition checks it will not continue checking 'elif'.
I have an app which asks a series of 8 math questions to the user. The user has a keypad with which he can manipulate a label. This label answer_text holds the answer, the user wants to submit. Since I had issues, there are two "mechanisms" which allow the checking of this answer (if it is true, the next qestion should pop up, if it is false, nothing should happen). Consider the following code, where I suppress everything unrelated to this problem.
from functools import partial
class KeyPad():
on_key_pad_pressed(self, btn, text):
cs = App.get_running_app().root.ids.calculation_screen
if text.isdigit():
answer_text.text += text
cs.current_answer = answer_text.text
elif text == 'clear':
answer_text.text = ''
elif text == 'submit':
cs.on_current_answer(self, btn, text)
elif text == 'joker'
Clock.schedule_once(partial(cs.on_current_answer(btn, MathBackEnd().get_true_answer()), 1)
class CalculationScreen(Screen):
current_answer = StringProperty('')
def __init__(self):
pass
def on_current_answer(self, btn, text):
true_answer = MathBackEnd().get_true_answer()
if text == true_answer:
Logger.info('CS: Cool! You got it')
else:
Logger.info('CS: Please try again.')
So there are at least 3 different ways in Kivy to achieve the wiring between the KeyPad (holds the information about the given answer) and the CalculationScreen (assess given answers). Note that the approach in the first if-case (cs.current_answer = answer_text.text) does work, because current_answer is a StringProperty. Therefore, when it changes, its callback on_current_answer will get called.
The submit button and the joker button both work fine. If you click 'submit' your answer gets checked. If you click 'joker' the correct answer gets checked with a one second penalty for pressing 'joker'. However answers should get checked automatically (first if-case in "on_key_pad_pressed") and this works except for one case: if the current true answer is the same as the former true answer. Suppose the first question is "3+4", you enter 7 and the game automatically checks your answer. It is true, so you get the next question "1+6". You enter 7... nothing happens. You click 'clear' to clear the answer_text label and press 7 again... nothing happens. You click 'submit'. The answer gets checked and you get the next question.
So, which of these 3 approaches is superior/inferior? Should I always use schedule_once (seems to be very robust)? Do you see why I get this buggy behavior in the first if case, if two successive (true) answers are equal? During calculation, the eventloop gets congested by a timer (timing the response of the user), which updates every tenth of a second. May this congestion lead to this weird behavior?
Any help is much appreciated!
I have a TextCtrl that has an EVT_KILL_FOCUS event that I use to validate the contents of the field, alerting the user when the value is wrong. After opening the MessageBox, I clear the field and i set the focus to the field that I left validate again. Problem is that the text blinking cursor that should appear inside the field disappears and I do not know why or how to fix it. This behavior causes the user to not know in which field the focus is positioned.
Does anyone have any ideas?
...
self.txtCode = wx.TextCtrl(self, value='')
self.txtCode.Bind(wx.EVT_KILL_FOCUS, self.__onTxtCodeKillFocus)
self.txtCode.Bind(wx.EVT_CHAR_HOOK, self.__onTxtCodeTabKey)
def __validateTxtCodeContent(self):
if self.txtCode.GetValue() == "":
self.MessageBox(self, "Error Text", _("Warning"))
return False
return True
def __onTxtCodeKillFocus(self, event):
event.Skip()
if self.__validateTxtCodeContent() == False:
self.txtCode.SetValue("")
self.txtCode.SetFocus()
def __onTxtCodeTabKey(self, event):
key = event.GetKeyCode()
shift = event.ShiftDown()
# 9 = TAB, 13 = ENTER
if key != 9 and key != 13:
event.Skip()
return
elif key == 9:
if self.__validateTxtCodeContent():
if shift:
self.btnSave.SetFocus()
else:
self.txtDescription.SetFocus()
else:
self.txtCode.SetValue("")
self.txtCode.SetFocus()
else:
return False
My validation is not only for empty field, but for example only empty field can be.
Important: In the EVT_CHAR_HOOK event this behavior does occurs too.
I tried to use this too:
self.txtCode.SetValue("")
self.txtCode.SetFocus()
self.txtCode.SetInsertionPointEnd()
self.txtCode.Refresh()
But it did not work well.
You can't call SetFocus() from KILL_FOCUS handler. The most direct workaround is to use CallAfter() to call it slightly later, but even though this would "work", it's a very bad idea because you should not prevent the user from leaving the window -- and there is no way to completely prevent it from happening anyhow.
Just mark the code as being invalid (e.g. change its background), but don't try to retain focus when you're losing it.
P.S. Calling MessageBox() from a focus event handler may be a bad idea too in many circumstances, it's better to use wxLogWarning() or CallAfter() to delay showing the message box until the next event loop iteration.
In addition to what Vadim already said:
You can do validation on the hitting "OK" button or when the dialog/panel is about to close
You can use wxValidator to validate the user input depending on your task at hand.
Today I progressed further into this Python roguelike tutorial, and got to the inventory. As of now, I can pick up items and use them. The only problem is, when accessing the inventory, it's only visible for a split second, even though I used the console_wait_for_keypress(True) function. I'm not sure as to why it disappears. Here's the code that displays a menu(in this case, the inventory):
def menu(header,options,width):
if len(options)>26: raise ValueError('Cannot have a menu with more than 26 options.')
header_height=libtcod.console_get_height_rect(con,0,0,width,SCREEN_HEIGHT,header)
height=len(options)+header_height
window=libtcod.console_new(width,height)
libtcod.console_set_default_foreground(window,libtcod.white)
libtcod.console_print_rect_ex(window,0,0,width,height,libtcod.BKGND_NONE,libtcod.LEFT,header)
y=header_height
letter_index=ord('a')
for option_text in options:
text='('+chr(letter_index)+')'+option_text
libtcod.console_print_ex(window,0,y,libtcod.BKGND_NONE,libtcod.LEFT,text)
y+=1
letter_index+=1
x=SCREEN_WIDTH/2-width/2
y=SCREEN_HEIGHT/2-height/2
libtcod.console_blit(window,0,0,width,height,0,x,y,1.0,0.7)
libtcod.console_flush()
key=libtcod.console_wait_for_keypress(True)
index=key.c-ord('a')
if index>=0 and index<len(options): return index
return None
I'd appreciate anyone's help or input to this problem.
It may be related to an old version of the library that has an event when you press a key, and another event when you release it. So this could cause it to appear and disappear when you release the key.
So try to see if the screen stays on if you keep the key pressed.
wait_for_keypress does indeed trigger on both press and release events. To fix this, replace wait_for_keypress with the sys_wait_for_event, specifying to trigger only on press events.
Documentation
[ ]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.