I am currently working on an assignment that states that I must build a GUI using Tkinter, that will load strings from a text file and display them in a text box. The instructions also state that classes must be utilized.
Being new to programming, I'm not sure how this all works out. My current text file looks like this:
(Item identity #, Quantity,Item, Location, Color)
(23871243, 20, Remote, California, White)
(94938443, 10, Socks, Canada, Black)
As per the requirements, each line must be an individual object, with
attributes, such as Quantity, Location, etc.
I'm fine with the GUI component, however the main problem I am having is telling Python that each line in the text file is a separate object, with certain attributes.
The 'OpenFile' function is likely where the issue is. As of right now it returns a list of strings, but I would like it to return an object with 5 attributes(as listed above, in the text file).
Any help would be greatly appreciated.
from tkinter import *
from tkinter import ttk
from tkinter import font
from tkinter.filedialog import askopenfile
class Manager:
def __init__(self, root):
#The frame for the GUI itself
mainframe = ttk.Frame(root, relief=SUNKEN, padding="3 10 12 12")
mainframe.grid(column=0, row=0, columnspan=10, rowspan=10, sticky="NW")
button_load= ttk.Button(mainframe, text="Load",command=self.OpenFile)
button_load.grid(row=35, column=17, sticky = "NE", padx=5, pady=10)
global text_identity
text_identity = Text(mainframe, width = 15, height = 2)
text_identity.grid(column=8, row=5, sticky=(N,W))
def OpenFile(self):
listing=[]
name = askopenfile(mode='r',initialdir="D:/Documents",
filetypes =(("Text File", "*.txt"),("All Files","*.*")),
title = "Choose a file.")
with name as rd:
global items
items=rd.readlines()
one=[x.strip('\n') for x in items]
return one
class Items:
identity=''
num=''
name = ''
location = ''
other = ''
def __init__(self,identity,num,name,location,other):
self.identity = identity
self.num = num
self.name = name
self.location = location
self.other = other
def main():
root = Tk()
Manager(root)
root.title("Data Management")
root.mainloop()
if __name__ == main():
main()
First of all, you should create a file named item_descriptions.csv and fill it with the following text:
item_id,quantity,item,location,color
23871243,20,Remote,California,White
94938443,10,Socks,Canada,Black
The first row of any CSV file will need to have a row of identifiers that can be used within Python. Why? Because the following program relies on field names to automatically generate a named tuple:
#! /usr/bin/env python3
import collections
import csv
import pathlib
import tkinter.filedialog
import tkinter.messagebox
import tkinter.scrolledtext
import tkinter.ttk
# Make the constants easy to refer to in the rest of the program.
from tkinter.constants import *
class Manager(tkinter.ttk.Frame):
"""Manager(master=None, **kw) -> Manager instance"""
#classmethod
def main(cls):
"""Create a root window for the Manager and display the widget."""
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Manager')
root.minsize(680, 420)
frame = cls(root)
frame.grid(sticky=NSEW)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.mainloop()
def __init__(self, master=None, **kw):
"""Initialize the Manager instance and its attributes."""
super().__init__(master, **kw)
self.initial_dir = pathlib.Path.home()
self.scrolled_text = tkinter.scrolledtext.ScrolledText(self)
self.load_button = tkinter.ttk.Button(self)
self.size_grip = tkinter.ttk.Sizegrip(self)
self.setup_widgets()
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
def setup_widgets(self):
""" Change options on the widgets so they work properly."""
self.scrolled_text.configure(state=DISABLED, wrap=WORD)
self.load_button.configure(text='Load', command=self.find_csv_file)
# Place widgets where they belong in the frame.
self.scrolled_text.grid(row=0, column=0, columnspan=2, sticky=NSEW)
self.load_button.grid(row=1, column=0, sticky=EW)
self.size_grip.grid(row=1, column=1, sticky=SE)
def find_csv_file(self):
"""Begin the process of loading a CSV file for display."""
source = tkinter.filedialog.askopenfilename(
parent=self,
title='Where is the file you want to open?',
multiple=False,
defaultextension='.csv',
filetypes=(('Spreadsheet', '.csv'), ('All Files', '*')),
initialdir=self.initial_dir
)
if source:
self.initial_dir = pathlib.Path(source).parent
self.show_records(self.load_records(source))
def load_records(self, source):
"""Open the requested file and try to yield out its records."""
with open(source, newline='') as file:
reader = csv.DictReader(file)
try:
Record = collections.namedtuple('Record', reader.fieldnames)
except Exception as error:
tkinter.messagebox.showerror(
'Exception',
f'{type(error).__name__}: {error}',
master=self
)
else:
self.scrolled_text.configure(state=NORMAL)
self.scrolled_text.delete(0.0, END)
yield from (Record(**row) for row in reader)
def show_records(self, iterable):
"""Display each record when able without locking up the GUI."""
try:
record = next(iterable)
except StopIteration:
self.scrolled_text.configure(state=DISABLED)
else:
self.scrolled_text.insert(END, f'{record}\n')
self.after_idle(self.show_records, iterable)
if __name__ == '__main__':
Manager.main()
If you need further help with your program, you may need to ask another question for more answers.
Related
I'm currently trying to create a desktop file converter application for myself using tkinter. It so far has a drag and drop area, and a button you can press to just select the file from the file explorer. However, I'm having trouble figuring out how to correctly position the widgets so they sit on top of each other. I want it so the drag and drop box is off the the left side of the screen, and in the middle of the box I want a text widget that says, "Drag and drop file, or select them", with a button widget below it that allows them to select from the file manager if they please.
import tkinter as tk
import tkinter.filedialog
from TkinterDnD2 import DND_FILES, TkinterDnD
from conversion import *
#global variables
path_to_file = " "
file_type = " "
compatable_converstion = []
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title("File Converter")
self.master.minsize(1000,600)
self.master.maxsize(1200,800)
self.pack()
self.create_widgets()
def create_widgets(self):
#Drag and drop files area
self.drop_box = tk.Listbox(root, selectmode=tk.SINGLE, background="#99ff99")
self.drop_box.pack(ipadx=170)
self.drop_box.pack(ipady=120)
self.drop_box.pack(side="left")
self.drop_box.drop_target_register(DND_FILES)
self.drop_box.dnd_bind("<<Drop>>", open_dropped_file)
#Select file button
self.select_file = tk.Button(self)
self.select_file["text"] = "Select File"
self.select_file["command"] = self.open_selected_file
self.select_file.place(relx=1.0, rely=1.0, anchor="se")
#Instructional Text
sentence = "Drag and drop or select your file"
self.instructions = tk.Text(root)
self.instructions.insert(tk.END, sentence)
self.instructions.place(relx=1.0, rely=1.0, anchor="se")
def open_selected_file(self):
path_to_file = tk.filedialog.askopenfilename(initialdir="/", title="Select A File", filetypes = (("jpeg files","*.jpg"),("all files","*.*")))
temp_str = " "
for chars in reversed(path_to_file):
if(chars == '.'):
break
temp_str += chars
file_type = temp_str[::-1]
compatable_converstion = retrieve_compatable_conversions(file_type)
def main():
global root
root = TkinterDnD.Tk()
app = Application(master=root)
app.mainloop()
if __name__ == "__main__":
main()
Just in case my explanation of how I want it laid out sucks, here is a picture:
You are creating the widgets self.drop_box and self.instructions on the root window. They should be created on self.
The place() geometry manager does not reserve space in a widget, so you will have to make the widget(self) as big as necessary with expand=True, fill='both'. I would advise using the grid() geometry manager for any design which is not really simple.
The widgets will be stacked in the order they are placed, which means that the Button will be hidden beneath the Text widget. Just change the order in which they are placed.
As for using ipadx and ipady in the pack() function, have a look at Why does the 'ipady' option in the tkinter.grid() method only add space below a widget?
Also; you don't need to make root a global variable because the application knows it as self.master.
Here is an example, not of the whole program but the parts I have mentioned:
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title("File Converter")
self.master.minsize(1000,600)
self.master.maxsize(1200,800)
self.pack(expand=True, fill='both') # Fill entire root window
self.create_widgets()
def create_widgets(self):
#Drag and drop files area
self.drop_box = tk.Listbox(self, selectmode=tk.SINGLE, background="#99ff99")
self.drop_box.pack(side="left", ipadx=170, ipady=120)
self.drop_box.drop_target_register(DND_FILES)
self.drop_box.dnd_bind("<<Drop>>", open_dropped_file)
#Instructional Text
sentence = "Drag and drop or select your file"
self.instructions = tk.Text(self)
self.instructions.insert(tk.END, sentence)
self.instructions.place(relx=1.0, rely=1.0, anchor="se")
#Select file button
self.select_file = tk.Button(self, text="Select File",
command=self.open_selected_file)
self.select_file.place(relx=1.0, rely=1.0, anchor="se")
This should let you work out most of your problems.
Creates two windows and gridding is not correct. Some additional comments in the code initiation.
I have used this approach, without the super init with no problem, many times.
Advice appreciated.
Thanks
# timhockswender#gmail.com
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self):
super(constants_page, self).__init__() # from stackoverflow
# if not used error = 'constants_page' object has no attribute 'tk'
# if used, another tiny window is opened
# in addtion to the constants_page
self.constants_page = tk.Tk()
self.constants_page.geometry("1000x500") #width*Length
self.constants_page.title("Owen's Unit Conversion App")
self.constants_page.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self.constants_page,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
# Problem: not gridding properly
self.title_label = ttk.Label(self.constants_page, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.constants_page.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.constants_page.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = constants_page()
program.mainloop()
if __name__ == "__main__":
Show_Page()
The super call expects you to provide a root window (an instance of tk.Tk()). If you don't provide one it defaults to the first root window opened, and if none has been opened yet then it helpfully opens one for you. A few lines later you open a second one yourself.
The easy fix is to remove the self.constants_page = tk.Tk() line. The proper fix is to make the Tk() instance outside of the class and pass it in. This allows you to use the Frame class itself to lay out widgets (use self instead of self.constants_page). Try this:
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
master.geometry("1000x500") #width*Length
master.title("Owen's Unit Conversion App")
self.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
self.title_label = ttk.Label(self, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = tk.Tk()
win = constants_page(program)
win.pack()
program.mainloop()
if __name__ == "__main__":
Show_Page()
I've write a simple app, see below, to redirect help function data of tkinter library on ScrolledText , something like
print (help(tkinter.Label)) on cli.
I' ve use a class written by #Bryan Oakley.
After launch the scipt press 'Load' button and after click on a voice on the left tree.
This cause the writing of help function data of the selected item on ScrolledText using sys.stdout by #Bryan Oakley class
sys.stdout.write(help(s))
All works but I can't refresh data on my ScrolledText even with
self.widget.delete('1.0', tk.END)
than using
sys.stdout.flush()
Basically I'm not able, when you click another item, to delete all data from ScrolledText and write new sys.stdout
Wath is wrong in my approach?
import sys
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
class TextRedirector(object):
"""Written Bryan Oakley
https://stackoverflow.com/users/7432/bryan-oakley
"""
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
#this generate an error
#self.widget.delete('1.0', tk.END)
self.widget.configure(state="normal")
#it works but generete an error
self.widget.insert("end", str, self.tag)
self.widget.configure(state="disabled")
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello Tkinter ")
self.selected = tk.StringVar()
self.init_ui()
def init_ui(self):
f = tk.Frame()
f1 = tk.Frame(f)
tk.Label(f, textvariable = self.selected).pack()
cols = (["#0",'','w',False,200,200],
["#1",'','w',True,0,0],)
self.Voices = self.get_tree(f1, cols, show="tree")
self.Voices.show="tree"
self.Voices.pack(fill=tk.Y, padx=2, pady=2)
self.Voices.bind("<<TreeviewSelect>>", self.on_selected)
f1.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
f2 = tk.Frame(f)
self.text = ScrolledText(f2)
self.text.pack(side="top", fill="both", expand=True)
self.text.tag_configure("stderr", foreground="#b22222")
sys.stdout = TextRedirector(self.text, "stdout")
sys.stderr = TextRedirector(self.text, "stderr")
f2.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
w = tk.Frame()
tk.Button(w, text="Load", command=self.set_values).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.Y, expand=0)
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def set_values(self,):
rs = []
for i in dir(tk):
rs.append(i)
for i in rs:
tree = self.Voices.insert("", tk.END, text=i, values=(i,'tree'))
def on_selected(self, evt=None):
selected_item = self.Voices.focus()
d = self.Voices.item(selected_item)
if d['values']:
item = (d['values'][0])
self.selected.set(item)
s = "tkinter.{}".format(item)
#this generate an error
#sys.stdout.flush()
sys.stdout.write(help(s))
def get_tree(self,container, cols, size=None, show=None):
headers = []
for col in cols:
headers.append(col[1])
del headers[0]
if show is not None:
w = ttk.Treeview(container,show=show)
else:
w = ttk.Treeview(container,)
w['columns']=headers
for col in cols:
w.heading(col[0], text=col[1], anchor=col[2],)
w.column(col[0], anchor=col[2], stretch=col[3],minwidth=col[4], width=col[5])
sb = ttk.Scrollbar(container)
sb.configure(command=w.yview)
w.configure(yscrollcommand=sb.set)
w.pack(side=tk.LEFT, fill=tk.BOTH, expand =1)
sb.pack(fill=tk.Y, expand=1)
return w
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
Adding a delete inside the write statement is the wrong solution since you do not always have control over what gets sent to the write statement. For example, help may actually call write more than once each time you call it. If it does, you will only ever see the results of the most recent call to write.
The correct solution is to delete the contents before calling help. For that, you need to enable the widget before deleting the contents since the redirector class as written leaves the widget disabled.
For example, you could add the method clear to the redirector class like so:
class TextRedirector(object):
...
def clear(self):
self.widget.configure(state="normal")
self.widget.delete("1.0", "end")
self.widget.configure(state="disabled")
You can then call it immediately before calling help:
def on_selected(self, evt=None):
...
if d['values']:
...
sys.stdout.clear()
help(s)
Note: you do not need to do sys.stdout.write(help(s)) because help(s) merely returns an empty string. help(s) is already sending its information to stdout.
I have this code in python 2.7 using tkinter that creates a Button on a Frame to open a file. There's a Label under it. I'm trying to make it so once the file is opened, the label prints the path, "file1.name" or whatever, on the Label, and if you open a new file it will change that Label again.
Also, I'm betting there's a better way to move data between the functions than I'm using here with global, but that's not worrying me right now.
I have to move the data from the opened files between functions so that I can mix the data and save to a new file. The code is:
from Tkinter import *
import Tkinter
import tkFileDialog
import tkMessageBox
root = Tkinter.Tk()
global rfile1
global rfile2
rfile1 = ""
rfile2 = ""
class Application(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.grid()
self.createWidgets1()
self.createLabels1()
self.createWidgets2()
self.createLabels2()
def createWidgets1(self):
self.oButton = Button(self, text = "open1", command = self.openfile1)
self.oButton.grid()
def createLabels1(self):
self.oLabel = Label(self, text = "whoops")
self.oLabel.grid()
def createWidgets2(self):
self.oButton = Button(self, text = "open2", command= self.openfile2)
self.oButton.grid()
def createLabels2(self):
self.oLabel2 = Label(self, text = "whoops2")
self.oLabel2.grid()
def openfile1(self):
file1 = tkFileDialog.askopenfile(title = "choose a file, *****", parent=root, mode = 'rb')
rfile1 = file1.read()
tkMessageBox.showinfo("oy", rfile1, parent=root)
def openfile2(self):
file2 = tkFileDialog.askopenfile(parent=root, mode='rb')
rfile2 = file2.read()
tkMessageBox.showinfo("hola", rfile2, parent=root)
app = Application()
app.master.title("whiggy whompus")
app.mainloop()
If I understand correctly, you want something like (untested):
def openfile1(self):
file1 = tkFileDialog.askopenfile(title = "choose a file, *****", parent=root, mode = 'rb')
self.oLabel.configure(text=file1.name)
rfile1 = file1.read()
tkMessageBox.showinfo("oy", rfile1, parent=root)
#mgilson has solved your first question. Your second question, about how to pass parameters between functions without using globals :
you might want to look at storing the variables as attributes on your application class :
The syntax self. is an attribute on the current instance (an instance being a particular example of a class - just like your car is a specific example of a class "car").
You can use instance attributes in this example as if they are globals.
from Tkinter import *
import Tkinter
import tkFileDialog
import tkMessageBox
root = Tkinter.Tk()
class Application(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.grid()
self.createWidgets1()
self.createLabels1()
self.createWidgets2()
self.createLabels2()
self.rfile1 = ""
self.rfile2 = ""
def createWidgets1(self):
self.oButton = Button(self, text = "open1", command = self.openfile1)
self.oButton.grid()
def createLabels1(self):
self.oLabel = Label(self, text = "whoops")
self.oLabel.grid()
def createWidgets2(self):
self.oButton = Button(self, text = "open2", command= self.openfile2)
self.oButton.grid()
def createLabels2(self):
self.oLabel2 = Label(self, text = "whoops2")
self.oLabel2.grid()
def openfile1(self):
file1 = tkFileDialog.askopenfile(title = "choose a file, *****", parent=root, mode = 'rb')
self.rfile1 = file1.read()
tkMessageBox.showinfo("oy", self.rfile1, parent=root)
def openfile2(self):
file2 = tkFileDialog.askopenfile(parent=root, mode='rb')
self.rfile2 = file2.read()
tkMessageBox.showinfo("hola", self.rfile2, parent=root)
app = Application()
app.master.title("whiggy whompus")
app.mainloop()
In the following method, I am trying to create a frame, put a label and text widget inside of it, and place them inside of another text widget. There are two problems with the results. How should it be changed to:
Have the inner text objects have the correct height based on the inserted text?
Get the frame and text to resize to the current dimensions of the outer widget?
Suggestions would be appreciated! It is somewhat difficult to get messages to appear as intended in the code. They are supposed to automatically wrap and resize as the main widget gets stretched around.
def display(self, name, message):
frame = tkinter.ttk.Frame(self.__text, borderwidth=1)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(1, weight=1)
name = tkinter.ttk.Label(frame, text=name)
name.grid(row=0, column=0)
text = tkinter.Text(frame, wrap=tkinter.WORD, height=1)
text.grid(row=0, column=1, sticky=tkinter.EW)
text.insert('1.0', message)
text.configure(state=tkinter.DISABLED)
self.__text.window_create('1.0', window=frame, stretch=tkinter.TRUE)
The code is supposed to generate a frame with a label in it along with word-wrapped text beside it. Each new message that is being displayed should be on top of older messages, and as the message list grows, it should be possible to scroll and read older messages (indefinitely). Unfortunately, this does not work any better than the code up above.
def display(self, name, message):
frame = tkinter.ttk.Frame(self.__text, borderwidth=1, relief='solid')
name = tkinter.ttk.Label(frame, text=name)
text = tkinter.Text(frame, wrap=tkinter.WORD, height=1)
frame.pack(expand=tkinter.TRUE, fill=tkinter.BOTH)
name.pack(fill=tkinter.BOTH, side=tkinter.LEFT)
text.pack(expand=tkinter.TRUE, fill=tkinter.BOTH)
text.insert('1.0', message)
text.configure(state=tkinter.DISABLED)
self.__text.window_create('1.0', window=frame)
The frame appears to be properly configured, but getting the outer text box to act like a geometry manager and setting the height property of the inner text box appear to be the main problems here. The outer text box is not currently resizing the frame, and I am not sure what code to write to resize the height of the inner text box based on how much text is inside of it. Here is the full code of the program:
import tkinter
import tkinter.ttk
import datetime
import getpass
import os
import uuid
################################################################################
class DirectoryMonitor:
def __init__(self, path):
self.__path = path
self.__files = {}
def update(self, callback):
for name in os.listdir(self.__path):
if name not in self.__files:
path_name = os.path.join(self.__path, name)
self.__files[name] = FileMonitor(path_name)
errors = set()
for name, monitor in self.__files.items():
try:
monitor.update(callback)
except OSError:
errors.add(name)
for name in errors:
del self.__files[name]
################################################################################
class FileMonitor:
def __init__(self, path):
self.__path = path
self.__modified = 0
self.__position = 0
def update(self, callback):
modified = os.path.getmtime(self.__path)
if modified != self.__modified:
self.__modified = modified
with open(self.__path, 'r') as file:
file.seek(self.__position)
text = file.read()
self.__position = file.tell()
callback(self.__path, text)
################################################################################
class Aggregator:
def __init__(self):
self.__streams = {}
def update(self, path, text):
if path not in self.__streams:
self.__streams[path] = MessageStream()
parts = text.split('\0')
assert not parts[-1], 'Text is not properly terminated!'
self.__streams[path].update(parts[:-1])
def get_messages(self):
all_messages = set()
for stream in self.__streams.values():
all_messages.update(stream.get_messages())
return sorted(all_messages, key=lambda message: message.time)
################################################################################
class MessageStream:
def __init__(self):
self.__name = None
self.__buffer = None
self.__waiting = set()
def update(self, parts):
if self.__name is None:
self.__name = parts.pop(0)
if self.__buffer is not None:
parts.insert(0, self.__buffer)
self.__buffer = None
if len(parts) & 1:
self.__buffer = parts.pop()
for index in range(0, len(parts), 2):
self.__waiting.add(Message(self.__name, *parts[index:index+2]))
def get_messages(self):
messages = self.__waiting
self.__waiting = set()
return messages
################################################################################
class Message:
def __init__(self, name, timestamp, text):
self.name = name
self.time = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
self.text = text
################################################################################
class MessageWriter:
def __init__(self, path, name):
assert '\0' not in name, 'Name may not have null characters!'
self.__name = str(uuid.uuid1())
self.__path = os.path.join(path, self.__name)
with open(self.__path, 'w') as file:
file.write(name + '\0')
#property
def name(self):
return self.__name
def write(self, text):
assert '\0' not in text, 'Text may not have null characters!'
timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
with open(self.__path, 'a') as file:
file.write(timestamp + '\0' + text + '\0')
################################################################################
class Logos(tkinter.ttk.Frame):
#classmethod
def main(cls, path):
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Logos 2.0')
root.minsize(320, 240) # QVGA
view = cls(root, path)
view.grid(row=0, column=0, sticky=tkinter.NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
def __init__(self, master, path, **kw):
super().__init__(master, **kw)
self.configure_widgets()
self.__writer = MessageWriter(path, getpass.getuser())
self.__monitor = DirectoryMonitor(path)
self.__messages = Aggregator()
self.after_idle(self.update)
def configure_widgets(self):
# Create widgets.
self.__text = tkinter.Text(self, state=tkinter.DISABLED)
self.__scroll = tkinter.ttk.Scrollbar(self, orient=tkinter.VERTICAL,
command=self.__text.yview)
self.__entry = tkinter.ttk.Entry(self, cursor='xterm')
# Alter their settings.
self.__text.configure(yscrollcommand=self.__scroll.set)
# Place everything on the grid.
self.__text.grid(row=0, column=0, sticky=tkinter.NSEW)
self.__scroll.grid(row=0, column=1, sticky=tkinter.NS)
self.__entry.grid(row=1, column=0, columnspan=2, sticky=tkinter.EW)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# Setup box for typing.
self.__entry.bind('<Control-Key-a>', self.select_all)
self.__entry.bind('<Control-Key-/>', lambda event: 'break')
self.__entry.bind('<Return>', self.send_message)
self.__entry.focus_set()
def select_all(self, event):
event.widget.selection_range(0, tkinter.END)
return 'break'
def send_message(self, event):
text = self.__entry.get()
self.__entry.delete(0, tkinter.END)
self.__writer.write(text)
def update(self):
self.after(1000, self.update)
self.__monitor.update(self.__messages.update)
for message in self.__messages.get_messages():
self.display(message.name, message.text)
def display(self, name, message):
frame = tkinter.ttk.Frame(self.__text, borderwidth=1, relief='solid')
name = tkinter.ttk.Label(frame, text=name)
text = tkinter.Text(frame, wrap=tkinter.WORD, height=1)
name.grid(row=0, column=0)
text.grid(row=0, column=1, sticky=tkinter.EW)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(1, weight=1)
text.insert('1.0', message)
text.configure(state=tkinter.DISABLED)
self.__text.window_create('1.0', window=frame)
################################################################################
if __name__ == '__main__':
Logos.main('Feeds')
.grid methods have always been a bit of a hassle for me to get resizing/stretching correctly.
For your code, I would change the .grid calls to the following .pack calls:
frame.pack(expand=1, fill='both')
name.pack(fill='both', side='left')
text.pack(expand=1, fill='both')
You can then drop your .grid_{row,column}configure calls as well.
Is your __text widget resizing properly? If it doesn't resize, it will not allow this frame widget to resize either.
Your description is hard to understand. Are you saying that even though you are placing the frame/text combo inside another text widget, you want the frame/text widget combo to grow and shrink to fit the outer text widget? If so, why are you using a text widget?
It's possible you're using the wrong type of widgets for the effect you are trying to achieve. What exactly are you trying to do that requires text widgets nested inside other text widgets?