views:

333

answers:

3

I have a space in which I would like certain elements (represented here by A, B, D, and G) to each be in its own "corner" of the design. The corners ought to line up as if each of the four elements was repelling the other; a rectangle. This is to be contained within an unresizable panel. I will have several similar panels and want to keep the location of the elements as identical as possible. (I needed something a little more complex than a wx.Wizard, but with the same general idea.)

AAAAAAAAAA      BB


CCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCC
CCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCC


DDD EEE FFF    GGG

A represents a text in a large font.

B represents a numeric progress meter (e.g. "1 of 7") in a small font.

C represents a large block of text.

D, E, F, and G are buttons.

The G button is separated from others for functionality.

I have attempted nested wx.BoxSizers (horizontal boxes inside of one vertical box) without luck. My first problem with wx.BoxSizer is that the .SetMinSize on my last row has not been honored. The second problem is that I have no idea how to make the G button "take up space" without growing comically large, or how I can jam it up against the right edge and bottom edge.

I have tried to use a wx.GridBagSizer, but ran into entirely different issues.

After plowing through the various online tutorials and wxPython in Action, I'm a little frustrated. The relevant forums appear to see activity once every two weeks. "Playing around with it" has gotten me nowhere; I feel as if I am trying to smooth out a hump in ill-laid carpet.

+1  A: 

First off, you need a vertical sizer to divide the window into 3 rows: AB,C, & DEFG:

frame_sizer = wx.BoxSizer(wx.VERTICAL)

Next, you need a horizontal sizer for the first row:

first_sizer = wx.BoxSizer(wx.HORIZONTAL)
# add the items A and B to first_sizer
# now we add first_sizer to frame_sizer
frame_sizer.Add(first_sizer)

Then, for the second row, we just add the C control to the sizer:

frame_sizer.Add(C)

...making sure of the folowing:

  • The control is created with a specified size (via its constructor)
  • The control is given a minimum size (C.SetMinSize())

Now, for the last row, probably your best bet is to use another horizontal sizer.

wx.BoxSizer(wx.HORIZONTAL)
# add DEF
# now add some space between them
wx.AddSizer(10)
# now add G

Hopefully this works for you. If not, your best bet is to use a GUI designer like wxSmith in Code::Blocks (which is designed for C++ but can be used to at least get an idea of how things would look - wxPython is supposed to have a 1-to-1 correspondence to wxWidgets)

George Edison
That was my previous strategy, which failed. Each row of the sizer wanted to be a different width than the other rows. I don't see a way to say, "You, there! You shall be x pixels wide, no more, no less. You over here! You get the same."It doesn't look like Code::Blocks runs on Windows 7. This makes my experience trying to design cross-browser sites in the early days of CSS seem trivial in comparison.
MetaHyperBolic
Oh yeah. I forgot - you have to call `Add` with the `wx.EXPAND` flag when you add the sizers. That should do the trick.
George Edison
@MetaHyperBolic: Code::Blocks should run fine on Windows 7.
George Edison
http://www.codeblocks.org/downloads/5 does not even list Windows 7 as an option. That page suggests that the compiler which comes with Code::Blocks won't work on Vista. And I apparently must compile wxWidgets to use wxSmith, so I had to go Windows XP.
MetaHyperBolic
@MetaHyperBolic: I use it on Vista with no problems (and I've used it extensively for over a year). It should work fine with Windows 7. You do NOT need to compile wxWidgets to use wxSmith. It comes pre-configured with Code::Blocks. Did the advice in my first comment work?
George Edison
Nope, wx.EXPAND is one of the first things for which I reached. No expansion occurred. Well, based on searches I ran off of the error messages which I received when trying to run wxSmith without having compiled wxWidgets, I was led to a page on their forums wherein the developers suggested at some point that visit this page which was about how you need to compile wxWidgets. Took a couple of hours of searching, but I finally have wxSmith up and running. I'm not getting a great sense of how this works, despite the tutorial.
MetaHyperBolic
Hmmm... I thought that would fix it for sure. Are each of the controls within the sizers set to `wx.EXPAND`? For wxSmith, try clicking the `Layout` tab and adding sizers. Then try adding controls to the sizers and seeing how they work. If you fiddle with the settings in the `Properties` panel, you just might find something that works. Hopefully... :)
George Edison
Can we see the code you have now?
George Edison
I have added a stripped-down code listing showing my attempts with tom10's code.
MetaHyperBolic
+1  A: 

