I am working on a Human machine interface design...
I have created an array of labels...I have used same label and properties for all the labels in the array, but when i executive my code, size of the labels do not match with each other....
This is my code....
from tkinter import *
import time
root = Tk()
class Clock:
def __init__(self):
self.time1 = ''
self.time2 = time.strftime('%H:%M:%S')
self.mFrame = Frame()
self.mFrame.pack(side=TOP,fill=X)
self.bottomFrame = Frame()
self.bottomFrame.pack(side=BOTTOM,fill=BOTH)
root.title('HUMAN MACHINE INTERFACE')
self.company_name=Label(self.mFrame, text='Host Company Name', font=('Arial',24,'bold','underline'),bg='white',relief=RAISED).pack(fill=X)
self.machine_name=Label(self.mFrame, text='Machine name', font=('Arial',24,'bold','underline'),bg='yellow',relief=RAISED).pack(fill=X)
self.my_company_name=Label(self.mFrame, text='SYNERGY AUTOMATION', font=('Arial',24,'bold','underline'),relief=SUNKEN).pack(side=LEFT)
self.watch = Label(self.mFrame, text=self.time2, font=('times',24,'bold'),relief=SUNKEN)
self.watch.pack(side=RIGHT)
self.clock() #first call it manually
num=['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16',]
r=0
c=0
for i in num:
Label(self.bottomFrame, text=i,bg='red',fg='white',font=(34),padx=50,pady=20,relief=SUNKEN).grid(row=r,column=c)
c = c + 1
if c == 4 or c == 8:
c=0
r=r+1
def clock(self):
self.time2 = time.strftime('%H:%M:%S')
self.watch.configure(text=self.time2)
self.mFrame.after(200, self.clock) #it'll call itself continuously
obj1 = Clock()
root.mainloop()
You aren't using the sticky option when you call grid. Without it, the labels will only be big enough to contain the text. If you want them to fill the table cells, use something like sticky=N+S+E+W
Label(...).grid(... sticky=N+S+E+W)
By the way,
self.company_name=Label(...).pack(fill=X)
You are setting self.company_name to None because that's what pack returns. A tkinter best practice is to separate the creation of the widget from laying it out on the screen:
self.company_name=Label(...)
...
self.company_name.pack(fill=X)
Related
I am creating a TicTacToe game in tkinter, consisting of a 3x3 grid made out of buttons.
In the code below, once a player has drawn on a tile (by clicking on the button), the program should remove this tile from the list 'self.flattenedButtons'. This is to prevent the computer (player 2) from drawing on the same tile.
The method this check is made in is self.add_move(). This works on all buttons apart from the bottom right, I assume this is as I took away 1 from the ending range. If I do not do this I am given an 'out of range' error.
How would I change my method so it works on all buttons?
CODE:
from tkinter import *
from functools import partial
from itertools import *
import random
class Window(Frame):
def __init__(self, master = None): # init Window class
Frame.__init__(self, master) # init Frame class
self.master = master # allows us to refer to root as master
self.rows = 3
self.columns = 3
self.guiGrid = [[None for x in range(self.rows)] for y in range(self.columns)] # use this for the computer's moves
self.buttonText = StringVar(value = '')
self.buttonText2 = StringVar(value = 'X')
self.buttonText3 = StringVar(value = 'O')
self.button_ij = None
self.flattenedButtons = []
self.create_window()
self.add_buttons()
def create_window(self):
self.master.title('Tic Tac Toe')
self.pack(fill = BOTH, expand = 1)
for i in range(0,3):
self.grid_columnconfigure(i, weight = 1)
self.grid_rowconfigure(i, weight = 1)
def add_buttons(self):
rows = 3
columns = 3
for i in range (rows):
for j in range(columns):
self.button_ij = Button(self, textvariable = self.buttonText, command = lambda i=i, j=j: self.add_move(i,j))
self.guiGrid[i][j] = self.button_ij # place button into 2d array to access later on
self.flattenedButtons.append(self.button_ij)
self.button_ij.grid(row = i,column = j, sticky =E+W+S+N)
def add_move(self, i,j):
pressedButton = self.guiGrid[i][j]
self.guiGrid[i][j].config(textvariable =self.buttonText2)
for i in range(0, len(self.flattenedButtons)-1):
if (self.flattenedButtons[i] == pressedButton):
self.flattenedButtons.remove(self.flattenedButtons[i])
print('removed')
else:
pass
root = Tk() # creating Tk instance
rootWidth = '500'
rootHeight = '500'
root.geometry(rootWidth+'x'+rootHeight)
ticTacToe = Window(root) # creating Window object with root as master
root.mainloop() # keeps program running
It is not recommended to operate the list when you iterate it.
If your code is:
for i in range(0, len(self.flattenedButtons)-1):
if (self.flattenedButtons[i] == pressedButton):
self.flattenedButtons.remove(self.flattenedButtons[i])
print('removed')
else:
pass
print(self.flattenedButtons)
You will see that your button 9 will never be removed.
Change your for loop to a easy list-comprehension:
self.flattenedButtons = [i for i in self.flattenedButtons if i != pressedButton]
print(self.flattenedButtons)
You will see the change.
I'm trying to build an image viewer that loads the images from a folder. It should have forward / backward and quit buttons. It seems to work just fine with one issue:
So I just take the image paths with this:
def get_files_from_folder(path, allowed_extensions):
found_paths = []
for (dir_path, _, file_names) in os.walk(path):
for file_name in file_names:
for allowed_extension in allowed_extensions:
if file_name.lower().endswith(allowed_extension.lower()):
found_paths.append(os.path.join(dir_path, file_name))
break
return found_paths
I have the tkinter UI for the image viewer:
class UI:
def __init__(self, icon_path, images_folder_path):
self.current_index = 0
self.root = tk.Tk()
self.root.title('Images')
self.root.iconbitmap(icon_path)
self.button_quit = tk.Button(self.root, text = 'Quit', padx = 60, command = self.root.quit)
self.button_forward = tk.Button(self.root, text = '>>', command = self.forward)
self.button_backward = tk.Button(self.root, text = '<<', command = self.backward)
self.button_quit.grid(row = 1, column = 1)
self.button_forward.grid(row = 1, column = 2)
self.button_backward.grid(row = 1, column = 0)
self.images_paths = get_files_from_folder(images_folder_path, ['.jpg', '.png'])
self.tk_images = []
print(get_files_from_folder)
for image_path in self.images_paths:
self.tk_images.append(ImageTk.PhotoImage(Image.open(image_path)))
self.current_image = tk.Label(image = self.tk_images[0])
self.current_image.grid(column = 0, row = 0, columnspan = 3)
self.root.mainloop()
And for some reason, here when I'm using the tk.DISABLED, it just won't disable it
def backward(self):
if self.current_index == 0:
self.button_backward = self.button_backward = tk.Button(self.root, text = '<<', command = self.backward, state = tk.DISABLED)
self.current_image.grid_forget()
self.current_index -= 1
self.current_image = tk.Label(image = self.tk_images[self.current_index])
self.current_image.grid(column = 0, row = 0, columnspan = 3)
same for the forward:
def forward(self):
self.current_image.grid_forget()
if self.current_index == len(self.tk_images)-1:
self.button_forward = self.button_forward = tk.Button(self.root, text = '>>', command = self.forward, state = tk.DISABLED)
else:
self.button_forward.state = tk.ACTIVE
self.current_index += 1
self.current_image = tk.Label(image = self.tk_images[self.current_index])
self.current_image.grid(column = 0, row = 0, columnspan = 3)
There's at least a couple of things wrong with your current code regarding the forward and backward Buttons commands. As for disabling the Buttons, that can be done by calling their config() method — not by creating a new one or assigning a new value to the existing ones state attribute (i.e. self.button_forward.state = tk.ACTIVE).
Similarly it's not a good practice to continually create new tk.Label widgets every time one of the buttons is pressed and the image on it changes. It's better to change the image= option of the existing Label. Doing this also often simplifies what needs to be done.
In addition your logic for clamping the self.current_index was flawed and would allow it to get out of range and IndexErrors to occur. This is more complicated than I anticipated, but I think I figured out a good way to handle it, namely by putting all the logic a private method _change_current_index() and calling it from the forward and backward callback functions.
Lastly, user #acw1668 commented that it loading all images into memory at program start might be two slow. To avoid that, I replaced the list of all the loaded images you had (self.tk_images) with calls to a another new private method named _open_image() which caches its results by having the functools.lru_cache decorator applied to it. This makes the method remember what values it has already returned for a limited number of indices thereby restricting the number of them in memory at any one time.
Note I also reformatted the code to better conform to the PEP 8 - Style Guide for Python Code guidelines to make it more readable.
from functools import lru_cache
import tkinter as tk
import os
from PIL import Image, ImageTk
class UI:
IMG_CACHE_SIZE = 10
def __init__(self, icon_path, images_folder_path):
self.current_index = 0
self.root = tk.Tk()
self.root.title('Images')
# self.root.iconbitmap(icon_path)
self.button_quit = tk.Button(self.root, text='Quit', padx=60, command=self.root.quit)
self.button_forward = tk.Button(self.root, text='>>', command=self.forward)
self.button_backward = tk.Button(self.root, text='<<', command=self.backward)
self.button_quit.grid(row=1, column=1)
self.button_forward.grid(row=1, column=2)
self.button_backward.grid(row=1, column=0)
self.image_paths = get_files_from_folder(images_folder_path, ['.jpg', '.png'])
self.current_image = tk.Label(image=self._open_image(0))
self.current_image.grid(column=0, row=0, columnspan=3)
self._change_current_index() # Initializes fwd and bkd button states.
self.root.mainloop()
#lru_cache(maxsize=IMG_CACHE_SIZE)
def _open_image(self, i):
image_path = self.image_paths[i]
return ImageTk.PhotoImage(Image.open(image_path))
def backward(self):
self._change_current_index(-1)
def forward(self):
self._change_current_index(1)
def _change_current_index(self, delta=0):
self.current_index += delta
# Update state of forward and backward buttons based on new index value.
bkd_state = (tk.DISABLED if self.current_index == 0 else tk.ACTIVE)
self.button_backward.config(state=bkd_state)
fwd_state = (tk.DISABLED if self.current_index == len(self.image_paths)-1 else tk.ACTIVE)
self.button_forward.config(state=fwd_state)
# Update image on Label.
self.current_image.config(image=self._open_image(self.current_index))
def get_files_from_folder(path, allowed_extensions):
found_paths = []
for (dir_path, _, file_names) in os.walk(path):
for file_name in file_names:
for allowed_extension in allowed_extensions:
if file_name.lower().endswith(allowed_extension.lower()):
found_paths.append(os.path.join(dir_path, file_name))
break
return found_paths
if __name__ == '__main__':
UI('.', './images_test')
everyone. I'm new to Python and trying to learn it as my future jobs will require me knowing it. I'm playing around with Tkinter, trying to get a pinging script to work. The result of this script will show a list of servers in column 0 and a list of whether it is up or down in column 1. I have it working except for one thing: the widgets overlap, causing this script to be a memory hog. For example, if the site "google.com" responds with "UP" and I take down my internet, it will show as "DOWN". However, as soon as a plug my internet back in, it will show as "UP" but I can see the remnants of the word "DOWN" behind the label. I've tried different ways to destroy the widget before every utilization but can not get it to work. I understand if my code is a little messy so I'm definitely open to criticism. Below is the code I have with a few example sites listed in the "host" variable:
import pyping
import Tkinter as tk
from Tkinter import *
import time
host = ["google.com", "yahoo.com", "espn.com"]
root = tk.Tk()
class PingTest:
result = []
resultfc = []
def __init__(self, hostname, inc):
self.hostname = hostname
self.inc = inc
self.ping(hostname)
def results(self, result1, resultfc1):
self.result = result1
self.resultfc = resultfc1
def ping(self, y):
self.y = y
q = ""
try:
x = pyping.ping(self.y, count=1)
q = x.ret_code
except Exception:
pass
finally:
if q == 0:
self.results("UP", "green")
else:
self.results("DOWN", "red")
self.window()
def window(self):
self.label1 = Label(root, text=self.hostname)
self.label2 = Label(root, text=self.result, fg=self.resultfc, bg="black")
a = Label(root, text=self.hostname)
b = Label(root, text=self.result, fg=self.resultfc, bg="black")
b.update()
b.update_idletasks()
if b == TRUE:
b.grid_forget() # These two lines don't seem to help my cause
b.destroy()
a.grid(row=self.inc, column=0)
b.grid(row=self.inc, column=1)
while TRUE:
i = 0
for h in host:
PingTest(h, i)
i += 1
time.sleep(1)
I would update labels instead of destroying them.
We can use threading to check each site without having to block the mainloop().
By creating a list of labels you can use the index of the list to set up the labels on your GUI and at the same time we can start a thread per object in list to check on the site status and return if site is up or down. I chose to use urllib and threading to make this work.
import tkinter as tk
import urllib.request
import threading
import time
host = ["google.com", "yahoo.com", "espn.com"]
class CheckURL:
def __init__(self, host, widget):
self.host = host
self.widget = widget
self.update_labels()
def update_labels(self):
if urllib.request.urlopen("http://www." + self.host).getcode() == 200:
self.widget.config( text='UP', fg='green')
else:
self.widget.config(text='DOWN', fg='red')
time.sleep(5)
self.update_labels()
root = tk.Tk()
labels = []
for ndex, x in enumerate(host):
tk.Label(root, text=x).grid(row=ndex, column=0)
labels.append(tk.Label(root, text='DOWN', fg='red'))
labels[-1].grid(row=ndex, column=1)
threading._start_new_thread(CheckURL, (x, labels[-1]))
root.mainloop()
I'm new in Python and I'm currently trying to use tkinter as first GUI. I was used to making it without classes. And it is my first time to use import tkinter as tk instead of import *
import tkinter as tk
def update():
pass
#Game.statsFrame #doesn't work Game.statsFrame.stat1_amountLabel too
#Game.stat1_amountLabel #doesnt work < want to use update_idletasks() or
#just type new cofnig...
#just errors like: "Game' has no attribute 'statsFrame" etc #Game
class character:
name = ""
experience = 0
level = 0
gold = 0
stat1 = 0
stat2 = 0
stat3 = 0
stat4 = 0
stat5 = 0
avaiblePoints = 0
def add_stat1(self):
if self.avaiblePoints >= 1:
self.stat1 += 1
self.avaiblePoints -= 1
update()
else:
pass
def add_stat2(self):
if self.avaiblePoints >= 1:
self.stat2 += 1
self.avaiblePoints -= 1
update()
[...]
myChar = character()
myChar.avaiblePoints = 3
class Game:
def __init__(self, parent):
self.myParent = parent
self.myGame = tk.Frame(parent)
self.myGame.grid()
self.statsFrame = tk.Frame(self.myGame).grid()
self.stat1Label = tk.Label(self.statsFrame)
self.stat1Label.config(text="Strength:")
self.stat1Label.grid(column=1, row=1)
self.stat1_amountLabel = tk.Label(self.statsFrame)
self.stat1_amountLabel.config(text=myChar.stat1)
self.stat1_amountLabel.grid(column=2, row=1)
self.add_stat1Button = tk.Button(self.statsFrame)
self.add_stat1Button.config(text="+", command=myChar.add_stat1)
self.add_stat1Button.grid(column=3, row=1)
root = tk.Tk()
myapp = Game(root)
root.mainloop()
But I can't get to (for example) stat1Label and change text inside it and after it use update_idletasks(). It's like it doesnt exist. Errors shows that Game has not atributtes like stat1Label etc.
I want to use it becouse I have read that __init__ method is better and I want to swtich between pages. I have no idea, when I wasn't using class in tkinter some things (like this) was easier and had no problems. I'm very confused guys.
It's excellent that you're using import tkinter as tk instead of the dreaded "star" import, and that you're trying to organize your code with classes. It can be a little confusing at first, but it makes your code more modular, which helps enormously, especially when the GUI gets large.
There are a few problems with your code. The most important one is this line:
self.statsFrame = tk.Frame(self.myGame).grid()
The .grid method (and .pack and .place) all return None. So that line saves None to self.statsFrame, not the Frame widget. So when you later try to do stuff with self.statsFrame it won't do what you expect.
Another problem is that the text attribute of your self.stat1_amountLabel doesn't track the value of myChar.stat1, so when you change the value of myChar.stat1 you need to explicitly update the Label with the new value. Alternatively, you could use the textvariable attribute with an IntVar to hold the character's stat. See the entry for textvariable in the Label config docs for info.
Your character class has a whole bunch of attributes like name, experience etc as class attributes. That's not a good idea because class attributes are shared by all instances of a class. But you probably want each character instance to have their own instance attributes. So you should give character an __init__ method where you set those attributes. OTOH, it's ok to use class attributes for default values that get overridden by instance attributes.
Anyway, here's a repaired version of your code with a Button that updates the Strength stat. I've put the stats in a list, rather than having a bunch of separate named stats that would have to be managed separately. And I've given Game a make_stat method so you can easily add rows for the other stats.
import tkinter as tk
class Character:
def __init__(self, availablePoints=0):
self.name = ""
self.experience = 0
self.level = 0
self.gold = 0
self.stats = [0] * 5
self.availablePoints = availablePoints
def add_stat(self, idx):
if self.availablePoints >= 1:
self.stats[idx] += 1
self.availablePoints -= 1
class Game:
def __init__(self, parent):
self.myParent = parent
self.myGame = tk.Frame(parent)
self.myGame.grid()
self.statsFrame = tk.Frame(self.myGame)
self.statsFrame.grid()
self.make_stat("Strength:", 0, 0)
def make_stat(self, text, idx, row):
label = tk.Label(self.statsFrame, text=text)
label.grid(column=1, row=row)
amount = tk.Label(self.statsFrame, text=myChar.stats[idx])
amount.grid(column=2, row=row)
def update():
myChar.add_stat(idx)
amount["text"] = myChar.stats[idx]
button = tk.Button(self.statsFrame, text="+", command=update)
button.grid(column=3, row=row)
myChar = Character(3)
root = tk.Tk()
myapp = Game(root)
root.mainloop()
This code is still not ideal, but it's an improvement. ;) For example, it would be good to give Game a method for creating new characters, rather than creating them in the global context. You could store them in a dict attribute of Game, using the character's name as the key.
Here's a new version that works on separate named stat attributes. As I said in the comments, doing it this way is more complicated (and less efficient) than using a list to hold the stats.
import tkinter as tk
class Character:
def __init__(self, availablePoints):
self.name = ""
self.experience = 0
self.level = 0
self.gold = 0
self.stat1 = 0
self.stat2 = 0
self.stat3 = 0
self.stat4 = 0
self.stat5 = 0
self.availablePoints = availablePoints
class Game:
def __init__(self, parent):
self.myParent = parent
self.myGame = tk.Frame(parent)
self.myGame.grid()
self.statsFrame = tk.Frame(self.myGame)
self.statsFrame.grid()
self.make_stat("Strength:", "stat1", 1, 1)
def make_stat(self, text, stat, column, row):
label = tk.Label(self.statsFrame, text=text)
label.grid(column=column, row=row)
amount = tk.Label(self.statsFrame, text=getattr(myChar, stat))
amount.grid(column=(column+1), row=row)
def update():
if myChar.availablePoints >= 1:
v = getattr(myChar, stat) + 1
setattr(myChar, stat, v)
myChar.availablePoints -= 1
amount["text"] = v
button = tk.Button(self.statsFrame, text="+", command=update)
button.grid(column=(column+2), row=row)
myChar = Character(5)
root = tk.Tk()
myapp = Game(root)
root.mainloop()
I'm trying to write a python class to display data in a tabular format. I'm sure there are classes out there already to do the same thing, however, I'm using this exercise as a way to teach myself Python and tkinter. For the most part, I have the class working the way I want it to, however I cannot get the header and data cells to fill their entire cell, while being aligned left. Here is what my class currently generates for a table:
I went ahead and changed the sticky on the cells to be (W,E) rather than just W, in order to show how I want the table to look, except each cell left justified. Below is what I'm shooting for:
Based on the research I've done, it would seem I need to be using the weight attribute of grid_columnconfigure and grid_rowconfigure, however every way I have tried using them I cannot, get it to work.
Here is the code for my class (I am using Python 3.4):
from tkinter import *
from tkinter import ttk
from tkinter import font
class TableData:
def __init__(self,parent,attributes,columns,data):
self.parent = parent
self.tableName = StringVar()
self.tableName.set(attributes['tableName'])
self.columns = columns
self.columnCount = 0
self.borderColor = attributes['borderColor']
self.titleBG = attributes['titleBG']
self.titleFG = attributes['titleFG']
self.titleFontSize = attributes['titleFontSize']
self.headerBG = attributes['headerBG']
self.headerFG = attributes['headerFG']
self.headerFontSize = attributes['headerFontSize']
self.dataRowColor1 = attributes['dataRowColor1']
self.dataRowColor2 = attributes['dataRowColor2']
self.dataRowFontSize = attributes['dataRowFontSize']
self.dataRowFG = attributes['dataRowFG']
self.data = data
self.tableDataFrame = ttk.Frame(self.parent)
self.tableDataFrame.grid(row=0,column=0)
self.initUI()
def countColumns(self):
cnt = 0
for i in self.columns:
cnt += 1
self.columnCount = cnt
def buildTableTitle(self):
tableTitleFont = font.Font(size=self.titleFontSize)
Label(self.tableDataFrame,textvariable=self.tableName,bg=self.titleBG,fg=self.titleFG,font=tableTitleFont, highlightbackground=self.borderColor,highlightthickness=2).grid(row=0,columnspan=self.columnCount,sticky=(W,E), ipady=3)
def buildHeaderRow(self):
colCount = 0
tableHeaderFont = font.Font(size=self.headerFontSize)
for col in self.columns:
Label(self.tableDataFrame,text=col,font=tableHeaderFont,bg=self.headerBG,fg=self.headerFG,highlightbackground=self.borderColor,highlightthickness=1).grid(row=1,column=colCount,sticky=W, ipady=2, ipadx=5)
colCount += 1
def buildDataRow(self):
tableDataFont = font.Font(size=self.dataRowFontSize)
rowCount = 2
for row in self.data:
if rowCount % 2 == 0:
rowColor = self.dataRowColor2
else:
rowColor = self.dataRowColor1
colCount = 0
for col in row:
Label(self.tableDataFrame,text=col,bg=rowColor,fg=self.dataRowFG,font=tableDataFont,highlightbackground=self.borderColor,highlightthickness=1).grid(row=rowCount,column=colCount,sticky=W,ipady=1, ipadx=5)
colCount += 1
rowCount += 1
def initUI(self):
self.countColumns()
self.buildTableTitle()
self.buildHeaderRow()
self.buildDataRow()
Here is a test file referencing the TableData class:
from tkinter import *
from tkinter import ttk
from tableData import TableData
import sqlite3
root = Tk()
root.geometry('1000x400')
mainframe = ttk.Frame(root).grid(row=0,column=0)
attributes = {}
attributes['tableName'] = 'Title'
attributes['borderColor'] = 'black'
attributes['titleBG'] = '#1975D1'
attributes['titleFG'] = 'white'
attributes['titleFontSize'] = 16
attributes['headerBG'] = 'white'
attributes['headerFG'] = 'black'
attributes['headerFontSize'] = 12
attributes['dataRowColor1'] = 'white'
attributes['dataRowColor2'] = 'grey'
attributes['dataRowFontSize'] = 10
attributes['dataRowFG'] = 'black'
columns = ['Col 1', 'Column 2', 'Column 3','Column 4']
results = [('1','Key','Desc','Attribute'),('2','Key Column','Description Column','AttributeColumn')]
table = TableData(mainframe,attributes,columns,results)
root.mainloop()
Thanks in advance for any insight. Please, let me know if there is any other info that would be helpful.
For any grid of geometry, add option sticky="W", for example,
self.tableDataFrame.grid(sticky="W", row=0, column=0)
If you want the text in a label to be left-aligned, use the anchor option. It takes a string representing a point on a compass (eg: "w" = "west", meaning the text is anchored to the left):
for col in row:
Label(..., anchor="w").grid(...)
You are not defining the width of your label so tkinter sets the width of Label equal to the width of the text. Thats why your labels do not occupy entire width of the cell. To make them occupy all the available width use stickey = E+W
Label(self.tableDataFrame,text=col,font=tableHeaderFont,bg=self.headerBG,fg=self.headerFG,highlightbackground=self.borderColor,highlightthickness=1).grid(row=1,column=colCount,sticky=W+E, ipady=2, ipadx=5)
Try to set the columnspan attribute when griding:
self.tableDataFrame.grid(row=0, column=0, columnspan=2)
2 may not be the correct size, try 'til you get it like you want it.