How can I create a non-unique browse button in python? - python

I am using; Python 3.4, Windows 8, tkinter. I am trying to create a generic browse button that will get a file name and assign it to a variable.
I have created the following code to do this.
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
class Application(Frame):
# A GUI Application.
# Initialize the Frame
def __init__(self, master):
Frame.__init__(self, master)
nbook = ttk.Notebook(root)
nbook.pack(fill='both', expand='yes')
f1 = ttk.Frame(nbook)
nbook.add(f1, text='QC1')
self.qc1_tab(f1)
# create QC1 tab contents
def qc1_tab(self, tab_loc):
# Set up file name entry.
Label(tab_loc, text="Select file:").grid(pady=v_pad, row=0, column=0, sticky=W)
self.flnm = ttk.Entry(tab_loc, width=60)
self.flnm.focus_set()
self.flnm.grid(pady=v_pad, row=0, column=1, columnspan=2, sticky=W)
ttk.Button(tab_loc, text="Browse...", width=10, command=self.browse).grid(row=0, column=3)
def browse(self):
temp = filedialog.askopenfilename()
self.flnm.delete(0, END)
self.flnm.insert(0, temp)
root = Tk()
app = Application(root)
root.mainloop()
The only problem with this is that the browse button is tied to self.flnm and cannot be used for anything else. I plan to use the browse button several times to acquire the file name of several different files and would rather not have multiple browse commands.
I need to call it from a button and somehow assign it to a variable afterwards.
I was thinking of something like
ttk.Button(..., command=lambda: self.flnm = self.browse)
...
def browse(self):
filename = filedialog.askopenfilename()
return filename
but that failed terribly.
How can I make a general purpose browse button?

You can write:
def browse(self, target):
temp = filedialog.askopenfilename()
target.delete(0, END)
target.insert(0, temp)
ttk.Button(..., command=lambda: self.browse(self.flnm))

Related

Python: Tkinter open custom widget in new window

My aim is to generate a window with a button "NewWindow" on it. If I press the button, the program should open a new window. This window I stored in a class "NewWindow" to quickly reproduce it.
In this "NewWindow" I have another button. If I press it the label of basic window should be updated and the window "NewWindow" should be closed automatically.
Here is my code:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command= self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
window.basic_lb.text = "Hello"
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()
Problems:
At start both windows NewWindow and BasicWindow are displayd. I only want to open BasicWindow and NewWindow should be opened after button basic_bt is clicked. How can I solve it? (already solved by commed below)
Why the label text in basic_lb did not get some update after pressing self.bt1?
How is it possible to close NewWindow with use of bt_press method?
You have a few typos/errors in your code that are casuing some of your problems. As #Tim said, when you pass a function to a command like command=function(), it will be called on runtime, not when the button is pressed. You need to pass the function handle to the command, command=function. You got around this by using a lambda function in your button command, but it is easier to just have command=self.bt_press
Answering your second question, window.basic_lb.text = "Hello" is not how you change the text in a tkinter Label, use <Label>.config(text="Hello"). You also should use self.master and define self.master = master in __init__ instead of just using window, because while you can access window due to it not being defined in local scope, it's better to explicitly define it.
You can close a window using window.destroy().
Your working code is now:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.master = master
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command=self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
self.master.basic_lb.config(text="Hello")
self.destroy()
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()

How do I prevent prevent multiple instances of the same window opening?

