Having Issues Hiding DirectScrolledFrame

I’m trying to hide the DirectScrolledFrame objects in my code; however, despite using the hide function, it will not work. When I do it, it appears to hide the information in the frame, but it does not hide the frame itself.

I made a module to build menus more easily, so here is the code to build it from the module:

from direct.gui.DirectGui import *

import Common

class MenuBuilder():
    ...
    def menu_scroll_builder(self, frame_size_stats: tuple, frame_position: tuple, canvas_size_stats: tuple):
        self.menu_scrolled = DirectScrolledFrame(canvasSize = canvas_size_stats, 
                                                 frameSize= frame_size_stats,
                                pos = frame_position,
                                relief = DGG.FLAT,
                                autoHideScrollBars = True,
                                scrollBarWidth = 0.02,
                                frameTexture = "PackMenu/PackMenuAttempt2.png")
        
        self.scroll_canvas = self.menu_scrolled.getCanvas() 
        
        return self.scroll_canvas, self.menu_scrolled

Here is the code creating the frame as well as the code to hide it:

def gear_normal_buffs(self):
        self.gear_normal_buff_menu = MenuBuilder(0.3)

        self.normal_buff_canvas, self.gear_normal_buff_frame = self.gear_normal_buff_menu.menu_scroll_builder((-0.2, 0.2, -0.64, -0.3), GEAR_STATS_MENU_POSITIONS[self.battleposition_index], (-0.2, 0.2, -1.44, -0.3))

def gear_normal_buffs_rows(self):
    ...
    self.gear_normal_buff_frame.hide()

      try:
          self.normal_buff_canvas.hide()
      except:
          pass

I have made sure to have both of the functions run, so I’m really struggling to see what the issue is.

Well, based on a quick experiment on my side, the hiding of a DirectScrolledFrame seems to be working in-and-of-itself. Thus I suspect that the problem lies elsewhere in your code.

I see that you’re storing references to your frames in some instance-variables–could you perhaps be overwriting those variables elsewhere, resulting in the later call to “hide” being directed to the wrong object…?

(If I may ask, why does the “MenuBuilder” class keep a reference to the menus that it creates–especially if you intend to keep references to those menus in the objects that call its methods?)

Let’s see. Looking at the code to build the menus, I’m not sure what would be overwritten. Here is the rest of the code for gear_normal_rows():

def gear_normal_buffs_rows(self):
        self.gear_normal_buffs_column_1 = []
        self.gear_normal_buffs_column_2 = []
        self.gear_normal_buffs_column_3 = []

        self.gear_normal_buffs_pos = [(0, 0, -0.4), (0, 0, -0.7), (0, 0, -0.9)]

        self.normal_buffs_base_text = ["Charge: x", "Attack Up: +", "Overall Status Effect Resistance: -", "Trick Resistance: -", "Sue Resistance: -", "Soak Resistance: -", "Daze Resistance: -"]
        self.normal_buffs_base = ["Charge", "Attack", "Status Effect", "Status Effect", "Status Effect", "Status Effect", "Status Effect"]
        self.normal_buffs_status_effect = ["Overall", "Trick", "Sue", "Soak", "Daze"]

        for count, stat in enumerate(self.normal_buffs_base_text):
            if self.normal_buffs_base[count] != "Status Effect":
                self.gear_normal_buffs_column_1.append([stat + str(self.gear_active_buffs[self.normal_buffs_base[count]]["Current Amount"])])
                self.gear_normal_buffs_column_1[count].append("Text")
                self.gear_normal_buffs_column_1[count].append(None)
            else:
                self.gear_normal_buffs_column_1.append([stat + str(self.gear_active_buffs[self.normal_buffs_base[count]][self.normal_buffs_status_effect[count - 2]]["Current Amount"])])
                self.gear_normal_buffs_column_1[count].append("Text")
                self.gear_normal_buffs_column_1[count].append(None)

        self.normal_buffs_resistance_text = ["Overall: +", "Attack: +", "Support: +"]
        self.normal_buffs_resistance = ["Overall", "Attack", "Support"]

        for count, stat in enumerate(self.normal_buffs_resistance_text):
            self.gear_normal_buffs_column_2.append([stat + str(self.gear_active_buffs["Resistance"][self.normal_buffs_resistance[count]]["Current Amount"]) + "%"])
            self.gear_normal_buffs_column_2[count].append("Text")
            self.gear_normal_buffs_column_2[count].append(None)

        self.normal_buffs_evasion_text = ["Attack: +", "Support: +"]
        self.normal_buffs_evasion = ["Attack", "Support"]

        for count, stat in enumerate(self.normal_buffs_evasion_text):
            self.gear_normal_buffs_column_3.append([stat + str(self.gear_active_buffs["Evasion"][self.normal_buffs_evasion[count]]["Current Amount"])])
            self.gear_normal_buffs_column_3[count].append("Text")
            self.gear_normal_buffs_column_3[count].append(None)

        self.gear_normal_buff_columns = {
            "Column 1" : {
                    "Position" : self.gear_normal_buffs_pos[0],
                    "Values" : self.gear_normal_buffs_column_1
                },
            "Column 2" : {
                    "Position" : self.gear_normal_buffs_pos[1],
                    "Values" : self.gear_normal_buffs_column_2
                },
            "Column 3" : {
                    "Position" : self.gear_normal_buffs_pos[2],
                    "Values" : self.gear_normal_buffs_column_3
                },
            "Column 4" : {
                "Position" : (0, 0, -1.24),
                "Values" : [["Back", "Button", self.menu_toggle, [self.gear_normal_buff_frame, self.gear_buff_type_frame]]]
            }
        }

        self.gear_normal_buff_column_items = self.gear_normal_buff_menu.menu_column_builder(self.gear_normal_buff_columns, self.gear_normal_buff_frame, 0.025, -0.03)

        for item in self.gear_normal_buff_column_items:
            item.reparentTo(self.normal_buff_canvas)

