Python / Tkinter: есть ли способ постоянно «сканировать» входы? - PullRequest
0 голосов
/ 16 апреля 2019

Я довольно новичок в python и tkinter и написал простую программу, в которой пользователь может заполнить ввод 4 переменных и распечатать их одним нажатием кнопки.Теперь мне интересно: есть ли способ заставить программу постоянно сканировать окна ввода пользователя и обновлять вывод, как только пользователь изменил ввод?

Это моя программа:

from tkinter import *

def calc():
    val1 = e1.get()
    val2 = e2.get()
    val3 = e3.get()
    val4 = e4.get()
    res = val1 + " " + val2 + " " + val3 + " " + val4
    label2 = Label(master)
    label2["text"] = res
    label2.grid(row=4, column = 1)

master = Tk()

Label(master, text="Main Value").grid(row=0, sticky = E)
Label(master, text="Second Value").grid(row=1, sticky = E)
Label(master, text="Third Value").grid(row=2, sticky = E)
Label(master, text="Fourth Value").grid(row=3, sticky = E)

e1 = Entry(master)
e2 = Entry(master)
e3 = Entry(master)
e4 = Entry(master)

e1.grid(row=0, column=1)
e2.grid(row=1, column=1)
e3.grid(row=2, column=1)
e4.grid(row=3, column=1)

button1 = Button(master, text="Calculate", command=calc)
button1.grid(row=4, column=0, sticky=W, pady=4)

master.mainloop()

Я хочу, чтобы выход изменился, как только пользователь изменит один из входов.

Ответы [ 3 ]

3 голосов
/ 16 апреля 2019

Я бы использовал функцию trace_add классов переменных tkinter . Это гарантирует, что функция вызывается каждый раз, когда изменяется содержимое записи. Недостатком этого является то, что вам нужно создавать StringVar объект для каждой записи, но вы уверены, что вы фиксируете все изменения, вы не вызываете функцию, когда вам это не нужно, и нет задержки.

Рядом с этим вы создаете новый виджет Label каждый раз, когда вы звоните calc сейчас. Вместо того, чтобы создавать новую метку каждый раз, вы должны создать одну метку и обновить ее текст в calc.

Соединение этих изменений:

from tkinter import *


# Let calc accept the arguments passes by trace_add
def calc(*args):
    # Get the values from the StringVar objects
    val1 = v1.get()
    val2 = v2.get()
    val3 = v3.get()
    val4 = v4.get()
    res = val1 + " " + val2 + " " + val3 + " " + val4
    # Only change the text of the existing Label
    label2["text"] = res

master = Tk()

# make this Label once
label2 = Label(master)
label2.grid(row=4, column=1)

Label(master, text="Main Value").grid(row=0, sticky=E)
Label(master, text="Second Value").grid(row=1, sticky=E)
Label(master, text="Third Value").grid(row=2, sticky=E)
Label(master, text="Fourth Value").grid(row=3, sticky=E)

# Create StringVars
v1 = StringVar()
v2 = StringVar()
v3 = StringVar()
v4 = StringVar()

e1 = Entry(master, textvariable=v1)
e2 = Entry(master, textvariable=v2)
e3 = Entry(master, textvariable=v3)
e4 = Entry(master, textvariable=v4)

# Trace when the StringVars are written
v1.trace_add("write", calc)
v2.trace_add("write", calc)
v3.trace_add("write", calc)
v4.trace_add("write", calc)

e1.grid(row=0, column=1)
e2.grid(row=1, column=1)
e3.grid(row=2, column=1)
e4.grid(row=3, column=1)

master.mainloop()
1 голос
/ 17 апреля 2019

Trace - правильный подход, tkinter заботится об изменениях.Я бы использовал его со структурой данных и сразу же создавал бы виджеты в цикле с трассировкой, потому что кода гораздо меньше.Ниже приведен полный пример графического интерфейса пользователя, в котором используется много отслеживаемых входных данных с добавлением трассировки при создании.Каждое изменение в записи или щелчке по кнопке проверки отслеживается и инициирует вызов метода, который обрабатывает это изменение.

Это пример изучения трассировки всех питонов> = 3 tkinter:

# inputpanel.py derived and improved from my Coolprop GUI on github
#
from tkinter import *
import tkinter.ttk as ttk

