当我缩小 window - Tkinter - Python 时,小部件变得不可见

Widgets become invisible when i shrink my window - Tkinter - Python

我一直在使用 Tkinter 和 python 为大学创建一个 GUI 应用程序。 然而,我遇到了一个问题,当应用程序首次加载时,或者当我将 window 变小时,唯一可见的小部件是绘图仪(扩展 canvas)小部件。但是,如果我展开 window,其他的就会变得可见。

这是我的代码:

from assign2_support import *
import tkinter as tk
from tkinter import *
from tkinter.messagebox import * 
import random
from tkinter.filedialog import askopenfilename


def get_station_name(filename):
    temp1 = list(filename.split("/"))

    temp = list((temp1[len(temp1) - 1]).split("."))
    return temp[0]

def isInDict(value, dic):
    if value in dic:
        return True
    else:
        return False

#TemperaturePlotApp class

class TemperaturePlotApp(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.stations = TemperatureData()
        self.color = ['#f90909', '#ffa405', '#c0c203', '#1abd04', '#058096', '#042ee1', 
           '#d30af1','#ec06b3']
        self.selected = dict()
        self.usedColors = dict()
        self.master.title("Max Temperature")


        self.button = tk.Button(self, text="File", command=self.load_file, width=10)
        self.button.pack(side = 'top', anchor = tk.W)

        self.plotter = Plotter(self,width=850, height=400, bg="white", highlightthickness=0)
        self.plotter.pack(fill='both', expand=tk.YES)
        self.plotter.bind("<B1-Motion>", self.onPlotClicked)
        self.plotter.bind("<Button 1>", self.onPlotClicked)

        # tag all of the drawn widgets TODO delete
        self.plotter.addtag_all("all")

        self.df = DataFrame(self)
        self.df.pack(fill = tk.X, anchor = tk.N, pady = 10)

        self.sf = SelectionFrame(self)
        self.sf.pack(fill = tk.X, anchor = tk.N)



        self.pack(fill = 'both', side = 'left', expand = tk.YES)

    def loadStation(self, stationName):
        self.stations.load_data(stationName + ".txt")


    def onPlotClicked(self, event):
        x = event.x

        year = self.ct.get_year(x)
        self.df.setYear(year)
        try:
            self.plotter.delete(self.l)
        except:
            pass

        self.l = self.plotter.create_line(x, 0, x, self.plotter.winfo_height(), fill = "black")
        for s in self.stations.get_stations():
            if self.selected[s] == True:
                temp = self.stations.get_data()[s].get_temp(int(year))
                print(temp)
                self.df.setDatumText(s, temp)



    def plotData(self):
        self.plotter.delete(tk.ALL)
        minY, maxY, minT, maxT = self.stations.get_ranges()
        self.ct = CoordinateTranslator(self.plotter.winfo_width(),self.plotter.winfo_height(), minY, maxY, minT, maxT)
        self.i = 0

        data = self.stations.get_data()

        for s in self.stations.get_stations():
            firstRun = True

            if s in self.usedColors:
                pass
            else:
                self.usedColors[s] = random.choice(self.color)

            if self.sf.isCheckButton(s) == False:
                self.sf.addCheckButton(s, self.usedColors[s], lambda: self.toggleCheckButton(s))
                self.selected[s] = self.stations.is_selected(self.i)

            if self.selected[s] == True:
                if self.df.isInDataFrameLabels(s) == False:
                    self.df.addDatum("", self.usedColors[s], s)
                if self.df.isHidden(s) == True:
                    self.df.showDatum(s)


                for d in data[s].get_data_points():


                    if firstRun:
                        self.lastX, self.lastY = self.ct.temperature_coords(d[0], d[1])
                        firstRun = False

                    else:
                        x, y = self.ct.temperature_coords(d[0], d[1])
                        self.plotter.create_line(self.lastX, self.lastY, x, y, fill = self.usedColors[s])
                        self.lastX = x
                        self.lastY = y
            else:
                self.df.hideDatum(s)
            self.i = self.i + 1


    def toggleCheckButton(self, stationName):

        if self.selected[stationName] == True:
            self.selected[stationName] = False

        else:

            self.selected[stationName] = True


        self.plotData()


    def load_file(self):
        fname = askopenfilename(filetypes=([("Text files","*.txt")]))
        if fname:
            fn = get_station_name(fname)
            self.loadStation(fn)
            self.plotData()
            try:

                print(fname) # TODO Delete




            except:                 
                showinfo("Failed to read file", "failed to read file: " + fname)

            return




# Start DataFrame class
class DataFrame(tk.Frame):
    def __init__(self,parent, *args,**kwargs):
        tk.Frame.__init__(self, parent,*args,**kwargs)
        self.lb = dict()

        self.l = tk.Label(self, text="Data for ")
        self.l.pack(side = 'left')
        self.year = tk.Label(self, text="")
        self.year.pack(side = 'left')
        self.hidden = dict()



    def addDatum(self, txt, color, stationName):

        l1 = tk.Label(self, text=txt, fg = color)
        self.lb[stationName] = l1
        l1.pack(side = 'left')
        self.hidden[stationName] = False

    def setDatumText(self, stationName, txt):
        self.lb[stationName].configure(text = txt)

    def hideDatum(self, stationName):
        self.lb[stationName].pack_forget()
        self.hidden[stationName] = True

    def showDatum(self, stationName):
        self.lb[stationName].pack(side = 'left')
        self.hidden[stationName] = False

    def isHidden(self, stationName):
        return self.hidden[stationName]

    def setYear(self, year):
        self.year.configure(text = str(year) + ":")

    def getDataFrameLabels(self):
        return self.lb
    def isInDataFrameLabels(self,stationName):
        return isInDict(stationName, self.lb)

# Start SelectionFrame Class

class SelectionFrame(tk.Frame):
    def __init__(self,parent,*args,**kwargs):
        tk.Frame.__init__(self, parent,*args,**kwargs)
        self.cb = dict()
        self.l = tk.Label(self, text="Station Selection: ").pack(side = 'left')


    def addCheckButton(self, text, color, com):

        c = tk.Checkbutton(self, text = text, fg = color, activeforeground = color, command = com)
        self.cb[text] = c
        c.select()
        c.pack(side = 'left')

    def getCheckButtons(self):
        return self.cb

    def isCheckButton(self, stationName):
        if stationName in self.cb:
            return True
        else:
            return False





# Start Plotter Class

class Plotter(tk.Canvas):

    def __init__(self, parent,*args,**kwargs):
        Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()


    def on_resize(self,event):
        # determine the ratio of old width/height to new width/height
        wscale = float(event.width)/self.width
        hscale = float(event.height)/self.height
        self.width = event.width
        self.height = event.height
        # resize the canvas 
        self.config(width=self.width, height=self.height)
        # rescale all the objects tagged with the "all" tag
        self.scale("all",0,0,wscale,hscale)






#Begin TemperatureData class

class TemperatureData:
    def __init__(self):
        self._data = dict()
        self._stationNames = list()
        self._stationsSelected = list()



    def load_data(self, filename):
        station_name = get_station_name(filename)
        self._stationNames.append(station_name)
        self._stationsSelected.append(True)

        station = Station(filename) 

        self._data[station_name] = station






    def get_data(self):
        return self._data

    def toggle_selected(self, i):
        if self._stationsSelected[i] == True:
            self._stationsSelected[i] = False
        else:
            self._stationsSelected[i] = True

    def is_selected(self, i):
        return self._stationsSelected[i]

    def get_stations(self):
        return self._stationNames

    def get_ranges(self):
        min_year = None
        max_year = None
        min_temp = None
        max_temp = None
        for k, v in self._data.items():

            if min_year == None or max_year == None or min_temp == None or max_temp == None:
                min_year, max_year = v.get_year_range()
                min_temp, max_temp = v.get_temp_range()

            else:
                t_min_year, t_max_year = v.get_year_range()
                t_min_temp, t_max_temp = v.get_temp_range()
                min_year = min(min_year, t_min_year)
                max_year = max(max_year, t_max_year)
                min_temp = min(min_temp, t_min_temp)
                max_temp = max(max_temp, t_max_temp)

        return (min_year, max_year, min_temp, max_temp)


#End TemperatureData class











# My support
def load_stations(stations_file):
    """Return the list of station names

    load_stations() -> list(str)
    """
    fd = open(stations_file, "r")
    stations = []
    for line in fd:
        line = line.strip()
        if not line:
            continue
        stations.append(line)
    fd.close()
    return stations












##################################################
# !!!!!! Do not change (or add to) the code below !!!!!
###################################################

def main():
    root = tk.Tk()
    app = TemperaturePlotApp(root)
    app.pack()
    root.geometry("800x400")
    root.mainloop()

if __name__ == '__main__':
    main()

如果有人不介意向我指出发生这种情况的原因,我将不胜感激,因为作业将在 4 小时内完成,而我不知道该怎么做。

编辑: assign2_support.py 文件代码:

#
# Support for assignment 2
#

# Imports for use in your assignment
import tkinter as tk
import os.path
from tkinter import filedialog
from tkinter import messagebox

# colours for drawing lines and text
COLOURS = ['#f90909', '#ffa405', '#c0c203', '#1abd04', '#058096', '#042ee1', 
           '#d30af1','#ec06b3']

def load_data_points(filename):
    """Return the data contained in the given file.

    load_data_points(str) -> dict(int:float)
    """
    fd = open(filename, 'r')
    data = {}
    for line in fd:
        parts = line.split(',')
        data[int(parts[0])] = float(parts[1])
    return data


class FileExtensionException(Exception):
    pass

class Station(object):
    """A class for storing yearly average temperature data for a given station
    """
    def __init__(self, stationfile):
        """ Constructor: Station(str)"""
        self._data = load_data_points(stationfile)
        keys = self._data.keys()
        self._min_year = min(keys)
        self._max_year = max(keys)
        temps = self._data.values()
        self._min_temp = min(temps)
        self._max_temp = max(temps)
        base = os.path.basename(stationfile)
        if not base.endswith('.txt'):
            raise(FileExtensionException())
        self._name = base.replace(".txt", "")

    def get_temp(self, year):
        """Return the temperature average for the given year.

        get_temp(int) -> float
        """
        return self._data.get(year)

    def get_data_points(self):
        """Return the data as a list of points in year order

        get_data_points() -> list((int, float))
        """
        return [(year, self._data[year]) for year in sorted(self._data.keys())]

    def get_year_range(self):
        """ Return the range of years in the data

        get_year_range() -> (int, int)
        """
        return (self._min_year, self._max_year)

    def get_temp_range(self):
        """Return the range of temperatures in the data

        get_temp_range() -> (float, float)
        """
        return (self._min_temp, self._max_temp)

    def get_name(self):
        return self._name

    def __repr__(self):
        return "Station({0})".format(self._name)

class CoordinateTranslator(object):
    """A class which manages translation of data values into (x, y) coordinates.

    The application manages real-world data (year, temp), but the Canvas 
    drawings require (x, y) coordinates. This class
    converts between the two.

    """

    def __init__(self, width, height, min_year, max_year, min_temp, max_temp):
        """
        Create a CoordinateTranslator with the given canvas width/height,
        the smallest and largest years and 
        the smallest and largest temperatures

        Constructor: CoordinateTranslator(int, int, int, int, float, float)
        """
        self._min_year = min_year
        self._max_year = max_year
        self._min_temp = min_temp
        self._max_temp = max_temp
        self.resize(width, height)

    def resize(self, width, height):
        """Adjust the scaling factors to account for a new width/height.

        After the Canvas resizes, call this method to fix the scaling.
        """
        self._xscale = (self._max_year - self._min_year) / width
        self._yscale = (self._max_temp - self._min_temp) / height
        self._width = width
        self._height = height


    def temperature_coords(self, year, temperature):
        """Given a year and a temperature,
           return (x, y) coordinates to plot.

        temperature_coords(int, float) -> (float, float)
        """
        return ((year - self._min_year)/ self._xscale,
                self._height - (temperature - self._min_temp) / self._yscale)

    def get_year(self, x):
        """Given an x coordinate on the Canvas, return the year that it
           corresponds to.

        get_year(float) -> int
        """
        return int(x * self._xscale + 0.5) + self._min_year


## CSSE7030

def best_fit(points):
    """Given points are a list of (x,y) points ordered by x
    this function computes the best line fit over that range and 
    returns the coords of end points of the line.

    best_fit(list((floatt, float)) -> ((float, float), (float, float))
    """
    count = len(points)
    if count == 0:
        # needed to avoid division by zero
        # return something that will not appear on screen if drawn
        return ((-1,-1), (-1, -1))
    x_values = [x for x, _ in points]
    y_values = [y for _, y in points]
    sum_x = sum(x_values)
    sum_y = sum(y_values)
    sum_x2 = sum(x**2 for x in x_values)
    sum_y2 = sum(y**2 for y in y_values)
    sum_xy = sum(x*y for x,y in points)
    x_mean = sum_x/count
    y_mean = sum_y/count
    slope = (sum_xy - sum_x * y_mean) / (sum_x2 - sum_x * x_mean)
    y_inter = y_mean - slope * x_mean
    return ((x_values[0], slope * x_values[0]  + y_inter),
            (x_values[-1], slope * x_values[-1]  + y_inter))

谢谢堆 科里 :)

您正在创建请求尺寸为 850x400 的 canvas。您正在将 window 大小固定为 800x400。因为 window 中没有足够的空间来容纳所有内容,Tkinter 必须开始减少小部件,或者从视图中删除小部件。它不会尝试将小部件缩小到低于其请求的大小,因此它不会缩小您的 canvas。因此,下一个选项是开始从视图中隐藏小部件。

当 tkinter 必须开始从视图中隐藏部分或全部小部件时,它会从 "packing list" 中最后一个小部件开始——最后调用 pack(...) 的小部件。因此,如果您最后打包 canvas,在 之前 底部框架,它将是开始缩小到低于其请求大小的那个。

一个简单的修复方法是删除 canvas 的宽度和高度属性,同时删除 <Configure> 上的绑定。这让 tkinter 决定 canvas 的大小,当设置正确时,意味着它会增长和缩小以适应可用的 space.

您还可以将 canvas 的包装保存到最后,这使得它成为第一个在空间不足时开始获得 "chopped off" 的小部件。

有关打包算法的完整说明,请参阅 http://tcl.tk/man/tcl8.5/TkCmd/pack.htm#M26