Search code examples
pythontkinterframes

Why does clearing and rewriting this tkinter frame make it duplicate?


I'm working on an RPG-style game using the tkinter module in Python and I have a character creation screen with randomly-generated stats. The 6 stats are generated by using the random module.

The problem that I'm having is when I clear the screen to repack the numbers into the frame dedicated to them it works the first time, the numbers clear and a new set of numbers appears, however the second and third time (and so on) I press the button it just generates a new set of numbers inside the frame whilst the previous numbers remain, duplicating the amount of numbers.

I've tried running various different methods seen from this site such as .pack_forget and deleting from the screen, I even set the variables used to store the Labels to None but nothing I try seems to work.

I'll post the code below, the program is several hundred lines so forgive me if I missed anything related to the problem.

def CharManage2Option3Command(StrengthValue, DexterityValue, ConstitutionValue, WisdomValue, IntelligenceValue, CharismaValue, statframebottomleftright, statframebottomrightright):
    StrengthValue.destroy()
    DexterityValue.destroy()
    ConstitutionValue.destroy()
    WisdomValue.destroy()
    IntelligenceValue.destroy()
    CharismaValue.destroy()
    StrNumber = randrange(3, 18)
    DexNumber = randrange(3, 18)
    ConNumber = randrange(3, 18)
    WisNumber = randrange(3, 18)
    IntNumber = randrange(3, 18)
    ChaNumber = randrange(3, 18)
    StrengthValue = Label(statframebottomleftright, text=StrNumber, fg=DefaultColour, bg=WhiteBackgroundColour, font=DefaultFont)
    DexterityValue = Label(statframebottomleftright, text=DexNumber, fg=DefaultColour, bg=WhiteBackgroundColour, font=DefaultFont)
    ConstitutionValue = Label(statframebottomleftright, text=ConNumber, fg=DefaultColour, bg=WhiteBackgroundColour, font=DefaultFont)
    WisdomValue = Label(statframebottomrightright, text=WisNumber, fg=DefaultColour, bg=WhiteBackgroundColour, font=DefaultFont)
    IntelligenceValue = Label(statframebottomrightright, text=IntNumber, fg=DefaultColour, bg=WhiteBackgroundColour, font=DefaultFont)
    CharismaValue = Label(statframebottomrightright, text=ChaNumber, fg=DefaultColour, bg=WhiteBackgroundColour, font=DefaultFont)
    StrengthValue.pack(fill="both", expand=True)
    DexterityValue.pack(fill="both", expand=True)
    ConstitutionValue.pack(fill="both", expand=True)
    WisdomValue.pack(fill="both", expand=True)
    IntelligenceValue.pack(fill="both", expand=True)
    CharismaValue.pack(fill="both", expand=True)

Solution

  • The problem you're having is that the assignment e.g. StrengthValue = Label(...) inside the function does not affect the object that was passed in, it just assigns a new object to that name inside the function. Therefore the first time you call the function the original objects are destroyed as expected, but you don't retain a reference to the new objects (as they aren't returned from the function) so subsequent calls appear to behave incorrectly.

    One solution is to pass mutable arguments, e.g. lists containing the relevant objects:

    def CharManage2Option3Command(label_lists, stat_frames):
        for label_list, stat_frame in zip(label_lists, stat_frames):
            for index, label in enumerate(label_list):
                number = randrange(3, 18)
                label.destroy()
                label_list[index] = Label(stat_frame, text=number, ...)
                label_list[index].pack(...)
    

    (see the docs on zip and enumerate.). This would then be called e.g.:

    CharManage2Option3Command([[StrengthValue, DexterityValue, ConstitutionValue], 
                               [WisdomValue, IntelligenceValue, CharismaValue]],
                              [statframebottomleftright, statframebottomrightright]):
    

    Note this has the handy side-effect of reducing duplication in your function. You could also keep the Label instances in a list from the start, rather than keeping a separate reference to each one.


    Other solutions include explicitly returning the new labels from the function and assigning back to the original names:

    def CharManage2Option3Command(StrengthValue, DexterityValue, ConstitutionValue, WisdomValue, IntelligenceValue, CharismaValue, statframebottomleftright, statframebottomrightright):
        ...
        return StrengthValue, DexterityValue, ConstitutionValue, WisdomValue, IntelligenceValue, CharismaValue
    

    then calling it:

    StrengthValue, DexterityValue, ConstitutionValue, WisdomValue, IntelligenceValue, CharismaValue = CharManage2Option3Command(StrengthValue, DexterityValue, ConstitutionValue, WisdomValue, IntelligenceValue, CharismaValue, statframebottomleftright, statframebottomrightright)
    

    (which is obviously pretty awkward), using an IntVar as the textvariable for the Label and updating that or doing something class-based, such that you access e.g. self.StrengthValue everywhere.


    I also suggest you have a look at the style guide, which provides conventions for things like function and variable names.