class InputFrame(LabelFrame):
    #
    # This input frame creates Entries and selects for Variables
    # contained in a Dictionary structure. It traces the inputs 
    # and keeps the values updated according to the type of the value.
    # 
    # datadict needs at least the three dicts and the list below
    # for one key must be an entry in every dict
    # the list order is used for processing
    # You can pass a list order with only one field e.g. to init
    # and only this field will be processed
    #  
    # datadict={
    #             'verbose_names':{},
    #             'values':{},
    #             'callback_vars':{},
    #             'order':[],
    #             }
    # 
    # if a dict units is added to the datadict, the units will be displayed behind the entry widgets
    #

    def __init__(self, parent,cnf={}, title=None,datadict=None,order=None,frameborder=5, InputWidth=60,**kwargs):
        #
        LabelFrame.__init__(self, parent)
        #
        self.InputWidth=InputWidth
        if datadict :
            self.datadict=datadict
        else:
            self.datadict={
                'verbose_names':{},
                'values':{},
                'callback_vars':{},
                'order':[],
                }
        #
        if order :
            self.order=order
        else:
            self.order=self.datadict['order']
        #
        if title :
            self.IFrame = LabelFrame(parent, relief=GROOVE, text=title,bd=frameborder,font=("Arial", 10, "bold"))
        else:
            self.IFrame = LabelFrame(parent, relief=GROOVE,bd=frameborder,font=("Arial", 10, "bold"))
        #
        self.IFrame.grid(row=1,column=1,padx=8,pady=5,sticky=W)
        #
        self.InputPanel(self.IFrame)

    def InputPanel(self, PanelFrame, font=("Arial", 10, "bold")):
        '''
        '''
        #
        order_number=1
        for Dkey in self.order :
            if self.datadict['verbose_names'][Dkey] :
                #
                self.datadict['callback_vars'][Dkey].trace("w", lambda name, index, mode,
                                                         var=self.datadict['callback_vars'][Dkey],
                                                         value=self.datadict['values'][Dkey],
                                                         key=Dkey: self.InputPanelUpdate(var, key, value)
                                                         )
                Label(PanelFrame, text=self.datadict['verbose_names'][Dkey], font=font).grid(column=1, row=order_number, padx=8, pady=5, sticky=W)
                if type(self.datadict['values'][Dkey])==type(True):
                    Checkbutton(PanelFrame, width=self.InputWidth, variable=self.datadict['callback_vars'][Dkey], font=font).grid(column=2, row=order_number, padx=8, pady=5, sticky=W)
                else:
                    Entry(PanelFrame, width=self.InputWidth, textvariable=self.datadict['callback_vars'][Dkey], font=font).grid(column=2, row=order_number, padx=8, pady=5, sticky=W)
                try:
                    Label(PanelFrame, text=self.datadict['units'][Dkey],font=font).grid(column=3, row=order_number,padx=8,pady=5,sticky=W)
                except KeyError :
                    Label(PanelFrame, text='       ',font=font).grid(column=3, row=order_number,padx=8,pady=5,sticky=W)
            else :
                Label(PanelFrame, text=' ', font=font).grid(column=1, row=order_number, padx=8, pady=5, sticky=W)
            #
            order_number+=1

    def InputPanelUpdate(self, tkVar, key, value):
        #
        # Called on ever button press in an entry or click in a Checkbutton
        #
        if type(self.datadict['values'][key])==type(True):
            # For booleans we misuse a string because it is so easy
            self.datadict['values'][key] = True if tkVar.get()=='1' else False
        elif type(self.datadict['values'][key])==type(1): 
            # int
            self.datadict['values'][key] = int(tkVar.getint())
        elif type(self.datadict['values'][key])==type(1.1):
            # float
            self.datadict['values'][key] = float(tkVar.getdouble())
        else:
            # all the rest
            self.datadict['values'][key] = tkVar.get()

Это диалоговое окно для создания параметров для sphinx-quickstart.Он неполный, но если вам не нужны пользовательские шаблоны, он работает.Добавьте команду печати, содержащую параметры метода InputPaneUpdate класса SPInputFrame, и вы быстро поймете трассировку, отслеживая вывод консоли ...

# sphinx_quickstartpanel.py
#
from tkinter import filedialog
from tkinter import messagebox
from tkinter import *
import tkinter.ttk as ttk

from tkinter.simpledialog import Dialog

from .inputpanel import InputFrame

import os
#
import subprocess
#
from django.template.defaultfilters import slugify


class SpInputFrame(InputFrame):
    #
    # Add local functions to InputPanelUpdate
    #
    def InputPanelUpdate(self, tkVar, key, value):
        #
        # overwrite InputPanelUpdate
        #
        if type(self.datadict['values'][key])==type(True):
            self.datadict['values'][key] = True if tkVar.get()=='1' else False
        else:
            self.datadict['values'][key] = tkVar.get()
            if key=='project':
                #
                # On project update, update slugged name too
                #
                self.datadict['values']['project_fn']=slugify(self.datadict['values'][key])
                self.datadict['callback_vars']['project_fn'].set(self.datadict['values']['project_fn'])

