Odd sizer behavior

I sent this email to wx-users previously, but it got no response; reposting it here since I did after all use Python code…

I have a UI widget that consists of a grid of panel-based controls, which can either be active or inactive. I want all of the active panels to precede all of the inactive panels, with precisely one inactive panel shown, so that it
can be activated.

I put together an example script here: http://pastebin.com/iLAiZcj1

The
weird behavior that I’m getting is when an active panel that is not the
last panel is deactivated. What I want is for the newly-deactivated panel to be moved to the latter portion of the grid, next to the other inactive panels. Instead, it’s staying in its position. Or worse, sometimes the deactivated panel outright disappears!

Repro steps:

  1. Run the script in the pastebin

  2. Click the “toggle” buttons until you have a 2x2 grid of green panels.

  3. Click the top-left “toggle” button

3b) Expected behavior: Panel #0 moves to the bottom-right corner.

3c) Actual behavior: Panel #0 remains in place.

  1. Click on the top-right “toggle” button

4b) Expected behavior: panel remains visible but “inactive” (i.e. red)

4c) Actual behavior: panel vanishes (and will reappear if Panel #0 is reactivated)

What is going on here? What am I doing wrong?

Thanks for any assistance.

-Chris

Chris Weisiger wrote:

I sent this email to wx-users previously, but it got no response;
reposting it here since I did after all use Python code...

I have a UI widget that consists of a grid of panel-based controls,
which can either be active or inactive. I want all of the active
panels to precede all of the inactive panels, with precisely one
inactive panel shown, so that it can be activated.

I put together an example script here: import threadingimport timeimport wxclass Panel(wx.Panel): def __in - Pastebin.com

You, like another poster this week, also seem to have a misunderstanding
of the "global" statement. None of your uses of "global" are required.
Your statement:
    app = App(redirect = False)
creates a global variable and binds it to your App instance. You never
have to use "global" unless you intend to bind the global to something
else, and that's something you never do here. Further, when you do this:
        global app
        app = self
that services no purpose, because the "app" global is already bound to
that object.

The weird behavior that I'm getting is when an active panel that is
not the last panel is deactivated. What I want is for the
newly-deactivated panel to be moved to the latter portion of the grid,
next to the other inactive panels. Instead, it's staying in its
position. Or worse, sometimes the deactivated panel outright disappears!

The solution to your immediate problem is to add
    self.sizer.Layout()
immediately before your call to SetSizerAndFit in App.resetView. I also
had to add a "self.text.Refresh()" statement in the toggle event handler
to force the color to update.

There's an interesting stylistic issue in your event handler. You have
"toggle" as a local function in Panel.__init__, which means it's only
getting the "self" variable because it creates a closure. Usually, you
would just make "toggle" a normal class method:

        self.text.SetBackgroundColour((200, 100, 100))
        sizer.Add(self.text)
        button.Bind(wx.EVT_BUTTON, self.toggle)
        self.SetSizerAndFit(sizer)

    def toggle(self,event):
        if self.text.GetBackgroundColour() == (200, 100, 100):
            self.text.SetBackgroundColour((100, 200, 100))
        else:
            self.text.SetBackgroundColour((200, 100, 100))
        self.text.Refresh()
        app.resetView()

Also, right now you are adding the hidden panels to the sizer. I
wouldn't do that.

class App(wx.App):
    ...
    def resetView(self):
        self.sizer.Clear()
        activePanels =
        inactivePanels =
        for panel in self.panels:
            if panel.getIsOn():
                activePanels.append(panel)
            else:
                inactivePanels.append(panel)
        print "active"
        for panel in activePanels:
            print panel.text.GetLabel()
            self.sizer.Add(panel)
            panel.Show()
        print "inactive"
        for panel in inactivePanels:
            print panel.text.GetLabel()
            if panel is inactivePanels[0]:
                self.sizer.Add(panel)
            panel.Show(panel is inactivePanels[0])
        self.sizer.Layout()
        self.frame.SetSizerAndFit(self.sizer)

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

Thanks for the stylistic concerns, but most of them boil down to

a) I hate having objects “magically” enter scope without explicitly being declared beforehand (and am really not a fan of Python not having block scoping, alas), and

b) This is just a demo script and was intentionally terse.

Normally I make event handlers be class methods and generally have a more consistent style. I also don’t use “global” or have UI widgets that manually invoke methods on their creators (i.e. not by invoking a passed-in callback function).

Thanks for the pointer towards sizer.Layout()! That was the function I needed. Now I just have to sort out why my WXGLCanvas objects are throwing “invalid operation” methods when other canvases get hidden…that one will prove trickier to fix, I suspect. If I can get a repro case in a test app then I’ll be making another thread here…

-Chris

···

On Thu, Nov 21, 2013 at 10:48 AM, Tim Roberts timr@probo.com wrote:

Chris Weisiger wrote:

I sent this email to wx-users previously, but it got no response;

reposting it here since I did after all use Python code…

I have a UI widget that consists of a grid of panel-based controls,

which can either be active or inactive. I want all of the active

panels to precede all of the inactive panels, with precisely one

inactive panel shown, so that it can be activated.

I put together an example script here: http://pastebin.com/iLAiZcj1

You, like another poster this week, also seem to have a misunderstanding

of the “global” statement. None of your uses of “global” are required.

Your statement:

app = App(redirect = False)

creates a global variable and binds it to your App instance. You never

have to use “global” unless you intend to bind the global to something

else, and that’s something you never do here. Further, when you do this:

    global app

    app = self

that services no purpose, because the “app” global is already bound to

that object.

The weird behavior that I’m getting is when an active panel that is

not the last panel is deactivated. What I want is for the

newly-deactivated panel to be moved to the latter portion of the grid,

next to the other inactive panels. Instead, it’s staying in its

position. Or worse, sometimes the deactivated panel outright disappears!

The solution to your immediate problem is to add

self.sizer.Layout()

immediately before your call to SetSizerAndFit in App.resetView. I also

had to add a “self.text.Refresh()” statement in the toggle event handler

to force the color to update.

There’s an interesting stylistic issue in your event handler. You have

“toggle” as a local function in Panel.init, which means it’s only

getting the “self” variable because it creates a closure. Usually, you

would just make “toggle” a normal class method:

    self.text.SetBackgroundColour((200, 100, 100))

    sizer.Add(self.text)

    button.Bind(wx.EVT_BUTTON, self.toggle)

    self.SetSizerAndFit(sizer)



def toggle(self,event):

    if self.text.GetBackgroundColour() == (200, 100, 100):

        self.text.SetBackgroundColour((100, 200, 100))

    else:

        self.text.SetBackgroundColour((200, 100, 100))

    self.text.Refresh()

    app.resetView()

Also, right now you are adding the hidden panels to the sizer. I

wouldn’t do that.

class App(wx.App):

...

def resetView(self):

    self.sizer.Clear()

    activePanels = []

    inactivePanels = []

    for panel in self.panels:

        if panel.getIsOn():

            activePanels.append(panel)

        else:

            inactivePanels.append(panel)

    print "active"

    for panel in activePanels:

        print panel.text.GetLabel()

        self.sizer.Add(panel)

        panel.Show()

    print "inactive"

    for panel in inactivePanels:

        print panel.text.GetLabel()

        if panel is inactivePanels[0]:

            self.sizer.Add(panel)

        panel.Show(panel is inactivePanels[0])

    self.sizer.Layout()

    self.frame.SetSizerAndFit(self.sizer)

Tim Roberts, timr@probo.com

Providenza & Boekelheide, Inc.

You received this message because you are subscribed to the Google Groups “wxPython-users” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxpython-users+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.