I'm currently using tkinter to create a GUI for my program. If I open the golf quiz window and open the help window, then close the golf quiz window and re-open it, I am able to click the help window button and open another instance of the help button. How do I set the Help button to be disabled while the Help window is open?
from tkinter import *
from functools import partial
class Welcome_Screen:
def __init__(self, parent):
self.welcome_screen_frame = Frame(width=200, height=200, pady=10)
self.welcome_screen_frame.grid()
self.quiz_welcome_screen_label = Label(self.welcome_screen_frame, text = "quiz game", font="Arial 20 bold", padx=10)
self.quiz_welcome_screen_label.grid(row=0)
self.welcome_screen_buttons_frame = Frame(self.welcome_screen_frame)
self.welcome_screen_buttons_frame.grid(row=2)
self.golf_quiz_welcome_screen_button = Button(self.welcome_screen_buttons_frame, text="Golf Quiz", font="Arial 10 bold", command=self.golf_quiz_game, padx=10, pady=10)
self.golf_quiz_welcome_screen_button.grid(row=2, column=0, padx=5)
def golf_quiz_game(self):
get_golf_quiz_game = golf_quiz_game(self)
class golf_quiz_game:
def __init__(self, partner):
partner.golf_quiz_welcome_screen_button.config(DISABLED)
self.golf_quiz_box = Toplevel()
self.golf_quiz_box.protocol('WM_DELETE_WINDOW', partial(self.close_golf_quiz_game, partner))
self.golf_quiz_frame = Frame(self.golf_quiz_box)
self.golf_quiz_frame.grid()
self.golf_quiz_heading = Label(self.golf_quiz_frame, text="Golf Quiz game",
font="arial 18 bold", padx=10, pady=10)
self.golf_quiz_heading.grid(row=0)
self.golf_quiz_history_help_dismiss_buttons_frame = Frame(self.golf_quiz_frame)
self.golf_quiz_history_help_dismiss_buttons_frame.grid(row=6, pady=10)
self.help_button = Button(self.golf_quiz_history_help_dismiss_buttons_frame, text="Help", font="Arial 10 bold",command=self.Help, padx=10, pady=10)
self.help_button.grid(row=6, column=1, padx=5)
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
def Help(self):
get_help = Help(self)
class Help:
def __init__(self, partner):
partner.help_button.config(state=DISABLED)
self.help_box = Toplevel()
self.help_box.protocol('WM_DELETE_WINDOW', partial(self.close_Help, partner))
self.help_frame = Frame(self.help_box)
self.help_frame.grid()
self.help_heading = Label(self.help_frame, text="Help", font="arial 18 bold")
self.help_heading.grid(row=0)
self.help_text = Label(self.help_frame, text="Test",
width=60, wrap=400)
self.help_text.grid(row=1)
self.help_button = Button(self.help_frame, text="Dismiss", width=10, font="Arial 10 bold", command=partial(self.close_Help, partner), padx=10, pady=10)
self.help_button.grid(row=2, pady=10)
def close_Help(self, partner):
if partner.help_button.winfo_exists():
partner.help_button.config(state=NORMAL)
self.help_box.destroy()
# main routine
if __name__ == "__main__":
root = Tk()
root.title("quiz game")
something = Welcome_Screen(root)
root.mainloop()
Here is how you can do that (this is also the size (linewise (approx.)) of the minimal reproducible example which you should have provided):
from tkinter import Tk, Toplevel, Button
def close_top(top):
btn.config(state='normal')
top.destroy()
def open_help():
btn.config(state='disabled')
top = Toplevel(root)
top.protocol('WM_DELETE_WINDOW', lambda: close_top(top))
top.focus_force()
# put the rest of help stuff here
root = Tk()
btn = Button(root, text='Help', command=open_help)
btn.pack()
root.mainloop()
Class based approach:
from tkinter import Tk, Toplevel, Button
# this would be the window from where you open the help window
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.btn = Button(self, text='Help',
command=lambda: self.open_help(self.btn))
self.btn.pack()
def open_help(self, btn):
HelpWindow(self, btn)
# this would be the help window
class HelpWindow(Toplevel):
def __init__(self, master, button):
Toplevel.__init__(self, master)
self.button = button
self.button.config(state='disabled')
self.focus_force()
self.protocol('WM_DELETE_WINDOW', self.close)
def close(self):
self.button.config(state='normal')
self.destroy()
MainWindow().mainloop()
Few things:
First of you can simply inherit from container and window classes that way you don't have to separately create them in the class and you can easily reference them in the class using just self
.focus_force() does what it says, it forces focus on the widget
Important (suggestions)
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.
You need to make use of class variables inside golf_quiz_game in order to have only one instance of Help window:
class golf_quiz_game:
# class variables
_help_button = None # reference to instance "Help" button
_help_win = None # reference to instance "Help" window
def __init__(self, partner):
...
# set button state based on whether "Help" window is open or not
self.help_button.config(state="normal" if self.__class__._help_win is None else "disabled")
# update class reference of "Help" button
self.__class__._help_button = self.help_button
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
# update class reference of "Help" button
self.__class__._help_button = None
def Help(self):
if self.__class__._help_win is None:
# no "Help" window is open, create one
self.__class__._help_win = Help(self)
def help_closed(self):
if self.__class__._help_button:
# enable the "Help" button
self.__class__._help_button.config(state="normal")
# update "Help" window status
self.__class__._help_win = None
class Help:
...
def close_Help(self, partner):
self.help_box.destroy()
# notify partner that "Help" window is closed
partner.help_closed()

