Getting index of kivy spinner selection - spinner

Usually the spinner widget returns the selected text. In my app i plan to use some non standard letters (like: ü,ö,è) as values to populate the spinner.
The below example works on my windows machine but I hope to avoid handling any Non-ASCII characters to prevent problems on other OS's.
Is there a way to access the index of the spinner selection directly without using the list.index(text) method?
# This Python file uses the following encoding: utf-8
from kivy.base import runTouchApp
from kivy.uix.spinner import Spinner
spinnervalues = ['one_ö','two_ü','three_ä']
spinner = Spinner(
# default value shown
text='Home',
# available values
values=spinnervalues,
# just for positioning in our example
size_hint=(None, None),
size=(100, 44),
pos_hint={'center_x': .5, 'center_y': .5})
def show_selected_value(spinner, text):
print('The spinner', spinner, 'have text', text)
list_index = spinnervalues.index(text)
print('This list item index:', list_index)
spinner.bind(text=show_selected_value)
runTouchApp(spinner)
I have tried something like:
spinner.bind(index=show_selected_value)
but without success.

I know this original question is very old now, but for future reference you could try:
list_index = spinner.values.index(text)
print('This list item index:', list_index)
which prints:
'This list item index:', 2
assuming you picked the 3rd item in the list of spinner.values (zero based index).
I use a similar construct in my code because the text (spinnervalues) may change based on the language in use.
And this ties the value back to current spinner object, incase you're using multiple spinners in your application.
hope that helps someone.

Related

Connecting comboboxes created in a loop in PyQt5

