Автоматическое изменение размера в Python - PullRequest
2 голосов
/ 18 февраля 2011

В следующем методе я пытаюсь создать фрейм, поместить в него метку и текстовый виджет и поместить их в другой текстовый виджет. Есть две проблемы с результатами. Как это должно быть изменено на:

  1. Имеют ли внутренние текстовые объекты правильную высоту на основе вставленного текста?
  2. Получить рамку и текст для изменения размера до текущих размеров внешнего виджета?

Предложения будут оценены! Несколько трудно заставить сообщения отображаться так, как задумано в коде. Предполагается, что они автоматически оборачиваются и изменяют размеры, когда основной виджет растягивается.

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')

Ответы [ 2 ]

3 голосов
/ 18 февраля 2011

.grid-методы всегда доставляли мне неудобства, чтобы правильно изменить размер / растяжение.

Для вашего кода я бы изменил вызовы .grid на следующие вызовы .pack:

frame.pack(expand=1, fill='both')
name.pack(fill='both', side='left')
text.pack(expand=1, fill='both')

Затем вы можете сбросить свои .grid_ {row, column} настройки вызовов.

Правильно ли изменяется размер вашего __text-виджета?Если размер не изменится, размер этого фрейм-виджета также не будет изменен.

2 голосов
/ 18 февраля 2011

Ваше описание трудно понять.Вы хотите сказать, что, даже если вы помещаете комбинированный фрейм / текст в другой текстовый виджет, вы хотите, чтобы комбинированный фрейм / текстовый виджет увеличивался и уменьшался, чтобы соответствовать внешнему текстовому виджету?Если да, то почему вы используете текстовый виджет?

Возможно, вы используете неправильный тип виджетов для эффекта, которого вы пытаетесь достичь.Что именно вы пытаетесь сделать, чтобы текстовые виджеты были вложены в другие текстовые виджеты?

...