How to prevent a QDoubleSpinBox from changing values - python

I've written a GUI for a script that does some geometrical calculations. Certain ranges of values break the computation (e.g. find the intersection of two shapes that don't intersect.) I raise exceptions in those cases. I'd like to prevent the user from adjusting the spinbox value beyond the point where exceptions are raised.
I've tried overwriting the validator method for the QDoubleSpinBox. This works great when I manually enter values with the keyboard. But, it doesn't prevent me from clicking the up and down arrows.
How I can limit the ability of the user to run-up the values outside of the acceptable range?
Note: The actual some_complicated_function involves the values from 5 different spinboxes.
from PyQt4 import QtCore, QtGui
import sys
def some_complicated_function(val_a):
if val_a + 3 < 10:
return True
else:
raise Exception("Giant number!")
class SpinBoxSpecial(QtGui.QDoubleSpinBox):
def validate(self, value, pos):
# print float(value)
try:
some_complicated_function(float(value))
print "yup"
return QtGui.QValidator.Acceptable, QtGui.QValidator.Acceptable
except:
print "nope"
return QtGui.QValidator.Invalid, QtGui.QValidator.Invalid
a = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
w.resize(320, 100)
w.setWindowTitle("PyQT Python Widget!")
spinbox = SpinBoxSpecial(w)
spinbox.move(20, 20)
spinbox.CorrectionMode = QtGui.QAbstractSpinBox.CorrectToPreviousValue
w.show()
sys.exit(a.exec_())
Edit:
The basic ask is: I want to call a function when the value of a spinbox changes (via mouse or keyboard). If that function throws an exception, I want the value of the spinbox to revert to what it was.

Here is a simple way to dynamically set the range on a spinbox:
class SpinBoxSpecial(QtGui.QDoubleSpinBox):
def __init__(self, parent=None):
super(SpinBoxSpecial, self).__init__(parent)
self._last = self.value()
self.valueChanged.connect(self.handleValueChanged)
def handleValueChanged(self, value):
try:
some_complicated_function(float(value))
print "yup", value
self._last = value
except:
print "nope", value
if value > self._last:
self.setMaximum(self._last)
else:
self.setMinimum(self._last)
EDIT:
Just realized the above won't work correctly if a value is typed in directly, because it could fix the min/max too early. So maybe this would be better:
def handleValueChanged(self, value):
try:
some_complicated_function(float(value))
print "yup", value
self._last = value
except:
print "nope", value
self.setValue(self._last)

Related

Choosing from a List of methods in a tkinter Button

Good Day,
I'm new to this forum (and quite new to programming), so I hope my question is properly formulated.
I've been trying to create a GUI in python using tkinter, and I want to have two buttons calling methods of two different classes. One method is defining an integer, the second one is reporting content. I'd have a list of objects of the latter class, and I want to choose the right instance by the integer. Here's a MWE:
import tkinter as tk
class data:
def __init__(self, content):
self.content = content
def report(self):
print("This is reported as self.content:" + str(self.content)) #This doesnt report the correct value for some reason?
print("The Class does register the correct idx:" + str(Selector.idx))
print("Using the Dict the correct value can be returned:" + str(vocables[Selector.idx].content))
class increment:
def __init__(self):
self.idx = 0
def increase(self):
self.idx += 1
print(self.idx)
vocables[self.idx].report()
root = tk.Tk()
Selector = increment()
vocables = []
for id in range(10):
vocables.append(data(id))
# print(vocables[id].content)
CheckVocable = tk.Button(root, text="Report", command=vocables[Selector.idx].report)
CheckVocable.pack()
NextVocable = tk.Button(root, text="Increase Index", command=Selector.increase)
NextVocable.pack()
root.mainloop()
I do not understand why the print of line 8 always reports the value of the first item in the list (vocabules[0] in this instance) instead of my desired value, which is returned in all other print cases. Am I messing up the work with classes or is the button behavior confusing me?
Thanks in advance!

Python - successive random calls to the same method

