So I am making a game and I am using Tkinter and Python 2.7, and I want to store map data in files. I am trying to make a menu to open the file and set to a variable. I am having trouble defining a function to use on the Tkinter Button.
The Tkinter window opens but when I click the button it gives an error.
Code:
#readfiles.py
class maps(object):
def __init__(self):
self.data
def mapset(var):
data = var
fselect = Tk()
...
buttons = Frame(fselect).pack()
Label(fselect, text="Select maps in folder:")
for i in listdir('./maps/'):
if i[-4:] == ".pvm":
Button(buttons, text=i[:-4], command=lambda i=i: mapset(open('./maps/'+i, 'r').read())).pack()
NameError: global name 'mapset' is not defined
Also, how can I access the data variable in maps (so it would be maps.data, right) in another file?
#main.py
from readfiles import *
print maps.data
AttributeError: type object 'maps' has no attribute 'data'
Let me know if you need any additional information.
Also, print "Happy New Years!
Here's a simple GUI class that stores the data from a map file in the self.data attribute.
To test this code I created a set of simple text files using this Bash command:
for i in {0..5}; do echo >"maps/map${i}.pvm" "Map $i data"; done
And here's the Python 3 code. To run it on Python 2, just change the Tkinter import statement to import Tkinter as tk, and add from __future__ import print_function before the other import statements.
I've added a "Print data" that lets us test that we have actually loaded the data correctly.
import tkinter as tk
import os
class Gui(object):
def __init__(self, mapspath):
self.data = None
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
tk.Label(frame, text="Select maps in folder:").pack()
for fbase in os.listdir(mapspath):
if fbase.endswith('.pvm'):
fname = os.path.join(mapspath, fbase)
#print(fname)
tk.Button(frame, text=fbase[:-4],
command=lambda fn=fname:self.read_map(fn)).pack()
tk.Button(frame, text="Print data", command=self.print_map).pack()
root.mainloop()
def read_map(self, fname):
with open(fname) as f:
self.data = f.read()
def print_map(self):
print("current map:", self.data)
Gui("./maps")
Unless these maps are huge it would probably be more convenient to store all of them at once. You could store them in a list, but it'd probably be better to store them in a dictionary, using the base name (minus the '.pvm' extension) as the key.
Related
I have this script where I initialize a tkinter object with some labels, entries, and buttons with commands. when I run the script, the init function executes fine and the instance of the tkinter window gets spawned and the buttons work just fine. The problem is that, the logic after the init function does not get executed and the tkinter window stays looping for ever unless I hit the exit button and then it just finishes the script. I want to be able to use the inputs from the tkinter.Entry() and the tkinter.filedialog.askopenfilename() functions to do more stuff later in the application such as loading an excel sheet based on the path passed on the askopenfilename() sections but I can't get it to continue.
Note: I am new to using classes so I believe my mistake is somewhere in the way I am laying down the building blocks of the instance itself. Also, while putting this scrip together so many other questions arised and it has been very hard to find concise answers online. I will post some of those questions here in case someone wants to answer them (not needed though)
1- Do I need to declare every single new variable that come in new methods of the class in the init function, if so, why? I just did it here because pycharm game me warning every time I did not do it but not sure what is the reason behind it.
2- When calling a variable on a new method within the class, and the variable is already defined in the init fucntion, do I need to include the variable in the arguments section of the method? if so, do I add the variable with the self argument or not?
import pandas as pd
from openpyxl import load_workbook
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter.messagebox import showinfo
from datetime import datetime
import openpyxl
class App(tk.Tk):
def __init__(self):
super().__init__()
# Define parameters
self.current_PR_report = ""
self.new_PR_report = ""
self.path = ""
self.raw_excel = None
self.new_report = None
self.current_report = ""
# Configure the root window
self.title("PR Reader")
self.geometry('250x200')
# Label
self.label = ttk.Label(self, text="Merge Problem Reports!")
self.label.pack()
# Date Label and Entry
self.date_label = tk.Label(self, text="Enter Date (MM-DD-YYYY)")
self.date_label.pack()
self.date_label_entry = tk.Entry(self)
self.date_label_entry.pack()
# Run button
self.button = ttk.Button(self, text="Run", command=self.select_file)
self.button.pack()
# Button for closing
self.exit_button = tk.Button(self, text="Exit", command=self.destroy)
self.exit_button.pack(pady=20)
# Bring Dialog box to obtain Excel files paths
def select_file(self):
self.current_PR_report = filedialog.askopenfilename(
title="Select Current PR Report",
initialdir="some path",
filetypes=[("Excel Files", "*.xlsx")])
self.new_PR_report = filedialog.askopenfilename(
title="Select New PR Report",
initialdir="some path",
filetypes=[("Excel Files", "*.xlsx")])
# Load the spreadsheets
self.new_report = self.read_excel_report(self.new_PR_report)
self.current_report = self.read_excel_report(self.current_PR_report)
# Define function to load Excel data
def read_excel_report(self, path):
try:
# sheet_name=None indicates you want a dictionary of data frames, each item in the dictionary
# representing a different worksheet.
self.raw_excel = pd.read_excel(path, sheet_name=-1, engine='openpyxl')
self.raw_excel = self.raw_excel.fillna('')
print("data extracted")
return self.raw_excel
except Exception as e:
print(e)
if __name__ == "__main__":
app = App()
app.mainloop()
My main issue is that after running the script my other methods were not getting executed. I learned that I not only need to define the methods on my class but also need to call them inside of it. I this case, I had to call them inside the logic of the "select_file" method since it is the method called when the "Run" button gets called.
I want to get into GUI automation in order to run tests on my own program. The Program I want to test is written in Python and uses Tkinter for the GUI. The testing code though does not necessarily have to be in python, CPP would also be alright. I've done a bit of research and I am already facing a problem.
From my research, I have found that "Windows Application Driver" is a free way to test GUI. there's also "WinAppDriver UI Recorder" which seems convenient to use with it. Additionally the `Inspect.exe' program from (In my case) "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86" is useful for getting information about the GUI elements.
Assuming I have a small python code like this (only for testing):
from Tkinter import *
import ttk
class Test():
def __init__(self):
self.root = Tk()
self.root.geometry("250x100")
self.text = StringVar()
self.text.set("Original Text")
self.buttonA = Button(self.root, textvariable=self.text)
self.buttonA.configure(text="test")
self.buttonB = Button(self.root,
text="Click to change text",
command=self.changeText
)
self.buttonA.pack(side=LEFT)
self.buttonB.pack(side=RIGHT)
self.root.mainloop()
def changeText(self):
self.text.set("Updated Text")
app=Test()
When running the code and inspecting buttonB with Inspect.exe the name I get as a result is "" (empty). what way is there to change that name to something informational and useful like in the calculator example, where the '7' button's name is "Seven". Which then is used in the tester like this:
self.driver.find_element_by_name("Seven").click()
which should look like this:
self.driver.find_element_by_name("buttonB").click()
for example in my case.
You can name tkinter widgets like:
self.buttonA = Button(self.root, textvariable=self.text,name = 'buttonA')
if WinAppDriver is not able to find tkinter widgets named this way. You can modify your code to invoke buttons (and get "hold" of other UI widgets) in a way that mimic UI automations frameworks:
I modified your sample to show how this can be done
from Tkinter import *
import ttk
def _widgets_by_name(parent,name,widgets):
if not parent.winfo_children():
if name == parent.winfo_name() :
widgets.append(parent)
else:
for child in parent.winfo_children():
_widgets_by_name(child,name,widgets)
def find_widget_by_name(parent,name):
''' ui automation function that can find a widget in an application/hierarchy of widgets by its name '''
widgets = []
_widgets_by_name(parent,name,widgets)
if len(widgets) == 0:
raise Exception(f'no widget named {name} found')
elif len(widgets) >1:
raise Exception(f'multiple widget named {name} found')
return (widgets[0])
class Test():
def __init__(self):
self.root = Tk()
self.root.geometry("250x100")
self.text = StringVar()
self.text.set("Original Text")
self.buttonA = Button(self.root, textvariable=self.text,name = 'button-a')
self.buttonA.configure(text="test")
self.buttonB = Button(self.root,
text="Click to change text",
command=self.changeText,
name = 'button-b'
)
self.buttonA.pack(side=LEFT)
self.buttonB.pack(side=RIGHT)
# self.root.mainloop() do not start the main loop for testing purpose
# can still be started outside of __init__ for normal operation
def changeText(self):
self.text.set("Updated Text")
app=Test()
# test the app step by step
# find one of the buttons and invoke it
find_widget_by_name(app.root,'button-b').invoke()
app.root.update() # replace the app mainloop: run the UI refresh once.
assert app.text.get() == "Updated Text"
I am quite new for python and Tkinter, please bear the messy code.
currently I have 2 buttons (one is open file, second one is run), and 1 text box (Entry or Scrolledtext), what I want to do is
press the open file button, then the file path will be displayed in the text box and this path will be also saved into local variable 'a' in main().
when I press the 'run' button, the program will get latest value of variable 'a' and do something else.
The problem I faced is that this local variable 'a' cannot updated while I click 'open file' button. I search this kind of questions and I get answer that either I should use 'global var' or 'make a class and treat the file path as a attribute'. But somehow I couldn't make it. Let me put my code below.
First is use global variable. I could get updated file1 by clicking 'btn_run', console will print latest value of file one, but script_path cannot updated.
p.s. I also try the 'trace()' method. yes, I found that the var. in the function could be updated but how do I get those updated var into my local variable main? it seems go back to original point again.
from functools import partial
from tkinter import scrolledtext,INSERT,END,Entry, messagebox, filedialog, ttk, Frame,Button
from os import path
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,NavigationToolbar2TkAgg
# Tkagg allows you to draw, navigation bar is to allow you save, zoomm ....the images
import numpy as np
from tkinter import *
def openfile(txt1):
global file1
file1= filedialog.askopenfilename(filetypes=(("all files","*.*"),("Text files","*.txt"),("csv file","*.csv")))
txt1.insert(0,file1)
# print (file1)
def print_f1():
print(file1)
def main ():
# Create Main window
window = Tk()
file1=''
window.title("WADC FFT trial")
window.minsize(1000,600)
input_str=StringVar()
input_str.set('')
# Create Frame for buttons, bars and picts
mis_frame = Frame(window)
mis_frame.grid(column=0, row=0)
#Create a scrolled text box on mis_frame
txt=Entry(mis_frame,textvariable=input_str,width=40)
txt.grid(column=0,row=0)
#define openfile
#Create a button on mis_frame, use lambda to avoid direct invoke the function called
# btn_path= Button(mis_frame,text="Open File",command=lambda:openfile(txt1))
#another workaround is to use partial funciton in python, assign a new func with lesser variables.
op_func=partial(openfile,txt)
btn_path=Button(mis_frame,text='Open File',command=op_func)
btn_path.grid(column =1,row =0)
script_path=input_str
print('script path is '+str(script_path))
btn_run = Button(mis_frame, text='Run',command=print_f1)
btn_run.grid(column=3,row=0,padx=100)
window.mainloop()
if __name__=='__main__':
main()
Second come with the class attribute. this one I only make 1 button which can openfile and get file path.
The point is I want to pass this 'filename' to local variable 'a', the thing is like one I run the code, it will pass 'a' the default value 'filename' and not update after I click the 'browse' button.
Thanks for suggestions you provided, it could be helpful
from tkinter import *
import tkinter as tk
class Canvas(tk.Tk): #inherient from Tkinter.tk
def __init__(self):
tk.Tk.__init__(self) #inheritate all attributes that tk has
self.filename ='' #declare filepath
tk.Button(self, text='Browse', command=self.openfile).grid(column=0,row=0)
def openfile(self):
self.filename = filedialog.askopenfilename(title="Open file")
def main ():
b=Canvas()
b.title("Test")
b.minsize(600,400)
a=b.filename
print(a)
b.mainloop()
if __name__=='__main__':
main()
Problems
script_path=input_str
assigns the StringVar to script_path, not the value of it.
To get the value use script_path = input_str.get()
When this line
a = b.filename
is executed, it assigns the default value of filename to a because you didn't call openfile.
Solution
To pass a value to the global scope you need neither classes nor global keywords.
Just use a tkinter variable
file1 = StringVar()
def openfile(txt1):
file1.set(filedialog.askopenfilename(filetypes=( ("all files","*.*"),("Text files","*.txt"),("csv file","*.csv") ) ) )
txt1.insert(0,file1)
or make your functions return something.
def openfile(txt1):
file1 = filedialog.askopenfilename(filetypes=( ("all files","*.*"),("Text files","*.txt"),("csv file","*.csv") ))
txt1.insert(0,file1)
return file1
Ive rewritten this for more context, in a logical order this is what i want the program to do
1 by pressing open file it needs to open a specified file and put it into the text widget (done)
2 by pressing the extract file it needs to extract something from a specified subtree(in an xml file)
3 export the extracted data to a text file
but lets go back to point nr 2 as i have not yet written the extractor(easy part) first i need to refference a file i want to edit, and that is where i run into my problem. inside extract it cant acess vars in openfile and i dont want to reopen the file again.
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import tkinter as tk
interface = tk.Tk()
interface.geometry("500x500")
interface.title("Text display")
def openfile():
filename = filedialog.askopenfilename()
print(filename)
file = open(filename)
txt = file.read()
print(txt)
T = tk.Text(interface, height=10, width=50)
T.insert(tk.END, txt)
T.grid(column=1, row=2)
return txt
def extract():
print(txt)
button = ttk.Button(interface, text="Open text File", command=openfile) # <------
button.grid(column=1, row=1)
buttonex = ttk.Button(interface, text="Extract subtitles", command=extract) # <------
buttonex.grid(column=2, row=1)
interface.mainloop()
NameError: name 'txt' is not defined (when i press extract)
As the edit of the initial questions shows that a GUI is planned I would suggest to move your TK widgets into an interface class as in the tkinter documentation. Further, if you plan to do more complex manipulations you should make an own class to hold your data resp. manipulate it.
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
# Create your buttons and connect them to the methods
self.button_load = tk.Button(self)
self.button_load["command"] = self.loadTextFromFile()
self.button_load["text"] = "Load Data"
self.button_load.gird(column=1, row=1)
self.buttonex = tk.Button(self)
self.buttonex["text"] = "Extract subtitles"
self.buttonex["command"] = self.extract()
self.buttonex.grid(column=2, row=1)
# Create the text widget
self.text_widget = tk.Text(self, height=10, width=50)
self.text_widget.grid(column=1, row=2)
def loadTextFromFile(self):
filename = filedialog.askopenfilename()
print(filename)
try:
file = open(filename)
txt = file.read()
file.close()
self.text_widget.insert(tk.END, txt)
except Exception:
# If anything went wrong, close the file before reraising
file.close()
raise
def extract(self):
# Now you have access to self.text_widget and it holds your read in text
do_something(self.text_widget)
# Maybe at the following functions to make your file importable without directly executing it. Could come in handy later on.
def run():
# create the application
myapp = App()
#
# here are method calls to the window manager class
#
myapp.master.title("My Do-Nothing Application")
myapp.master.maxsize(1000, 400)
# start the program
myapp.mainloop()
if __name__ == '__main__':
run()
Have a look at the tkinter documentation for further examples: https://docs.python.org/3/library/tkinter.html
The additional if clause checks if your module is the main module and executes the run function. This defines an entry point if you directly run your module and prevents functions from execution at import time if you intend to import the module into another module. A more detailed explanation can be found here:
What does if __name__ == "__main__": do?
---------- Old Answer --------------
As pointed out in the comment you need to somehow return your local variables back to the calling function in order to be able to use them somewhere else. This could be achieved by a simple return statement:
def openfile():
""" It is the responsibility of the caller to close the return value """
filename = filedialog.askopenfilename()
print(filename)
try:
file = open(filename)
txt = file.read()
T = tk.Text(interface, height=10, width=50)
T.insert(tk.END, txt)
T.grid(column=1, row=2)
return file
except Exception:
# If anything went wrong, close the file before reraising
file.close()
raise
would for example return the open fileid for further manipulation. E.g.:
def extract():
with openfile() as file:
txtex = file.read()
print (txtex)
If your goal is howeer, to manipulate the file content and read it later you would need to save your manipulations back into the file otherwise your second read would not see the changes. Dont forget to close your file again.
I changed three lines where I believe to have come across typos:
From: self.button_load["command"] = self.loadTextFromFile() to: self.button_load["command"] = self.loadTextFromFile
From: self.button_load.gird(column=1, row=1) to: self.button_load.grid(column=1, row=1)
From: self.buttonex["command"] = self.extract() to: self.buttonex["command"] = self.extract
Thank you for this example!
I am new in tkinter please help me out . I have implemented a module(PDF2Text.Py) that its class has a function (convert_pdf_to_txt(path)) that takes a path of a pdf file and converts the pdf file into text.
I also implemented another module(TopicModeling.py) that its class has a function (creat_LDA_model(text)) that takes a text and do topic modeling on the text.
Now, I want the tkinter GUI that is, upon clicking the "Browse" button it browses the path with filedialog.askopenfilename and its command function sends the given path to convert_pdf_to_txt(path) function of PDF2Text.Py.
Then by clicking the "Model" button, its command function get the text and send it creat_LDA_model(text) function in TopicModeling.py and show the result in an Entry widget or any other widget types .
I would like to know the structure of the GUI module; how to call or get and set the parameters to other modules/functions from the GUI module in command functions of the buttons.
let me explain more:
suppose this is my code:
import tkinter
from tkinter import filedialog
import TopicModeling
import Pdf2Text
window = tkinter.Tk()
window.title("Welcome to test tkinter app")
window.geometry('750x600')# Setting Window Size
lbl = tkinter.Label(window, text="Select a pdf file:", font=("Arial Bold", 15))#Set Label Font Size
lbl.grid(column=0, row=0)#set its position on the form
def Return_pdfText():
filename = filedialog.askopenfilename(filetypes = (("pdf files","*.pdf"),("all files","*.*")))
mytext = Pdf2Text.Pdf2Text(filename)
PdfText=mytext.convert_pdf_to_txt(filename)
return PdfText
Button=tkinter.Button(window,text="Browse",command=Return_pdfText)
Button.grid(column=0, row=1)
window.mainloop()
I want to put another button,namely 'Model'. its command function should get the pdfText as input and model it ,like this:
Button=tkinter.Button(window,text="Model",command=Model)
Button.grid(column=1, row=1)
def Model(pdfText): #??
my_LDA_model= TopicModeling.TopicModeling(PdfText)
model=my_LDA_model.create_lda_model()
my_LDA_model.WordCloud_topics(model)
...????
the result (topics and the graphs) should be shown in the GUI in some widgets.
My problem is about this command function. how to pass pdfText to model function, how show the results, and in which type of widgets?
thanks
When you have a small program the simplest way is to use global variables for shared data:
def Return_pdfText():
global PdfText # Put the variable in the global scope
filename = filedialog.askopenfilename(filetypes = (("pdf files","*.pdf"),("all files","*.*")))
mytext = Pdf2Text.Pdf2Text(filename)
PdfText = mytext.convert_pdf_to_txt(filename) # Assign to global variable
After PdfText is assigned a value it will be available for the Model() function and you don't need to pass it:
def Model():
my_LDA_model = TopicModeling.TopicModeling(PdfText)
model = my_LDA_model.create_lda_model()
my_LDA_model.WordCloud_topics(model)