Обновление содержимого виджета записи на основе другого виджета записи - PullRequest
2 голосов
/ 14 октября 2019

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

Мой первыйподход состоял в том, чтобы связать функции обновления с событием нажатия клавиши, но я столкнулся с ошибкой, которая приводит к выполнению связанной функции до изменения текстовой переменной. Проблема описана здесь: Tkinter, обновите виджет Entry перед вызовом binding . Предложенное решение заключается в использовании отслеживания переменных вместо привязки события нажатия клавиши. Однако, поскольку в моем случае изменение одной переменной выполняет функцию, которая изменяет другую переменную, я ожидал бы, что функции будут выполнять друг друга в бесконечном цикле. Этого не происходит, но желаемый результат определенно не достигается.

import tkinter as tk
import tkinter.ttk as ttk

class App:

    def __init__(self, master, aspect_ratio):
        self.master = master
        self.aspect_ratio = aspect_ratio
        self.shape_x = tk.DoubleVar()
        self.shape_y = tk.DoubleVar()
        self.shape_x.trace('w', self.update_y)
        self.shape_y.trace('w', self.update_x)

        # Entry for x dimension
        self.entry_x = ttk.Entry(self.master, textvariable=self.shape_x, width=5)
        self.entry_x.pack(side='left', fill='x', expand='yes')

        # Label for multiplication sign
        self.entry_label = tk.Label(self.master, text='x', fg='gray')
        self.entry_label.pack(side='left')

        # Entry for y dimension
        self.entry_y = ttk.Entry(self.master, textvariable=self.shape_y, width=5)
        self.entry_y.pack(side='left', fill='x', expand='yes')

    def update_x(self, *args):
        self.shape_x.set(self.shape_y.get() * self.aspect_ratio)

    def update_y(self, *args):
        self.shape_y.set(self.shape_x.get() / self.aspect_ratio)

if __name__ == '__main__':        
    root = tk.Tk()
    app = App(master=root, aspect_ratio=2)
    root.mainloop()

Каков будет правильный путь для достижения того, что я ищу?

Редактировать: заменено * в update_y на /

Ответы [ 3 ]

2 голосов
/ 14 октября 2019

Возможно, самое простое решение - связать событие <KeyRelease>, так как изменения происходят на клавише , нажимающей . При этом вам не нужно использовать атрибут textvariable.

self.entry_x.bind("<Any-KeyRelease>", self.update_y)
self.entry_y.bind("<Any-KeyRelease>", self.update_x)
0 голосов
/ 14 октября 2019

Лично я бы назвал свои поля ввода и просто проверил, какая запись была в фокусе во время редактирования. Поскольку обе трассировки активированы, мы можем использовать фокус, чтобы сообщить нам, какое поле редактировать.

Мне пришлось создать инструкцию try / исключением для обработки нечетного поведения get(), не извлекающего значение из полейдля любой причины. Когда я проверял свои операторы печати, он, кажется, получил правильный номер, но, возможно, из-за двойного вызова он не справился с get ().

Вот пример кода:

import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self, aspect_ratio):
        super().__init__()
        self.aspect_ratio = aspect_ratio
        self.previous_x = 0
        self.previous_y = 0
        self.shape_x = tk.DoubleVar(self)
        self.shape_y = tk.DoubleVar(self)
        self.shape_x.trace('w', self.update_x_y)
        self.shape_y.trace('w', self.update_x_y)

        ttk.Entry(self, name='x', textvariable=self.shape_x, width=5).pack(side='left', fill='x', expand='yes')
        tk.Label(self, text='x', fg='gray').pack(side='left')
        ttk.Entry(self, name='y', textvariable=self.shape_y, width=5).pack(side='left', fill='x', expand='yes')

    def update_x_y(self, *args):
        name = self.focus_get().winfo_name()
        try:
            x = self.shape_x.get()
            y = self.shape_y.get()
            if name == 'x':
                self.previous_y = x * self.aspect_ratio
                self.shape_y.set(self.previous_y)
            elif name == 'y':
                self.previous_x = y * self.aspect_ratio
                self.shape_x.set(self.previous_x)

            else:
                print('Focus is not one of the 2 entry fields. Do nothing.')
        except:
            print('Handling odd error from the double call to the function returning empty string from get().')


if __name__ == '__main__':
    App(aspect_ratio=2).mainloop()