On the following strip-down version of my program, I want to simulate an exchange where the user and the computer will act following a random sequence.
Here the variable row contains the sequence order. A value of 0 means the program is waiting for a user input (method val0). A value of 1 means there should be an automatic process (method val1).
It seems to work at the beginning when alternating 0 and 1 but as soon as we are waiting for two successive automatic calls, it goes out of whack.
I tried using the after method but I can't see how and where to insert it.
A while loop might do the trick on this example but the end program is more complex, with sequence interruption, reevaluation of the sequence and so on. So I don't know if it would still apply then.
from tkinter import *
class Application:
def __init__(self,master = None):
self.master = master
Label(master,text='press next').grid()
Button(master,text='Next',command=self.val0).grid()
self.index = IntVar()
self.index.set(0)
self.index.trace("w",self.nextTurn)
def val0(self):
print("User action")
self.index.set(self.index.get() +1)
def val1(self):
print("Automatic action")
self.index.set(self.index.get() +1)
def nextTurn(self, *args):
i = self.index.get()
if i >= len(row):
self.master.destroy()
return
if row[i] == 1:
self.val1()
if __name__ == "__main__":
row = [0,1,0,0,1,1,0,0,0,1,1,1]
root = Tk()
win = Application(root)
root.mainloop()
You can easily solve your problem by calling the nextTurn directly in the automatic action function:
def val1(self):
print("Automatic action")
self.index.set(self.index.get() +1)
self.nextTurn() # call nextTurn after this action
So if it was an automatic action, you step into the next row position, and call nextTurn again.
However this may become a problem if your row becomes too large because it uses recursion. In that case you will want another approach with the while you mentioned. For this second option you would only need to change nextTurn:
def nextTurn(self, *args):
i = self.index.get()
# while it is an automatic action and it has row values, keep calling val1
while i < len(row) and row[i] == 1:
self.val1() # call automatic action
i = self.index.get() #update the row position
else:
if i >= len(row):
self.master.destroy()
return

How to return a drop-down value PyQT to another method Python

I am trying to create a PyQt Dropdown menu(combo box), whose value I need to pass to another function.
Here is a snippet of my code
def combo_box(self):
combo = QtGui.QComboBox(self)
...#Filled in code here
for i in range(0,len(arr)):
combo.addItem(arr[i])
#I want to get the value of the selected Drop-down content to be stored in value
value = combo.activated[str].connect(self.ComboValue)
print value
def ComboValue(self,Text):
print Text
return Text
When I print the variable Text in the ComboValue method it prints it right, but when I print value from the combo_box method it prints None.
I wanted to know why this happens, and is there an alternative to return the value to the other method?
combo.activated[str].connect(self.ComboValue) is signal and signal never return you anything back, so that's why you getting None. Take a look http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html
You may use this value = str(combo.currentText()), but don't know you want this.
And change combo.activated[str].connect to combo.currentIndexChanged [str].connect to get your values properly
create the combobox, then wait till user activates an item:
def combo_box(self):
combo = QtGui.QComboBox(self)
...#Filled in code here
for i in range(0,len(arr)):
combo.addItem(arr[i])
combo.activated.connect(self.ComboValue)
# save it and wait for user to do something:
self.combo = combo
def ComboValue(self, Text):
# user has selected an item, save it:
self.value = Text
assert combo.currentText() == Text
... do something with it ...
Not tested so details may be wrong.

tkinter Entry widget and variables

