开发者

Automatic Resizing in Python's tkinter

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:

  1. Have the inner text objects have the correct height based on the inserted text?
  2. 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.__entr开发者_运维技巧y.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?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