Tkinter strange behavior on destroy() - python-2.7

I am developing an application on Tkinter, Python 2.7.
In one of my processes I build a portion of my window (root) with 26 widgets:
23 Labels, 2 Buttons and 1 Entry.
While building them I keep adding their names to a list for further destroying
when their use is finished. For that I use the press of one of the buttons ("Done")
to read the list created and destroy() them inside a "for" loop.
The widgets get destroyed erratically, not in the order on the list. And I need
several presses of the button to finish it.
I found out of frustration, not insight, that if the list is reversed() in
the "for" loop they all get "destroyed" in the first attempt.
Is this an expected behaviour? Very puzzling!
I am ready to post the portion of my application with the strange behavior
expunged of unnecessary code unless somebody already knows the reason for it.
I am still busting my chops with Python and not ready to use Classes...
Thank you!
I am including the relevant portion of my program. I edited the original to reduce it size. Tested the edited version and it has the same behavior. I commented some of my code to show where to correct.
Happy that Mr. Oakley has taken interest. I am not sure if on transcribing my code the proper indentation was not affected.
My code:
# testdestroy.py
from Tkinter import *
root = Tk()
root.title('Information container')
root.geometry('1160x900+650+50')
global todestroy, screen, font1, lbl1txt, lbl2txt
global col, row, colincr, rowincr, bxincr, entries
todestroy = []
screen = ''
col = 10
row = 10
font1 = 'verdana 12 bold '
colincr = 370
rowincr = 40
bxincr = 145
entries = {' Last updated: ' : '11/08/2016 at 11:55',
' Login id: ' : 'calfucura',
' Password: ': 'munafuca',
'card number' : '1234567890',
'check number': '445',
'expiry' : '12/06/2018',
'PIN' : '9890',
'Tel:' : '1-800-234-5678',
'emergency' : 'entry nine',
'use for' : 'gas, groceries'}
def position(col, row, what): # returns the position for the place command
colincr = 370
rowincr = 40
bxincr = 145
if what == 'down':
row += rowincr
col -= colincr
if what == 'side':
col += colincr
if what == 'button1':
row += rowincr
col += colincr - bxincr
if what == 'button':
col -= bxincr
if what == 'reset':
col = col
row = row
return col, row
def done(event): # Button "Done"
print 'Done pressed'
for name in todestroy: # DOES NOT WORK!!!!
# THIS WORKS in the previous line:
# for name in reversed(todestroy):
name.destroy()
todestroy.remove(name)
def accept(event): # Button "Accept"
print 'Name to show: ', entry1.get()
scr2d1(entries)
def scr2d(): # Name to show
lbl1txt = 'Enter name to show: '
screen = 'scr2d'
scr2(lbl1txt)
# scr2d1(entries)
def scr2(lbl1txt):
global todestroy, col, row, entry1
lbl1 = Label(root, text = lbl1txt, anchor = E, width = 25, font = font1)
entry1 = Entry(root, width = 25, show = '*', font = font1)
Accept = Button(root, text = 'Accept', font = font1, bg = 'green', width = 9)
cmd = eval('Accept'.lower())
Accept.bind('<ButtonRelease-1>', cmd)
col, row = position(200, 200, 'reset')
lbl1.place(x = col, y = row)
col, row = position(col, row, 'side')
entry1.place(x = col , y = row )
col, row = position(col, row, 'button1')
Accept.place(x = col, y = row)
todestroy = []
todestroy.extend([lbl1, entry1, Accept])
def scr2d1(entries): # show entries
global todestroy, col, row
lblup = 1
lbl = 'lbl' + str(lblup)
lbl = Label(root, text = 'Entry', font = font1, width = 20 )
row = rowincr * 7
col = 600
col, row = position(col, row, 'down')
lbl.place(x = col, y = row)
todestroy.append(lbl)
lblup += 1
lbl = 'lbl' + str(lblup)
lbl = Label(root, text = 'Contents', font = font1, width = 20)
col, row = position(col, row, 'side')
lbl.place (x = col, y = row)
todestroy.append(lbl)
for name in sorted(entries):
lblup += 1
lbl = 'lbl' + str(lblup)
lbl = Label(root, text = name, bg = 'yellow', font = font1, width = 25, anchor = E)
col, row = position(col, row, 'down')
lbl.place(x = col, y = row)
todestroy.append(lbl)
lblup += 1
lbl = 'lbl' + str(lblup)
lbl = Label(root, text = entries[name], bg = 'yellow', font = font1, width = 25, anchor = W)
col, row = position(col, row, 'side')
lbl.place(x = col , y = row)
todestroy.append(lbl)
cmd = eval('done')
Done = Button(root, text = 'Done', font = font1, bg = 'green', width = 9)
Done.bind('<ButtonRelease-1>', cmd)
col, row = position(col, row, 'button1')
Done.place(x = col, y = row)
todestroy.append(Done)
scr2d()
root.mainloop()