Using BoxSizers and some flags like wx.EXPAND and wx.ALIGN_RIGHT, etc, you can get what you want. Here's an example

import wx
import string
AF = string.ascii_uppercase[:7]

class MyFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, size=(300,300))

        a, b, c, d, e, f, g = [wx.Button(self, -1, 4*c) for c in AF]
        a.SetMaxSize((200,100))   # 'a' won't get bigger than 200 wide

        # build the top row (with sizers I usually work from inside to out)
        hs0 = wx.BoxSizer(wx.HORIZONTAL)
        hs0.Add(a, 1, wx.ALIGN_LEFT)  # 1 here means expand with weight 1
        hs0.Add((0,0), 1)  # also expand the spacing
        hs0.Add(b, 0, wx.ALIGN_RIGHT)  # 0 means don't expand this

        # build the bottom row
        hs1 = wx.BoxSizer(wx.HORIZONTAL)
        hs1.Add(d, 0, wx.ALIGN_LEFT)
        hs1.Add((10,10), 0)
        hs1.Add(e, 0)
        hs1.Add((10,10), 0)
        hs1.Add(f, 0)
        hs1.Add((10, 10), 1)
        hs1.Add(g, 0, wx.ALIGN_RIGHT)

        # stack the rows vertically
        vs = wx.BoxSizer(wx.VERTICAL)
        vs.Add(hs0, flag=wx.EXPAND)   # expand in the orthogonal direction to sizer (here horizontal)
        vs.Add(c, 1, flag=wx.EXPAND)  
        vs.Add(hs1, flag=wx.EXPAND)
        self.SetSizer(vs)

app = wx.PySimpleApp()
frame = MyFrame()
frame.Show(1)
app.MainLoop()

alt text