As for having the MenuBuilder class keep a reference, I just thought that was the only way to do it. I just didn’t know any other way. I removed the “self.” attached the DirectScrollFrame variables to try to fix that, though it does not appear to have made any change to the code.

Hmm… I don’t see anything obvious there, offhand…

But then, I don’t see what’s calling the “gear_normal_buffs” function–could you show that, please?

Ah, I see!

Well, no: there’s nothing stopping you from creating temporary variables (i.e. variables not attached to an object–“self” just being here a reference to the object within whose method or function one is working). Especially if you’re going to return them from the function and then store them in variables outside of the class.

(Note that it does make sense to have whatever is calling " menu_scroll_builder" keep references, since it is going to do something with those references at a later stage (in this case hiding them).)

To call the function, I use these variables in the Gear Class.

def gear_ui(self):
        self.gear_ui_creation()
        self.gear_ui_creation_rows()
        self.gear_ui_face_frame.show()

    def gear_ui_creation(self):
        self.gear_ui_menu()
        self.gear_ui_status()
        self.gear_ui_specs()
        self.gear_pack_resistance()
        self.gear_pack_evasion()
        self.gear_stat_resistance()
        self.gear_buff_type()
        self.gear_spawn_buffs()
        self.gear_normal_buffs()
        self.gear_temporary_buffs()
        self.gear_indefinite_buffs()
        self.gear_smt_buffs()
        self.gear_debuffs()

    def gear_ui_creation_rows(self):
        self.gear_ui_menu_rows()
        self.gear_ui_status_rows()
        self.gear_ui_specs_rows()
        self.gear_pack_resistance_rows()
        self.gear_pack_evasion_rows()
        self.gear_stat_resistance_rows()
        self.gear_buff_type_rows()
        self.gear_spawn_buffs_rows()
        self.gear_normal_buffs_rows()
        self.gear_temporary_buffs_rows()
        self.gear_indefinite_buffs_rows()
        self.gear_smt_buffs_rows()
        self.gear_debuffs_rows()

And I call the gear_ui method in the Spawning function in the Spawning Module.

def gear_active_system():
    if gear_parameters["Spawn Type"] is None:
        if len(gears_total) > 0:
            for count, gear in enumerate(gears_total):
                if len(gears_total) == 0:
                    break
                gears_active.append(gear)
                if len(gears_active) == gear_parameters["Gear Party Max"]:
                    break

        for count_2, gear in enumerate(gears_active):
            try:
                gear.battleposition = GEAR_SPAWN_POINTS[count_2]
                gear.battleposition_index = count_2
                gear.actor.setPos(gear.battleposition)
                gear.gear_ui()
            except:
                pass

The gear_ui_face_frame is just a normal DirectFrame Menu. I just tried to remove it, but it made no difference.

Hmm… Is it possible that “gear_active_system” is being called more than once with the same set of “Gear” objects…?

If that were the case, then the “Gear” objects would have their “gear_ui” method called more than once, in turn calling “gear_normal_buffs” more than once, and so initialising the scrolled-frame more than once and overwriting which frame is stored in the “Gear”-object’s “gear_normal_buff_frame”-variable.

If you’re not sure, then you could perhaps test this: in “gear_normal_buffs”, add a line printing out the ID of the object–something like this:
print ("Gear normal buffs called on:", self)

That should then (depending on the design of your program) output to the console something like this:
<__main__.Gear object at 0x6ce01a5010a1>

