Related
I have a settings page with lots of checkbuttons on it so I am trying to reduce the code but I am struggling when it comes to getting the checkbutton value to work so far I have:-
def _create_checkbox(self, label, index, state=0):
x = label.replace(" ", "-").lower()
self.settings_list[x] = state
ttk.Label(self.settings_frame, text=label).grid(
row=index, column=0)
ttk.Checkbutton(
self.settings_frame, variable=self.settings_list[x]
).grid(row=index, column=1)
the idea was to put the checkbutton names in a dict and then update the dict with the value but it is not working as planned, with my code all checkbutton values update as if they were one.
example list:
self.settings_list = {"force-gamemode": "0", "allow-cheats": "1"}
Edit to show minimal working example, I did originally try to use variables (IntVar) but it failed (I cant remember why) but that's why I then switched to a dict:-
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("tkinter dynamic checkbox example")
self.geometry("700x450")
self.settings_list = {"force-gamemode": "0", "allow-cheats": "1"}
self.settings_frame = tk.Frame(self)
self.settings_frame.grid(row=0, column=0)
# create settings content
self._create_checkbox("Force Gamemode", 0, 0)
tk.Label(
self.settings_frame, text="Label to show content between checkboxes"
).grid(row=1, column=0)
self._create_checkbox("Allow Cheats", 2, 0)
tk.Button(
self.settings_frame,
text="Create Properties File",
command=self._create_properties,
).grid(row=3, column=0, sticky="ew")
def _create_checkbox(self, label, index, state=0):
x = label.replace(" ", "-").lower()
self.settings_list[x] = state
ttk.Label(self.settings_frame, text=label).grid(
row=index, column=0, padx=5, pady=5, sticky="w"
)
ttk.Checkbutton(self.settings_frame, variable=self.settings_list[x]).grid(
row=index, column=1, padx=5, pady=5, sticky="w"
)
def _create_properties(self):
print(self.settings_list["force-gamemode"])
print(self.settings_list["allow-cheats"])
if __name__ == "__main__":
app = App()
app.mainloop()
Since you passed an integer 0 as the textvariable option of those Checkbutton widgets, an implicit IntVar will be created with name "0" for them. Therefore they will be changed together because they share same tkinter variable.
You need to change
self.settings_list[x] = state
to
self.settings_list[x] = tk.IntVar(value=state)
inside _create_checkbox().
In addition to acw1668's answer, to check the value
def _create_properties(self):
print(self.settings_list["force-gamemode"].get())
print(self.settings_list["allow-cheats"].get())
so your code is ...
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("tkinter dynamic checkbox example")
self.geometry("700x450")
self.settings_list = {"force-gamemode": "0", "allow-cheats": "0"}
self.settings_frame = tk.Frame(self)
self.settings_frame.grid(row=0, column=0)
# create settings content
self._create_checkbox("Force Gamemode", 0, 0)
tk.Label(self.settings_frame, text="Label to show content between checkboxes"
).grid(row=1, column=0)
self._create_checkbox("Allow Cheats", 2, 0)
tk.Button(self.settings_frame, text="Create Properties File",
command=self._create_properties
).grid(row=3, column=0, sticky="ew")
def _create_checkbox(self, label, index, state=0):
x = label.replace(" ", "-").lower()
self.settings_list[x] = tk.IntVar(value=state)
ttk.Label(self.settings_frame, text=label
).grid(row=index, column=0, padx=5, pady=5, sticky="w")
ttk.Checkbutton(self.settings_frame, variable=self.settings_list[x]
).grid(row=index, column=1, padx=5, pady=5, sticky="w")
def _create_properties(self):
print(self.settings_list["force-gamemode"].get())
print(self.settings_list["allow-cheats"].get())
if __name__ == "__main__":
app = App()
app.mainloop()
I am new to python, the above figure is two UI, I was trying to insert the two tab from the right side UI into the left side UI. But I had met some error and no idea how to solve.
Now below is the original coding of the left side UI
import tkinter as tkk
from tkinter import *
from tkinter import messagebox
import os.path
import hashlib
import sys
import time
import getpass
from tkinter import filedialog
import platform
import getpass
import os,sys
import tkinter.font as font
from tkinter import ttk
from tkinter import StringVar
import Consts
import shutil
class Page(tkk.Frame):
def __init__(self, *args, **kwargs):
tkk.Frame.__init__(self, *args, **kwargs)
def show(self):
self.lift()
class Page1(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
my_system=platform.uname()
#computer information
comsys=my_system.system
comnode=my_system.node
comrel=my_system.release
comver=my_system.version
commac=my_system.machine
compro=my_system.processor
comuser=getpass.getuser()
label1 = tkk.Label(self, text="System: "+comsys,bg='white')
label1.grid(row=1,column=1,pady=10,sticky='w')
label2 = tkk.Label(self, text="Computer Name: "+comnode,bg='white')
label2.grid(row=2,column=1,pady=10,sticky='w')
label3 = tkk.Label(self, text="Release: "+comrel,bg='white')
label3.grid(row=3,column=1,pady=10,sticky='w')
label4 = tkk.Label(self, text="Version: "+comver,bg='white')
label4.grid(row=4,column=1,pady=10,sticky='w')
label5 = tkk.Label(self, text="Machine: "+commac,bg='white')
label5.grid(row=5,column=1, pady=10,sticky='w')
label6 = tkk.Label(self, text="Processor: "+compro,bg='white')
label6.grid(row=6,column=1, pady=10,sticky='w')
label7 = tkk.Label(self, text="Username: "+comuser,bg='white')
label7.grid(row=7,column=1,pady=10,sticky='w')
#computer usage hold first, no idea how to do
class Page2(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
tabControl=ttk.Notebook(self)
qsFrame = ttk.Frame(tabControl)
fsFrame = ttk.Frame(tabControl)
csFrame = ttk.Frame(tabControl)
#tab
tabControl.add(qsFrame, text='Quick Scan')
tabControl.add(fsFrame, text='Full Scan')
tabControl.add(csFrame, text='Custom Scan')
tabControl.pack(expand=1,fill="both")
class Page3(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
label = tkk.Label(self, text="This is page 3")
label.grid(row=2,column=1)
def mytools():
total, used, free = shutil.disk_usage("/")
print("Total:%d GB" %(total // (2**30)))
print("Used:%d GB" %(used // (2**30)))
print("Free:%d GB" %(free // (2**30)))
if free <= total/2:
clean = os.popen('Cleanmgr.exe/ sagerun:1').read()
#print(clean)
def btn1():
if __name__ =="__main__":
mytools()
button1=ttk.Button(self,text="Clean Up",command=btn1)
button1.grid(row=3,column=2)
class Page4(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
label = tkk.Label(self, text="This is page 4")
label.grid(row=2,column=1)
class MainView(tkk.Frame):
def __init__(self, *args, **kwargs):
tkk.Frame.__init__(self, *args, **kwargs)
p1 = Page1(self)
p2 = Page2(self)
p3 = Page3(self)
p4 = Page4(self)
buttonframe = tkk.Frame(self)
container = tkk.Frame(self,bg='white')
buttonframe.pack(side="left", fill="x", expand=False)
container.pack(side="left", fill="both", expand=True)
buttonframe.grid_rowconfigure(0,weight=1)
buttonframe.grid_columnconfigure(0,weight=1)
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p3.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p4.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
stats_btn = tkk.PhotoImage(file='C:/FYP/SecuCOM2022/icon&pic/stats.png')
scanner_btn = tkk.PhotoImage(file='C:\FYP\SecuCOM2022\icon&pic\scanner.png')
speedup_btn = tkk.PhotoImage(file='C:\FYP\SecuCOM2022\icon&pic\speedup.png')
settings_btn = tkk.PhotoImage(file='C:\FYP\SecuCOM2022\icon&pic\settings.png')
#logo
logo=tkk.PhotoImage(file="C:\FYP\SecuCOM2022\icon&pic\g (1).png")
label=tkk.Label(buttonframe,image=logo)
label.grid(row=0,column=0, padx=10,pady=10)
logo.image = logo
b1 = tkk.Button(buttonframe, image=stats_btn, command=p1.show, borderwidth=0)
b2 = tkk.Button(buttonframe, image=scanner_btn, command=p2.show, borderwidth=0)
b3 = tkk.Button(buttonframe, image=speedup_btn, command=p3.show, borderwidth=0)
b4 = tkk.Button(buttonframe, image=settings_btn, command=p4.show, borderwidth=0)
b1.image = stats_btn
b2.image = scanner_btn
b3.image = speedup_btn
b4.image = settings_btn
b1.grid(row=1,column=0,padx=10,pady=10)
b2.grid(row=2,column=0,padx=10,pady=10)
b3.grid(row=3,column=0,padx=10,pady=10)
b4.grid(row=4,column=0,padx=10,pady=10)
if __name__ == "__main__":
root= Tk()
main = MainView(root)
main.pack(side="top", fill="both", expand=True)
main.grid_rowconfigure(0,weight=1)
main.grid_columnconfigure(0,weight=1)
root.title("SecuCOM2022")
root.geometry("600x300")
root.maxsize(600,375)
root.minsize(600,375)
root.iconbitmap('C:\FYP\SecuCOM2022\icon&pic\g.png')
root.mainloop()
root.mainloop()
#GUI end`
Next below here is the right side UI coding, there are multiple files for it. I just show files related with the UI only.
Consts.py
ENTRY_WIDTH = 50
FileReportTab.py
from tkinter import filedialog
from tkinter import messagebox
from tkinter import ttk
from tkinter import StringVar
import time
import os.path
import sys
from VTPackage import Consts
class FileReportTab:
def __init__(self, root, frame, vtClient):
self.root = root
self.frame = frame
self.vtClient = vtClient
self.mainVTURLframe = ttk.LabelFrame(frame, text=' File report')
self.mainVTURLframe.grid(column=0, row=1, padx=8, pady=4)
ttk.Label(self.mainVTURLframe, text="Progress:").grid(column=0, row=1, sticky='W') # <== right-align
self.progressBar = ttk.Progressbar(self.mainVTURLframe, orient='horizontal', length=300, mode='determinate')
self.progressBar.grid(column=1, row=1)
ttk.Label(self.mainVTURLframe, text="File path:").grid(column=0, row=2, sticky='W') # <== right-align
self.filePath = StringVar()
filePathEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.filePath, state='readonly')
filePathEntry.grid(column=1, row=2, sticky='W')
ttk.Label(self.mainVTURLframe, text="Status:").grid(column=0, row=3, sticky='W') # <== right-align
self.status = StringVar()
statusEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.status, state='readonly')
statusEntry.grid(column=1, row=3, sticky='W')
ttk.Label(self.mainVTURLframe, text="Positive Indications:").grid(column=0, row=4, sticky='W') # <== right-align
self.positiveIndications = StringVar()
positiveIndicationsEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.positiveIndications, state='readonly')
positiveIndicationsEntry.grid(column=1, row=4, sticky='W')
ttk.Label(self.mainVTURLframe, text="SHA1:").grid(column=0, row=5, sticky='W') # <== right-align
self.sha1 = StringVar()
sha1Entry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.sha1, state='readonly')
sha1Entry.grid(column=1, row=5, sticky='W')
ttk.Label(self.mainVTURLframe, text="SHA256:").grid(column=0, row=6, sticky='W') # <== right-align
self.sha256 = StringVar()
sha256Entry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.sha256, state='readonly')
sha256Entry.grid(column=1, row=6, sticky='W')
chooseFileButton = ttk.Button(self.mainVTURLframe, text="Choose File", width=40, command=self._scanFile).grid(column=1, row=0)
self.scanCheckingTimeInterval = 25000 # This is the amount of time we are going to wait before asking VT again if it already processed our scan request
for child in self.mainVTURLframe.winfo_children():
child.grid_configure(padx=4, pady=2)
def showResults(self, results):
try:
#self.file_Path = self.filePath
self.sha1.set(results["sha1"])
self.sha256.set(results["sha256"])
self.positiveIndications.set(results["positives"])
if results["positives"] == 0:
messagebox.showwarning("Analysis Info","File is Safe.\nOur Scanners found nothing Malicious")
elif results["positives"] <= 5:
messagebox.showwarning("Analysis Alert", "Given File may be Malicious")
elif results["positives"] >= 5:
messagebox.showwarning("Analysis Alert", f"Given File is Malicious.\nAdvice you remove the file from your System!")
res = messagebox.askyesno("Analysis Alert","The given file is highly Malicious.\nDo you want to Delete it permanently?")
if res == 1:
print("Attemting to delete file...")
time.sleep(1)
os.remove(self.filePath1)
#if os.PathLike(_scanFile.filePath):
# os.remove(self.filePath)
else:
print("This file cannot be deleted. Please do not use the fie. It's Malicious")
except Exception as e:
messagebox.showerror('Error', e)
def checkStatus(self):
try:
self.scanResult = self.vtClient.get_file_report(self.scanID)
print(self.scanResult)
if self.scanResult["response_code"] == -2: # By reading the next line, you can understand what is the meaning of the -2 response ode
self.status.set("Scanning...")
self.progressBar['value'] = self.progressBar['value'] + 5
self.root.update_idletasks()
self.mainVTURLframe.after(self.scanCheckingTimeInterval, self.checkStatus)
else:
self.hasScanFinished = True
self.showResults(self.scanResult)
self.status.set("Finished!")
self.progressBar['value'] = 100
except Exception as e:
if "To much API requests" in str(e):
pass
def _scanFile(self):
try:
self.progressBar['value'] = 0
self.filePath1 = filedialog.askopenfilename(initialdir="/", title="Select file for VT", filetypes=(("EXE files", "*.exe"), ("all files", "*.*")))
if (self.filePath): # Only if the user chose a file, we will want to continue the process
self.filePath.set(self.filePath1)
self.status.set("Sending file...")
self.progressBar['value'] = 10
self.root.update_idletasks()
self.scanID = self.vtClient.scan_file(self.filePath1)
self.hasScanFinished = False
if not self.hasScanFinished:
self.scanResult = self.vtClient.get_file_report(self.scanID)
print(self.scanResult)
self.checkStatus()
# We could have been using time.sleep() or time.wait(), but then our UI would get stuck.
# by using after, we are initiating a callback in which does not blocks our event loop
except Exception as e:
messagebox.showerror('Error', e)
URLreportTab.py
from tkinter import ttk
from tkinter import StringVar
from VTPackage import Consts
class URLreportTab:
def __init__(self, root, frame, vtClient):
self.root = root
self.frame = frame
self.mainVTURLframe = ttk.LabelFrame(frame, text=' URL report tab!')
# using the tkinter grid layout manager
self.mainVTURLframe.grid(column=0, row=0, padx=8, pady=4)
ttk.Label(self.mainVTURLframe, text="URL:").grid(column=0, row=0, sticky='W') # What does sticky does? Sticky sayes where to stick the label to : N,S,E,W
urlEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH)
urlEntry.grid(column=1, row=0, sticky='E')
ttk.Label(self.mainVTURLframe, text="Positive Indications:").grid(column=0, row=1, sticky='W') # <== right-align
Positive = StringVar()
PositiveEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=Positive, state='readonly')
PositiveEntry.grid(column=1, row=1, sticky='W')
ttk.Label(self.mainVTURLframe, text="Detections:").grid(column=0, row=2, sticky='W') # <== right-align
detections = StringVar()
detectionsEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=detections, state='readonly')
detectionsEntry.grid(column=1, row=2, sticky='W')
self.notificationFrame = ttk.LabelFrame(self.frame, text=' Notifications', width=40)
# using the tkinter grid layout manager
self.notificationFrame.grid(column=0, row=1, padx=8, pady=10, sticky='W')
ttk.Label(self.notificationFrame, text="Errors:").grid(column=0, row=0, sticky='W') # <== increment row for each
Error = StringVar()
ErrorEntry = ttk.Entry(self.notificationFrame, width=Consts.ENTRY_WIDTH, textvariable=Error, state='readonly')
ErrorEntry.grid(column=1, row=0, sticky='W')
def _cleanErrorMessage(): # We could have been doing this without a function, but it is more neat that way
Error.set("")
def _getReport():
# the _ notation before a function means that this function is internal to the class only. As python cannot really prevent you from using it outside the class (as C# for example) the notation is being used to warn other developers not to call this function outside the class
try:
_cleanErrorMessage() # Starting with cleaning the error message bar
if not urlEntry.get():
print('Please enter a URL')
Error.set("Please enter a URL!")
return
urlToCheck = urlEntry.get()
response = vtClient.get_url_report(urlToCheck)
print(response)
Positive.set(response["positives"])
scans = response["scans"]
findings = set()
for key, value in scans.items():
if value["detected"]:
findings.add(value["result"])
detections.set(",".join([str(finding) for finding in findings]))
except Exception as e:
print(e)
Error.set(e)
checkURLinVTButton = ttk.Button(self.mainVTURLframe, text='Check Now!', command=_getReport).grid(column=2, row=0)
# Instead of setting padding for each UI element, we can just iterate through the children of the main UI object.
for child in self.mainVTURLframe.winfo_children():
child.grid_configure(padx=4, pady=2)
for child in self.notificationFrame.winfo_children():
child.grid_configure(padx=4, pady=2)
VTApp.py
import tkinter as tk
import configparser
from tkinter import Menu
from tkinter import ttk
from tkinter import messagebox
from VTPackage import URLreportTab
from VTPackage import FileReportTab
from VTPackage import VTClient
config = configparser.ConfigParser()
config.read('config.ini')
class VTApp:
def __init__(self):
# Loading the config file
self.config = configparser.ConfigParser()
self.config.read('config.ini')
self.virusTotalAPIkey = config['VirusTotal']['apiKey']
self.vtClient = VTClient.VTClient(self.virusTotalAPIkey)
self.root = tk.Tk()
self.root.title("Virus Total UI")
self.menuBar = Menu()
self.root.config(menu=self.menuBar)
self.fileMenu = Menu(self.menuBar, tearoff=0)
self.fileMenu.add_command(label="New")
self.fileMenu.add_separator()
self.menuBar.add_cascade(label="File", menu=self.fileMenu)
if not self.vtClient.is_API_key_valid():
messagebox.showerror('Error', "API key is not valid! Check your config file")
def _quit():
self.root.quit() # The app will exist when this function is called
self.root.destroy()
exit()
self.fileMenu.add_command(label="Exit", command=_quit) # command callback
self.tabControl = ttk.Notebook(self.root) # Create Tab Control
self.urlFrame = ttk.Frame(self.tabControl)
self.urlTab = URLreportTab.URLreportTab(self.root, self.urlFrame, self.vtClient)
self.tabControl.add(self.urlFrame, text='URL')
self.fileFrame = ttk.Frame(self.tabControl)
self.fileTab = FileReportTab.FileReportTab(self.tabControl, self.fileFrame, self.vtClient)
self.tabControl.add(self.fileFrame, text='File')
self.tabControl.pack(expand=1, fill="both") # Pack to make visible
def start(self):
self.root.mainloop()
Main.py
from VTPackage import VTApp
vtApp = VTApp.VTApp()
vtApp.start()
This is the original code, Sorry for the spacing error, I copy&paste from vsc and it seem like the got some spacing error after Class. So basically this is the original code and I try like import VTApp and code inside class Page2 like
vtApp = VTApp.VTApp()
vtApp.start()
and change some coding in the VTApp.py but it doesn't work.... Does anyone know how to make the script works? I been trying and trying for a week and still couldn't get the solution.
You cannot move a widget from one window to another in tkinter. You will have to recreate the tab in the other window.
How to duplicate tkinter text widget so that you can add it in every Notebook tab? I am writing an editor using tkinter, and I have also added undo functions. The problem is, when I add a new tab, the undo function works only for that tab. When I delete that tab, it is not working for other tabs either.
from tkinter.ttk import Notebook
import tkinter.messagebox
class TextClass(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.text = Text(self, bg='white', foreground="black", undo=True,
insertbackground='black', height=35, width=135,
selectbackground="blue")
self.scrollbar = Scrollbar(self, orient=VERTICAL, command=self.text.yview)
self.text.configure(yscrollcommand=self.scrollbar.set)
self.numberLines = TextLineNumbers(self, width=40, bg='#2A2A2A')
self.numberLines.attach(self.text)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.numberLines.pack(side=LEFT, fill=Y, padx=(5, 0))
self.text.pack(fill='both', expand=True)
self.text.bind("<Key>", self.onPressDelay)
self.text.bind("<Button-1>", self.numberLines.redraw)
self.scrollbar.bind("<Button-1>", self.onScrollPress)
self.text.bind("<MouseWheel>", self.onPressDelay)
def undo():
try:
self.text.edit_undo()
except TclError:
tkinter.messagebox.showerror(
"Nothing to Undo",
"You have not typed anything to undo. Please type and try again!"
)
undo_button = Button(toolbar, text='Undo', relief='ridge', command=undo, bg='black', fg='white',
width=6)
undo_button.place(x=170, y=5)
hover(undo_button, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
def redo():
try:
self.text.edit_redo()
except TclError:
tkinter.messagebox.showerror(
"Nothing to Redo",
"You have not done an undo to redo. Please type and try again!"
)
redo_button = Button(toolbar, text='Redo', relief='ridge', command=redo, bg='black', fg='white',
width=6)
redo_button.place(x=230, y=5)
hover(redo_button, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
def onScrollPress(self, *args):
self.scrollbar.bind("<B1-Motion>", self.numberLines.redraw)
def onScrollRelease(self, *args):
self.scrollbar.unbind("<B1-Motion>", self.numberLines.redraw)
def onPressDelay(self, *args):
self.after(2, self.numberLines.redraw)
def get(self, *args, **kwargs):
return self.text.get(*args, **kwargs)
def insert(self, *args, **kwargs):
return self.text.insert(*args, **kwargs)
def delete(self, *args, **kwargs):
return self.text.delete(*args, **kwargs)
def index(self, *args, **kwargs):
return self.text.index(*args, **kwargs)
def redraw(self):
self.numberLines.redraw()
class TextLineNumbers(Canvas):
Canvas.text_widget = None
def attach(self, text_widget):
self.text_widget = text_widget
def redraw(self, *args):
self.delete("all")
i = self.text_widget.index("#0,0")
while True:
d_line = self.text_widget.dlineinfo(i)
if d_line is None:
break
y = d_line[1]
line_numbers = str(i).split(".")[0]
self.create_text(2, y, anchor="nw", text=line_numbers, fill="white")
i = self.text_widget.index("%s+1line" % i)
def add_tab():
global tab
tab = Frame(notebook)
notebook.add(tab, text=f'{"Untitled1.txt": ^20}')
global text
text = TextClass(tab, bg='white')
text.pack()
text.text.focus()
def close_tab():
notebook.forget('current')
def hover(widget, entrance_foreground, exit_fg, on_entrance, on_exit):
widget.bind("<Enter>", func=lambda e: widget.config(
bg=on_entrance,
fg=entrance_foreground
))
widget.bind("<Leave>", func=lambda e: widget.config(
bg=on_exit,
fg=exit_fg
))
root = Tk()
root.config(bg='white')
root.geometry("1260x680")
toolbar = Frame(root, bg='white', height=45)
toolbar.pack(expand=False, fill='x')
notebook = Notebook(root)
tab = Frame(notebook)
notebook.add(tab, text=f'{"Untitled.txt": ^20}')
notebook.place(x=50, y=60)
text = TextClass(tab, bg='white')
text.pack()
add_tab_btn = Button(toolbar, text='Add new tab', command=add_tab, relief='ridge', bg='black', fg='white')
hover(add_tab_btn, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
add_tab_btn.place(x=15, y=5)
delete_tab_btn = Button(toolbar, text='Delete tab', command=close_tab, relief='ridge', bg='black', fg='white')
hover(delete_tab_btn, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
delete_tab_btn.place(x=100, y=5)
root.after(200, text.redraw())
scr_menu()
root.mainloop()
As I suspected you keep making new undo/redo buttons over the old ones each time you create a new tab. This means that the button only works for the last tab. To fix that problem you have to move the undo/redo buttons inside the notebook. Just as a proof of concept change this line:
undo_button = Button(toolbar, text='Undo', relief='ridge', command=undo, bg='black', fg='white',
width=6)
to
undo_button = Button(self, text='Undo', relief='ridge', command=undo, bg='black', fg='white',
width=6)
Below is the the code that I currently have. I am beginner programmer and am writing a small program that will automate some workflow for primer design (biologist / bioinformaticists represent).
The issue I have right now is that my lack of understanding of how OOP works with TKinter. I have read numerous stackoverflow posts and watched youtube videos and read guides that try to explain it but I am still somewhat at a loss. My current understanding is that each window should be its own object, with the window above it as its parent. I've attempted to do this with my program.
Currently I have two classes, AUTOPRIMER, and BlastAPI. AUTOPRIMER is the main window. There is a button in that window that I have created that should open up a new window when clicked. From my understanding, I have created a new object for that window called BlastAPI which deals with that particular requirement of my program. I see many guides that suggest the parent should be put in the init of the new object, but there are so many initialization variations I have seen from parent to master to args*, kwargs**. What is appropriate when? Also, currently the stack trace provides this feedback as it doesn't even compile properly.
Traceback (most recent call last):
File "/Users/Thunderpurtz/Desktop/CGIStuff/AUTOPRIMER/autoprimercode/test1.py", line 201, in <module>
autoprimer = AUTOPRIMER(root)
File "/Users/Thunderpurtz/Desktop/CGIStuff/AUTOPRIMER/autoprimercode/test1.py", line 105, in __init__
self.blast = BlastAPI(self)
File "/Users/Thunderpurtz/Desktop/CGIStuff/AUTOPRIMER/autoprimercode/test1.py", line 150, in __init__
eValueSetting = Entry(parent)
File "/anaconda3/lib/python3.6/tkinter/__init__.py", line 2673, in __init__
Widget.__init__(self, master, 'entry', cnf, kw)
File "/anaconda3/lib/python3.6/tkinter/__init__.py", line 2289, in __init__
BaseWidget._setup(self, master, cnf)
File "/anaconda3/lib/python3.6/tkinter/__init__.py", line 2259, in _setup
self.tk = master.tk
AttributeError: 'AUTOPRIMER' object has no attribute 'tk'
[Finished in 0.289s]
Fundamentally, I think my understanding of gui programming isn't solid so if anyone can provide some insight that would be great. If this question is sort of broad, I'll be happy to clarify in the comments.
import subprocess
from tkinter import *
from tkinter.filedialog import *
import tkinter.messagebox
class AUTOPRIMER:
def __init__(self, master):
#some functions, their content is removed as i do not believe they are relevant
def button1():
pass
def button2():
pass
def button3():
pass
def getPrimers():
pass
def PrimerParser():
pass
def poolPrimers():
pass
self.master = master
self.input = ""
self.output = ""
self.param = ""
self.inputbool = False
self.outputbool = False
self.parambool = False
self.p3filestring = '-p3_settings_file='
self.blast = BlastAPI(self)
master.title("Complete Genomics Inc.")
########## WIDGETS ##########
entry_1 = Entry(master) #input
entry_2 = Entry(master) #output
entry_3 = Entry(master) #parameters
label_1 = Label(master, text="AUTOPRIMER")
button_1 = Button(master, text="Input Filepath: ", command=button1)
button_2 = Button(master, text="Output Filepath: ", command=button2)
button_3 = Button(master, text="Parameters Filepath: ", command=button3)
button_get = Button(master, text="Get Primers", command=getPrimers)
button_parse = Button(master, text="Parse Primers", command = PrimerParser)
button_pool = Button(master, text="Pool Primers", command=poolPrimers)
button_blast = Button(master, text="Blast Primers", command=self.blast)
button_quit = Button(master, text="Quit", command=master.destroy)
########## LAYOUT ##########
label_1.grid(row=0, columnspan=4) #grid doesnt take left right, it takes NSEW directions
button_1.grid(row=1, sticky=E, padx=1, pady=1)
button_2.grid(row=2, sticky=E, padx=1, pady=1)
button_3.grid(row=3, sticky=E, padx=1, pady=1)
button_get.grid(row=4)
button_parse.grid(row=4, sticky=W, column=1)
button_pool.grid(row=4, sticky=W, column=2)
button_blast.grid(row=4, sticky=W, column=3)
button_quit.grid(row=4, sticky=W, column=4)
entry_1.grid(row=1, column=1, sticky=W, padx=1, pady=1)
entry_2.grid(row=2, column=1, sticky=W, padx=1, pady=1)
entry_3.grid(row=3, column=1, sticky=W, padx=1, pady=1)
class BlastAPI:
#class that does blast alignment on primers
from Bio.Blast import NCBIWWW
from Bio.Blast import NCBIXML
def __init__(self, parent):
self.parent = parent
super(BlastAPI, self).__init__() #saw this on another stackoverflow don't truly understand what it means
eValueSetting = Entry(parent)
closeButton = Button(parent, text="Close", command=self.destroy)
inputButton = Button(parent, text="Input file", command=doNothing)
entryField = Entry(parent)
#layout
self.title('Complete Genomics Inc.')
def blastPrimers():
filename = askopenfilename()
with open(filename) as file:
string = file.read()
fasta = fasta_string
result_handle = NCBIWW.qblast("blastn", "nt", fasta)
with open("my_blast.xml", "w") as out_handle:
out_handle.write(result_handle.read())
result_handle.close()
result_handle = open('my_blast.xml')
blast_record = NCBIXML.parse(result_handle)
evalue = 1 #add make it a GUI alterable value blastPrimers
item = next(blast_record)
E_VALUE_THRESH = eValueSetting
while True:
with open('BlastResults.txt', w) as blast:
try:
for alignment in item.alignments:
for hsp in alignment.hsps:
if hsp.expect < E_VALUE_THRESH: #use this to determine if the result will be applicable / HAVE USER SET / default value?
blast.write("****Alignment****")
blast.write("sequence:", alignment.title)
blast.write("length:", alignment.length)
blast.write("e value:", hsp.expect)
blast.write(hsp.query[0:75] + "...")
blast.write(hsp.match[0:75] + "...")
blast.write(hsp.sbjct[0:75] + "...")
item = next(blast_record)
except StopIteration:
print("Done!")
break
root = Tk()
autoprimer = AUTOPRIMER(root)
root.mainloop()
Thanks guys.
Ok so there is a lot that needs work here. I imagine the missing bits are part of your main code but without them testing your code is just not possible. I work with what I could and set up your class's to inherit from the tkinter objects they needed to be. Judging by your button command in your BlastAPI class I am assuming this class should be a Toplevel() window.
I have made some changes to your code and without having Bio.Blast I have changed some things to what I think you might need to do.
import subprocess
import tkinter as tk # import tkinter as tk is good for compatibility and maintainability. Don't use *
from tkinter.filedialog import *
import tkinter.messagebox
from Bio.Blast import NCBIWWW
from Bio.Blast import NCBIXML
class AutoPrimer(tk.Tk): # Class names should normally use the CapWords convention.
def __init__(self):
#initialization
tk.Tk.__init__(self)
self.title("Complete Genomics Inc.")
self.input = ""
self.output = ""
self.param = ""
self.inputbool = False
self.outputbool = False
self.parambool = False
self.p3filestring = '-p3_settings_file='
self.entry_input = tk.Entry(self)
self.entry_output = tk.Entry(self)
self.entry_parameters = tk.Entry(self)
self.entry_input.grid(row=1, column=1, padx=1, pady=1, sticky="w")
self.entry_output.grid(row=2, column=1, padx=1, pady=1, sticky="w")
self.entry_parameters.grid(row=3, column=1, padx=1, pady=1, sticky="w")
self.label1 = tk.Label(self, text="AUTOPRIMER").grid(row=0, columnspan=4)
tk.Button(self, text="Input Filepath: ", command=lambda: self.button1).grid(row=1, padx=1, pady=1, sticky="e")
tk.Button(self, text="Output Filepath: ", command=lambda: self.button2).grid(row=2, padx=1, pady=1, sticky="e")
tk.Button(self, text="Parameters Filepath: ", command=lambda: self.button3).grid(row=3, padx=1, pady=1, sticky="e")
tk.Button(self, text="Get Primers",).grid(row=4)
tk.Button(self, text="Parse Primers",).grid(row=4, column=1, sticky="w")
tk.Button(self, text="Pool Primers",).grid(row=4, column=2, sticky="w")
tk.Button(self, text="Blast Primers", command=lambda: BlastAPI(self)).grid(row=4, column=3, sticky="w")
tk.Button(self, text="Quit", command=self.destroy).grid(row=4, column=4, sticky="w")
#CLASS METHODS
#Series of buttons methods that take in the filepath and displays it in the text widget to the user
def button1(self):
self.entry_input.delete(0,END)
ifp = askopenfilename()
self.setInput(ifp)
self.entry_input.insert(0, ifp)
self.setInputBool(True)
def button2(self):
self.entry_output.delete(0,END)
ofp = asksavefilename()
self.setOutput(ofp)
self.entry_output.insert(0, ofp)
self.setOutputBool(True)
def button3(self):
self.entry_parameters.delete(0,END)
pfp = askopenfilename()
self.entry_parameters.insert(0, pfp)
self.setParameterBool(True)
#Methods that rely on class attributes after using above buttons to set
def get_primers(self):
pass
def primer_parser(self):
pass
def pool_primers(self):
pass
#Setters and Getters
def setInput(self, value):
self.input = value
def setOutput(self, value):
self.output = value
def setParam(self, value):
self.param = value
def setInputBool(self, value):
self.inputbool = value
def setOutputBool(self, value):
self.outputbool = value
def setParameterBool(self, value):
self.parambool = value
class BlastAPI(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title('Complete Genomics Inc.')
self.e_value_thresh = ""
self.e_value_setting = tk.Entry(self)
self.e_value_setting.pack() # Used pack here for quick testing. You will need to work on geometry yourself.
tk.Button(self, text="Close", command=self.destroy).pack()
tk.Button(self, text="Input file").pack()
self.entry_field = tk.Entry(self)
self.entry_field.pack()
def blast_primers(self): # Nothing is calling this function in your example.
filename = askopenfilename()
with open(filename) as file:
string = file.read() # string is not being used here.
fasta = string # No such var name in code.
result_handle = NCBIWWW.qblast("blastn", "nt", fasta) # This had a typo NCBIWW instead of NCBIWWW.
with open("my_blast.xml", "w") as out_handle:
out_handle.write(result_handle.read())
result_handle.close()
result_handle = open('my_blast.xml')
self.blast_record = NCBIXML.parse(result_handle)
evalue = 1 # Is not being used here.
self.item = next(self.blast_record)
self.e_value_thresh = self.e_value_setting.get()
self.blast_write_loop()
def blast_write_loop(self):
# I don't really like while loops and they have problems in event based GUI's.
# I don't think a while loop is needed here anyway.
with open('BlastResults.txt', 'w') as blast:
try:
for alignment in self.item.alignments:
for hsp in alignment.hsps:
if hsp.expect < self.e_value_thresh:
blast.write("****Alignment****")
blast.write("sequence:", alignment.title)
blast.write("length:", alignment.length)
blast.write("e value:", hsp.expect)
blast.write(hsp.query[0:75] + "...")
blast.write(hsp.match[0:75] + "...")
blast.write(hsp.sbjct[0:75] + "...")
self.item = next(self.blast_record)
except StopIteration:
print("Done!")
autoprimer = AutoPrimer()
autoprimer.mainloop()
I have this code:
def show_hide(a, b, c, d):
if not b[0]["text"]: # check if text's length is 0
b[0].configure(text="{}".format(d[0]), bd=2, bg="white", command=lambda: activate_deactivate(a[0], c[0])) # configure button_1
c[0]["text"] = "Status: {}".format(a[0].get()) # set text for label_1
b[1].configure(text="{}".format(d[1]), bd=2, bg="white", command=lambda: activate_deactivate(a[1], c[1])) # configure button_2
c[1]["text"] = "Status: {}".format(a[1].get()) # set text for label_2
else:
b[0].configure(text="", bd=0, bg="#F0F0F0", command=None) # hide the button_1
c[0]["text"] = "" # hide the label_1
b[1].configure(text="", bd=0, bg="#F0F0F0", command=None) # hide the button_2
c[1]["text"] = "" # hide the label_2
My button which calls this function has this command value:
command=lambda: show_hide([status, status_2], [button, button_2], [label, label_2], ["Perform action #1", "Perform action #2"]))
By using it I can show/hide buttons but rewriting the same thing changing a digit in a few places would be tedious. To fix it I tried using this code instead of the original:
def show_hide(a, b, c, d):
for i in range(0, len(a)): # iterates over indexes of items in a,b,c,d lists (all 4 have same length) and passes them to the if/else statement
if not b[i]["text"]: # check if text length is 0
b[i].configure(text="{}".format(d[i]), bd=2, bg="white", command=lambda: activate_deactivate(a[i], c[i])) # configure buton
c[i]["text"] = "Status: {}".format(a[i].get()) # set label's text
else:
b[i].configure(text="", bd=0, bg="#F0F0F0", command=None) # hide button
c[i]["text"] = "" # hide label
Theoretically, this should work fine and using just 4 lines of code (not counting for/if/else lines) do the same thing as original function which would need 4 lines for EACH status+button+label created. BUT, as a matter of fact, it makes my 2 buttons work totally wrong.
I don't really understand what is wrong with it so I can't fully describe the problem, but you can see for yourself by using the test-script I made and replacing the show_hide function definition with the one using for-loop:
import tkinter as tk
import random
class ActionButton(tk.Button):
def __init__(self, *args, **kwargs):
tk.Button.__init__(self, *args, **kwargs)
self.configure(text="", font="courier 20", bd=0)
class ActionLabel(tk.Label):
def __init__(self, *args, **kwargs):
tk.Label.__init__(self, *args, **kwargs)
self.configure(text="", font="courier 14")
def multi(*args):
for func in args:
return func
def show_hide(a, b, c, d):
if not b[0]["text"]:
b[0].configure(text="{}".format(d[0]), bd=2, bg="white", command=lambda: activate_deactivate(a[0], c[0]))
c[0]["text"] = "Status: {}".format(a[0].get())
b[1].configure(text="{}".format(d[1]), bd=2, bg="white", command=lambda: activate_deactivate(a[1], c[1]))
c[1]["text"] = "Status: {}".format(a[1].get())
else:
b[0].configure(text="", bd=0, bg="#F0F0F0", command=None)
c[0]["text"] = ""
b[1].configure(text="", bd=0, bg="#F0F0F0", command=None)
c[1]["text"] = ""
def activate_deactivate(a, c):
if a.get() == "Can be done":
a.set("To be done")
c.configure(text="Status: {}".format(a.get()), fg="blue")
else:
a.set("Can be done")
c.configure(text="Status: {}".format(a.get()), fg="black")
def step_forward(a, b, c):
if a.get() == "To be done":
b.configure(text="", bd=0, bg="#F0F0F0", state="disabled")
c["text"] = ""
result = random.choice(["success", "failure"])
if result == "success":
a.set("Accomplished")
c["fg"] = "green"
else:
a.set("Failed")
c["fg"] = "red"
else:
b.configure(text="", bd=0, bg="#F0F0F0", command=None)
c["text"] = ""
root = tk.Tk()
status = tk.StringVar()
status.set("Can be done")
status_2 = tk.StringVar()
status_2.set("Can be done")
main = tk.Button(root, text="Show/Hide", bg="white", font="courier 30",
command=lambda: show_hide([status, status_2],
[button, button_2],
[label, label_2],
["Perform action #1", "Perform action #2"]))
main.pack()
frame = tk.Frame(root, pady=10)
frame.pack()
frame_1 = tk.Frame(frame, padx=10)
frame_1.pack(side="left")
frame_2 = tk.Frame(frame, padx=10)
frame_2.pack(side="left")
button = ActionButton(frame_1)
button.grid(row=0, column=0)
label = ActionLabel(frame_1)
label.grid(row=1, column=0)
button_2 = ActionButton(frame_2)
button_2.grid(row=0, column=1)
label_2 = ActionLabel(frame_2)
label_2.grid(row=1, column=1)
next_day = tk.Button(root, text="Next day", bg="white", font="courier 30",
command=lambda: multi(step_forward(status, button, label),
step_forward(status_2, button_2, label_2)))
next_day.pack()
root.mainloop()
I hope there's someone who may know how to fix this and maybe even has an idea about how the function could be changed to perform everything properly.