Python Canvas Click Event - Multiple Choice Answers - python

I apologize in advance as I've read through a few threads with almost an identical question, but I've yet to definitively find an answer to the difficulty I'm currently experiencing. I'm basically trying to set up a GUI that isn't so... like a GUI - plain and gray. Therefore, I'm using the Canvas widget, creating a background image for a little color and life, and displaying a question with four multiple choice answers.
There's no 'ENTER' button or Entry field, so I'm relying on click events to determine the selected answer. I understand I need to give the created text a tag, then bind to initiate a callback for the event. I used *.find_closest(event.x, event.y) to get the widget's tag, but it's not returning the tag I've assigned it...
In reference to the threads I've already read through, one has a parameter that apparently is not valid in Python 3.10 - I can use a tags parameter, but another post shows tag as the parameter which does not work. The end result I seem to get is a tuple with a three digit number i.e. (297,) and this is from printing the variable I assigned to the .find_closest(event.x, event.y) function.
At first I thought ... okay, if it won't acknowledge the tag I tried to assign, the numbers were sequential for a, b, c, and d which would be good enough! But no... the numbers are not necessarily the same each time I run the program.
I'm not too keen on posting my sloppy code on here, but I understand my rambling on isn't explaining the issue as well as a simple visual component would. So, to go ahead and provide that visual aid:
def update_screen(self, question, choices, answer_index):
self.correct_answer = answer_index
self.display_screen.delete('all')
self.display_screen.create_image((400, 225), image=self.background)
self.display_screen.create_text(self.QUIZ_TEXT, text=question,
fill='#EE82EE', font=self.gui_font)
a = self.display_screen.create_text(self.OPT_A_TEXT, tags='A', text=choices[0],
fill='#DA70D6', font=self.gui_font)
b = self.display_screen.create_text(self.OPT_B_TEXT, tags='B', text=choices[1],
fill='#DA70D6', font=self.gui_font)
c = self.display_screen.create_text(self.OPT_C_TEXT, tags='C', text=choices[2],
fill='#DA70D6', font=self.gui_font)
d = self.display_screen.create_text(self.OPT_D_TEXT, tags='D', text=choices[3],
fill='#DA70D6', font=self.gui_font)
self.display_screen.update()
self.display_screen.tag_bind(a, '<Button-1>', self.check_answer)
self.display_screen.tag_bind(b, '<Button-1>', self.check_answer)
self.display_screen.tag_bind(c, '<Button-1>', self.check_answer)
self.display_screen.tag_bind(d, '<Button-1>', self.check_answer)
return
Obviously, the function is just one part of the root Tk class I created, so if the full code is needed or more information is required, I apologize, and please just inform me.
To reiterate the question, I'm attempting to retrieve the selected answer from the multiple choice options displayed through the Canvas as .create_text() ... I've tried callbacks for the click events using tag bindings with both regular strings and the .create_text() assigned variable - both are represented in the code and the aforementioned threads have demonstrated both.
I only used both in the code I posted to mention that I've tried both ways ... exclusively... I still can't seem to get a valid ID or tag returned. I realize each choice has its own callback bound to the click event, so I suppose I could just use the x, y coordinates alone...
Should this be the method I use?

Related

How to specify input language for Entry or any other input field?