The problem is that you are altering the list as you iterate over it, which is not something you should do. The reason that it works with reversed is because you are iterating over a copy of the original list. You get the same result if you use for name in todestroy[:], which also iterates over a copy of the list.
The quickest solution is to not remove anything from the list,and simply reset the list after you've deleted everything:
def done(event):
global todestroy
for name in todestroy:
name.destroy()
todestroy = []
A better solution would be to put all of the widgets you plan to destroy into a Frame. You can then destroy just the frame, and it will destroy all of its child widgets.

Related

python tkinter how to organize the rows and columns

Hi I'm trying to build a user interface and having problem with column and row positions. What I expect to see is some distance between buttons and entry widgets since I left two empty column between them. So why are they standing just next to the entry widgets and changing the distances between entry areas? Could anyone give me some help about this?
Here is the code...
from Tkinter import*
HMCC=Tk()
HMCC.title(" GUI v1.0 ")
HMCC.geometry("500x300")
entry_1 = Entry(HMCC)
entry_2 = Entry(HMCC)
entry_3 = Entry(HMCC)
entry_4 = Entry(HMCC)
entry_5 = Entry(HMCC)
entry_6 = Entry(HMCC)
entry_7 = Entry(HMCC)
entry_8 = Entry(HMCC)
entry_1.grid(row=2,column=1)
entry_2.grid(row=3,column=1)
entry_3.grid(row=4,column=1)
entry_4.grid(row=5,column=1)
entry_5.grid(row=6,column=1)
entry_6.grid(row=7,column=1)
entry_7.grid(row=8,column=1)
entry_8.grid(row=9,column=1)
Channel_1 = Label(HMCC, text = "Channel 1 : ")
Channel_2 = Label(HMCC, text = "Channel 2 : ")
Channel_3 = Label(HMCC, text = "Channel 3 : ")
Channel_4 = Label(HMCC, text = "Channel 4 : ")
Channel_5 = Label(HMCC, text = "Channel 5 : ")
Channel_6 = Label(HMCC, text = "Channel 6 : ")
Channel_7 = Label(HMCC, text = "Channel 7 : ")
Channel_8 = Label(HMCC, text = "Channel 8 : ")
Channel_1.grid( row = 2, column = 0, sticky = E)
Channel_2.grid( row = 3, column = 0, sticky = E)
Channel_3.grid( row = 4, column = 0, sticky = E)
Channel_4.grid( row = 5, column = 0, sticky = E)
Channel_5.grid( row = 6, column = 0, sticky = E)
Channel_6.grid( row = 7, column = 0, sticky = E)
Channel_7.grid( row = 8, column = 0, sticky = E)
Channel_8.grid( row = 9, column = 0, sticky = E)
#button1 = Button(text=" START " , fg="red" )
#button2 = Button(text=" PAUSE " , fg="blue" )
#button3 = Button(text=" STOP ", fg="green")
#button4 = Button(text="QUIT" , fg="black",command=HMCC.quit)
#button1.grid( row = 1, column = 3)
#button2.grid( row = 2, column = 3)
#button3.grid( row = 3, column = 3)
#button4.grid( row = 4, column = 3)
HMCC.mainloop()
Current view
Thanks in advance
If there is nothing in column 2, then tkinter will ignore it.
In addition to the comment posted above which contains the answer to your question, you can clean up your code significantly by just using a loop:
num_rows = 8
entries = [None]*num_rows
channels = [None]*num_rows
for i in range(num_rows):
channels[i] = Label(HMCC, text = "Channel {0} : ".format(i+1))
channels[i].grid(row=i+2,column=0,sticky=E)
entries[i] = Entry(HMCC)
entries[i].grid(row=i+2, column=1)
Better yet, use list comprehension:
num_rows = 8
entries = [Entry(HMCC).grid(row=i+2, column=1) for i in range(num_rows)]
channels = [Label(HMCC, text = "Channel {0} : ".format(i)).grid(row=i+2,column=0,sticky=E) for i in range(num_rows)]

How to modify a variable when a while loop is running Python

I am using wx.python along with VPython to make an orbit simulator, however i'm having trouble trying to get the sliders in the GUI to effect the simulation, I assume it's because I am trying to get the number associated with the slider button to go into a while loop when it is running.
So my question is, how do i get the function SetRate to update in the while loop located at the bottom of the code? (I have checked to see that the slider is retuning values)
Here is my code for reference:
Value = 1.0
dt = 100.0
def InputValue(Value):
dt = Value
def SetRate(evt):
global Value
Value = SpeedOfSimulation.GetValue()
return Value
w = window(menus=True, title="Planetary Orbits",x=0, y=0, width = 1000, height = 1000)
Screen = display(window = w, x = 30, y = 30, width = 700, height = 500)
gdisplay(window = w, x = 80, y = 80 , width = 40, height = 20)
p = w.panel # Refers to the full region of the window in which to place widgets
SpeedOfSimulation = wx.Slider(p, pos=(800,10), size=(200,100), minValue=0, maxValue=1000)
SpeedOfSimulation.Bind(wx.EVT_SCROLL, SetRate)
TestData = [2, 0, 0, 0, 6371e3, 5.98e24, 0, 0, 0, 384400e3, 0, 0, 1737e3, 7.35e22, 0, 1e3, 0]
Nstars = TestData[0] # change this to have more or fewer stars
G = 6.7e-11 # Universal gravitational constant
# Typical values
Msun = 2E30
Rsun = 2E9
vsun = 0.8*sqrt(G*Msun/Rsun)
Stars = []
colors = [color.red, color.green, color.blue,
color.yellow, color.cyan, color.magenta]
PositionList = []
MomentumList = []
MassList = []
RadiusList = []
for i in range(0,Nstars):
s=i*8
x = TestData[s+1]
y = TestData[s+2]
z = TestData[s+3]
Radius = TestData[s+4]
Stars = Stars+[sphere(pos=(x,y,z), radius=Radius, color=colors[i % 6],
make_trail=True, interval=10)]
Mass = TestData[s+5]
SpeedX = TestData[s+6]
SpeedY = TestData[s+7]
SpeedZ = TestData[s+8]
px = Mass*(SpeedX)
py = Mass*(SpeedY)
pz = Mass*(SpeedZ)
PositionList.append((x,y,z))
MomentumList.append((px,py,pz))
MassList.append(Mass)
RadiusList.append(Radius)
pos = array(PositionList)
Momentum = array(MomentumList)
Mass = array(MassList)
Mass.shape = (Nstars,1) # Numeric Python: (1 by Nstars) vs. (Nstars by 1)
Radii = array(RadiusList)
vcm = sum(Momentum)/sum(Mass) # velocity of center of mass
Momentum = Momentum-Mass*vcm # make total initial momentum equal zero
Nsteps = 0
time = clock()
Nhits = 0
while True:
InputValue(Value) #Reprensents the change in time
rate(100000) #No more than 100 loops per second on fast computers
# Compute all forces on all stars
r = pos-pos[:,newaxis] # all pairs of star-to-star vectors (Where r is the Relative Position Vector
for n in range(Nstars):
r[n,n] = 1e6 # otherwise the self-forces are infinite
rmag = sqrt(sum(square(r),-1)) # star-to-star scalar distances
hit = less_equal(rmag,Radii+Radii[:,newaxis])-identity(Nstars)
hitlist = sort(nonzero(hit.flat)[0]).tolist() # 1,2 encoded as 1*Nstars+2
F = G*Mass*Mass[:,newaxis]*r/rmag[:,:,newaxis]**3 # all force pairs
for n in range(Nstars):
F[n,n] = 0 # no self-forces
Momentum = Momentum+sum(F,1)*dt
# Having updated all momenta, now update all positions
pos = pos+(Momentum/Mass)*dt
# Update positions of display objects; add trail
for i in range(Nstars):
Stars[i].pos = pos[i]
I know nothing about vpython but in a normal wxPython app, you will use wx.Timer instead of while loop.
here is an example of wx.Timer modified from https://www.blog.pythonlibrary.org/2009/08/25/wxpython-using-wx-timers/
You will want to separate the while loop part from your SetRate class method and put it in update.
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Timer Tutorial 1",
size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
SpeedOfSimulation = wx.Slider(p, pos=(800,10), size=(200,100), minValue=0, maxValue=1000)
SpeedOfSimulation.Bind(wx.EVT_SCROLL, SetRate)
self.SpeedOfSimulation = SpeedOfSimulation
def update(self, event):
# Compute all forces on all stars
SpeedOfSimulation = self.SpeedOfSimulation.GetValue()

Please point out my mistake in the following program