tkinter TopLevel destroy raising AttrbuteError

I'm writing a multi-window GUI in tkinter. Clicking on the 'Load CSV Data' button in the the Main Window creates an instance of the LoadWindow class which inherits from tkinter.TopLevel and the MainGui instance is passedto LoadWindow since I want to manipulate it from LoadWindow. However when I call self.destroy to close the Load CSV window when the Load CSV button is clicked, I get the following error even though the Load CSV data window closes.
if self._name in self.master.children:
AttributeError: 'MainGUI' object has no attribute 'children'
Below is the code:
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import os
import pandas as pd
class MainGUI:
def __init__(self, master):
self.master = master
master.title("Main Window")
master.geometry("700x500")
# create all elements in main window
load_csv_button = ttk.Button(master,
text='Load CSV Data',
command=lambda: LoadWindow(master=self))
load_db_button = ttk.Button(master, text='Load from Database')
save_db_button = ttk.Button(master, text='Save Current File to Database')
data_transform_button = ttk.Button(master, text='Data Transformation')
data_analysis_button = ttk.Button(master, text='Data Analysis')
#update later
preview_df_button = ttk.Button(master, text='Preview Data Frame',
command=lambda: print(self.main_df))
self.text_box = tk.Text(master, bg='grey')
# insert welcome message into text box and disable
self.text_box.insert(tk.END, 'Welcome to the Data Analysis Hub')
self.text_box.config(state='disabled')
# snap all elements to grid
load_csv_button.grid(row=0, column=1, sticky='NSEW')
load_db_button.grid(row=1, column=1, sticky='NSEW')
save_db_button.grid(row=2, column=1, sticky='NSEW')
data_transform_button.grid(row=0, column=2, sticky='NSEW')
data_analysis_button.grid(row=1, column=2, sticky='NSEW')
preview_df_button.grid(row=2, column=2, sticky='NSEW')
self.text_box.grid(row=4, column=1, columnspan=2)
self.main_df = None
def update_textbox(self, message):
self.text_box.config(state='normal')
self.text_box.delete('1.0', 'end')
self.text_box.insert(tk.END, message)
self.text_box.config(state='disabled')
class LoadWindow(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self)
self.title("Load CSV")
self.geometry("200x200")
self.master = master
# get csvs in current directory
listbox = tk.Listbox(self, selectmode=tk.SINGLE)
csv_files = self.find_csv_files(os.getcwd())
for csv in csv_files:
listbox.insert(tk.END, csv)
listbox.grid(row=1, column=1, columnspan=3, sticky='NSEW')
# assign selected csv to maindf
active = listbox.get(tk.ACTIVE)
load_csv_button = ttk.Button(self, text='Load CSV',
command=lambda: self.load_selected(active))
load_csv_button.grid(row=2, column=1)
def find_csv_files(self, path):
# Check for csvs in path
filenames = os.listdir(path)
csv_files = [x for x in filenames if x.endswith('.csv')]
return csv_files
def load_selected(self, active):
try:
csv_path = os.getcwd()+"/"+active
main_df = pd.read_csv(csv_path)
# update maingui variable
self.master.main_df = main_df
# update maingui status on df loaded
self.master.update_textbox(f'{active} loaded as DataFrame')
self.destroy()
except pd.errors.ParserError:
error = 'Looks like you either have no csvs in working directory ' \
'or loaded a file that is not a csv, please try another file'
messagebox.showerror(title='Load error', message=error)
if __name__ == '__main__':
window = tk.Tk()
maingui = MainGUI(window)
window.mainloop()
Start with this line:
LoadWindow(master=self)
self is not a window. Inside the __init__ you do self.master = master. Thus, self.master inside of LoadWindow is not a widget and thus it has no children attribute.
You need to change how you create LoadWindow to be something like this:
LoadWindow(master=self.master)

Get search terms from input box