In short, i try to type letters (in input components like "Entry", "Text") that are allowed by Windows language-keyboard (i'm using "Latvan(QWERTY)" keyboard) and i can't write long letters like 'ā', 'č', 'ģ' and others.
For example, when i try to write 'ā', the result is 'â'.
The interesting part - when i focus on specific GUI input fiend and change Windows keyboard-language (with "Alt+Shift" shortcut or manually) twice (for example, from "Latvan(QWERTY)" to "Russian" and back to "Latvan(QWERTY)") - then i can write all letters i needed.
What i want is to set all input fields keyboard-language so i could write all letters i want without doing things mentioned above every time i launch my GUI program.
If you need more info or there is already place where this question is answered, please leave a comment and i will act accordingly.
Edit 1:
I am using PyCharm to write my Python Tkinter code. I tried to assign necessary keyboard to my program's generated GUI form according to this guide but it didn't work (i guess that because i used it on temporary created GUI forms).
I was able to solve my problem by using pynput.
Here is simplified version of my final code:
from tkinter import *
from pynput.keyboard import Key, Controller
def change_keyboard_lang(event):
keyboard = Controller()
for i in range(2):
keyboard.press(Key.alt)
keyboard.press(Key.shift_l)
keyboard.release(Key.shift_l)
keyboard.release(Key.alt)
root = Tk()
word_input = Entry(root)
word_input.focus_set()
word_input.bind("<FocusIn>", change_keyboard_lang)
word_input.pack()
root.mainloop()
In short, if cursor is focused on Entry field "word_input", system calls function "change_keyboard_lang" that changes input language from original to other and back to original - and now i can write necessary letters.
It's not the best solution since i need to bind event to every input field in my GUI form but it gets the job done. If you have a better solution please post it here.

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 connect() call affecting multiple widgets

Making PushButton widgets for a program. The intent was to create each PushButton, connect it to a function which compared two string values, ap.parse_answer(), then add the PushButton to the appropriate cell of a QGridLayout:
answers = ["this", "that", "the other", "one more"]
correct_answer = "this"
for grid_pos in [(i,j) for i in range(0,2) for j in range(0,2)]:
answer_disp = AnswerDisplay()
current_answer = answers.pop()
answer_disp.setText(current_answer)
answer_disp.clicked.connect(
lambda: self.ap.parse_answer(current_answer, answer))
answer_grid.addWidget(answer_disp, *grid_pos)
Here is the AnswerDisplay class:
class AnswerDisplay(QtGui.QPushButton):
def __init__(self):
super(AnswerDisplay, self).__init__()
answer_font = QtGui.QFont()
answer_font.setWeight(24)
answer_font.setPixelSize(20)
self.setFont(answer_font)
Unfortunately, what happens is the same function gets connected to each button. The last function generated end up on all the buttons, so it seems the connect is being reapplied to previous created buttons. But how do I address this? My approach can't be completely invalid because the setText() function correctly sets the text for each button without overwriting the previous assignments.
I tried to address the issue making an single AnswerDisplay and then copying it with deepcopy():
for grid_pos in [(i,j) for i in range(0,2) for j in range(0,2)]:
disp = AnswerDisplay()
answer_disp = deepcopy(disp)
super(AnswerDisplay, answer_disp).__init__()
...
but it produced the same undesired result.
I've done some searching, but all I've found are questions from people trying to get the kind of result I'm trying not to get. Any help would be appreciated.
Your issue is that you're not capturing the values in the lambda function. Because of the way Python's scoping rules work, you're using the same value (the last one) each time.
Change the lambda line to this to capture the variables you want:
answer_disp.clicked.connect(
lambda ca=current_answer, a=answer: self.ap.parse_answer(ca, a))
There are other related questions/answers that may give you more of an explanation about this (like here)

Creating A UI Window For My Previous Script

folks! So, thanks to you guys I was able to figure out what it was I was doing wrong in my previous script of staggering animation for selected objects in a scene. I am now on part two of this little exercise: Creating a UI for it.
This involves creating a window with a button and user input of how much the animation will be staggered by. So, instead of me putting how much the stagger should increment by (which was two in my previous script), I'd now allow the user to decide.
The script I have so far created the window, button, and input correctly, though I am having some trouble with getting the UI to properly execute, meaning when I click on the button, no error pops up; in fact, nothing happens at all to change the scene. I get the feeling it's due to my not having my increment variable in the correct spot, or not utilizing it the right way, but I'm not sure where/how exactly to address it. Any help would be greatly appreciated.
The code I have (with suggested edits) is as follows:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
print "BLAH"
Moving the comments into an answer because I think I've got it all figured out finally:
First of all, the better practice is to pass the stagger object to the button command rather than the string. so that would be:
cmds.button(label="My Life For Aiur!", command=stagger)
Secondly, the count isn't getting updated, so it stays 0 as per your third line. To update that:
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
But wait, where did fieldgroup come from? We need to pass it into the function. So go back to your button code and take out the command entirely, also saving the object to a variable:
button = cmds.button(label="My Life For Aiur!")
Now store the object for the fieldgroup when you make it:
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
Now that you have fieldgroup, you can pass that in the function for the button, like this:
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
I had to wrap the function in a lambda because we're passing fieldgroup, but if I just put stagger(fieldgroup) it would call that and pass the result of that into the command for the button
Also update stagger def with fieldgroup argument:
def stagger(fieldgroup):
One final note that won't actually affect this, but good to know:
when you shift the keyframes inside stagger you're using a DIFFERENT count variable than the one you declared as 0 up above. The outer one is global, and the inner is local scope. Generally it's best to avoid global in the first place, which fortunately for you means just taking out count = 0
Putting that all together:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
count = 0
increment = cmds.floatFieldGrp(fieldgroup, query=True, value=True)[0]
print count
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
count += increment
print "BLAH"

How to get a value from QSpinBox, how to disable NextButton in QWizard

Python 3.3, PyQt 4.8.4. My knowledge of PyQt and Python (and English, I think;)) isn't good, so there are many questions in my studies. I made a draft in Qt Designer, still I can't understand how to make it work properly. I have wizard, on the first page - 2 QSpinBoxes, second - generated quantity of spinboxes, equals to the entered values in boxes on the first page. Tried to search about signals and slots, but still can't understand.
How to get this value inside my program, to use it for generation of spinboxes on the 2nd page?
Of course, I need to keep other pages out of reach while this first value isn't correct. I think that can be realized by keeping NextButton unavailable, but how - I don't know. Need kind of help!)
My code itself
http://pastebin.com/UxvzFvJR
Launcher was made separately http://pastebin.com/2sYtyg9z
1: Move your spinboxes generate code(line list_x = [] to f += 1) to page2's initializePage. Dirty but works:
class Ui_Wizard(object):
def addSpins(self):
list_x = []
...
...
self.wizardPage2.initializePage = self.addSpins
2: Register them as mandatory field:
self.wizardPage1.registerField("cols*", self.spinBox_col)

Categories

Resources