С Tkinter, как позиционировать диалоговое окно при инициализации ИЛИ как автоматически изменять размер окна после настройки геометрии? - PullRequest
0 голосов
/ 01 ноября 2018

Отказ от ответственности: По всей вероятности, это вполне может быть проблемой XY, я был бы признателен, если бы вы указали мне правильное направление.

Цель: У меня есть файл, который я хотел бы, чтобы tkinter прочитал, а затем динамически создал виджеты на основе содержимого файла. Я хочу, чтобы окно всегда открывалось по центру моего курсора.

Ради MCVE давайте рассмотрим простой текстовый файл, который гласит:

Madness?
This
IS
T K I N T E R

И tkinter должен создать 4 виджета с метками, размер главного окна которых можно изменить. Это очень просто ... до тех пор, пока файл не будет зашифрован. Прежде чем я смогу прочитать зашифрованный файл, мне нужно askstring() сначала ввести пароль (MCVE: давайте также предположим, что мы запрашиваем только key здесь).

Мои вопросы:

  1. Диалог askstring всегда находится в положении по умолчанию. Я знаю, что это должно быть относительно родителя (корень) ...

  2. Но я не могу установить geometry() до того, как создаю виджеты, иначе он не будет изменен в соответствии с виджетами ...

  3. И я не могу заранее определить размер, необходимый для geometry(), так как не могу открыть файл без пароля (ключа) ...

Вот пример MCVE моей самой успешной попытки:

import tkinter as tk
from tkinter.simpledialog import askstring
from cryptography.fernet import Fernet

class GUI(tk.Tk):
    def __init__(self):
        super().__init__()

        # side note: If I don't withdraw() the root first, the dialog ends up behind the root
        # and I couldn't find a way to get around this.
        self.withdraw()
        self.create_widgets()

        # Attempt: I tried doing this instead of self.create_widgets():
            # self.reposition()
            # self.after(ms=1,func=self.create_widgets)
        # The dialog is positioned correctly, but the window size doesn't resize

        self.deiconify()

        # I have to do the reposition AFTER mainloop or else the window size becomes 1x1
        self.after(ms=1, func=self.reposition)
        self.mainloop()

    def get_key(self):
        return askstring('Locked','Enter Key', show='*', parent=self)

    def create_widgets(self):
        self.lbls = []
        with open('test2.txt', 'rb') as file:
            encrypted = file.read()
        key = self.get_key()
        suite = Fernet(key)
        self.data = suite.decrypt(encrypted).decode('utf-8')
        for i in self.data.split('\n'):
            self.lbls.append(tk.Label(self, text=i.strip()))
            self.lbls[-1].pack()

    def reposition(self):
        width, height = self.winfo_width(), self.winfo_height()
        self.geometry(f'{width}x{height}+{self.winfo_pointerx()-int(width/2)}+{self.winfo_pointery()-int(height/2)}')

gui = GUI()

Что достигает (с чем я могу жить):

  • ✓ Правильный размер в зависимости от содержимого файла
  • ✓ Корень расположен в центре курсора
  • ⨯ Запрашивает клавишу в центре курсора

Мои вопросы:

  1. Можно ли снова выполнить автоматическое изменение размера корня на основе функции виджетов, аналогичной pack_propagate() после установки geometry()? Кажется, как только geometry() установлен, корень больше не будет распространяться.

  2. Если нет, то как я могу вручную изменить его размер в коде? Я попытался получить общую высоту виджетов height = sum([i.winfo_reqheight() for i in self.lbls]), но height просто становится 0. Но когда я print(self.lbls[-1].winfo_reqheight()) в self.create_widgets() возвращает 26 каждый, и они на самом деле печатают после моего self.reposition() вызова, что странно.

  3. В противном случае возможно ли разместить диалоговое окно askstring() до создания виджетов?

Я в тупике. Мне кажется, что я поступаю об этом неправильно, но я не уверен, как правильно справиться с этой ситуацией, чтобы разорвать цикл зависимости.


Чтобы помочь с воспроизведением, здесь есть зашифрованная строка и ключ:

Данные:

gAAAAABb2z3y-Kva7bdgMEbvnqGGRRJc9ZMrt8oc092_fuxSK1x4qlP72aVy13xR-jQV1vLD7NQdOTT6YBI17pGYpUZiFpIOQGih9jYsd-u1EPUeV2iqPLG7wYcNxYq-u4Z4phkHllvP

Key:

SecretKeyAnyhowGoWatchDareDevilS3ItsAmazing=

Редактировать: На основании ответа @ BryanOakley здесь Я могу вызвать self.geometry("") для сброса, однако он возвращается к исходному 200x200 размеру и все еще не распространяет виджеты.

1 Ответ

0 голосов
/ 01 ноября 2018

После нескольких десятков попыток, я думаю, я заработал, вот соответствующие части, которые я модифицировал:

def __init__(self):
    super().__init__()
    self.withdraw()

    # reposition the window first...
    self.reposition()

    # For some reason I can't seem to figure out, 
    # without the .after(ms=1) the dialog still goes to my top left corner
    self.after(ms=1, func=self.do_stuff)
    self.mainloop()

# wrap these functions to be called by .after()
def do_stuff(self):
    self.create_widgets()
    self.deiconify()

def reposition(self):
    width, height = self.winfo_width(), self.winfo_height()
    self.geometry(f'{width}x{height}+{self.winfo_pointerx()-int(width/2)}+{self.winfo_pointery()-int(height/2)}')

    # reset the geometry per @BryanOakley's answer in the linked thread
    # Seems it resets the width/height but still retains the x/y
    self.geometry("")

Казалось бы, у BryanOakley есть ответы для всех вещей tkinter.

Я все еще жду, чтобы увидеть, будут ли другие люди, которые будут связываться по этому вопросу, и будем рады принять ваш ответ, чтобы внести некоторую ясность по этому вопросу.

...