I am trying to create a GUI for an auto-complete prototype and am new to tkinter. I want to get the entire input when Space is pressed but I am unable to do so. The idea is to get all the entries in the text box so that I can do some analysis inside a function call.
This is the code:
def kp(event):
app.create_widgets(1)
import random
def getFromScript(text):
#########THIS IS A PLACE HOLDER FOR ACTUAL IMPLEMENTATION
i= random.randint(1,100)
return ['hello'+str(i),'helou'+text]
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets(0)
# Create main GUI window
def create_widgets(self,i):
self.search_var = StringVar()
self.search_var.trace("w", lambda name, index, mode: self.update_list(i))
self.lbox = Listbox(self, width=45, height=15)
if i==0:
self.entry = Entry(self, textvariable=self.search_var, width=13)
self.entry.grid(row=0, column=0, padx=10, pady=3)
self.lbox.grid(row=1, column=0, padx=10, pady=3)
# Function for updating the list/doing the search.
# It needs to be called here to populate the listbox.
self.update_list(i)
def update_list(self,i):
search_term = self.search_var.get()#### THIS LINE SHOULD READ THE TEXT
# Just a generic list to populate the listbox
if(i==0):
lbox_list = ['Excellent','Very Good','Shabby', 'Unpolite']
if(i==1):
lbox_list = getFromScript(search_term)####### PASS TEXT HERE
self.lbox.delete(0, END)
for item in lbox_list:
if search_term.lower() in item.lower():
self.lbox.insert(END, item)
root = Tk()
root.title('Filter Listbox Test')
root.bind_all('<space>', kp)
app = Application(master=root)
app.mainloop()
Any kind of help is highly appreciable. Thanks in advance
Problem is you are creating a new StringVar on each create_widgets call.
Create StringVar in your __init__.
class Application(Frame):
def __init__(self, master=None):
...
self.search_var = StringVar()
...

Why are my tkinter window objects (OOP tkinter) not BOTH showing?

I am trying to learn about tkinter from an OOP point of view so that I can create multiple windows.
I have created two files (main.py and Humanclass.py).
Why are both windows not being created? I thought that I had created a Class and in the main program created 2 instance of that class with different data?
Main.py:
import humanclass
from tkinter import *
window = Tk()
human1 = humanclass.Human(window, "Jim", "78", "British")
human2 = humanclass.Human(window, "Bob", "18", "Welsh")
window.mainloop()
humanclass.py:
from tkinter import *
class Human():
def __init__(self, window, name, age, nation):
self.window=window
self.window.geometry("500x200+100+200")
self.window.title(name)
self.label1 = Label(self.window, text=age).grid(row=0, column=0, sticky=W)
self.label2 = Label(self.window, text=nation).grid(row=1, column=0, sticky=W)
self.button = Button(self.window, text="Close", width=5, command=self.clicked).grid(row=3, column=0, sticky=W)
def clicked(self):
self.window.destroy()
Any help to show me the errors in my limited understanding would be gratefully received.
It's because window is only one active window, i.e. the root window. If you want to create multiple windows you will need to spawn them off of that root window. Simply assigning things to that window would overwrite whatever was previously there. That's why only your bottom instance is showing. While technically you could get away with implementing threading and running two root windows with two mainloops, it is highly advised not to do that.
What you should do is create Toplevel instances off of the root window. Think of these as like popup windows that are independent. You can make them independent of the root window or have them anchored to it. That way if you close the root window all the Toplevels off of it will close. I suggest you look more into Toplevels and you'll find what you're looking for. You probably want something like this:
Main.py
import humanclass
from Tkinter import *
window = Tk()
# Hides the root window since you will no longer see it
window.withdraw()
human1 = humanclass.Human(window, "Jim", "78", "British")
human2 = humanclass.Human(window, "Bob", "18", "Welsh")
window.mainloop()
humanclass.py
from Tkinter import *
class Human():
def __init__(self, window, name, age, nation):
# Creates a toplevel instance instead of using the root window
self.window=Toplevel(window)
self.window.geometry("500x200+100+200")
self.window.title(name)
self.label1 = Label(self.window, text=age).grid(row=0, column=0, sticky=W)
self.label2 = Label(self.window, text=nation).grid(row=1, column=0, sticky=W)
self.button = Button(self.window, text="Close", width=5, command=self.clicked).grid(row=3, column=0, sticky=W)
def clicked(self):
self.window.destroy()

Categories