Where the number after the “0x” varies per object. Thus, if you see the same number twice, then the method has presumably been called on the same object twice.

OK, I tried that option, and unfortunately, that option did not work. Looking at the results, each frame was only called once; there do not appear to be any duplicates. I am trying to look at the code, but I can not seem to find any more ideas. I can try to break down the process of how I get to the main battle.

First, the base file is loaded:

class TrialBase(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        #Disabling Mouse Control of the Camera.
        self.disableMouse()       

        #Making the Window.
        fenetre = WindowProperties()
        fenetre.setSize(1250, 900)
        self.win.requestProperties(fenetre)

        #Calls method to allow for loading the model.
        simplepbr.init()

        #Loading in the simple background.
        Common.my_game_object = self

        #Sets the camera.
        self.camera.setPos(0, 10, 6)
        self.camera.setP(0)

        try:
            load_saves()
        except:
            pass
        
        main_menu_creation()
        main_menu_showcase()

trial = TrialBase()
trial.run()

Next, I select Debug Spawning from the main menu, which will set all of the battle parameters which will spawn the player characters and the enemies:

def debug_spawning():
    ...
    spawning_things_selected = gear_spawning_selector()
    gear_spawning_system(spawning_things_selected)
    gear_active_system()

The first two function just spawn all of the enemy characters, while the last function brings a few of them onto the screen and creates the UI to show their stats.

Hmm… Very odd.

Okay, two more ideas:

First, are you perhaps calling “show” on the DirectScrolledFrame somewhere…? That is, could it be that it’s being “shown” immediately after being “hidden”, thus seeming to never go away?

And second, I might suggest that you use a debugger to place breakpoints in “gear_normal_buffs” and “gear_normal_buffs_rows”. Then, when the breakpoint is hit in the former, make note of the ID of the object referenced in “self.gear_normal_buff_frame”, and when the breakpoint is hit in the latter, check that the ID referenced in the same variable is unchanged.

OK, I did some testing, and I found an area that might be related to the issue. Here is the code I have for gear_normal_buffs_rows:

def gear_normal_buffs_rows(self):
        self.gear_normal_buffs_column_1 = []
        self.gear_normal_buffs_column_2 = []
        self.gear_normal_buffs_column_3 = []

        self.gear_normal_buffs_pos = [(0, 0, -0.4), (0, 0, -0.7), (0, 0, -0.9)]

        self.normal_buffs_base_text = ["Charge: x", "Attack Up: +", "Overall Status Effect Resistance: -", "Trick Resistance: -", "Sue Resistance: -", "Soak Resistance: -", "Daze Resistance: -"]
        self.normal_buffs_base = ["Charge", "Attack", "Status Effect", "Status Effect", "Status Effect", "Status Effect", "Status Effect"]
        self.normal_buffs_status_effect = ["Overall", "Trick", "Sue", "Soak", "Daze"]

        print("Last place the code responds.")

        print(len(self.normal_buffs_base_text))

        for count, stat in enumerate(self.normal_buffs_base_text):
            print("Running 1")
            if self.normal_buffs_base[count] != "Status Effect":
                self.gear_normal_buffs_column_1.append([stat + str(self.gear_active_buffs[self.normal_buffs_base[count]]["Current Amount"])])
                self.gear_normal_buffs_column_1[count].append("Text")
                self.gear_normal_buffs_column_1[count].append(None)
            elif self.normal_buffs_base[count] == "Status Effect":
                self.gear_normal_buffs_column_1.append([stat + str(self.gear_active_buffs[self.normal_buffs_base[count]][self.normal_buffs_status_effect[count - 2]]["Current Amount"])])
                self.gear_normal_buffs_column_1[count].append("Text")
                self.gear_normal_buffs_column_1[count].append(None)

        print("Anything 1?")

For whatever reason here, the for loop only goes twice, stopping at “Overall Status Effect Resistance: -”, and after that, it does not execute any of the rest of the code in the method. Because of that, this is probably the reason why nothing is being hidden.

The weird thing is that it doesn’t even crash anything or cause an error, and I am not using try/except blocks here. After it loops twice, it just stops for some reason.

Well, it looks then like it may be hitting a problem once it encounters an entry in “self.normal_buffs_base” that is equal to “Status Effect”.

Quite what that problem may be I’m not sure–I’m not seeing an obvious problem. I’d suggest using a debugger to step through that section of code and observe what it’s actually doing.

(By the way, if the test ‘self.normal_buffs_base[count] != “Status Effect”’ is not “True”, then you don’t need to check whether that same variable is equal to “Status Effect” in the else-statement, I daresay. After all, if it’s not not equal, then it’s presumably equal.)

OK, I did some more testing and tweaking, and I think I know what is happening:

  • The Base For Loop loops one time and works fine.

  • The Base For Loop loops a second time, and it works until it reaches the if statement.

  • The code gives up working on the rest of UI and moves onto the next Gear.

That is about the best way to describe what happens. The code essentially just stops running the rest of the gear_normal_buffs_rows method, and then it does not even attempt to run the rest of the UI row methods. It seems like a performance issue, yet I have a fairly strong computer, and its resources don’t even appear to be used from running the code. I wonder if there is some sort of time out from how long it takes to get through all of the methods.

That… seems unlikely…

Did you use a debugger to step through your code, as I suggested? That should give you a much clearer impression of what’s actually happening.

(If you’re not using a programming environment that provides a debugger, then I strongly suggest that you get one. It’s an incredible aid, and can make dealing with strange execution issues like this so, so much easier, I daresay.)

Let’s see. I have been using the debugger on VSCode, and it always ends up stopping at the same spot.

if self.normal_buffs_base[count] != "Status Effect":

Also, I sometimes get this message:

self.normal_buffs_base[count] : The term 'self.normal_buffs_base[count]' is not recognized as the name of a cmdlet, function, script file, or operable program. 
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ self.normal_buffs_base[count] != "Status Effect" c:; cd 'c:\Users\hih ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (self.normal_buffs_base[count]:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

I am not sure how this error message happens, but it is fairly uncommon, but it does pop up every once in a while.

Ah! So it is stopping somewhere, likely due to an exception of some sort! It’s not a strange performance issue or anything like that, I daresay–just an exception in the code, I imagine!

Okay, when it stops at that spot, what error or exception does it show? There should be output somewhere that shows it.

(I don’t know what’s going on with that intermittent message that you mention–it doesn’t make a lot of sense to me.)

And at one quick stab in the dark, if I may: could you please show me the code that fills in “gear_active_buffs”? It… looks like a dictionary of dictionaries, except in the case of a status effect, in which case it’s a dictionary of dictionaries of dictionaries…?

Interestingly, I just put a modified version of the relevant code into a Python-file on my end–filling in a dummy “gear_active_buffs”, since I didn’t have that–and… it runs perfectly well?

See here:
(Note: This is intended to be run as a program by itself, not incorporated into a larger program!)

gear_normal_buffs_column_1 = []

gear_active_buffs = {"Charge" : {"Current Amount" : 0}, "Attack" : {"Current Amount" : 1}, "Status Effect" : {"Overall" : {"Current Amount" : 2.1}, "Trick" : {"Current Amount" : 2.2}, "Sue" : {"Current Amount" : 2.3}, "Soak" : {"Current Amount" : 2.4}, "Daze" : {"Current Amount" : 2.5}}}
normal_buffs_base_text = ["Charge: x", "Attack Up: +", "Overall Status Effect Resistance: -", "Trick Resistance: -", "Sue Resistance: -", "Soak Resistance: -", "Daze Resistance: -"]
normal_buffs_base = ["Charge", "Attack", "Status Effect", "Status Effect", "Status Effect", "Status Effect", "Status Effect"]
normal_buffs_status_effect = ["Overall", "Trick", "Sue", "Soak", "Daze"]

print("Last place the code responds.")

print(len(normal_buffs_base_text))

for count, stat in enumerate(normal_buffs_base_text):
    print("Running 1")
    if normal_buffs_base[count] != "Status Effect":
        gear_normal_buffs_column_1.append([stat + str(gear_active_buffs[normal_buffs_base[count]]["Current Amount"])])
        gear_normal_buffs_column_1[count].append("Text")
        gear_normal_buffs_column_1[count].append(None)
    elif normal_buffs_base[count] == "Status Effect":
        gear_normal_buffs_column_1.append([stat + str(gear_active_buffs[normal_buffs_base[count]][normal_buffs_status_effect[count - 2]]["Current Amount"])])
        gear_normal_buffs_column_1[count].append("Text")
        gear_normal_buffs_column_1[count].append(None)

print (gear_normal_buffs_column_1)

(Note that I’ve left off the “self.” bits, as I haven’t bothered to put this little piece of test-code into a class.)

OK, with that message. I was finally able to see what the problem was. My code had an error because I did not reference my dictionaries correctly, and the reason it just stopped everything without an error is because of the try block in gear_active_system when triggering gear_ui. I removed that try block, and it came me the necessary errors, and I was able to fix all of them, and the Menus all work now.

Thank you for all of your help over these 2 and a half days!

1 Like

Aaah, excellent! I’m really glad that you found the problem, and were able to fix it! And well done on doing so! :slight_smile:

(Honestly, if you’re going to have those “try”-blocks, it might be worth having their “except”-blocks log any exceptions.)

And it’s my pleasure to have helped! :slight_smile: