Tkinter label height to fit content - python

I have a label into which I am going to put content of different sizes. I would like to know how high I need to make the label so that I can size the window so it can stay the same for the different content sizes. I have a strategy, but it seems more complicated then it should be.
I want to set a label to a given width and wraplength:
l = Label(root)
l['width'] = 30
l['wraplength'] = 244
l['text'] = "testing this"
Now I want to query the label to find how many lines are used. l['height'] stays at 0, so the best I have been able to come up with is to use l.winfo_height() and convert the height given in pixels to the number of lines used. Nothing in dir(l) seems to give me the information directly, but this strategy is fragile to font changes and other changes.
Any suggestions?
Update: using Brian Oakley's suggestion (which is similar to what I got on usenet) I have the following approximation to a solution (needs polishing, e.g. doesn't take into account that Label breaks at whitespace):
import Tkinter as Tk
import tkFont
import random
import sys
def genstr (j):
rno = random.randint(4,50)
ret_val = str(j) + ":"
for i in range (0, rno):
ret_val += "hello" + str(i)
return ret_val
def gendata (lh):
ret_val = []
for i in range(0,lh):
ret_val.append (genstr (i))
return ret_val
data = gendata (100)
root = Tk.Tk()
font = tkFont.Font(family='times', size=13)
class lines:
def __init__ (self):
self.lastct = 1 # remember where the cutoff was last work from there
def count (self, text, cutoff = 400):
global font
no_lines = 1
start_idx = 0
idx = self.lastct
while True:
if idx > len (text):
idx = len (text)
# shrink from guessed value
while font.measure (text[start_idx:idx - 1]) > cutoff:
if idx <= start_idx:
print "error"
sys.exit ()
else:
idx -= 1
self.lastct = idx - start_idx # adjust since was too big
# increase from guessed value (note: if first shrunk then done)
while (idx < len (text)
and font.measure (text[start_idx:idx]) < cutoff):
idx += 1
self.lastct = idx - start_idx # adjust since was too small
# next line has been determined
print "*" + text[start_idx:idx-1] + "*"
if idx == len(text) and font.measure (text[start_idx:]) < cutoff:
return no_lines
elif idx == len(text):
return no_lines + 1
else:
no_lines += 1
start_idx = idx - 1
idx = start_idx + self.lastct
lin = lines()
for i in range(0,len(data)):
lin.count(data[i], 450)
for i in range(0,min(len(data),10)):
l = Tk.Label(root)
l.pack()
l['text'] = data[i]
print i
no = lin.count (data[i], 450)
print "computed lines", no
l['width'] = 50
l['justify'] = Tk.LEFT
l['anchor'] = 'w'
l['wraplength'] = 450
l['padx']=10
l['pady'] = 5
l['height'] = no
l['font'] = font
if i % 2 == 0:
l['background'] = 'grey80'
else:
l['background'] = 'grey70'
root.mainloop()

You are correct that the height attribute doesn't change. That attribute doesn't tell you the actual height, only the height it is configured to be. The actual height depends on factors such as how much text is in it, the wrap length, the font, and how the widget geometry is managed.
tkinter Font objects have a measure method which lets you determine how tall and wide a string is for a given font. You can get the font for the widget and use that method to determine how much space is required for your string.

Related

tkinter - Infinite Canvas "world" / "view" - keeping track of items in view

I feel like this is a little bit complicated or at least I'm confused on it, so I'll try to explain it by rendering the issue. Let me know if the issue isn't clear.
I get the output from my viewing_box through the __init__ method and it shows:
(0, 0, 378, 265)
Which is equivalent to a width of 378 and a height of 265.
When failing, I track the output:
1 false
1 false
here ([0.0, -60.0], [100.0, 40.0]) (0, 60, 378, 325)
The tracking is done in _scan_view with the code:
if not viewable:
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current-('viewable',)
else:
print('here',points, (x1,y1,x2,y2))
new = ''
self.inview_items.discard(item)
So the rectangle stays with width and height of 100, the coords however failing to be the expected ones. While view width and height stays the same and moves correctly in my current understanding. Expected:
if x1 <= point[0] <= x2 and y1 <= point[1] <= y2: and it feels like I've created two coordinate systems but I don't get it. Is someone looking on it and see it immediately?
Full Code:
import tkinter as tk
class InfiniteCanvas(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.inview_items = set() #in view
self.niview_items = set() #not in view
self._xshifted = 0 #view moved in x direction
self._yshifted = 0 #view moved in y direction
self._multi = 0
self.configure(confine=False,highlightthickness=0,bd=0)
self.bind('<MouseWheel>', self._vscroll)
self.bind('<Shift-MouseWheel>', self._hscroll)
root.bind('<Control-KeyPress>',lambda e:setattr(self,'_multi', 10))
root.bind('<Control-KeyRelease>',lambda e:setattr(self,'_multi', 0))
print(self.viewing_box())
return None
def viewing_box(self):
'returns x1,y1,x2,y2 of the currently visible area'
x1 = 0 - self._xshifted
y1 = 0 - self._yshifted
x2 = self.winfo_reqwidth()-self._xshifted
y2 = self.winfo_reqheight()-self._yshifted
return x1,y1,x2,y2
def _scan_view(self):
x1,y1,x2,y2 = self.viewing_box()
for item in self.find_withtag('viewable'):
#check if one felt over the edge
coords = self.coords(item)
#https://www.geeksforgeeks.org/python-split-tuple-into-groups-of-n/
points = tuple(
coords[x:x + 2] for x in range(0, len(coords), 2))
viewable = False
for point in points:
if x1 <= point[0] <= x2 and y1 <= point[1] <= y2:
#if any point is in viewing box
viewable = True
print(item, 'true')
else:
print(item, 'false' )
if not viewable:
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current-('viewable',)
else:
print('here',points, (x1,y1,x2,y2))
new = ''
self.inview_items.discard(item)
self.itemconfigure(item,tags=new)
for item in self.find_overlapping(x1,y1,x2,y2):
#check if item inside of viewing_box not in inview_items
if item not in self.inview_items:
self.inview_items.add(item)
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current+('viewable',)
elif isinstance(current, str):
if str:
new = (current, 'viewable')
else:
new = 'viewable'
self.itemconfigure(item,tags=new)
print(self.inview_items)
def _create(self, *args):
if (current:=args[-1].get('tags', False)):
args[-1]['tags'] = current+('viewable',)
else:
args[-1]['tags'] = ('viewable',)
ident = super()._create(*args)
self._scan_view()
return ident
def _hscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
canvas.move('all', offset,0)
self._xshifted += offset
self._scan_view()
def _vscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
canvas.move('all', 0,offset)
self._yshifted += offset
self._scan_view()
root = tk.Tk()
canvas = InfiniteCanvas(root)
canvas.pack(fill=tk.BOTH, expand=True)
size, offset, start = 100, 10, 0
canvas.create_rectangle(start,start, size,size, fill='green')
canvas.create_rectangle(
start+offset,start+offset, size+offset,size+offset, fill='darkgreen')
root.mainloop()
PS: Before thinking this is over-complicated and using just find_overlapping isn't working, since it seems the item needs to be at least 51% in the view to get tracked with tkinters algorithm.
You can find an improved version now on CodeReview!
I still don't know what I have done wrong but it works with scan_dragto.
import tkinter as tk
class InfiniteCanvas(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.inview_items = set() #in view
self.niview_items = set() #not in view
self._xshifted = 0 #view moved in x direction
self._yshifted = 0 #view moved in y direction
self._multi = 0
self.configure(confine=False,highlightthickness=0,bd=0)
self.bind('<MouseWheel>', self._vscroll)
self.bind('<Shift-MouseWheel>', self._hscroll)
root.bind('<Control-KeyPress>',lambda e:setattr(self,'_multi', 10))
root.bind('<Control-KeyRelease>',lambda e:setattr(self,'_multi', 0))
return None
def viewing_box(self):
'returns x1,y1,x2,y2 of the currently visible area'
x1 = 0 - self._xshifted
y1 = 0 - self._yshifted
x2 = self.winfo_reqwidth()-self._xshifted
y2 = self.winfo_reqheight()-self._yshifted
return x1,y1,x2,y2
def _scan_view(self):
x1,y1,x2,y2 = self.viewing_box()
for item in self.find_withtag('viewable'):
#check if one felt over the edge
coords = self.coords(item)
#https://www.geeksforgeeks.org/python-split-tuple-into-groups-of-n/
points = tuple(
coords[x:x + 2] for x in range(0, len(coords), 2))
viewable = False
for point in points:
if x1 <= point[0] <= x2 and y1 <= point[1] <= y2:
#if any point is in viewing box
viewable = True
if not viewable:
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current-('viewable',)
else:
print('here',points, (x1,y1,x2,y2))
new = ''
self.inview_items.discard(item)
self.itemconfigure(item,tags=new)
for item in self.find_overlapping(x1,y1,x2,y2):
#check if item inside of viewing_box not in inview_items
if item not in self.inview_items:
self.inview_items.add(item)
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current+('viewable',)
elif isinstance(current, str):
if str:
new = (current, 'viewable')
else:
new = 'viewable'
self.itemconfigure(item,tags=new)
print(self.inview_items)
def _create(self, *args):
if (current:=args[-1].get('tags', False)):
args[-1]['tags'] = current+('viewable',)
else:
args[-1]['tags'] = ('viewable',)
ident = super()._create(*args)
self._scan_view()
return ident
def _hscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
cx,cy = self.winfo_rootx(), self.winfo_rooty()
self.scan_mark(cx, cy)
self.scan_dragto(cx+offset, cy, gain=1)
self._xshifted += offset
self._scan_view()
def _vscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
cx,cy = self.winfo_rootx(), self.winfo_rooty()
self.scan_mark(cx, cy)
self.scan_dragto(cx, cy+offset, gain=1)
self._yshifted += offset
self._scan_view()
root = tk.Tk()
canvas = InfiniteCanvas(root)
canvas.pack(fill=tk.BOTH, expand=True)
size, offset, start = 100, 10, 0
canvas.create_rectangle(start,start, size,size, fill='green')
canvas.create_rectangle(
start+offset,start+offset, size+offset,size+offset, fill='darkgreen')
root.mainloop()

Issues with 2d array indexing python when making game

I'm attempting to program my own connect four game using Python. I'm trying to sort the circles that I have drawn into a 2d array. However when I try to assign my shape object to the array it gives me an index error. I can't really see an issue with counterrow and countrercolumn, can anyone else? Btw my space class just has an initialiser setting x1, x2, y1, y2, taken, and id
from tkinter import *
from space import *
master = Tk();
w = Canvas(master, width = 600, height = 500)
w.pack()
spaceList = []
for i in range(7):
spaceList.append([0] * 6)
currentmove = 'PLAYER1'
won = False
counterrow = 0
countercolumn = 0
for i in range(0,560,80):
for j in range(0,480,80):
w.create_oval(10+i, 10+j, 90+i, 90+j)
newspace = Space(10+i, 10+j, 90+i, 90+j, False, 'EMPTY')
spaceList[counterrow][countercolumn] = newspace
countercolumn = countercolumn + 1
counterrow = counterrow + 1
while(not won):
movecol = int(input("Please select a column!"))
def move(column):
for i in spaceList:
return 0
mainloop()
You have to reset the countercolumn:
for i in range(0,560,80):
# add this:
countercolumn = 0
for j in range(0,480,80):
# omitted
Otherwise it becomes seven and larger and you get an overflow.

Trouble with global dictionary

import tkinter as tk
#messagebox is not imported automatically w/ tkinter
from tkinter import messagebox as tkMessageBox
from tkinter import ttk
from random import random as rand
class Square(object):
""" class to use for each square """
def __init__(self):
self.mine_yn = False
self.flag_yn = False
self.prox_num = 0 # number of nearby mines, parse_mines() will fill this in.
self.button = None # ttk.Button instance.
def parse_mines():
"""Look at how many mines are next to a given square,
store in each Square instance that is inside of sqr_dict. """
global sqr_dict
global mine_frame
print('in parse_mines, sqr_dict='+str(sqr_dict))
def try_a_square(sq): #sq = coordinate string(key)
try:
if sqr_dict[sq].mine_yn == True: return 1
if sqr_dict[sq].mine_yn == False: return 0
except KeyError:
print('KeyError for '+sq)
return 0
n = 0
for x in range(5):
for y in range(4):
#check the 8 adjacent squares.
n = n + try_a_square('x'+str(x+1)+'y'+str(y+1))
n = n + try_a_square('x'+str(x+1)+'y'+str(y ))
n = n + try_a_square('x'+str(x+1)+'y'+str(y-1))
n = n + try_a_square('x'+str(x )+'y'+str(y+1))
n = n + try_a_square('x'+str(x )+'y'+str(y-1))
n = n + try_a_square('x'+str(x-1)+'y'+str(y+1))
n = n + try_a_square('x'+str(x-1)+'y'+str(y ))
n = n + try_a_square('x'+str(x-1)+'y'+str(y-1))
if sqr_dict[('x'+str(x)+'y'+str(y))].mine_yn == False:
(sqr_dict[('x'+str(x)+'y'+str(y))]).prox_num = n
print('x'+str(x)+'y'+str(y)+': '+str(n)) #(debug) print n for each sq
#sqr_dict[('x'+str(x)+'y'+str(y))].button.text=(str(n)) #(debug) show n on each button.
n = 0
def create_mine_field():
global mine_frame
global sqr_dict
sqr_dict = {}
mine_frame = tk.Toplevel(root)
mine_frame.grid()
#what to do if user hit 'X' to close window.
mine_frame.protocol("WM_DELETE_WINDOW", mine_frame_close)
# create grid of squares (buttons)
for x in range(5):
for y in range(4):
coord = 'x'+str(x) + 'y'+str(y)
sqr_dict[coord] = Square()
#print('coord='+coord) #debug
#populate with mines
if ( rand()*100 < mines_pct ):
sqr_dict[coord].mine_yn = True
print(str(sqr_dict[coord].mine_yn))
else:
sqr_dict[coord].mine_yn = False
if sqr_dict[coord].mine_yn:
t = '*'
else: t = ' '
# draw boxes
sqr_dict[coord].button = ttk.Button(mine_frame, text=t, width=3 )
sqr_dict[coord].button.grid(column=x, row=y)
# done, next: parse!
print('in create_mines, sqr_dict='+str(sqr_dict))
#parse_mines()
def root_close():
root.destroy()
def mine_frame_close():
root.destroy()
root = tk.Tk()
root.title("MineSweeper")
mines_pct = 20
start_button = ttk.Button(root,text="Start",command=create_mine_field)
start_button.pack()
root.mainloop()
I'm trying to make a simple minesweeper game with tkinter. If I run the above code a simple mine field appears. However if I uncomment the call to parser() then nothing shows up and it seems like it never finds any mines in the sqr_dict dictionary. (parser() will fill in the numbers of adjacent mines for each square)
I don't understand why this function would cause trouble before it is even called. No mine field appears when it's called. Please kindly give me your suggestions. Thanks!
The reason nothing shows up is because you are using both pack and grid on widgets that are children of the root window. Within any given window you must only use one or the other.

How to clear a bunch of python labels but only when they are called

Hello fellow stackoverflowers,
I'm writing a small program that does some database stuff and I am trying to delete a label but only when it is called.
Here is one of my full functions but the part at the bottom is where I am trying to delete the labels.
def entry_a_result(self):
count = 0
counta = 0
fullfile = fullGUI.f_or()
if len(self.e.get()) > 0:
#print(self.e.get())
file_obj = open(fullfile, "r+")
fullstring = self.e.get()
x = fullstring[5:9]
try:
while(fullstring.startswith("get *") and count < 1):
self.la = Label(root, text = file_obj.read() + "\n" + "--------")
self.la.pack(side=TOP, anchor=N)
count = 1
while(fullstring.startswith("get |" + x) and count < 1):
count = 1
lines = file_obj.readlines()
for line in lines:
if x in line:
self.lb = Label(root, text=line + "\n" + "--------")
self.lb.pack(side=TOP, anchor=N)
else:
pass
**while(fullstring.startswith("clear") and count < 1):
count = 1
labela = self.la.winfo_exists()
labelb = self.lb.winfo_exists()
if labela == 1:
self.la.destroy()
if labelb == 1:
self.lb.destroy()**
What seems to be happening is that winfo_exists() is only working when the label is created and won't attach itself to the variable otherwise.
To clear up any confusion,
I'm trying to destroy or remove a label when and only when the label is existing in this case whenever I call a certain parameter within the program, which is currently working(in that my params work)

Python hangman problems

So I have my hangman almost working however, it is only placing the first correct guess on the first line, and not continuing if I guess the second letter correctly. Also, no matter what letter I guess, it always draws the head on the third letter guessed. I have it narrowed down to my getGuessLocationInPuzzle function, where my index is always printing out zero even if I have guessed my second letter. I've been staring at this code for 2 and 1/2 hours, and can't solve it. Please help!
''' Import Statements '''
from graphics import *
import copy
import random
import string
def drawGallows(window):
gallows = Rectangle(Point(300,280),Point(200,300))
gallows.draw(window)
gallows = Line(Point(250,300),Point(250,100))
gallows.draw(window)
gallows = Line(Point(250,100),Point(120,100))
gallows.draw(window)
gallows = Line(Point(120,100),Point(120,120))
gallows.draw(window)
def getPuzzle():
fh = open("puzzle.txt","r")
fileList = fh.read()
files = string.split(fileList)
puzzle = random.choice(files)
return puzzle
def drawPuzzle(length, window):
lines = Line(Point(15,275),Point(25,275))
for index in range(length):
newline = copy.copy(lines)
newline.move(20,0)
newline.draw(window)
def getGuess(entry, window):
letter = entry.getText()
return letter
def getValidGuess(Validguesses, entry, window):
guess = getGuess(entry, window)
for index in Validguesses:
if guess == index:
return guess
def createTextOverlay(guess):
textstring = ""
num = ord(guess)
for index in range(num - ord("a")):
textstring = textstring + " "
textstring= textstring + guess
for index in range(25-(ord(guess)-ord("a"))):
textstring = textstring+" "
overlay = Text(Point(150, 375), textstring)
overlay.setFace("courier")
overlay.setFill("red")
return overlay
def updateGallows(numWrongGuesses, window):
if numWrongGuesses==1:
head = Circle(Point(120,140),20)
head.draw(window)
if numWrongGuesses==2:
body = Line(Point(120,220),Point(120,160))
body.draw(window)
if numWrongGuesses==3:
arms1 = Line(Point(100,200),Point(120,160))
arms1.draw(window)
if numWrongGuesses==4:
arms2 = Line(Point(120,160),Point(140,200))
arms2.draw(window)
if numWrongGuesses==5:
leg1 = Line(Point(120,220),Point(100,250))
leg1.draw(window)
if numWrongGuesses==6:
leg2 = Line(Poin(120,220),Point(140,250))
leg2.draw(window)
def updatePuzzleDisplay(locations, letter, window):
# For each value in the locations list, print out the letter above the
# appropriate blank space.
# For each value in locations list, create a text object.
locationonspaces = 35
for index in locations:
locationonspaces = locationonspaces + 5
letterthatisguessed = Text(Point(locationonspaces, 265 ), letter)
letterthatisguessed.draw(window)
# Create text object
def getGuessLocationInPuzzle(puzzle, guess):
word = len(puzzle)
locations = []
for index in range(word):
if puzzle[index]==guess:
locations.append(index)
print guess
print index
return locations
def main():
window = GraphWin("Hangman Final", 400, 400)
drawGallows(window)
lettersToGuess = Text(Point(150, 375), "abcdefghijklmnopqrstuvwxyz")
lettersToGuess.setFace("courier")
lettersToGuess.draw(window)
guessInput = Entry(Point(250, 325), 10)
guessInput.draw(window)
puzzle = getPuzzle()
print puzzle
length = len(puzzle)
drawPuzzle(length,window)
guess = getGuess(guessInput,window)
window.getMouse()
Validguesses = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
numWrongGuesses=0
while True:
guess = getValidGuess (Validguesses,guessInput,window)
#print guess
window.getMouse
locations = getGuessLocationInPuzzle(puzzle,guess)
# print locations
if locations == []:
overlay = createTextOverlay(guess)
overlay.draw(window)
updateGallows(numWrongGuesses,window)
numWrongGuesses = numWrongGuesses + 1
else:
updatePuzzleDisplay(locations, guess, window)
overlay = createTextOverlay(guess)
overlay.draw(window)
if guess !=0:
Validguesses.remove(guess)
window.getMouse()
window.getMouse()
main()

Categories