Widgets become invisible when i shrink my window -

2019-09-02 08:13发布

I have been creating a GUI application for university using Tkinter and python. I am however having a problem where when the application first loads, or when i make the window smaller, the only widget visible is the Plotter (extends canvas) widget. If i expand the window however, the others become visible.

This is my code:

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()

If someone wouldnt mind pointing out to me why this is happening, i would much appreciate it, as the assignment is due in 4 hours, and i have no idea what to do.

EDIT: assign2_support.py file code:

#
# 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))

Thanks heaps Corey :)

1条回答
何必那么认真
2楼-- · 2019-09-02 09:01

You are creating a canvas with a requested size of 850x400. You are fixing the window size to be 800x400. Because there's not enough room in the window to fit everything in, Tkinter has to start reducing widgets, or removing widgets from view. It won't attempt to reduce a widget below its requested size, so it's not going to shrink your canvas. So, it's next option is to start hiding widgets from view.

When tkinter has to start hiding part or all of a widget from view, it starts with the widget last in the "packing list" -- the last widget to have called pack(...). Thus, if you pack the canvas last, before the bottom frame, it will be the one that starts getting shrunk below its requested size.

A simple fix is to remove the width and height attributes of the canvas, and also remove the binding on <Configure>. This lets tkinter decide the size of the canvas, which when set up properly, means that it will grow and shrink to fit the available space.

You can also save packing of the canvas to the very last, which makes it the first widget to start getting "chopped off" when there isn't enough room.

For the complete description of the packing algorithm see http://tcl.tk/man/tcl8.5/TkCmd/pack.htm#M26

查看更多
登录 后发表回答