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!
Related
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
I am using python, Pyside2 on my interface.
I have a tableWidget and I want to get current text after press enter.
So I have below codes:
I add below function in to current page open:
self.ui.edit_table.keyPressEvent = self.KeyPressed
Then I add below function:
def KeyPressed(self, event):
if event.key() == QtCore.Qt.Key_Return:
print('Enter Key Pressed')
self.ui.edit_table.setEditTriggers(QtWidgets.QTableWidget.CurrentChanged)
print(self.ui.edit_table.item(0, 1).text())
newListValues = []
for i in range(0, 46):
newListValues.append(self.ui.edit_table.item(i, 1).text())
print(newListValues)
newListValues.clear()
When I change the value, and press the enter I see Enter Key Pressed but I couldnt see new value. But if I press the enter button one more without any changing, I can see new values in newListValue. Why the new value shows me with delay ?
For example item(0, 1) value is 5, when I change this value to 12 and press enter I couldnt see any changing in print(self.ui.edit_table.item(0, 1).text()) but when I press enter again I can see 12 value.
Why this happening ?
When pressing Return or Enter during the editing state, the data is not instantly submitted to the model.
The key press event is handled by the delegate editor (usually, the line edit) which will eventually validate or fixup the entered value and, finally, submit the data.
If you want to know when data has actually changed, connect to the itemChanged signal.
Also note that:
"patching" event handlers like this is discouraged, as often leads to silent errors or mishandled events (since the scope of the function is not the instance of the class that should handle the event); you either use an event filter, or a promoted widget;
you should always call the base implementation of the event handler, unless you're completely sure that you don't want to handle it; for instance, in your case your implementation completely prevents keyboard navigation or the possibility to enter the edit mode by pressing Return or F2;
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.
I am creating an experiment using Psychopy builder.
The participant is presented with an image containing numbers, e.g. 10 and 20.
They enter what they think is the mean of the numbers, 15 in this case, and then press the spacebar to move on to the next image.
I am trying to have it so there is a display/box on screen that shows them their entry, as with larger numbers in the hundreds of thousands and millions I think they might lose track of what they have pressed.
The ability to change their entry would be excellent also, but really I am most interested in them being able to see it on screen.
In builder I can't find a way to do this, and the ratings scale is not appropriate for huge numbers.
I found these solutions in code to do something that sounds like it:
http://www.psychopy.org/wiki/home.php/Snippets/LiveUpdatingText
However when I try to add them using the code insert function , or just adding them to the compiled script the screen locks up when I try to run the experiment. I am a novice at python, and am not sure where to start fixing this. Is what I'm trying to do possible?
I'm happy to provide some example code from the compiled builder experiment.
Thanks in advance!
Those code snippets are designed for Coder, where you control everything that is happening and when. The same thing can be done in Builder, but you will have to amend the code to fit in with Builder's event loop cycle. i.e. Builder does certain things at the start of an experiment, on every trial, on every screen refresh and so on. So you can't just insert this sort of code without modification, because, for example, it attempts to wait indefinitely for a keypress. Builder meanwhile, is checking the keyboard every screen refresh (typically at 60 Hz), so if you try to wait indefinitely for a keypress in code, you'll be halting Builder from doing everything else it needs to do.
In essence, you just need to break up the code into snippets that go in the appropriate tab in a Builder Code Component (for code to be executed at experiment start, on each frame, and so on), and avoid indefinite functions like event.waitKeys() in favour of instantaneous checking via event.getKeys()
e.g. to adapt the second example from Jonas Lindeløv, in the "Begin Routine" tab, put:
chars = list('0123456789.') # the valid characters
meanText = '' # start with an empty answer on each trial
In the "Each Frame" tab, put something like:
response = event.getKeys() # get a list of keys pressed at this instant
if len(response) > 0: # if there was one,
key = response[0] # just convenient shorthand
if key in chars:
meanText = meanText + response[0]
elif key == 'space':
meanText = meanText + ' '
elif key == 'backspace' and len(meanText) > 0:
meanText = meanText[:-1]
elif key == 'return':
thisExp.addData('Answer', meanText) # save the response
continueRoutine = False # finish this trial
# update the appropriate text stimulus with the current response value:
insertNameOfYourTextStimulusComponent.text = meanText
I have an application that, for a part of it, will take a user's input and format it into a standardized time format. To do this, i have a time input that has a focusout event tied to it that calls the time parser/replacer method. However, while testing it and just setting a message label to output some stuff, i noticed that it only triggers once...
Below is some sample code to show the problem.
from Tkinter import *
root=Tk()
message_var = StringVar()
message = Label(root, textvariable=message_var, height=2, width=35, bg="light grey")
time_var = StringVar()
time = Entry(root, textvariable=time_var, validate="focusout", validatecommand=time_update)
lose_focus_var = StringVar()
lose_focus_textbox = Entry(root, textvariable=lose_focus_var)
message_var.set("Enter a time below.")
lose_focus_var.set("Click here to lose focus.")
def time_update():
"""
Updates the time field to show standardized times.
"""
cur_entry = time_var.get()
if len(cur_entry) == 0:
message_var.set("0!")
elif len(cur_entry) == 1:
message_var.set("1!")
elif len(cur_entry) == 2:
message_var.set("2!")
elif len(cur_entry) == 3:
message_var.set("3!")
elif len(cur_entry) == 4:
message_var.set("4!")
elif len(cur_entry) == 5:
message_var.set("5!")
else:
message_var.set("TOO MANY!")
time_var.set("")
message.pack()
time.pack()
lose_focus_textbox.pack()
To reproduce my issue, run the code above. In the window that appears, click into the blank textbox, enter any number of characters, then click into the textbox that says "Click here to lose focus." You'll see that the message widget updates correctly! Yay!
However, if you click into the first textbox again, change the number of characters, then click the Lose Focus box again, the message will not update again. You will need to kill the window and re-run the code for the messages widget to update again.
If i add the time_update call to the other textbox (and refactor time_update to figure out which textbox called it), the message update will happen once for each text box. But only once.
Is there a way for me to re-trigger the <FocusOut> event, other than destroying the Entry widget and recreating it? Why doesn't it trigger every time?
One problem, as you mention in a comment to your own question, is that you're not returning True or False. The whole point of the validation is to let tkinter know if it should allow the edit or not, and it does this based on the return value.
Beyond that, however, the main problem is that you're changing the value of the widget within the validation function. When you do that, tkinter will automatically disable the validation. There are workarounds, but you really shouldn't be changing the value in the validation function. If I were a user, I would find this behavior to be extremely frustrating.
If you want to change the value when it loses focus, consider adding a binding to <FocusOut> rather than using the validation functions.