class sphinx_startpanel(Dialog):
    #
    # use gui to run sphinx-quickstart
    #
    def __init__(self, parent, title=None, data=None):
        #
        # Constructor
        #
        self.parent=parent
        self.data=data
        #
        self.Row1Frame = LabelFrame(parent, relief=GROOVE, text=' 1.) Enter project name',bd=5,font=("Arial", 10, "bold"))
        self.Row1Frame.grid(row=1,column=1,padx=8,pady=5,sticky=W+E, columnspan=3)
        #
        self.Row2Frame = LabelFrame(parent, relief=GROOVE, text=' 2.) Choose base directory' ,bd=5,font=("Arial", 10, "bold"))
        self.Row2Frame.grid(row=2,column=1,padx=8,pady=5,sticky=W+E, columnspan=3 )
        #
        self.Row3Frame = LabelFrame(parent, relief=GROOVE, text=' 3.) Enter main parameters',bd=5,font=("Arial", 10, "bold"))
        self.Row3Frame.grid(row=3,column=1,padx=8,pady=5,sticky=W)
        #
        self.Row4Frame = LabelFrame(parent, relief=GROOVE, text=' 4.) Run quickstart',bd=5,font=("Arial", 10, "bold"))
        self.Row4Frame.grid(row=4,column=1,padx=8,pady=5,sticky=W)
        #
        self.Row1IFrame=SpInputFrame(self.Row1Frame, title='Project Name',datadict=self.data,order=['project'])
        #
        self.b2=Button(self.Row2Frame,text="Choose parent directory of your new project")
        self.b2.grid(row=1,column=1,padx=8,pady=5,stick=W+E, columnspan=3)     
        self.b2.bind("<ButtonRelease-1>", self.Button_2_Click)
        #
        self.Row3IFrame=SpInputFrame(self.Row3Frame, title='Main configuration',datadict=self.data)
        #
        self.b4=Button(self.Row4Frame,text="Run this configuration and build the empty project")
        self.b4.grid(row=1,column=1,padx=8,pady=5,stick=W+E, columnspan=3)     
        self.b4.bind("<ButtonRelease-1>", self.runQuickstart)
        #

    def Button_2_Click(self,event): 
        #
        START_DIR = os.path.dirname(os.path.abspath(__file__) )
        #
        BASE_DIR = filedialog.askdirectory(parent=self.parent, initialdir=START_DIR ,title="Basisverzeichnis auswählen")
        self.data['values']['BASE_DIR']=BASE_DIR
        self.data['callback_vars']['BASE_DIR'].set(self.data['values']['BASE_DIR'])
        #
        self.data['values']['path']=os.path.join(BASE_DIR,self.data['values']['project_fn'])
        self.data['callback_vars']['path'].set(self.data['values']['path'])
        #
        print(self.data['values'])

    def getCommandline(self):
        '''
        creates the command for subprocess.Popen
        '''
        print('Running getCommandline ')
        # 
        cmdline=['sphinx-quickstart']
        cmdline.append(self.data['values']['path'])
        cmdline.append('-q')
        #
        print('getCommandline cmdline :',str(cmdline))
        #
        for key in self.data['argument_keys']:
            #
            if key in ['path','project_fn' ,'BASE_DIR'] :
                pass
            else:
                if self.data['values'][key] not in ['',False,' ']:
                    cmdline.append(self.data['argument_keys'][key])
                    if type(self.data['values'][key])==type(True):
                        pass
                    else :
                        cmdline.append(self.data['values'][key])
        #
        print(cmdline)
        return cmdline


    def runQuickstart(self,event):
        '''
        run sphinx quickstart -q with gathered information
        '''
        cmd=self.getCommandline()
        #
        retval = subprocess.call(["/bin/mkdir", "-p",self.data['values']['path']])
        #
        fproc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
        #
        formbuffer,errortext=fproc.communicate()
        #
        print(errortext)