tom10
I have put an answer below with code and a picture. Or at least a link to a tinypic. Can't seem to get the hang of linking images.
MetaHyperBolic
Yes, sure, I'll get right to studying it. Without even a thanks or an upvote or any explanation of why my answer doesn't work for you (though I see you've pasted my code without attribution in your answer), or without you even taking the time to figure out how to upload a pic, I can't wait to spend the rest of my afternoon helping you solve your problem...
tom10
Cosmetically? Looks great. But your code has brought back the same problem I had switching between panels. Looks okay the first time, but as soon as I switch between panels, the layout decides that it would like to be different than the frame. It's a bit premature to upvote when I don't know what actually solves the problem yet.
MetaHyperBolic
@MetaHyperBolic: So what exactly isn't working now?
George Edison
A: 

I have but one frame. I have several panels built into the frame. They are all hidden but one. A "Next" button performs .Hide() on that panel and then calls a function called .ShowYourself. I wanted something Wizard-Like, which you can see in a previous question

I am attempting to put a sizer on the panel. As you can see from the picture, I cannot get the sizer to take up the whole of the panel. The picture isn't showing on preview, so here it is at http://tinypic.com/r/14nh651/5.

import wx
import sys

class MyApp(
    wx.App      # This makes a subclass of the wx.App class

):

def OnInit(self):
    # image = wx.Image('wxPython.jpg', wx.BITMAP_TYPE_JPEG)
    self.frame = MyFrame(None)
    self.frame.Show()
    self.SetTopWindow(self.frame)
    return True

def OnExit(self):
    print 'Dying ...'

####################################################################################################

class MyFrame(
    wx.Frame    # This makes a subclass of the wx.Frame class
    ):
    """Frame class that displays an image."""

    def __init__(self, 
        image, 
        parent=None,                # "None" as a parameter means this is a top-level window
        id=-1,                  # "-1" means automatically generate a new ID
        title='Generic Title',          # What words appear in the title bar?
        pos=wx.DefaultPosition,         # Where is the upper left-hand corner?
        style=wx.CAPTION | wx.STAY_ON_TOP   # Style with only a caption
        ):      

    """Create a wx.Frame instance and display image."""

    size = (500, 500)
    wx.Frame.__init__(self, parent, id, 'Program Title', pos, size, style)

    sizer_h = wx.BoxSizer(wx.HORIZONTAL)

    self.panelX = TestLayout3(self)     
    sizer_h.Add(self.panelX)

    self.SetSizer(sizer_h)

    self.panelX.ShowYourself()

def ShutDown(self):

        self.Destroy()

####################################################################################################

class TestLayout3(wx.Panel):

# 0 - Splash screen with "Next" and "Cancel and Exit" buttons

def __init__(self, parent, id=-1):
    size = (600, 600)
    wx.Panel.__init__(self, parent, id, size)

    a, b, c, d, e, f, g = [wx.Button(self, -1, 4*c) for c in 'ABCDEFG']
    a.SetMaxSize((200,100))   # 'a' won't get bigger than 200 wide

    # build the top row (with sizers I usually work from inside to out)
    hs0 = wx.BoxSizer(wx.HORIZONTAL)
    hs0.Add(a, 1, wx.ALIGN_LEFT)  # 1 here means expand with weight 1
    hs0.Add((0,0), 1)  # also expand the spacing
    hs0.Add(b, 0, wx.ALIGN_RIGHT)  # 0 means don't expand this

    # build the bottom row
    hs2 = wx.BoxSizer(wx.HORIZONTAL)
    hs2.Add(d, 0, wx.ALIGN_LEFT)
    hs2.Add((10,10), 0)
    hs2.Add(e, 0)
    hs2.Add((10,10), 0)
    hs2.Add(f, 0)
    hs2.Add((10, 10), 1)
    hs2.Add(g, 0, wx.ALIGN_RIGHT)

    # stack the rows vertically
    vs = wx.BoxSizer(wx.VERTICAL)
    vs.Add(hs0, flag=wx.EXPAND)   # expand in the orthogonal direction to sizer (here horizontal)
    vs.Add(c, 1, flag=wx.EXPAND)  
    vs.Add(hs2, flag=wx.EXPAND)
    self.SetSizer(vs)

    self.Raise()
    self.SetPosition((0,0))
    self.Fit()  
    self.Hide()


# reveal this panel
def ShowYourself(self):
    self.Raise()
    self.SetPosition((0,0))
    self.Fit()
    self.Show()


# what do we do when the "Back" button is clicked?
def OnBack(self, event):
    self.Hide()
    self.GetParent().panel1.ShowYourself()

# what do we do when the "Next" button is clicked?
def OnNext(self, event):
    self.Hide()
    self.GetParent().panel1.ShowYourself()

    ![# what do we do when the "Cancel and Exit" button is clicked?
    def OnCancelAndExit(self, event):
        self.GetParent().ShutDown()

####################################################################################################

def main():
    app = MyApp(redirect = False)
    app.MainLoop()

####################################################################################################

if __name__ == '__main__':
    main()][3]
MetaHyperBolic
@MetaHyperBolic: Well, for one thing, when you add the panel to the sizer, you're not using the `wx.EXPAND` flag. Also, you may want to try calling `sizer_h.SetSizeHints(self)` after the `SetSizer` call. Try that and see if it works. As for linking the image in your post, did you add it like: `<img src="http...">`?
George Edison
Right. I tried adding the Panel to the sizer with wx.EXPAND before (you're seeing a very stripped-down script, with previous problems commented out) -- I ended up with all kinds of strange problems because I had seven panels added to the sizer on the top-level frame.
MetaHyperBolic
I just clicked the image icon on the toolbar and went with that, rather than attempting to add HTML, with which I am more familiar.
MetaHyperBolic
Give me some time and I'll try your script. Maybe I can find the problem then - this one really has me baffled.
George Edison
I was able to reproduce the problem. Now I'll try to find a solution.
George Edison
All I did was change `sizer_h.Add(self.panelX)` to `sizer_h.Add(self.panelX,1,wx.EXPAND)` and now the panel takes up the whole window. Isn't that what you want?
George Edison
I tried that, and now it works with my Very Very Very Stripped Down version. I think part of the problem is that this is my first project with wxPython and it is a relatively complex one. I'm not even within shouting distance of competence and have probably introduced any number of subtle issues.
MetaHyperBolic
Keep trying. You're doing well. It took me some time before I got the hang of how sizers work.
George Edison