【问题标题】:When importing modules written with tkinter and ttk, stuff doesn't work导入用 tkinter 和 ttk 编写的模块时,东西不起作用
【发布时间】:2013-05-13 21:53:40
【问题描述】:

我是编程、Python、这个网站的新手,并且实际上一般都在使用这些类型的网站,所以请听我说。

我一直在使用 tkinter 模块和 ttk 模块为一个更大的程序编写一个模块,当我将自己的模块导入主程序时,由于某种原因,没有一个 ttk 的东西可以正常工作。我的意思是,它出现了,但是我为它编写的样式(s=ttk.Style(); s.configure...等)无论如何都不会改变它。当我自己运行模块时,一切正常。当它被导入主程序时,它只是没有。

不仅如此,而且在使用输入框时,我才刚刚发现我被告知使用它们的方式,例如,var=StringVar() 作为文本变量(当模块自行运行),现在在调用 var.get() 时将变量 var 保留为空。现在我已经通过删除所有提到 StringVar() 来对此进行排序(希望我知道这些实际上是多么多余),但我仍然想知道为什么将它们导入主程序会导致它们出现如此严重的故障.我会给你一些示例代码,但是我很难有足够的选择性......

如果您能提供任何指导,我将不胜感激。

编辑:给你这样的东西会有帮助吗?

stackoverflowmodule.py

import sys
from tkinter import *
from tkinter import ttk
import time
from random import randint, choice

class Decimals():
    def Question1(self):
        DECFrame.destroy()
        frame1=ttk.Frame(DECmaster, height=height, width=width, style="NewFrame.TFrame")
        frame1.pack()
        Q1Label=ttk.Label(frame1, text="Question 1:", style="TitleLabel.TLabel")
        Q1Label.grid(column=0, row=0, pady=(50,0))     
        answer=StringVar()
        entry1=ttk.Entry(frame1, textvariable=answer)
        entry1.grid(column=0, row=1, pady=(200,0))
        # Typing in Hello should give a correct answer.
        def Question1Attempt():            
            attempt=answer.get()            
            if attempt!="Hello":
                print("Incorrect")
            else:
                print("Correct")
        button=ttk.Button(frame1, text="Ok", command=Question1Attempt)
        button.grid(column=0, row=2, pady=(30,0))


def Start():
    global DECmaster
    global s
    global DECFrame
    global DEC
    global width
    global height
    DECmaster = Tk()

    width=str(1000)
    height=str(800)
    x1=str(0)
    y1=str(0)

    DECmaster.geometry(width+"x"+height+"+"+x1+"+"+y1)
    DECmaster.configure(bg="#8afff0")

    s=ttk.Style()
    s.configure("NewFrame.TFrame", background="#8afff0")
    s.configure("TitleLabel.TLabel", foreground= "blue", background="#8afff0")

    DECFrame=ttk.Frame(DECmaster, style="NewFrame.TFrame")
    DECFrame.pack()

    TitleLabel=ttk.Label(DECFrame, text="Test for Decimals", style="TitleLabel.TLabel")
    TitleLabel.grid(column=1, row=0, pady=(50,0), sticky=N)

    DEC=Decimals()
    button=ttk.Button(DECFrame, text="Start", command=DEC.Question1)
    button.grid(column=2, row=2, pady=(200,0), sticky=N)

    DECmaster.mainloop()

stackoverflowprogram.py

from tkinter import *
from tkinter import ttk
import time
import stackoverflowmodule



root = Tk()

width=str(1000)
height=str(800)
x1=str(0)
y1=str(0)
##width=str(1228)
##height=str(690)
##x1=str(-1)
##y1=str(-22)

root.geometry(width+"x"+height+"+"+x1+"+"+y1)
root.configure(bg="#8afff0")

s=ttk.Style()
s.configure("NewFrame.TFrame", background="#8afff0")
s.configure("TitleLabel.TLabel", foreground= "blue", background="#8afff0")

Testframe=ttk.Frame(root, height=height, width=width, style="NewFrame.TFrame")
Testframe.pack()
Titlelabel=ttk.Label(Testframe, text="Start Test:", style="TitleLabel.TLabel")
Titlelabel.grid(column=0, row=0, pady=(50,0))

def StartTest():
    stackoverflowmodule.Start()


button=ttk.Button(Testframe, text="Start", command=StartTest)
button.grid(column=0, row=1, pady=(100,0))

root.mainloop()

我意识到那里有很多东西,但如果没有这一切,我就无法真正证明我的观点。再次感谢。

【问题讨论】:

  • 没有看到任何代码,很难确定,但我的第一个猜测是您正在模块中创建一个tkinter.Tk 对象,并在导入它的脚本中创建另一个对象。如果是这样,您的一半代码将无法正常工作(因为它使用 Tk 而没有 mainloop),或者您的一半代码甚至无法运行(因为另一个的 mainloop 不会返回直到你退出)。
  • 更重要的是:请提供足以证明问题的模块和程序的stripped-down example 源代码。
  • 啊哈...这听起来很熟悉...有什么替代方案?
  • 哦,好吧...谢谢你,正如我所说,这一切都是新手,所以感谢指针。
  • 无论是模块还是脚本都必须是 Tk 术语中的“主应用程序”。让我写一个给出例子的答案。

标签: python tkinter ttk


【解决方案1】:

问题的根源在于您不止一次创建Tk 的实例。一个 Tkinter 应用程序只能有一个 Tk 类的实例,并且您必须只调用一次 mainloop。如果您需要额外的窗口,您应该创建Toplevel (http://effbot.org/tkinterbook/toplevel.htm) 的实例。

如果您想创建具有可重用代码的模块,请让您的模块创建 Frame 的子类(如果您正在创建 dialos,则创建 Toplevel)。然后,您的主脚本将创建Tk 的实例,并将这些框架放置在主窗口或子窗口中。

如果您想有时将模块用作可重用组件,有时用作可运行程序,请将“可运行程序”部分放在特殊的 if 语句中:

# module1.py
import Tkinter as tk
class Module1(tk.Frame):
    def __init__(self, *args, **kwargs):
        label = tk.Label(self, text="I am module 1")
        label.pack(side="top", fill="both", expand=True)

# this code will not run if this module is imported
if __name__ == "__main__":
    root = tk.Tk()
    m1 = Module1(root)
    m1.pack(side="top", fill="both", expand=True)

在上面的代码中,如果你像python module1.py 一样运行它,那么最后的 if 语句中的代码就会运行。它将创建一个根窗口,创建一个框架实例,并使该框架填充主窗口。

但是,如果您将上述代码导入另一个程序,则 if 语句中的代码将运行,因此您不会得到多个 Tk 的实例。

假设您有两个像上面这样的模块,并且想要编写一个使用它们的程序,并且每个模块都应该放在一个单独的窗口中。您可以通过编写同时使用它们的第三个脚本来做到这一点:

# main.py
import Tkinter as tk
from module1 import Module1
from module2 import Module2

# create the main window; every Tkinter app needs
# exactly one instance of this class
root = tk.Tk()
m1 = Module1(root)
m1.pack(side="top", fill="both", expand=True)

# create a second window
second = tk.Toplevel(root)
m2 = Module2(second)
m2.pack(side="top", fill="both", expand=True)

# run the event loop
root.mainloop()

通过以上,您可以在两个模块中使用代码,它们可以以三种方式使用:作为独立程序,作为单个窗口中的单独框架,或作为单独窗口中的单独框架。

【讨论】:

  • 哇...嗯,非常感谢!多么棒的答案,谢谢!
【解决方案2】:

您不能创建tkinter.Tk 的两个实例。如果你这样做,就会发生以下两种情况之一。

脚本中的大部分代码可能无法运行,因为它正在等待模块的 mainloop 完成,直到您退出才会发生。

如果您以不同的方式构造事物,您最终会得到两个 Tk 实例,其中只有一个实际在运行。你的脚本中的一些代码会碰巧找到正确的 Tk 实例(或正确的实际 Tk 对象),因为有很多共享的全局东西只是假设有一个 Tk “某处或其他”并管理找到。但是其他代码会找到错误的代码,并且没有任何效果。或者,有时,事情会产生错误的影响,或者导致崩溃,或者谁知道呢。

您需要将顶级应用程序放在一个地方,无论是模块还是使用它的脚本,并让另一个地方从那里访问它。


一种方法是编写模块,使其代码可以使用Tk 实例调用。然后,使用__main__ 技巧,这样,如果您直接将模块作为脚本运行(而不是从另一个脚本导入),它会创建一个Tk 实例并调用该代码。这是一个非常简单的例子。

tkmodule.py:

from tkinter import *

def say_hi():
    print("Hello, world!")

def create_interface(window):
    hi = Button(window, text='Hello', command=say_hi)
    hi.pack()

if __name__ == '__main__':
    root = Tk()
    create_interface(root)
    root.mainloop()

tkscript.py:

from tkinter import *
import tkmodule

i = 0
def count():
    global i
    i += 1
    print(i)

def create_interface(window):
    countbtn = Button(window, text='Count', command=count)
    countbtn.pack()

root = Tk()
create_interface(root)
window = Toplevel(root)
tkmodule.create_interface(window)
root.mainloop()

现在,当您运行tkscript.py 时,它拥有一个Tk 实例,并将其传递给它自己的create_frametkmodule.create_frame。但如果你只是运行tkmodule.py拥有一个Tk 实例,它将传递给它自己的create_frame。无论哪种方式,都有一个 Tk 实例和一个主循环,每个人都可以使用它。

请注意,如果您想要两个顶级窗口,则必须在某处显式创建Toplevel。 (并且您不想总是tkmodule.py 中创建一个,或者当您运行模块本身时,它会创建一个新窗口并将默认窗口留空。)


当然,更简单的方法是将所有 GUI 内容放入 从不创建自己的 Tk 实例的模块中,然后编写导入适当模块并驱动它们的脚本.

【讨论】:

  • 我不太确定我是否理解您的示例......当我运行它时,经过一些调整以消除一些错误,我得到的只是一个空窗口。
  • 好的,我现在有了要显示的按钮,但我还是不明白……我真的更喜欢两个窗口,而不仅仅是两个框架!
  • 抱歉,您需要的是Toplevel,而不是Frame。而且你想做一些花哨的事情来确保两者中的一个只创建一个Frame 而不是Toplevel(因为如果他们每个创建一个TopLevel,你会结束加上默认的空窗口,加上你想要的两个窗口)。让我编辑代码。
猜你喜欢
  • 2020-02-11
  • 1970-01-01
  • 1970-01-01
  • 2018-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-04
  • 2015-01-20
相关资源
最近更新 更多