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.
Related
I just started programming with Python and I'm trying to create a GUI using tkinter where it would ask the user to select a zip file and file destination to send it to after extracting. What I have noticed is that when a user re-enters a destination, it would still store the previous file directory as well. How do I prevent this?
import tkinter as tk
from tkinter import filedialog
# Setting the window size
screenHeight = 450
screenWidth = 350
root = tk.Tk()
# get the location of the zip file
def input_dir():
input_filedir = filedialog.askopenfilename(initialdir='/', title='Select File',
filetypes=(("zip", "*.zip"), ("all files", "*.*")))
my_label.pack()
return input_filedir
# get location of destination for file
def output_dir():
output_filename = filedialog.askdirectory()
# Setting the canvas size to insert our frames
canvas = tk.Canvas(root, height=screenHeight, width=screenWidth)
canvas.pack()
# Setting the frame size to insert all our widgets
frame = tk.Frame(root, bg='#002060')
frame.place(relwidth=1, relheight=1)
# button to get user to chose file directory
openFile = tk.Button(frame, text='Choose the file path you want to extract', command=input_dir)
openFile.pack(side='top')
# button to get destination path
saveFile = tk.Button(frame, text="Chose the location to save the file", command=output_dir)
saveFile.pack(side='bottom')
extractButton = tk.Button(frame, text="Extract Now")
root.mainloop()
I have tried adding this line of code in the def input_dir function but it changed the positioning of the buttons. I'm still working on the code for extracting the zip.
for widget in frame.winfor_children():
if isinstance(widget, tk.Label):
widget.destroy()
The files/directories the user clicks are not saved really. Your variables input_filedir and output_filename are garbage collected when their respective functions are completed. If you are talking about how the dialog boxes go back to the most recent places opened in a dialog, that is not really something you can mess with too much. The easy answer is you can add the keyword 'initialdir' when you make the dialog to just pick where it goes like this:
output_filename = filedialog.askdirectory(initialdir='C:/This/sort/of/thing/')
The long answer is that filedialog.askdirectory actually creates an instance of filedialog.Directory and in that class, it saves that information for later in a method called _fixresult (but that method also does other things that are important.) You could overwrite this by doing something like this:
class MyDirectory(filedialog.Directory):
def _fixresult(self, widget, result):
"""
this is just a copy of filedialog.Directory._fixresult without
the part that saves the directory for next time
"""
if result:
# convert Tcl path objects to strings
try:
result = result.string
except AttributeError:
# it already is a string
pass
self.directory = result # compatibility
return result
def askdirectory (**options):
"Ask for a directory, and return the file name"
return MyDirectory(**options).show()
and then using your own askdirectory function instead of filedialog.askdirectory, but that is really convoluted so I recommend using initialdir instead if you can.
P.S. When a button is clicked, it calls the function you set after "command=" when you made the button; it seems you got that. But these functions are 'void', in that their return is just ignored. That is to say, the "return input_filedir" line does nothing.
I have the following basic app using tkinter, which has two buttons. With the first button I can open the folder including the files I want to run an analysis on and the second button then runs the analysis.
from tkinter import filedialog
from tkinter import *
from pathlib import Path
from run_analysis import create_tmp_file
class MyApp():
def __init__(self,master):
frame = Frame(master)
frame.pack()
self.button_filedialog = Button(frame, text="Öffnen", fg="red",command=self.open_filedialog)
self.button_analyse = Button(frame, text="Starte Analyse", fg="green", command=self.make_analysis)
## Unpack buttons
self.button_filedialog.pack()
self.button_analyse.pack()
def open_filedialog(self):
start_path = Path.cwd()
self.data_path = filedialog.askdirectory(initialdir=start_path)
def make_analysis(self):
create_tmp_file(self.data_path,1,0.12)
root = Tk()
app = MyApp(root)
root.mainloop()
The code runs fine. However, it is actually not what I want.
I want to call my imported function create_tmp_file directly in the second button. However, if I do replace the line
self.button_analyse = Button(frame, text="Starte Analyse", fg="green", command=self.make_analysis)
with
self.button_analyse = Button(frame, text="Starte Analyse", fg="green", command=create_tmp_file(self.data_path,1,0.12))
The code doesn't work and I receive the following error message:
AttributeError: 'MyApp' object has no attribute 'data_path'
What am I doing wrong?
Thanks!
What's happening here is pretty straightforward. You're setting an attribute on the class - in this case the data_path attribute... - inside the method. But, that only happens when the method is actually invoked.
Setting the command is just a reference to it, so until it's actually called that attribute doesn't exist.
This is clearly problematic when you're giving a reference to a method - which sets the attribute but hasn't been called - then immediately invoking the method which assumes it's existence.
So when I am calling the list directory code below directly then it's working fine. But when I am calling main.py which uses app class from gui which calls the list directory function, It prints "yoo" but prints an empty list instead of list of directories. I am stuck and can't figure out why that's happening. Any ideas?
Outputs:
When list directory called directly :
["/home/shubham/Desktop/movies/djangounchained.mkv"]
"yoo"
When called by main.py with same argument:
[]
"yoo"
Here is my main script
from gui import app
from list_directory import display_files
import tkinter as tk
root = tk.Tk()
directory = input("Enter directory name:")
root.geometry("400x300")
widgets_creator = app(root)
name = "get list"
directory_button = widgets_creator.create_button(name,function=display_files,path=directory)
root.mainloop()
Here is my gui script
import tkinter as tk
class app(tk.Frame):
def __init__(self,master):
super(app,self).__init__(master=master)
self.master = master
self.init_window()
def init_window(self):
# changing the title of our master widget
self.master.title("GUI")
# allowing the widget to take the full space of the root window
self.pack(fill=tk.BOTH, expand=1)
# creating a button instance
quitButton = tk.Button(self, text="Quit")
# placing the button on my window
quitButton.place(x=0, y=0)
def create_button(self,button_name,function,path):
button = tk.Button(self.master,text=button_name,command=lambda: function(path))
button.place(x=200,y=5)
return button
Here is my list_directory code:
import glob
def display_files(path):
x = glob.glob(path)
print(x)
print("yoo")
if __name__ == '__main__':
display_files("/home/shubham/Desktop/movies/*")
I might have found your problem. The code works fine, the problem is your argument. For example, if I enter '/Users/rudy/Desktop/*' when the input prompt comes up, I have the same result as you.
However, when I enter /Users/rudy/Desktop/* (without quotes), everything works fine. input() already saves the input as a string, so you don't need to add additional quotes.
I have written some code primarily to be used with the console, but was asked to create a simple GUI for it for ease of use. In it, I am setting up the main frame with widgets, and using the widget command to call upon the function that I import. However, the imported functions and modules all write to the output console. Is there a means of returning the output string/console output to be updated in the GUI as the subprocess runs?
Example script:
import Tkinter as *
import myModule1
class MyGUI(Frame):
def __init__(self):
# Initialization stuff.
self.initGUI()
def initGUI(self):
self.downloadButton = Button(text="Download data.")
self.downloadButton["command"] = myModule1.function
# This function from myModule1 will constantly print to the console as the process is performed - is there any way to have this displayed in the GUI as a ScrollBar?
.....
Alternatively, is there a way to make a dialog window show up while the process is running? I ask because I have embedded a lot of print statements in the myModule1 and its submodules that return what is going on to the console. It would be nice to display those for the users one the GUI is working and I convert it to a .exe for ease of use of those who will be using it.
Thank you.
EDIT: An example of what myModule1.function can look like:
import otherModule
def function1():
log = otherModule.function2():
if log == True:
print "Function 2 worked."
elif log == False:
print "Function 2 did not work."
However, function2 in otherModule prints to console as it performs its calculations. That is not explicitly shown here, but the console output would essentially be a series of calculations followed by the example if/elif clause shown above.
It won't be extremely simple, but one way to do this is create a method or function in your GUI that writes to a text widget or some other widget whose content can be updated easily. I'll call it outputMethod. Then, pass this function or method to myModule1.function(outputMethod). Within the module1.function, replace print statements with the outputMethod call, providing the appropriate parameters to it. Without seeing module1.function, it's difficult to provide a complete example that would work. Added the following example once the OP posted myModule1 sample code.
from Tkinter import *
import myModule1
class MyGUI(Frame):
def __init__(self, parent):
# Initialization stuff.
self.initGUI(parent)
def initGUI(self, parent):
Frame.__init__(self, parent)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1)
self.pack(expand='yes', fill='both')
self.downloadButton = Button(self, text="Download data.")
self.downloadButton["command"] = lambda m=self.outputMethod: myModule1.function(m)
self.text = Text(self)
self.downloadButton.grid(row=0, column=0)
self.text.grid(row=1, column=0, sticky='news')
self.sy = Scrollbar(self, command=self.text.yview)
self.text['yscrollcommand'] = self.sy.set
self.sy.grid(row=1, column=1, sticky='nsw')
def outputMethod(self, the_output):
self.text.insert('end', the_output + '\n')
# Scroll to the last line in the text widget.
self.text.see('end')
if __name__ == '__main__':
# Makes the module runable from the command line.
# At the command prompt type python gui_module_name.py
root = Tk()
app = MyGUI(root)
# Cause the GUI to display and enter event loop
root.mainloop()
Now for module1...
def function(log):
# Note that log is merely a pointer to 'outputMethod' in the GUI module.
log("Here is a string you should see in the GUI")
log("And this should appear after it.")
# Now demonstrate the autoscrolling set up in outputMethod...
for i in range(50):
log("Inserted another row at line" + str(i + 2))
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.