I'm trying to build a GUI that is generated dynamically based on some input dictionary. I'm using the GridLayout, iterate over the dictionary keys and generate per iteration/grid row the key of the dictionary as a QLineEdit (to give some background color as a visual cue) and two comboboxes next to it. Ideally, the second combobox would change its items based on what is selected in the first combobox. Here's the relevant part of my code:
from PyQt5.QtCore import Qt
from PyQt5.Widgets import QApplication, QMainWindow, QVBoxLayout, QGridLayout, QLineEdit, QComboBox
class GUI(QMainWindow):
"""App View"""
def __init__(self, data):
super().__init__()
self.data = data
self.generalLayout = QVBoxLayout()
self._displayview()
def _displayview(self):
self.layout = QGridLayout()
subdict = self.data
combolist = ['Auto', 'Select value', 'Calculate']
maxwidth = sum([len(key) for key in subdict.keys()])
self.count = 0
for item in subdict.keys():
color = subdict[item]['Color']
# Display tags and bg-color
display = QLineEdit()
display.setMaximumWidth(maxwidth-maxwidth//4)
display.setStyleSheet("QLineEdit"
"{"
f"background : {color}"
"}")
display.setText(item)
display.setAlignment(Qt.AlignLeft)
display.setReadOnly(True)
# Combobox action
self.cb_action = QComboBox()
if color == 'Lightgreen':
self.cb_action.addItem('Auto')
self.cb_action.setEnabled(False)
else:
self.cb_action.addItems(combolist)
# Combobox value
self.cb_value = QComboBox()
self.cb_value.addItem('Text')
self.cb_action.currentIndexChanged.connect(self.react)
self.layout.addWidget(display, self.count, 0)
self.layout.addWidget(self.cb_action, self.count, 1)
self.layout.addWidget(self.cb_value, self.count, 2)
self.count += 1
self.generalLayout.addLayout(self.layout)
def react(self):
... # my humble approaches here
Basically, the first combobox has the three options: Auto, Select value and Calculate and based on that selection, the combobox next to it should present different options (right now, it only has 'Text' for testing purposes).
I tried different approaches in the self.react(), e.g. a simple self.cb_value.addItem('something'), which would however add the item in the last combobox (which makes sense). I also tried to simply build a new combobox with self.layout.addWidget(), however without an index, that won't work. Lastly, I tried to simply create that second column of comboboxes anew in another iteration using self.cb_action.currentText() as help, however, that again returns only the text from the last combobox.
I understand that due to the nature of creating everything while iterating I get these problems. It's not unlikely that I haven't fully understood the concept of widgets. Would anybody be so kind to point me in the right direction how I would do this with variable input data? I'd probably face the same issue when I tried to extract all these information from the comboboxes to get some output I can work with.
Thank you and have a good day.

Is there a way I can make buttons in Tkinter with a for loop, while also giving each one a different command?

I'm making a revision system for school, and I want it to be able to use a modular amount of subjects just in case a subject is added to the system, so I need a way to make a number of buttons with different subject names, and be able to differentiate between those buttons using tkinter. So for example, if they were to click the Mathematics button, it would take them to another bit of code specially suited for mathematics(Although, it can't be solely for Mathematics, since then I would need definitions for subjects that haven't even been added yet)
First I simply tried setting the command to "print(subjectnames[subcount-1])", thinking it would print the name of the button, but that just printed both names out straight away without even pressing a button. Then I tried changing the variable name subject to the name of the button, which I didn't expect to work, I was just stumped and desperate
Here I started setting up the definition
def chooseQuiz():
clearWindow()
subjectnames=[]
button=[]
This was probably unimportant, just labels for the title and spacing
Label(mainWindow, text="Which quizzes would you like to take?", bg='purple3', font=('constantia',25,"bold")).grid(row=0, column=0, padx=100, pady=0)
Label(mainWindow, bg='purple3').grid(row=1, column=0, padx=0, pady=15)
Here I extract data from an SQL table to get all subject names from all topics, again probably unimportant but here is where most of the variables are made
c.execute("SELECT Subject_name FROM topics")
for row in c.fetchall():
if row[0] in subjectnames:
pass
elif row[0] not in subjectnames:
subjectnames.append(row[0])
else:
messagebox.showerror("Error", "subjectnames are not appending")
chooseQuiz()
This is the main part of this question, where I tried to form a fluid number of buttons all with different commands, but to no avail
for subcount in range(len(subjectnames)):
button.append(Button(mainWindow, text=str(subjectnames[subcount-1]), bg='grey', fg='black', font=('cambria',15), width=25, command=(subject==subjectnames[subcount-1])))
button[-1].grid(row=subcount+2,column=0, padx=0, pady=15)
I expected the subject variable to be the same as the button I pressed, but it remained at 0(original value). I think this is due to wrong use of the command function in tkinter on my part. The buttons still showed up fine(only 2 subjects currently, Mathematics and Physics, and both showed up fine).
Yes, it is possible.
The following example creates a window with a reset button; upon clicking reset, a frame is populated with buttons corresponding to a random number of buttons chosen randomly from possible subjects. Each button has a command that calls a display function that redirects the call to the proper topic, which, in turn prints the name of its topic to the console, for simplicity of the example. You could easily create functions/classes corresponding to each topic, to encapsulate more sophisticated behavior.
Adding subjects, is as simple as adding a key-value pair in SUBJECTS
Pressing reset again, removes the current button and replaces them with a new set chosen randomly.
import random
import tkinter as tk
from _tkinter import TclError
SUBJECTS = {'Maths': lambda:print('Maths'),
'Physics': lambda:print('Physics'),
'Chemistry': lambda:print('Chemistry'),
'Biology': lambda:print('Biology'),
'Astronomy': lambda:print('Astronomy'),
'Petrology': lambda:print('Petrology'),}
topics = []
def topic_not_implemented():
print('this topic does not exist')
def get_topics():
"""randomly creates a list of topics for this example
"""
global topics
topics = []
for _ in range(random.randrange(1, len(SUBJECTS))):
topics.append(random.choice(list(SUBJECTS.keys())))
return topics
def reset_topics():
global topics_frame
try:
for widget in topics_frame.winfo_children():
widget.destroy()
topics_frame.forget()
topics_frame.destroy()
except UnboundLocalError:
print('error')
finally:
topics_frame = tk.Frame(root)
topics_frame.pack()
for topic in get_topics():
tk.Button(topics_frame, text=topic, command=lambda topic=topic: display(topic)).pack()
def display(topic):
"""redirects the call to the proper topic
"""
SUBJECTS.get(topic, topic_not_implemented)()
root = tk.Tk()
reset = tk.Button(root, text='reset', command=reset_topics)
reset.pack()
topics_frame = tk.Frame(root)
topics_frame.pack()
root.mainloop()

Ever expanding amount of Entries with TKinter

What I'm trying to do is to make a GUI where when you start typing in an entry-box another shows up just beneath the one you are typing in. Then when you start typing in the one that popped up, another pops up. Is this possible with TKinter and Python?
Edit:
So what I currently have is this:
entry1 = StringVar()
numberLabel3 = Label(window, text = "3. External meeting attendees")
r+=1
numberLabel3.grid(column = 0, row = r, sticky = W)
externalAtendeesLabel = Label(window, text = "input name of external meeting atendee: ")
r+=1
externalAtendeesLabel.grid(column = 1, row = r, sticky = E)
externalAtendeesEntry = Entry(window, textvariable = entry1)
externalAtendeesEntry.grid(column = 2, row = r)
#Note to self: Find a smart way of dynamically expanding this "list" of entries
(There is more code above and below this, but this is the relevant code for my question)
where r is a variable I made to make it easier to insert stuff into the middle of my rather long code.
The imports I'm using are:
from tkinter import *
from PIL import ImageTk
from PIL import Image
import os
I use the image modules and OS to insert an image further up in my GUI.
What I was thinking was to make a function that I could somehow setup to check the newest Entry-box, but I've run into the problem that for this to be potentially infinite I would have to dynamically create new variables, so that I can access the information that the user inputs. These variables would save the info just like my entry1 variable does it for the externalAtendeesEntry.
I would also have to dynamically make variables for more entries.
How do I dynamically create a potentially infinite amount of variables?
I know that this is kind of a re-post, but the other ones I've found all say that you should use dictionaries, but in that case it can't be infinite. It can only be finite to the point where my dictionary is no longer.
For one, you don't need to use StringVar. It only complicates your code without providing any real value. The other part of the answer is to store the entries in a list.
For example, create a function called addEntry that creates an entry and adds it to a list:
entries = []
...
def addEntry():
entry = tk.Entry(...)
entry.pack(...)
entries.append(entry)
To get the values at a later date, just iterate over the list:
for entry in entries:
print(entry.get())
With that, you can add entries whenever you want. You could, for example, bind to <Any-KeyRelease> to create a new entry as the user types (being sure to only do it if there isn't already a blank entry). Or, bind to <Return> or <FocusOut>, or on the click of a "new person" button, or however else you decide.

PyQt QScrollArea doesn't display widgets

I am somewhat new to GUI programming and very new to PyQt, and I'm trying to build a GUI that displays a list of questions. I have created a QuestionBank class that subclasses QWidget and overrides the .show() method to display the list properly. I have tested this alone and it works correctly. However, the list of questions can be quite long, so I've been trying to make it scrollable. Rather than add a QScrollBar to the widget and then set up the event triggers by hand, I've been trying to my QuestionBank widget in a QScrollArea based on the syntax I've seen in examples online. While the scroll area shows up fine, it does not at all display the question bank but rather just shows a blank outline.
The QuestionBank class looks like this:
class QuestionBank(QWidget):
BUFFER = 10 # space between questions (can be modified)
def __init__(self, parent, questions):
# `parent` should be the QWidget that contains the QuestionBank, or None if
# QuestionBank is top level
# `questions` should be a list of MasterQuestion objects (not widgets)
QWidget.__init__(self, parent)
self.questions = [MasterQuestionWidget(self, q) for q in questions]
self.bottomEdge = 0
def show(self, y=BUFFER):
QWidget.show(self)
for q in self.questions:
# coordinates for each each question
q.move(QuestionBank.BUFFER, y)
q.show()
# update y-coordinate so that questions don't overlap
y += q.frameGeometry().height() + QuestionBank.BUFFER
self.bottomEdge = y + 3 * QuestionBank.BUFFER
# ... other methods down here
My code for showing the scroll bar looks like this:
app = QApplication(sys.argv)
frame = QScrollArea()
qs = QuestionBank(None, QFileManager.importQuestions())
qs.resize(350, 700)
frame.setGeometry(0, 0, 350, 300)
frame.setWidget(qs)
frame.show()
sys.exit(app.exec_())
I have tried many variants of this, including calling resize on frame instead of qs, getting rid of setGeometry, and setting the parent of qs to frame instead of None and I have no idea where I'm going wrong.
If it helps, I'm using PyQt5
Here is the question bank without the scroll area, to see what it is supposed to look like:
Here is the output of the code above with the scroll area:
This variation on the code is the only one that produces any output whatsoever, the rest just have blank windows. I'm convinced its something simple I'm missing, as the frame is obviously resizing correctly and it obviously knows what widget to display but its not showing the whole thing.
Any help is much appreciated, thank you in advance.

tkinter GUI design: managing variables from multiple widgets/toolbars

{Edit: the answer by Bryan Oakley in the suggested duplicate question enter link description here a) fires a response on change to the array variable (arrayvar.trace mode="w"), and I need it triggered on FocusOut, as described in my original question; b) works for Python 2, but I'm having trouble converting it to work in Python 3.5. I'm currently using his and pyfunc's answers as leads and trying to figure out a similar solution using a FocusOut event.}
I am working on a tkinter GUI that lets a user select a particular type of calculation, using a pair of radio button lists. Based on the selections, a tool bar is populated with multiple modular entry widgets, one for each variable the calculation requires. The goal is to have the numerical entry values passed to the model, which will return data to be graphed on a canvas or matplotlib widget.
My question is: what typical strategy is used for gathering and continually refreshing values from multiple widgets, in order to update displays and to pass them on to the model? The trick here is that there will be a large number of possible calculation types, each with their own toolbar. I'd like the active toolbar to be "aware" of its contents, and ping the model on every change to a widget entry.
I think the widgets and the toolbar would have to be classes, where the toolbar can query each widget for a fresh copy of its entry values when a change is detected, and store them as some collection that is passed to the model. I'm not entirely sure how it can track changes to the widgets. Using a "validate='focusout' " validation on the entry widget (e.g. as in
this validation reference )
suggests itself, but I already use "validate='key' " to limit all entries to numbers. I don't want to use "validate=all" and piggyback onto it because I don't want to continually ask the model to do a lengthy calculation on every keypress.
I'm new to GUI programming, however, so I may be barking up the wrong tree. I'm sure there must be a standard design pattern to address this, but I haven't found it.
Below is a screenshot of a mockup to illustrate what I want the GUI to do. The Task radiobutton controls which secondary button menu appears below. The selection in the second menu populates the top toolbar with the necessary entry widgets.
The following code does (mostly) what I want. The ToolBar frame objects will store the values from its contained widgets, and call the appropriate model as needed. The VarBox objects are Entry widgets with extra functionality. Hitting Tab or Return refreshes the data stored in the ToolBar dictionary, tells the ToolBar to send data to the model, and shifts focus to the next VarBox widget.
from tkinter import *
# Actual model would be imported. "Dummy" model for testing below.
def dummy_model(dic):
"""
A "dummy" model for testing the ability for a toolbar to ping the model.
Argument:
-dic: a dictionary whose values are numbers.
Result:
-prints the sum of dic's values.
"""
total = 0
for value in dic.values():
total += value
print('The total of the entries is: ', total)
class ToolBar(Frame):
"""
A frame object that contains entry widgets, a dictionary of
their current contents, and a function to call the appropriate model.
"""
def __init__(self, parent=None, **options):
Frame.__init__(self, parent, **options)
self.vars = {}
def call_model(self):
print('Sending to dummy_model: ', self.vars)
dummy_model(self.vars)
class VarBox(Frame):
"""
A customized Frame containing a numerical entry box
Arguments:
-name: Name of the variable; appears above the entry box
-default: default value in entry
"""
def __init__(self, parent=None, name='', default=0.00, **options):
Frame.__init__(self, parent, relief=RIDGE, borderwidth=1, **options)
Label(self, text=name).pack(side=TOP)
self.widgetName = name # will be key in dictionary
# Entries will be limited to numerical
ent = Entry(self, validate='key') # check for number on keypress
ent.pack(side=TOP, fill=X)
self.value = StringVar()
ent.config(textvariable=self.value)
self.value.set(str(default))
ent.bind('<Return>', lambda event: self.to_dict(event))
ent.bind('<FocusOut>', lambda event: self.to_dict(event))
# check on each keypress if new result will be a number
ent['validatecommand'] = (self.register(self.is_number), '%P')
# sound 'bell' if bad keypress
ent['invalidcommand'] = 'bell'
#staticmethod
def is_number(entry):
"""
tests to see if entry is acceptable (either empty, or able to be
converted to a float.)
"""
if not entry:
return True # Empty string: OK if entire entry deleted
try:
float(entry)
return True
except ValueError:
return False
def to_dict(self, event):
"""
On event: Records widget's status to the container's dictionary of
values, fills the entry with 0.00 if it was empty, tells the container
to send data to the model, and shifts focus to the next entry box (after
Return or Tab).
"""
if not self.value.get(): # if entry left blank,
self.value.set(0.00) # fill it with zero
# Add the widget's status to the container's dictionary
self.master.vars[self.widgetName] = float(self.value.get())
self.master.call_model()
event.widget.tk_focusNext().focus()
root = Tk() # create app window
BarParentFrame = ToolBar(root) # holds individual toolbar frames
BarParentFrame.pack(side=TOP)
BarParentFrame.widgetName = 'BarParentFrame'
# Pad out rest of window for visual effect
SpaceFiller = Canvas(root, width=800, height=600, bg='beige')
SpaceFiller.pack(expand=YES, fill=BOTH)
Label(BarParentFrame, text='placeholder').pack(expand=NO, fill=X)
A = VarBox(BarParentFrame, name='A', default=5.00)
A.pack(side=LEFT)
B = VarBox(BarParentFrame, name='B', default=3.00)
B.pack(side=LEFT)
root.mainloop()

Resources