I am connecting frontend to backend of a database application using Tkinter and sqlite3.
Need help finding potential reasons that resulted in this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Peng\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "frontend.py", line 9, in get_selected_row
index=list1.curselection()[0]
IndexError: tuple index out of range
But list1.curselection()[0] is just the id, why is it out of range?
Any help on finding where went wrong will be greatly appreciated!!!
My code:
frontend.py:
from tkinter import *
import backend
window=Tk()
list1.bind('<<ListboxSelect>>',get_selected_row)
def update_command():
backend.update(selected_tuple[0],title_text.get(),author_text.get(),year_text.get(),isbn_text.get())
def get_selected_row(event):
global selected_tuple
index=list1.curselection()[0]
selected_tuple=list1.get(index)
e1.delete(0,END)
e1.insert(END,selected_tuple[1])
e2.delete(0,END)
e2.insert(END,selected_tuple[2])
e3.delete(0,END)
e3.insert(END,selected_tuple[3])
e4.delete(0,END)
e4.insert(END,selected_tuple[4])
backend.py:
import sqlite3
def update(id,title,author,year,isbn):
conn=sqlite3.connect("books.db")
cur=conn.cursor()
cur.execute("UPDATE book SET title=?,author=?,year=?,isbn=? WHERE id=?",(title,author,year,isbn,id))
conn.commit()
conn.close()
Your method is being triggered when there is nothing selected. The easiest fix is to simply check if the tuple is empty:
def get_selected_row(event):
global selected_tuple
index=list1.curselection()
if index: # if the tuple is not empty
selected_tuple=list1.get(index[0])
e1.delete(0,END)
e1.insert(END,selected_tuple[1])
e2.delete(0,END)
e2.insert(END,selected_tuple[2])
e3.delete(0,END)
e3.insert(END,selected_tuple[3])
e4.delete(0,END)
e4.insert(END,selected_tuple[4])
A more proper fix is to find out when it's being triggered like that and prevent it.
it happens when you click into the list1 while it is empty
this is the line that triggers it
list1.bind('<<ListboxSelect>>',get_selected_row)
the solution mentioned above is suitable since you don't seem to have any other code manipulating this event
however, i would word the solution a bit differently, to be consistent with your code naming conventions:
def get_selected_row(event):
global selected_tuple
if list1.curselection():
index=list1.curselection()[0]
selected_tuple=list1.get(index)
e1.delete(0,END)
e1.insert(END,selected_tuple[1])
e2.delete(0,END)
e2.insert(END,selected_tuple[2])
e3.delete(0,END)
e3.insert(END,selected_tuple[3])
e4.delete(0,END)
e4.insert(END,selected_tuple[4])
Since the listbox is empty, then list1.curselection()will be an empty list with no items. Trying to access the first item of that list with [0] in line 3 will throw an error since there is no first item in the list.
def get_selected_row(event):
try:
global selected_tuple
index=list1.curselection()
selected_tuple=list1.get(index[0])
e1.delete(0,END)
e1.insert(END,selected_tuple[1])
e2.delete(0,END)
e2.insert(END,selected_tuple[2])
e3.delete(0,END)
e3.insert(END,selected_tuple[3])
e4.delete(0,END)
e4.insert(END,selected_tuple[4])
except IndexError:
pass
When the get_selected_row function is called, Python will try to execute the indentedblock under try. If there is an IndexErrornone of the lines under try will be executed. Instead the line under except will be executed which is pass. Thepass stetementmeans do nothing. So the function will do nothing when there's an empty listbox.
Related
After binding a function once and then unbinding it, I cannot bind it again later to the same function when later needed. I saw it works fine when I do not use '+' the binding function (I'm using bind_all incase it makes any difference). But when I use '+', it does bind it again...
IDLE:
>>> def Released(evnt):
print(evnt.keysym, ' Removed')
>>> funcid = _Win.bind_all("<KeyRelease>", Released, '+')
I hit some keys and on releasing I got the print statement in Released()
>>> _Win.unbind("<KeyRelease>", funcid)
>>> funcid = _Win.bind_all("<KeyRelease>", Released, '+')
I hit some keys but no output came. But there were no errors either.
So how do I fix this issue?
Thanks in Advance :)
Since no one answered, I thought a bit more and I came to a workaround to the problem of unbinding... Its not a solution but a workaround...
class FunctionManager:
def __init__(self, funcs = []):
self.funcs = list(funcs)
def __call__(self, evnt):
for func in self.funcs:
func(evnt)
def add(self, func):
self.funcs.append(func)
def remove(self, func):
self.funcs.remove(func)
So now instead of binding and unbinding function, which didn't work for me, I will just bind an instance of this class containing all required functions and remove functions from it later when required...
The program works as intended when I simply use tkinter's widgets. When I use ttk's widgets the program repeats itself twice. I tried almost everything in my knowledge to fix this, I believe that *args have something to do with it. Is there anyway to prevent my function _up_options from running twice?
from tkinter import *
from tkinter import ttk
root = Tk()
first = StringVar(root)
second = StringVar(root)
Ore = {'Options': [''], 'Yes': ['One'], 'No': ['Two']}
entry1 = ttk.OptionMenu(root, first, *Ore.keys())
entry2 = ttk.OptionMenu(root, second, '')
entry1.pack()
entry2.pack()
def _up_options(*args):
print('update_options')
ores = Ore[first.get()]
second.set(ores[0])
menu = entry2['menu']
menu.delete(0, 'end')
for line in ores:
print('for')
menu.add_command(label=line, command=lambda choice=line: second.set(choice))
first.trace('w', _up_options)
root.mainloop()
PS, I used *args in my function to work. If anyone can explain this, I would be very grateful
I think I figured this out. The problem is that the variable actually is set twice by the ttk OptionMenu.
Take a look at this piece of code from the tkinter OptionMenu:
for v in values:
menu.add_command(label=v, command=_setit(variable, v, callback))
This adds a button to the menu for each value, with a _setit command. When the _setit is called it sets the variable and another callback if provided:
def __call__(self, *args):
self.__var.set(self.__value)
if self.__callback:
self.__callback(self.__value, *args)
Now look at this piece of code from the ttk OptionMenu:
for val in values:
menu.add_radiobutton(label=val,
command=tkinter._setit(self._variable, val, self._callback),
variable=self._variable)
Instead of a command this adds a radiobutton to the menu. All radiobuttons are "grouped" by linking them to the same variable. Because the radiobuttons have a variable, when one of them is clicked, the variable is set to the value of the button. Next to this, the same command is added as in the tkinter OptionMenu. As said, this sets the variable and then fires another command of provided. As you can see, now the variable is updated twice, once because it is linked to the radiobutton and once more because it is set in the _setit function. Because you trace the changing of the variable and the variable is set twice, your code also runs twice.
Because the variable is set twice from within the ttk code, I guess there's not much you can do about that. If you don't change the variable from any other part of your code than from the OptionMenu though, you could choose to not trace the variable, but instead add your function as command to the OptionMenu:
entry1 = ttk.OptionMenu(root, first, *Ore.keys(), command=_up_options)
P.S. this was introduced with this commit after this bugreport.
I guess when adding the variable=self._variable the command should have been changed to just command=self._callback.
You can understand the problem in the error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\tkinter__init__.py", line 1699, in call
return self.func(*args)
TypeError: _up_options() takes 0 positional arguments but 3 were given
Initially, you don't use _up_options When you change the Options you call _up_options to trace the first StringVar and change it to the value of the next object in the dictionary.
Now when you do that you are running on all the objects in the dictionary, therefore, you need the *args so the lambda function will run on all args given!
As for your problem:
When I use ttk's widgets the program repeats itself twice.
EDIT
See #fhdrsdg's answer!
The solution is just to change command=tkinter._setit(self._variable, val, self._callback) to command=self._callback.
Hope you find this helpful!
Instead of tracing the StringVar, add a callback as command argument for OptionMenu constructor.
I created a subclass of ttk.OptionMenu to solve this (as well as to provide slightly simpler usage of the widget and a more useful callback). I think this is a more stable approach than modifying the original class directly or just overriding the original method because it guarantees compatibility with potential changes to the built-in/original widget in future Tkinter versions.
class Dropdown( ttk.OptionMenu ):
def __init__( self, parent, options, default='', variable=None, command=None, **kwargs ):
self.command = command
if not default:
default = options[0]
if not variable:
variable = Tk.StringVar()
if command:
assert callable( command ), 'The given command is not callable! {}'.format( command )
ttk.OptionMenu.__init__( self, parent, variable, default, *options, command=self.callBack, **kwargs )
else:
ttk.OptionMenu.__init__( self, parent, variable, default, *options, **kwargs )
def callBack( self, newValue ):
self.command( self, newValue )
You can then use it like this:
def callback( widget, newValue ):
print 'callback called with', newValue
print 'called by', widget
options = [ 'One', 'Two', 'Three' ]
dropdown = Dropdown( parent, options, command=callback )
dropdown.pack()
Besides avoiding the double-trace issue, other notable differences from the original ttk.OptionMenu includes not needing to supply a Tkinter variable or default value if you don't need them (the default item will be the first item in the options list if not provided), and being able to get the widget that called the callback function when it fires. The latter is very helpful if you have many dropdown widgets sharing the same callback and you need to know which one is being used within the call.
Soon after writing this, I also found another solution using lambda: Passing OptionMenu into a callback (or retrieving a reference to the used widget)
I thought I might still share this Dropdown widget anyway since it can make the higher-level code simpler, and it provides a good base if you have some other custom methods to add in.
In the following Program, the calculator gui is closing itself when i press "=" for getting the result. I think there is some problem with the eval function. if i remove the eval then i didn't encounter any problem with the program. Need help in figuring this out. Is there any other approach i can try besides this? Thanks in advance.
class Calci(QWidget):
def __init__(self, Parent=None):
super(Calci,self).__init__()
self.initgui()
def initgui(self):
self.list1=
["%","rt","pow","1/x","CE","C","BCK","/","7","8","9","*","4","5","6","-
","1","2","3","+","+-","0",".","="]
self.list2=[(i,j) for i in range(2,8) for j in range(0,4)]
self.button={}
self.data1=""
self.data2=0
self.lineedit=QLineEdit()
self.lineedit.setFocus()
grid=QGridLayout()
self.setLayout(grid)
self.font=QFont("Bookman Old Style",15,25)
self.lineedit.setFont(self.font)
for x,y in zip(self.list1,self.list2):
self.button[y]=QPushButton(x)
grid.addWidget(self.lineedit,0,0,2,4)
self.lineedit.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
self.lineedit.setMinimumHeight(70)
grid.addWidget(self.button[y],*y)
self.button[y].setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
self.button[y].setMinimumHeight(70)
self.lineedit.setAlignment(Qt.AlignRight)
self.button[y].clicked.connect(lambda state,x=x: self.click(x))
def click(self,n):
if (n=="="):
data1=self.lineedit.text()
self.lineedit.clear()
self.lineedit.insert(eval(data1))
else:
self.lineedit.insert(n)
app=QApplication(sys.argv)
calci=Calci()
calci.show()
app.exec_()
It's most likely failing because of a TypeError when you try to set the line edit text to an integer value. I also don't think self.lineedit.insert() is the method that you want to use. This will add the calculation after the line after the text already entered.
try:
self.lineedit.setText(str(eval(data1)))
This will clear the line edit and set the text to the calculated value.
But:
Please don't use eval like this. This is a very dangerous practice as any python code entered in the text edit will be run.
Ive been researching this all day, and despite looking at things like:
http://www.hardcoded.net/articles/using_qtreeview_with_qabstractitemmodel
http://trevorius.com/scrapbook/uncategorized/pyqt-custom-abstractitemmodel/
http://blog.rburchell.com/2010/02/pyside-tutorial-model-view-programming_22.html
I am entirely stumped. I either can't even get the example working, or when i can, I am unable to bend it to do what I want.
Basically I want to build a model to which i can input a list of dicts. The len() of my list would be my rows, and then len() of any of their keys would be my column count.
I am entirely new to these item models (I usually use TableWidget, ListWidget, etc and their built in implementations), and I'm not understanding how to properly set data, add rows with the beginInsertRows/endInsertRows, and even when i accidnetally get that working, trying to figure out when and how to add my columns as well always causes all sorts of bad.
I was wondering if anyone had any examples I can draw from - something simple with the basis of what I'm missing. for instance:
given
myList = [
{'name': 'John Smith', 'occupation': 'Police', 'Gender': 'Male'},
{'name': 'Jane Smith', 'occupation': 'CEO', 'Gender': 'Female'},
{'name': 'Ranoutta Ideas', 'occupation': 'Not Creativity', 'Gender': 'Male'},
]
I would want to insert that into my model and get 3 rows, each with 3 columns, and maybe the key names as headers.
Is anyone able to point me on a good path?
Thanks in advance!
EDIT
I tried an example from the comments:
import sys
import signal
import PyQt4.QtCore as PCore
import PyQt4.QtGui as PGui
class OneRow(PCore.QObject):
def __init__(self):
self.column0="text in column 0"
self.column1="text in column 1"
class TableModel(PCore.QAbstractTableModel):
def __init__(self):
super(TableModel,self).__init__()
self.myList=[]
def addRow(self,rowObject):
row=len(self.myList)
self.beginInsertRows(PCore.QModelIndex(),row,row)
self.myList.append(rowObject)
self.endInsertRows()
#number of row
def rowCount(self,QModelIndex):
return len(self.myList)
#number of columns
def columnCount(self,QModelIndex):
return 2
#Define what do you print in the cells
def data(self,index,role):
row=index.row()
col=index.column()
if role==PCore.Qt.DisplayRole:
if col==0:
return str( self.myList[row].column0)
if col==1:
return str( self.myList[row].column1)
#Rename the columns
def headerData(self,section,orientation,role):
if role==PCore.Qt.DisplayRole:
if orientation==PCore.Qt.Horizontal:
if section==0:
return str("Column 1")
elif section==1:
return str("Column 2")
if __name__=='__main__':
PGui.QApplication.setStyle("plastique")
app=PGui.QApplication(sys.argv)
#Model
model=TableModel()
model.addRow(OneRow())
model.addRow(OneRow())
#View
win=PGui.QTableView()
win.setModel(model)
#to be able to close wth ctrl+c
signal.signal(signal.SIGINT, signal.SIG_DFL)
#to avoid warning when closing
win.setAttribute(PCore.Qt.WA_DeleteOnClose)
win.show()
sys.exit(app.exec_())
However i end up with a one of errors:
TypeError: invalid result type from TableModel.headerData()
TypeError: invalid result type from TableModel.data()
and no text shows in the cells. I also dont get header labels.
If i change it from returning strings to return QVariant's - i get the text in the cells/headers, but still get tons of those error prints
EDIT:
If i make it fall back to return an empty QVariant, no more error prints - totally missed that. Im going to play with this set up and see if i can get it working as i need, and if so, ill post it here as an answer in case others come accross my struggles
Hi I've been struggling to get this to work, each time i change something I receive another error. I've been trying to create an entry box with a function and then get the variable from the entry box into a label, created by a button press. When I tried to do this often this error came up.
TypeError: get() missing 1 required positional argument: 'self'
I then put self in in the method brackets.
command = lambda: x.myFunc(self.my_variable.get(self))
Then another error, which I'm not sure how to sort out.
AttributeError: 'My_Class' object has no attribute '_tk'
Here's the full code, I'm new to classes and self, so any corrections are welcome.
from tkinter import *
import time
class My_Class:
def start(self):
self.root=Tk()
self.my_variable=StringVar
self.entry_box=Entry(self.root, textvariable=self.my_variable)
self.entry_box.pack()
self.button=Button(self.root,text="Pass variable now",
command=lambda:x.myFunc(self.my_variable.get(self)))
self.button.pack()
def myFunc(self,my_variable):
self.lab=Label(self.root,text=self.my_variable)
self.lab.pack()
x=My_Class()
x.start()
This is the correct way to create a StringVar object:
text = StringVar() # note additional ()
Can you explain me what x is in the following statement:
lambda: x.myFunc(self.my_variable.get(self))
x is not visible inside the class, because it's declared outside the class.
myFunc is not indented correctly: you should indent it like the __init__ method.
I really recommend you to watch some tutorials on OOP before proceeding. You are basically trying to guess how OOP works.
If you make myFunc A method if the class (which you might be trying to do; it's hard to know because your indentation is wrong), you don't have to pass anything to myFunc. That function has access to everything in the class, so it can get what it needs, when it needs it. That lets you eliminate the use of lambda, which helps reduce complexity.
Also, you normally don't need a StringVar at all, it's just one more thing to keep track of. However, if you really need the label and entry to show exactly the same data, have them share the same textvariable and the text is updated automatically without you having to call a function, or get the value from the widget, or set the value n the label.
Here's an example without using StringVar:
class My_Class:
def start(self):
...
self.entry_box = Entry(self.root)
self.button = Button(..., command = self.myFunc)
...
def myFunc(self):
s = self.entry_box.get()
self.lab = Label(..., text = s)
...