Here m trying to extract a list of blacklist IP from a specific site and trying to make an excel sheet of the following fields :
IP , date ...... New updated code is :
import xlwt
import urllib
def Bl():
link = 'https://www.dshield.org/ipsascii.html?limit=100'
p = urllib.urlopen(link)
text = p.readlines()
wbk = xlwt.Workbook()
sheet = wbk.add_sheet('python')
sheet.write(0,0,'Blacklist IP')
sheet.write(0,1,'Reports')
sheet.write(0,2,'abcd')
sheet.write(0,3,'date')
sheet.write(0,4,'Date')
row = 1 #counter for rows.
col = 0 #counter for columns.
x = 0 #counter for printing the n'th element in the string w.
for line in text:
li = line.strip()
w = line.split()
if not li.startswith("#"):
sheet.write(row,col,w[0])
sheet.write(row,1,w[1])
sheet.write(row,2,w[2])
sheet.write(row,3,w[3])
sheet.write(row,4,w[4])
row = row + 1
wbk.save('new.xls')
Bl()
I believe it should be this, though my python isn't great:
import xlwt
import urllib
link = 'https://www.dshield.org/ipsascii.html?limit=100'
p = urllib.urlopen(link)
text = p.readlines()
wbk = xlwt.Workbook()
sheet = wbk.add_sheet('Blacklist IP')
row = 1 #counter for rows.
col = 0 #counter for columns.
#x = 0 #counter for printing the n'th element in the string w.
for line in text:
li = line.strip()
w = line.split()
print "The line " + line + " was split into " + len(w) + " size"
for i in range(0,len(w)-1):
if not li.startswith("#") and not li.isspace():
sheet.write(row,col,w[i])
#x = x + 1
col = col + 1
if col > 256:
print "Line of " + li
print "Row: " + row
print "Col: " + col
raise ValueError('Too many columns, can\'t insert ' + w)
row = row + 1
wbk.save('new.xls')
Reasoning is that I believe you should be changing column every time you write a value, since that appears to be causing your current error. I also believe You should be changing row after each line, not just at the end of the code which then appears superfluous
[Edit update after user feedback]

Getting selected items from a Tkinter listbox without using a listbox bind

I have 2 listboxes (which are connected so that items can move from one to the other) and at the end I would like to get all the entries in the second listbox by using a 'Ok' button (or simply closing the frame). I could add/remove values to a list every time an item is selected (as shown in the commented section of the code below) but I would rather have a single line of code along the lines of [master.selected.get(idx) for idx in master.selected.curselection()] in the close function but I am unable to get it working.
Code:
def measurementPopup(self,master):
self.chargeCarrier = StringVar()
self.massModifiers = StringVar()
self.chargeCarrier.set("[M+xH]")
def onselect1(evt):
w = evt.widget
index = int(w.curselection()[0])
value = w.get(index)
# My Dirty fix -> Here I could enter the selected value to a buffer list (to be returned in the ok function).
master.selected.insert(END,value)
master.avail.delete(index)
def onselect2(evt):
w = evt.widget
index = int(w.curselection()[0])
value = w.get(index)
# My Dirty fix -> Here I could remove the selected value from a buffer list (to be returned in the ok function).
master.selected.delete(index)
master.avail.insert(END,value)
def close(self):
# Here I would return the buffer list and close the window
master.measurementWindow = 0
top.destroy()
if master.measurementWindow == 1:
return
master.measurementWindow = 1
top = self.top = Toplevel()
top.protocol( "WM_DELETE_WINDOW", lambda: close(self))
self.charge = Label(top, text = "Charge", width = 10)
self.charge.grid(row = 0, column = 0, sticky = W)
self.min = Label(top, text = "Min", width = 5)
self.min.grid(row=0, column = 1, sticky = W)
self.minCharge = Spinbox(top, from_= 1, to = 3, width = 5)
self.minCharge.grid(row = 0, column = 2, sticky = W)
self.max = Label(top, text = "Max", width = 5)
self.max.grid(row = 0, column = 3, sticky = W)
self.maxCharge = Spinbox(top, from_ = 1, to=3, width=5)
self.maxCharge.grid(row = 0, column = 4, sticky = W)
self.chargeCarrier = OptionMenu(top, self.chargeCarrier, "[M+xH]", "[M+xNa]")
self.chargeCarrier.grid(row = 0, column = 5, sticky = W)
self.availMass = Label(top, text = "Available")
self.availMass.grid(row = 1, column = 1, sticky = W)
self.selectMass = Label(top, text = "Selected")
self.selectMass.grid(row = 1, column = 3, sticky = W)
self.massMod = Label(top, text = "Mass Mods")
self.massMod.grid(row = 2, column = 0, sticky = W)
self.avail = Listbox(top)
for i in UNITS:
if BLOCKS[i]['available'] == 1:
self.avail.insert(END,BLOCKS[i]['human_readable_name'])
self.avail.grid(row = 2, column = 1, columnspan = 2, sticky = W)
self.avail.bind('<<ListboxSelect>>',onselect1)
self.selected = Listbox(top)
self.selected.grid(row = 2, column = 3, columnspan = 2, sticky = W)
self.selected.bind('<<ListboxSelect>>',onselect2)
self.ok = Button(top,text = 'Ok',command = lambda: close(self))
self.ok.grid(row = 3, column = 0, sticky = W)
I have tried to use the following small snippet in the close function:
values = [master.selected.get(idx) for idx in master.selected.curselection()]
print ', '.join(values)
However, the for segment doesn't return anything. I would expect that this is due to the fact that nothing is actually selected but that I would need something opposite, along the lines of master.selected.allitems() (if it exists and if I understand it correctly).
Summary
How would one get all the items in 1 specific listbox?
The .get() function for the Listbox widget allows you to specify a range of items, which can be specified as 0 to END to return a tuple of all the items.
Example:
from Tkinter import *
root = Tk()
l = Listbox(root, width = 15)
l.pack()
l.insert(END, "Hello")
l.insert(END, "world")
l.insert(END, "here")
l.insert(END, "is")
l.insert(END, "an")
l.insert(END, "example")
def close():
global l, root
items = l.get(0, END)
print(items)
root.destroy()
b = Button(root, text = "OK", command = close).pack()
root.mainloop()
I hope this helps, if it's not what you were looking for let me know in a comment and I can try expand my answer.

