В следующем методе я пытаюсь создать фрейм, поместить в него метку и текстовый виджет и поместить их в другой текстовый виджет. Есть две проблемы с результатами. Как это должно быть изменено на:
- Имеют ли внутренние текстовые объекты правильную высоту на основе вставленного текста?
- Получить рамку и текст для изменения размера до текущих размеров внешнего виджета?
Предложения будут оценены! Несколько трудно заставить сообщения отображаться так, как задумано в коде. Предполагается, что они автоматически оборачиваются и изменяют размеры, когда основной виджет растягивается.
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)
Код должен генерировать фрейм с надписью в нем и завернутым в текст текстом рядом с ним. Каждое новое отображаемое сообщение должно быть поверх старых сообщений, и по мере роста списка сообщений должна быть возможность прокручивать и читать старые сообщения (на неопределенный срок). К сожалению, это работает не лучше, чем приведенный выше код.
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)
Кажется, что рамка правильно настроена, но основные проблемы здесь заключаются в том, чтобы заставить внешнее текстовое поле действовать как менеджер геометрии и установить свойство высоты внутреннего текстового поля. Внешнее текстовое поле в настоящее время не изменяет размеры фрейма, и я не уверен, какой код писать, чтобы изменить размер внутреннего текстового поля в зависимости от того, сколько текста внутри него. Вот полный код программы:
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')