class Sphinxdialog:

    def __init__(self, master):
        dummyvar = sphinx_startpanel(master,data=self.getData())

    def getData(self):
        #
        # Define, create and deliver the initial data structure
        #
        # datadict needs at least the three dicts and the list below
        #  
        # datadict={
        #             'verbose_names':{},
        #            'values':{},
        #            'callback_vars':{},
        #            'order':[],
        #            }
        #
        # for each key must be an entry in every dict
        # the list order is used for processing
        # You can pass a list order with only one field e.g. to init
        # and only this field will be processed
        # 
        # see self.Row1IFrame above, passig the full dict but order contains only ['project'] 
        #
        # if a dict units is added to the datadict, the units will be displayed behind the entry widgets
        # the units dict can be incomplete
        # 
        # the argument_keys dict was added to call quickstart by commandline 
        #
        datadict = {
            'verbose_names':{
                'path'          :  'The directory name for the new project',
                'sep'           :  'if True, separate source and build dirs',
                'dot'           :  'replacement for dot in _templates etc.',
                'project'       :  'project name',
                'project_fn'    :  'Slugged project name for filenames',
                'author'        :  'author names',
                'version'       :  'version of project',
                'release'       :  'release of project',
                'language'      :  'document language',
                'suffix'        :  'source file suffix',
                'master'        :  'master document name',
                'epub'          :  'use epub',
                'autodoc'       :  'enable autodoc extension',
                'doctest'       :  'enable doctest extension',
                'intersphinx'   :  'enable intersphinx extension',
                'todo'          :  'enable todo extension',
                'coverage'      :  'enable coverage extension',
                'imgmath'       :  'enable imgmath for pdf, disable mathjax)',
                'mathjax'       :  'enable mathjax extension',
                'ifconfig'      :  'enable ifconfig extension',
                'viewcode'      :  'enable viewcode extension',
                'githubpages'   :  'enable githubpages extension',

                'BASE_DIR'      :  'Directory to create your project folder',

                'makefile'      :  'Create Makefile',
                'batchfile'     :  'Create batch command file',

                'TEMPLATE_DIR'  :  'Where to find the script templates (OPTIONAL)',

                },
            'values':{
                'path'          :  '.',
                'sep'           :  True,
                'dot'           :  '_',
                'project'       :  'project name',
                'project_fn'    :  'Slugged project name for filenames',
                'author'        :  'author names',
                'version'       :  'version of project',
                'release'       :  'release of project',
                'language'      :  'de',
                'suffix'        :  '.rst',
                'master'        :  'index',
                'epub'          :  False,
                'autodoc'       :  True,
                'doctest'       :  False,
                'intersphinx'   :  True,
                'todo'          :  False,
                'coverage'      :  False,
                'imgmath'       :  False,
                'mathjax'       :  True,
                'ifconfig'      :  True,
                'viewcode'      :  False,
                'githubpages'   :  False,

                'BASE_DIR'      :  '.',

                'makefile'      :  True,
                'batchfile'     :  False,

                'TEMPLATE_DIR'  :  '',
                },
            'argument_keys':{
                'path'          :  ' ',
                'sep'           :  '--sep',
                'dot'           :  '--dot',
                'project'       :  '--project',
                'project_fn'    :  None,
                'author'        :  '--author',
                'version'       :  '-v',
                'release'       :  '--release',
                'language'      :  '--language',
                'suffix'        :  '--suffix',
                'master'        :  '--master',
                'epub'          :  '--epub',
                'autodoc'       :  '--ext-autodoc',
                'doctest'       :  '--ext-doctest',
                'intersphinx'   :  '--ext-intersphinx',
                'todo'          :  '--ext-todo',
                'coverage'      :  '--ext-coverage',
                'imgmath'       :  '--ext-imgmath',
                'mathjax'       :  '--ext-mathjax',
                'ifconfig'      :  '--ext-ifconfig',
                'viewcode'      :  '--ext-viewcode',
                'githubpages'   :  '--ext-githubpages',

                'BASE_DIR'      :  None,

                'makefile'      :  '--makefile',
                'batchfile'     :  '--batchfile',

                'TEMPLATE_DIR'  :  '',
                },
            'order':[],
            'callback_vars':{},
            }
        #
        for key in datadict['verbose_names'] :
            datadict['callback_vars'][key]=StringVar()
            datadict['order'].append(key)
            datadict['callback_vars'][key].set(datadict['values'][key])

        return datadict

def main():
    root = Tk() 
    app = Sphinxdialog(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Вам нужно установить django, поскольку я использую его slugify, чтобы сделатьпример работы.

1 голос
/ 16 апреля 2019

Да, это возможно.

Вот мой путь

Вы можете использовать window.after(ms, func=None, args), чтобы продолжать работать в фоновом режиме, что обновит пользователяввод без нажатия кнопки.

Обновленный код

from tkinter import *

def calc():
    val1 = e1.get()
    val2 = e2.get()
    val3 = e3.get()
    val4 = e4.get()
    res = val1 + " " + val2 + " " + val3 + " " + val4
    label2["text"] = res

    # This will run the function in every 100ms (0.1 secs).
    master.after(100, calc)

master = Tk()

Label(master, text="Main Value").grid(row=0, sticky = E)
Label(master, text="Second Value").grid(row=1, sticky = E)
Label(master, text="Third Value").grid(row=2, sticky = E)
Label(master, text="Fourth Value").grid(row=3, sticky = E)

e1 = Entry(master)
e2 = Entry(master)
e3 = Entry(master)
e4 = Entry(master)

e1.grid(row=0, column=1)
e2.grid(row=1, column=1)
e3.grid(row=2, column=1)
e4.grid(row=3, column=1)

button1 = Button(master, text="Calculate", command=calc)
button1.grid(row=4, column=0, sticky=W, pady=4)

label2 = Label(master)
label2.grid(row=4, column = 1)

# Run the function and it will keep running in the background.
calc()

master.mainloop()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...