TclError: wrong # args error

I have no idea what is wrong but I keep getting this
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/7.3/lib/python2.7/lib-tk/Tkinter.py", line 1410, in call
return self.func(*args)
File "/Users/Zane/Desktop/Factorial GUI.py", line 72, in reveal2
self.text2.insert(0.0, message)
File "/Library/Frameworks/Python.framework/Versions/7.3/lib/python2.7/lib-tk/Tkinter.py", line 2986, in insert
self.tk.call((self._w, 'insert', index, chars) + args)
TclError: wrong # args: should be ".22186144.22187184 insert index chars ?tagList chars tagList ...?"
here is my code:`
from Tkinter import*
class App(Frame):
def fac(self, n):
if n >= 0:
if n == 1 or n == 0:
return 1
else:
return n*self.fac(n-1)
else:
print('Error')
def per(self, n, r):
y = (self.fac(n)) / self.fac(n - r)
print (y)
def __init__(self, master):
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
self.instruction1 = Label(self, text = "Factorial:")
self.instruction1.grid(row = 0, column = 0, columnspan = 1, sticky = W)
self.password1 = Entry(self)
self.password1.grid(row = 0, column = 1, sticky = W)
self.submit_button1 = Button(self, text ="Enter", command = self.reveal1)
self.submit_button1.grid(row = 2, column = 0, sticky = W)
self.text1 = Text(self, width = 30, height = 1, wrap = WORD)
self.text1.grid(row = 3, column = 0, columnspan = 2, sticky = W)
self.instruction2 = Label(self, text = "Permutation:")
self.instruction2.grid(row = 4, column = 0, columnspan = 1, sticky = W)
self.password2 = Entry(self)
self.password2.grid(row = 4, column = 1, sticky = W)
self.password3 = Entry(self)
self.password3.grid(row = 6, column = 1, sticky = W)
self.submit_button2 = Button(self, text ="Enter", command = self.reveal2)
self.submit_button2.grid(row = 7, column = 0, sticky = W)
self.text2 = Text(self, width = 30, height = 1, wrap = WORD)
self.text2.grid(row = 8, column = 0, columnspan = 2, sticky = W)
def reveal1(self):
y = int(self.password1.get())
message = self.fac(y)
self.text1.delete(0.0, END)
self.text1.insert(0.0, message)
def reveal2(self):
y = int(self.password2.get())
z = int(self.password3.get())
message = self.per(y, z)
self.text2.delete(0.0, END)
self.text2.insert(0.0, message)
root = Tk()
root.title('Factorial')
root.geometry("340x300")
app = App(root)
root.mainloop()
`
Almost the only way to get the error you say you get with the code you posted, is if the insert method is called when the data to insert is None. message comes from the result of per, but per returns None because you don't explicitly return anything else.
One of the first things to try when trying to debug is to check that the data you're sending to the failing function is what you think it is. You can do this in a very low-tech way by simply printing out the values being passed to the insert message. This instantly told me that message was None. Once I learned that, it's pretty simple to answer the question "why was it None?".