Im sitting in a situation i can't figure out myself.
When im using the Entry widget to get user interaction i am having a hard time finding the right way to validate the data.
The situation:
I have two Entry widgets in which the user must enter two variables which has to be floats.
While i can run a program that only works properly if the entered value is a float, if i then leave it blank or enter a letter shuts down - therefor i want to validate the entry to be a float:
variableentry = Entry(root and so on)
variableentry.grid()
I am using the:
variablename = float(variableentry.get())
And when i:
print(type(variablename)
i get the message:
<class 'float'>
thus i am unable to use the
#...
try:
if(variablename is not float):
messagebox.showerror("Error", "Incorrect parameter")
return
This obviously isnt working since the variablename is of class 'float' and not float, i have tried different ways of entering instead of float in the if statement - without any luck.
Any ideas?
In advance, thanks!
Best regards,
Casper
EDIT:
I have found the:
from Tkinter import *
class ValidatingEntry(Entry):
# base class for validating entry widgets
def __init__(self, master, value="", **kw):
apply(Entry.__init__, (self, master), kw)
self.__value = value
self.__variable = StringVar()
self.__variable.set(value)
self.__variable.trace("w", self.__callback)
self.config(textvariable=self.__variable)
def __callback(self, *dummy):
value = self.__variable.get()
newvalue = self.validate(value)
if newvalue is None:
self.__variable.set(self.__value)
elif newvalue != value:
self.__value = newvalue
self.__variable.set(self.newvalue)
else:
self.__value = value
def validate(self, value):
# override: return value, new value, or None if invalid
return value
from http://effbot.org/zone/tkinter-entry-validate.htm
However the rest of the code is not written in classes (i know this is not optimal but it is demanded by the teacher) will that effect the above example? And how would i make it fit my needs?
What you want to do is to try converting the contents of the entry box to a float, and report an error message if the conversion is not possible. Doing variablename = float(variableentry.get()) is fine, but to catch errors raised by float if the string it is given cannot be converted, you must wrap the line in a try block, and catch the ValueError raised by float. If there is no exception, you can proceed with the code:
try:
variablename = float(variableentry.get())
except ValueError:
# error messagebox, etc
else:
# do stuff with variablename

Fix values that are typed into a doublespinbox in PyQt

I want to create a doublespin box that changes values in steps of 0.2. But when the user enters a value that is not correct according to the steps. I normalizes that to the nearest correct value.
I tried something like the code shown below but I don't know how to stop values like 0.5 to be entered. Please help me on this.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class SigSlot(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setWindowTitle('spinbox value')
self.resize(250,150)
self.lcd1 = QLCDNumber(self)
self.spinbox1 = QDoubleSpinBox(self)
self.spinbox1.setSingleStep(0.2)
self.spinbox1.setCorrectionMode(1)
# create a Grid Layout
grid = QGridLayout()
grid.addWidget(self.lcd1, 0, 0)
grid.addWidget(self.spinbox1, 1, 0)
self.setLayout(grid)
# allows access to the spinbox value as it changes
self.connect(self.spinbox1, SIGNAL('valueChanged(double)'), self.change_value1)
def change_value1(self, event):
val = self.spinbox1.value()
self.lcd1.display(val)
app = QApplication([])
qb = SigSlot()
qb.show()
app.exec_()
You have two choices:
You can subclass the QSpinBox, override validate method and use an appropriate Q*Validator (e.g. QRegExpValidator) inside.
You can check the value in slot connected to valueChanged before using and correct it if necessary.
Since you are already using the valueChanged signal, second option should be fairly easy to implement. Just change your change_value method like this:
def change_value1(self, val): # new value is passed as an argument
# so no need for this
# val = self.spinbox1.value()
new_val = round(val*5)/5 # one way to fix
if val != new_val: # if value is changed, put it in the spinbox
self.spinbox1.setValue(new_val)
self.lcd1.display(new_val)
By the way, since you are using only one decimal precision, it might be logical to also use:
self.spinbox1.setDecimals(1)
in your __init__. And try to use the new style signals and slots. i.e.:
self.connect(self.spinbox1, SIGNAL('valueChanged(double)'), self.change_value1)
could be written as:
self.spinbox1.valueChanged[float].connect(self.change_value1)
Edit
Subclassing:
class MySpinBox(QDoubleSpinBox):
def __init__(self, parent=None):
super(MySpinBox, self).__init__(parent)
# any RegExp that matches the allowed input
self.validator = QRegExpValidator(QRegExp("\\d+[\\.]{0,1}[02468]{0,1}"), self)
def validate(self, text, pos):
# this decides if the entered value should be accepted
return self.validator.validate(text, pos)
then instead of using QDoubleSpinBox you would use MySpinBox and leave the input checking to this class.
In your change value method you can do something like this
val = round(self.spinbox1.value(), 1)
if val/2*10 - int(val/2*10):
val = round(val, 1) + .1
It's probably not the best way but it works.

Categories