0 голосов
/ 14 октября 2019

Таким образом, способ, которым я обошел это, заключается в том, чтобы иметь хранилище значений, виджет которого Entry последний раз обновлялся, и затем иметь функцию «проверки», которая проверяет через крошечные интервалы, чтобы увидеть, был ли обновлен либо Entry, изатем обновляет другое:

from tkinter import *

class App:
    def __init__(self, root):
        self.root = root

        self.aspect = 2

        self.widthvalue = StringVar(name="width")
        self.widthvalue.set(1)
        self.heightvalue = StringVar(name="height")
        self.heightvalue.set(str(int(self.widthvalue.get())*self.aspect))

        self.widthvalue.trace("w", lambda name, index, mode, width=self.widthvalue: self.entrychange(width))
        self.heightvalue.trace("w", lambda name, index, mode, height=self.heightvalue: self.entrychange(height))

        self.width = Entry(self.root, textvariable=self.widthvalue)
        self.height = Entry(self.root, textvariable=self.heightvalue)

        self.width.pack()
        self.height.pack()

        self.lastupdated = None

        self.root.after(10, self.update)

    def entrychange(self, value):
        self.lastupdated = str(value)

    def update(self):
        if self.lastupdated != None:
            if self.lastupdated == "width" and self.widthvalue.get() != "" and self.widthvalue.get() != "0":
                self.heightvalue.set(str(int(int(self.widthvalue.get())/self.aspect)))
                self.height.update()
                self.lastupdated = None
            elif self.lastupdated == "height" and self.heightvalue.get() != "" and self.heightvalue.get() != "0":
                self.widthvalue.set(str(int(self.heightvalue.get())*self.aspect))
                self.width.update()
                self.lastupdated = None
        self.root.after(10, self.update)

root = Tk()
App(root)
root.mainloop()

Давайте разберемся с этим.

Первое, что мы делаем, это устанавливаем соотношение сторон, чтобы придерживаться его. В этом случае мы говорим, что высота всегда будет вдвое больше ширины:

self.aspect = 2

Далее мы создаем StringVar переменные для хранения значений двух виджетов Entry и установки их значенийдо абсолютного минимума:

self.widthvalue = StringVar(name="width")
self.widthvalue.set(1)
self.heightvalue = StringVar(name="height")
self.heightvalue.set(str(int(self.widthvalue.get())*self.aspect))

Затем мы устанавливаем трассировки для этих переменных, чтобы при каждом их обновлении мы вызывали функцию entrychange. Эта функция относительно проста, она просто обновляет другую переменную с именем self.lastupdated, которая хранит либо width, либо height, в зависимости от того, какое Entry было обновлено в последний раз:

self.widthvalue.trace("w", lambda name, index, mode, width=self.widthvalue: self.entrychange(width))
self.heightvalue.trace("w", lambda name, index, mode, height=self.heightvalue: self.entrychange(height))

Затем мы делаем скучные биты. , нарисуйте виджеты, установите их textvariable, упакуйте их и установите значение по умолчанию для self.lastupdated:

self.width = Entry(self.root, textvariable=self.widthvalue)
self.height = Entry(self.root, textvariable=self.heightvalue)

self.width.pack()
self.height.pack()

self.lastupdated = None

После этого мы запускаем вызов .after(). Что позволяет вам иметь непрерывные циклы в tkinter:

self.root.after(10, self.update)

Это запускает процесс постоянного запуска нашей функции update.


Функция обновления начинается с проверки,или нет, либо Entry было обновлено:

if self.lastupdated != None:

Затем он проверяет, какое из них было обновлено в последний раз, и проверяет, не является ли поле пустым или равно 0. Это потому, что и пустые значения, и 0будет возиться с нашей математикой (а также недопустимыми размерами для изображения):

if self.lastupdated == "width" and self.widthvalue.get() != "" and self.widthvalue.get() != "0":

Затем мы устанавливаем значение противоположного виджета Entry как двойное или половинное (в зависимости от того, какой Entry был изменен), обновите виджет в графическом интерфейсе и установите для переменной self.lastupdated значение по умолчанию:

self.heightvalue.set(str(int(int(self.widthvalue.get())/self.aspect)))
self.height.update()
self.lastupdated = None

Для других Entry мы делаем более или менее то же самое.

...