I'm trying to implement a simple HEX Viewer by using three Text() boxes which are set to scroll simultaneously.
However, it seems as though there's some kind of "drift" and at some point, the first box loses alignment with the other two. I can't seem to figure out why.
import tkinter as tk
import os
def chunker(seq, size: int):
return (seq[pos:pos + size] for pos in range(0, len(seq), size))
TAG_JUSTIFY_RIGHT = 'justify_right'
class Application(tk.Frame):
BYTES_PER_ROW = 16
def __init__(self, parent = None):
tk.Frame.__init__(self, parent)
self.textbox_address = tk.Text(self, width = 17, padx = 10, wrap = tk.NONE, bd = 0)
self.textbox_address.pack(side = tk.LEFT, fill=tk.Y, expand = False)
self.textbox_address.tag_configure(TAG_JUSTIFY_RIGHT, justify = tk.RIGHT)
self.textbox_hex = tk.Text(self, width = 47, padx = 10, wrap = tk.NONE, bd = 0)
self.textbox_hex.pack(side = tk.LEFT, fill = tk.Y, expand = False)
self.textbox_ascii = tk.Text(self, width = 17, padx = 10, wrap = tk.NONE, bd = 0)
self.textbox_ascii.pack(side = tk.LEFT, fill = tk.Y, expand = False)
self.textboxes = [self.textbox_address, self.textbox_hex, self.textbox_ascii]
self.scrollbar = tk.Scrollbar(self)
self.scrollbar.pack(side = tk.RIGHT, fill = tk.Y, expand = False)
self.scrollbar['command'] = self._on_scrollbar
for textbox in self.textboxes:
textbox['yscrollcommand'] = self._on_textscroll
self.pack(fill = tk.BOTH, expand = True, side = tk.RIGHT)
def _on_scrollbar(self, *args) -> None:
for textbox in self.textboxes:
textbox.yview(*args)
def _on_textscroll(self, *args) -> None:
self.scrollbar.set(*args)
self._on_scrollbar('moveto', args[0])
def _populate_address_area(self, num_bytes: int) -> None:
num_lines = num_bytes // self.BYTES_PER_ROW
chars_per_byte = 2
format_pad_len = 8 * chars_per_byte
for i in range(num_lines + 1):
base_address = format(i * self.BYTES_PER_ROW, 'X').rjust(format_pad_len, '0')
self.textbox_address.insert(tk.END, f"{base_address}\n")
self.textbox_address.tag_add(TAG_JUSTIFY_RIGHT, 1.0, tk.END)
self.textbox_address.config(state = tk.DISABLED)
def populate_hex_view(self, byte_arr: bytes) -> None:
self._populate_address_area(len(byte_arr))
for chunk in chunker(byte_arr, self.BYTES_PER_ROW):
hex_format = chunk.hex(" ")
self.textbox_hex.insert(tk.END, f"{hex_format}\n")
ascii_format = "".join([chr(i) if 32 <= i <= 127 else "." for i in chunk])
self.textbox_ascii.insert(tk.END, f"{ascii_format}\n")
self.textbox_hex.config(state = tk.DISABLED)
self.textbox_ascii.config(state = tk.DISABLED)
if __name__ == "__main__":
root = tk.Tk()
root.title('Hex View')
app = Application(parent = root)
app.populate_hex_view(bytearray(os.urandom(0x4000)))
root.mainloop()
At first, everything is aligned correctly:
However, after a while, there's a visible misalignment between the first column and the other two:
At first I thought that this might be related to line heights, but setting spacing1, spacing2, and spacing3 didn't help. The problem also persists if I replace all non-whitespace characters with a single character such as 'O' (to make sure all characters have the same height).
How can I ensure that all three boxes stay aligned?
Inside _populate_address_area, there is a for loop: for i in range(num_lines + 1):. This is the cause of the problem. Using num_lines + 1 adds one too many linse to textbox_address. To fix this, there are two options: deleting the + 1, or using for i in range(1, num_lines + 1):. Either way, textbox_address will have the correct number of lines.
Related
I'm working on a Tkinter GUI. I want to make two separate frames have the same width so that they line up vertically. I do not know of any way to put them inside the same structure because they need to be independently scrollable in the vertical direction (that functionality is not yet in the code).
I tried various ways of what I thought was setting the lower frame width equal to the upper frame width because the lower frame has a smaller width by default, but that seems to have no effect.
See lines
# Match widths
self.vert_frames[-1][1].configure(width=self.vert_frames[-1][0].cget("width"))
I'm new to both stackoverflow and python, so let me know if you need more details or have corrections to my question formatting or anything else. I've been a longtime reader, so I'm excited to hear what solutions you all know about!
Thank you all in advance.
# Imports
import tkinter as tk
from tkinter import ttk
# Define GUI CLASS
class tl_GUI:
# initialize the GUI
def __init__(self, root):
self.root = root
# set geometry
# self.root.geometry("480x320+150+150") # remove if not used in final version
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
# SET STRUCTURE
# create data_canvas
self.data_canvas = tk.Canvas(self.root)
self.data_canvas.grid(row = 0, column = 0, sticky = "nsew")
# create 2-pane PanedWindow
self.data_panedwindow = ttk.Panedwindow(self.data_canvas, orient="vertical")
self.data_panedwindow.pack(fill="both", expand=True)
# create input canvas
self.in_canvas = tk.Canvas(self.data_panedwindow)
self.data_panedwindow.add(self.in_canvas, weight=1)
# create input frame
self.in_frame = ttk.Frame(self.in_canvas, relief="sunken")
self.in_frame.pack()
# create output canvas
self.out_canvas = tk.Canvas(self.data_panedwindow)
self.data_panedwindow.add(self.out_canvas, weight=1)
# create output frame
self.out_frame = ttk.Frame(self.out_canvas, relief="sunken")
self.out_frame.pack()
# CREATE INITIAL I/O FRAMES
self.curr_compares = 0
self.vert_frames = []
for init_frames in range(0, 2):
self.add_compareIO()
# define method to add a vertical I/O frame
def add_compareIO(self):
# create input and output frames
self.vert_frames.append([])
self.vert_frames[-1].append(ttk.Frame(self.in_frame, padding = 2, relief = "groove"))
self.vert_frames[-1].append(ttk.Frame(self.out_frame, padding = 2, relief = "groove"))
self.vert_frames[-1][0].grid(row = 0, column = self.curr_compares)
self.vert_frames[-1][1].grid(row = 0, column = self.curr_compares)
# close_frame
col = len(self.vert_frames)-1 # may be able to use self.curr_compares instead
self.vert_frames[-1][0].button_close = ttk.Label(self.vert_frames[-1][0],
text="[Image here]")
self.vert_frames[-1][0].button_close.grid(row = 0, column = 0, columnspan=2, stick = "e")
self.vert_frames[-1][0].button_close.bind("<Button-1>",
lambda e: self.destroy_frame(col_num=col))
self.vert_frames[-1][1].button_close = ttk.Label(self.vert_frames[-1][1],
text="[Image here]")
self.vert_frames[-1][1].button_close.grid(row = 0, column = 0, columnspan=2, stick = "e")
self.vert_frames[-1][1].button_close.bind("<Button-1>",
lambda e: self.destroy_frame(col_num=col))
# populate inputs
self.vert_frames[-1][0].label_hpp = ttk.Label(self.vert_frames[-1][0], text = "INPUT1")
self.vert_frames[-1][0].entry_hpp = ttk.Entry(self.vert_frames[-1][0], width = 13)
self.vert_frames[-1][0].entry_hpp.bind("<KeyRelease>", lambda e: self.refresh_calculations(col)) # recalculate when any keyboard button is pressed
self.vert_frames[-1][0].label_int_rate = ttk.Label(self.vert_frames[-1][0], text = "INPUT2")
self.vert_frames[-1][0].entry_int_rate = ttk.Entry(self.vert_frames[-1][0], width = 13)
self.vert_frames[-1][0].entry_int_rate.bind("<KeyRelease>", lambda e: self.refresh_calculations(col))
self.vert_frames[-1][0].label_RENAME = ttk.Label(self.vert_frames[-1][0], text = "INPUT3")
self.vert_frames[-1][0].entry_RENAME = ttk.Entry(self.vert_frames[-1][0], width = 13)
self.vert_frames[-1][0].entry_RENAME.bind("<KeyRelease>", lambda e: self.refresh_calculations(col))
self.vert_frames[-1][0].label_hpp.grid(row = 1, column = 0)
self.vert_frames[-1][0].entry_hpp.grid(row = 1, column = 1)
self.vert_frames[-1][0].label_int_rate.grid(row = 2, column = 0)
self.vert_frames[-1][0].entry_int_rate.grid(row = 2, column = 1)
self.vert_frames[-1][0].label_RENAME.grid(row = 3, column = 0)
self.vert_frames[-1][0].entry_RENAME.grid(row = 3, column = 1)
# populate outputs
self.vert_frames[-1][1].label_tcl = ttk.Label(self.vert_frames[-1][1], text = "OUTPUT1: {}".format(10))
self.vert_frames[-1][1].label_tcl.grid(row = 1, column = 0)
# Match widths
self.vert_frames[-1][1].configure(width=self.vert_frames[-1][0].cget("width"))
# update counter
self.curr_compares += 1
def destroy_frame(self, col_num):
self.vert_frames[col_num][0].grid_forget()
self.vert_frames[col_num][1].grid_forget()
self.vert_frames[col_num][0].destroy()
self.vert_frames[col_num][1].destroy()
self.vert_frames[col_num] = []
# define method to refresh calculations (wrapper for actual calcs)
def refresh_calculations(self, col):
pass
# Execute code
if __name__ == "__main__":
root = tk.Tk()
my_gui = tl_GUI(root)
root.mainloop()
I'm writing a scientific calculator with 2nd button. What is the function for second button so for example it changes sin to sin^-1, plus changing the sin button command; and if you click the 2nd button again, it changes sin^-1 back to sin
I would split my calculator up into section using different frames (one to show the calculations , one with buttons which won't have 2 functions and lastly the buttons which have 2 functions).
The reason I would split it up is because I would use destroying objects and making the new ones this splitting up means you can destroy the wanted frame rather than specific buttons (would require less code). Also for this I would have 3 create GUI defs. one would be the buttons with one function and the bit showing the calculations. one be the buttons which have 2 functions (their first function) and lastly the buttons which have 2 functions (their second functions). To decide between which GUI gen def to use have an if statement with global variable which gets changed each time 2nd function button called and that decides which def to use.
If it was just commands you wanted changing instead of both labels and commands I would have a variable which is etheir 1 or 2(change when 2nd button clicked) then in your definitions (ones which your buttons call) have an if statement to decide between to do normal action (e.g cos) or 2nd action (e.g cos-1).
Here is code below that uses what i have described in the first paragraph:
from tkinter import *
class Calc(Frame):
def __init__(self, master):
self.modefunction = 1
""" Initialize the frame. """
super(Calc,self).__init__(master)
self.grid()
self.calculations_frm = Frame(self, width=100, height=30)#bg = "red"
self.calculations_frm.grid(row = 0, column = 0, columnspan=2)
self.buttons_frm = Frame(self, width= 50, height=30,)#bg = "green")
self.buttons_frm.grid(row = 1, column = 1)
self.buttons_2_functions_1_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_1_frm.grid(row = 1, column = 0)
self.create_GUI()
def create_show_calculations(self):
self.calculation_lbl = Label(self.calculations_frm, text = "will show caluclations here").pack()
def create_buttons(self):
#mc stands for mode change
self.mode_change_btn = Button(self.buttons_frm, text = "mc", command = self.mode_change, height = 1, width = 5)
self.mode_change_btn.grid(row = 0,column = 0)
self.plus_btn = Button(self.buttons_frm, text = "plus", height = 1, width = 5)
self.plus_btn.grid(row = 1,column = 0)
def create_GUI(self):
self.create_show_calculations()
self.create_buttons()
self.create_1_function_gui()
def create_1_function_gui(self):
self.tan_btn = Button(self.buttons_2_functions_1_frm, text = "tan", height = 1, width = 5)
self.tan_btn.grid(row = 0,column = 0)
self.san_btn = Button(self.buttons_2_functions_1_frm, text = "san", height = 1, width = 5)
self.san_btn.grid(row = 0,column = 1)
self.coz_btn = Button(self.buttons_2_functions_1_frm, text = "coz", height = 1, width = 5)
self.coz_btn.grid(row = 1,column = 0)
def create_2_function_gui(self):
self.buttons_2_functions_2_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_2_frm.grid(row = 1, column = 0)
self.inverse_tan_btn = Button(self.buttons_2_functions_2_frm, text = "tan-1", height = 1, width = 5)
self.inverse_tan_btn.grid(row = 0,column = 0)
self.inverse_san_btn = Button(self.buttons_2_functions_2_frm, text = "san-1", height = 1, width = 5)
self.inverse_san_btn.grid(row = 0,column = 1)
self.inverse_coz_btn = Button(self.buttons_2_functions_2_frm, text = "coz-1", height = 1, width = 5)
self.inverse_coz_btn.grid(row = 1,column = 0)
def mode_change(self):
if self.modefunction == 1:
self.buttons_2_functions_1_frm.destroy()
self.modefunction = 2
self.buttons_2_functions_2_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_2_frm.grid(row = 1, column = 0)
self.create_2_function_gui()
else:
self.buttons_2_functions_2_frm.destroy()
self.modefunction = 1
self.buttons_2_functions_1_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_1_frm.grid(row = 1, column = 0)
self.create_1_function_gui()
root = Tk()
root.title("booking system")
root.geometry("500x500")
root.configure(bg="white")
app = Calc(root)
root.mainloop()
from tkinter import *
from random import *
from functools import partial
class Game:
def __init__(self):
self.root = Tk()
self.frame = Frame(width = 574, height = 574)
self.frame.grid(columnspan = 30, rowspan = 30)
self.minex = []
self.miney = []
self.clickx = 0
self.clicky = 0
blank = PhotoImage(file = 'C:\\Users\\PC\\Desktop\\Python Programs\\Minesweeper\\blank.gif')
for i in range(0,30):
for j in range(0,30):
button = Button(width = 15, height = 15, padx = 2, pady = 2, image = blank, command = partial(self.click, j, i))
button.grid(row = i, column = j)
self.mine_place()
self.root.mainloop()
def mine_place(self):
for i in range(0,15):
self.minex.append(randint(1,30))
self.miney.append(randint(1,30))
def click(self, j, i):
miss = PhotoImage(file = 'C:\\Users\\PC\\Desktop\\Python Programs\\Minesweeper\\miss.gif')
hit = PhotoImage(file = 'C:\\Users\\PC\\Desktop\\Python Programs\\Minesweeper\\hit.gif')
for k in range(0, len(self.minex)):
if j + 1 == self.minex[k] and i + 1 == self.miney[k]:
button = Button(image = hit)
button.grid(row = i, column = j)
else:
button = Button(image = miss)
button.grid(row = i, column = j)
app = Game()
In self.click, when I wish to create a button with this image I am given a blank image. If I create a button in init, the image comes out just fine. What is wrong?..............................................................
It looks like you're images are getting garbage collected you need to save a reference to the images after using PhotoImage.
ie - you create the image blank so save a reference as self.blank= blank and use image = self.hit
I'm writing a program that draws a rectangle or oval in a top frame depending on whether or not the user selects it via a radiobutton. There is a check button that determines whether the oval is filled as well. Both buttons are on the bottom frame. But for some reason when I run the code, it displays the window, but not the buttons themselves. How do I fix this?
Here's my code:
from tkinter import *
class GeometricFigures:
def __init__(self):
self.window = Tk()
self.window.title("Radiobuttons and Checkbuttons")
self.canvas = Canvas(self.window, width = 300, height = 100, bg = "white")
self.canvas.pack()
def drawButtons(self):
self.bottomframe = Frame(self.window)
self.bottomframe.pack()
self.check = IntVar()
cbtFilled = Checkbutton(self.bottomframe, variable = self.check, value = 0,
text = "Filled", command = self.processCheckbutton).pack(side = LEFT)
self.radio = IntVar()
rbRectangle = Radiobutton(self.bottomframe, variable = self.radio, value = 1,
text = "Rectangle", command = self.processRadiobutton.pack())
rbOval = Radiobutton(self.bottomframe, text = "Oval", variable = self.radio,
value = 2, command = self.processRadiobutton.pack())
cbtFilled.grid(row = 1, column = 2)
rbRectangle.grid(row = 1, column = 3)
rbOval.grid(row = 1, column = 4)
def processCheckbutton(self):
print("The check button is " +
("checked " if self.check.get() == 1 else "unchecked"))
def processRadiobutton(self):
print(("Rectangle" if self.radio.get() == 1 else "Oval")
+ " is selected ")
def drawRect(self):
self.canvas.create_rectangle(30, 10, 270, 60, tags = "rect")
def drawFillOval(self):
self.canvas.create_oval(30, 10, 270, 60, fill = 'blue', tags = "oval")
def drawOval(self):
self.canvas.create_oval(30, 10, 270, 60, tags = "oval")
def main(self):
test = GeometricFigures()
if self.check.get() == 1:
test.drawFillOval()
if self.radio.get() == 1:
test.drawRect()
else:
test.drawOval()
test.drawButtons()
if __name__ == '__main__':
main()
Thanks!
what i have is a window that opens up and it has a list box. this is created using one class. when i click the search button and results are found using a different class, i want the list box to update without having to open up another window. below is my code so far\n
from Tkinter import *
class addTask:
def __init__(self):
self.addWindow = Tk()
self.addWindow.configure(background = "black")
self.addWindow.geometry("450x450")
self.addWindow.resizable(width = False, height = False)
self.addWindow.title("Add Task")
self.addNameLabel = Label(self.addWindow,text="Add the name of the task",font = ("Helvetica",10,"italic"),bg = "black",fg = "white")
self.addNameLabel.place(relx=0.01, rely=0.05)
self.nameWiget = Text (self.addWindow, width = 63, height = 1)
self.nameWiget.place(relx=0.0, rely=0.1)
self.addDateLabel = Label(self.addWindow,text="Add the date of the task",font = ("Helvetica",10,"italic"),bg = "black",fg = "white")
self.addDateLabel.place(relx=0.01, rely=0.2)
self.dateWiget = Text (self.addWindow, width = 63, height = 1)
self.dateWiget.place(relx=0.0, rely=0.25)
self.addTaskLabel = Label(self.addWindow,text="Add the details of the task",font = ("Helvetica",10,"italic"),bg = "black",fg = "white")
self.addTaskLabel.place(relx=0.01, rely=0.35)
self.taskWiget = Text (self.addWindow, width = 63, height = 1)
self.taskWiget.place(relx=0.0, rely=0.4)
addButton = Button(self.addWindow,height = 5, width = 20, text="Add Task",highlightbackground="black",font=("Helvetica",10,"bold"),command=lambda:self.saveFuntion())
addButton.place(relx=0.25, rely=0.55)
def saveFuntion(self):
nameInfo = (self.nameWiget.get(1.0, END))
dateInfo = self.dateWiget.get(1.0, END)
taskInfo = self.taskWiget.get(1.0, END)
print nameInfo
task1 = Task(nameInfo,dateInfo,taskInfo)
task1.save()
self.nameWiget.delete(1.0,END)
class Task:
def __init__(self,name,date,task):
self.__name = name
self.__date = date
self.__task = task
def save(self):
fileName = open("dataFile.txt","a")
fileName.write(self.__name)
fileName.write(self.__date)
fileName.write(self.__task)
class editTask:
def __init__(self):
self.editWindow = Tk()
self.newWindow = Tk()
self.newWindow.geometry("450x750")
self.editWindow.configure(background = "black")
self.editWindow.geometry("450x750")
self.editWindow.resizable(width = False, height = False)
self.editWindow.title("Edit Task")
self.listBox = Listbox(self.editWindow,heigh = 15, width = 30)
self.listBox.place(relx = 0.2, rely = 0.6)
#drop down menu
self.var = StringVar(self.editWindow)
self.var.set("Select search critria")
self.choices = ["Name","Date"]
self.option = OptionMenu(self.editWindow,self.var,*self.choices)
self.option.configure(bg = "black")
self.option.place(relx = 0.5, rely = 0.2)
#edit label and text box
self.editLabel = Label(self.editWindow,text="Add the name of the task",font = ("Helvetica",10,"italic"),bg = "black",fg = "white")
self.editLabel.place(relx=0.01, rely=0.05)
self.editInfoWiget = Text (self.editWindow, width = 63, height = 1)
self.editInfoWiget.place(relx=0.0, rely=0.1)
# search button
searchButton = Button(self.editWindow,height = 5, width = 20, text="Search for Task",highlightbackground="black",font=("Helvetica",10,"bold"),command=lambda:self.searchFuntion())
searchButton.place(relx=0.3, rely=0.4)
def searchFuntion(self):
critria = self.var.get()
info = self.editInfoWiget.get(1.0,END)
thing = info.split("\n")
thing2 = thing[0]
search = searchCritria(critria,thing2)
search.search()
# def openListBox(self):
class searchCritria():
def __init__(self,critria,info):
self.__critria = critria
self.__info = info
def search(self):
self.file = open("dataFile.txt", "r+")
fileData = self.file.readlines()
self.file.close()
lengthOfFile = len(fileData)
counter = 1
self.name = []
self.date = []
self.details = []
for i in range (lengthOfFile):
split = fileData[i].split("\n")
while counter == 1:
self.name.append(split)
break
while counter == 2:
self.date.append(split)
break
while counter == 3:
self.details.append(split)
break
counter = counter +1
if counter > 3:
counter = 1
if self.__critria == "Name":
for x in range (len(self.name)):
self.taskName = self.name[x]
self.taskName2 = self.taskName[0]
if self.__info == self.taskName2:
openWindow = True
else :
openWindow = False
if openWindow == True:
editTask().listBox.insert(END,self.taskName2)
if self.__critria == "Date":
for x in range (len(self.date)):
self.taskDate = self.date[x]
self.taskDate2 = self.taskDate[0]
if self.__info == self.taskDate2:
print "found"
else :
print"not found"
class editTask2():
def __init__(self):
self.edit2Window = Tk()
self.edit2Window.configure(background = "black")
self.edit2Window.geometry("450x350")
self.edit2Window.resizable(width = False, height = False)
self.edit2Window.title("Edit Task")
any help would be great
thanks
The biggest problem in your code is that you're creating multiple instances of Tk. Tkinter simply isn't designed to work that way. You should only ever create a single instance of Tk. If you need additional windows, create instances of Toplevel.
Also, you need to call the mainloop function of this instance of Tk exactly once. Without it, your GUI will not work.
If you want to update a listbox in another class than where it was created, the concept is pretty simple: if A needs to update B, A needs a reference to B. So, either pass in a reference to the listbox when you create the other class, or give the other class a method you can call where you pass in the reference to the listbox after it was created.