Placing elements (panels) within a wx.GridBagSizer - python

I'm using a gridbagsizer to place two panels within a frame.
control_panel = wx.Panel(self, id=ID_CONTROL_PANEL)
main_panel = wx.Panel(self, id=ID_MAIN_PANEL)
frame_sizer = wx.GridBagSizer(1, 1)
frame_sizer.Add(control_panel, (0, 0), (2, 5), flag=wx.EXPAND)
frame_sizer.Add(main_panel, (2, 0), (5, 5), flag=wx.EXPAND)
frame_sizer.AddGrowableRow(0)
frame_sizer.AddGrowableCol(0)
self.SetSizer(frame_sizer)
I want the control_panel to be above the main_panel (as you can see above). However this is the output I get:
I don't know why the top panel is so much larger than the bottom panel, when I've specified the opposite. (2, 5) for the top and (5, 5) for the bottom. It also has a strange behaviour when I resize it smaller, it basically gives me what I want (see image below), but I don't know how to maintain the ratio when I make it larger again.
However when you start resizing larger (even by a small amount) you can see (below) the shapes and ratio change dramatically in the other direction (with the bottom becoming much smaller and the top much larger).
Still trying to learn this sizers and can't really find this problem with any of the examples I've seen. Also tried experimenting with all the parameters and doing some tests and I can't figure out what's going on here. Any help would be much appreciated.
EDIT: Image below added to aid comment below.

Personally, I would use a BoxSizer in VERTICAL orientation. Here's a simple example:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
control_panel = wx.Panel(panel)
control_panel.SetBackgroundColour("Yellow")
main_panel = wx.Panel(panel)
main_panel.SetBackgroundColour("Blue")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(control_panel, 3, wx.EXPAND)
sizer.Add(main_panel, 1, wx.EXPAND)
panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
I changed the background color of each panel to make it easier to tell which is which. Note that I gave the control panel a proportion of 3 and the main panel a proportion of 1. You might want to take a look at the Widget Inspection Tool as well to help you visualize it:
http://wiki.wxpython.org/Widget%20Inspection%20Tool

Of the seven rows and five columns in the grid you specified only the first row and the first column to be growable.
The control panel occupies the first row so it expands to take up the available space.
The wx.EXPAND flag only has an effect if the cell is in a growable row or column.

Related

wxpython table label background color overflowing the grid

I have to make a wxpython table using grid.I have set the background color of the table using grid.SetLabelBackgroundColour("green"). But it is overflowing the grid and changing color of the area outside the header also.
Can anybody please help me in fixing this.
import wx
import wx.grid
class GridFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
# Create a wxGrid object
grid = wx.grid.Grid(self, -1)
# Then we call CreateGrid to set the dimensions of the grid
# (100 rows and 10 columns in this example)
grid.CreateGrid(100, 10)
grid.SetLabelBackgroundColour("green")
# We can set the sizes of individual rows and columns
# in pixels
grid.SetRowSize(0, 60)
grid.SetColSize(0, 120)
# And set grid cell contents as strings
grid.SetCellValue(0, 0, 'wxGrid is good')
# We can specify that some cells are read.only
grid.SetCellValue(0, 3, 'This is read.only')
grid.SetReadOnly(0, 3)
# Colours can be specified for grid cell contents
grid.SetCellValue(3, 3, 'green on grey')
grid.SetCellTextColour(3, 3, wx.GREEN)
grid.SetCellBackgroundColour(3, 3, wx.LIGHT_GREY)
# We can specify the some cells will store numeric
# values rather than strings. Here we set grid column 5
# to hold floating point values displayed with width of 6
# and precision of 2
grid.SetColFormatFloat(5, 6, 2)
grid.SetCellValue(0, 6, '3.1415')
self.Show()
if __name__ == '__main__':
app = wx.App(0)
frame = GridFrame(None)
app.MainLoop()
I cannot find a way to force this to limit the label colour to just the number of defined labels.
I don't know if the following would be of any use to you but you could limit the size of the frame, so that the user does not see beyond the last column by seting a size to the frame and limiting the ability to resize it.
wx.Frame.__init__(self, parent, size=(950,500),style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)

wxpython: how would I configure a box sizer so that it has a center column that moves on resize, but doesn't change dimensions(pic explanation inside)

I can't figure out how to nest my sizers so that I get the following behavior.
When a user resizes the window, I would like for the configuration of all my widgets to stay the same, but have the central column housing everything to stay centered.
Using a box sizer, you should adjust the proportion accordingly. The spacers on the side should fill the remaining space, while the middle part should have a fixed amount of space. See the documentation of wx.BoxSizer:
It is the unique feature of a box sizer, that it can grow in both directions (height and width) but can distribute its growth in the main direction (horizontal for a row) unevenly among its children. This is determined by the proportion parameter give to items when they are added to the sizer. It is interpreted as a weight factor, i.e. it can be zero, indicating that the window may not be resized at all, or above zero. If several windows have a value above zero, the value is interpreted relative to the sum of all weight factors of the sizer, so when adding two windows with a value of 1, they will both get resized equally and each will receive half of the available space after the fixed size items have been sized.
See also this small example code (generated using wxFormBuilder). I've emphasized that the spacers have a proportion of 1 and the app has 0.
class MyFrame1 (wx.Frame):
def __init__(self):
super(MyFrame1, self).__init__()
fluid_sizer = wx.BoxSizer(wx.HORIZONTAL)
fluid_sizer.AddSpacer((0, 0), 1, wx.EXPAND, 5)
# ^--- proportion = 1
self.fixed_panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(-1,-1), wx.TAB_TRAVERSAL)
self.fixed_panel.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
fixed_sizer = wx.BoxSizer(wx.VERTICAL)
fixed_sizer.SetMinSize(wx.Size(150,-1))
self.m_button1 = wx.Button(self.fixed_panel, wx.ID_ANY, u"MyButton", wx.DefaultPosition, wx.DefaultSize, 0)
fixed_sizer.Add(self.m_button1, 0, wx.ALL, 5)
self.fixed_panel.SetSizer(fixed_sizer)
self.fixed_panel.Layout()
fixed_sizer.Fit(self.fixed_panel)
fluid_sizer.Add(self.fixed_panel, 0, wx.EXPAND |wx.ALL, 5)
# ^--- proportion = 0
fluid_sizer.AddSpacer((0, 0), 1, wx.EXPAND, 5)
# ^--- proportion = 1
self.SetSizer(fluid_sizer)
self.Layout()

How to make a dynamic number of horizontal BoxSizers?

I have a function that calculates the number of images that can be displayed on the screen, if there are more images than the ones that can be put on screen, I resize the images till they all can appear.
Then, I want to display them with one vertical box sizer and several horizontal box sizers!
The horizontal number of box sizers are dynamic, it can be only one or more depending on the number of images.
How can I define several box sizers and add them to the vertical box sizer?
Why not simply make the horizontal sizers in a loop, .Adding them to the same vertical sizer? E.g.
def HorzInVert(n):
vert = wx.BoxSizer(wx.VERTICAL)
horizontals = []
for i in range(n):
horz = wx.BoxSizer(wx.HORIZONTAL)
vert.Add(horz,1, wx.ALL, 0)
horizontals.append(horz)
return vert, horizontals
You can call this simple function from anywhere, it returns the vertical sizer and the list of n horizontal sizers in it -- then the caller adds stuff suitably to the horizontal sliders, an appropriate SetSizerwith the vertical sizer as the argument, and the vertical sizer's .Fit. Of course you can make it as much fancier as you want, with all sort of arguments to control exactly how the Adds are performed.
wx.GridSizer is the answer!

wxPython GridSizer not attached to panel?

I'm trying to build a level editor for a game I'm working on. It pulls data from a flat file and then based on a byte-by-byte search it'll assemble a grid from pre-set tiles. This part of the app I should have no issues with. The problem is that my test version of the editor which just loads a 16x16 grid of test tiles from 00 to FF is loading in the wrong place.
Example: The app frame looks like this:
|-T-------|
| | |
| | |
| | |
| | |
|---------|
Excusing my horrible ASCII art, the frame essentially has a horizontal sizer on it, with 2 vertical sizers, one for the left and one for the right. Each of these has a panel in it, which each have a second sizer in them. The left sizer has a 64 pixel-wide spacer in it, then a gridsizer which is later filled with images. The right second sizer is user sizable but a minimum of 960 pixels via a spacer there, then a gridsizer that's determined by the level width and height in code.
Essentially, for each side - there's a gridsizer inside a sizer which has a spacer for the width of the section, which are on a panel that's inside a sizer for that half of the sizer that's on the main frame. I hope this makes sense, as it confuses me at times :P
Here's the code that does all this:
#Horizontal sizer
self.h_sizer = wx.BoxSizer(wx.HORIZONTAL)
#Vertical sizer
self.v_sizer_left = wx.BoxSizer(wx.VERTICAL)
self.v_sizer_right = wx.BoxSizer(wx.VERTICAL)
#Create the 2 panels
self.leftPanel = wx.ScrolledWindow(self, style = wx.VSCROLL | wx.ALWAYS_SHOW_SB)
self.leftPanel.EnableScrolling(False, True)
self.rightPanel = wx.ScrolledWindow(self, style = wx.VSCROLL | wx.ALWAYS_SHOW_SB)
self.rightPanel.EnableScrolling(False, True)
#Create a sizer for the contents of the left panel
self.lps = wx.BoxSizer(wx.VERTICAL)
self.lps.Add((64, 0)) #Add a spacer into the sizer to force it to be 64px wide
self.leftPanelSizer = wx.GridSizer(256, 1, 2, 2) # horizontal rows, vertical rows, vgap, hgap
self.lps.Add(self.leftPanelSizer) #Add the tiles grid to the left panel sizer
self.leftPanel.SetSizerAndFit(self.lps) #Set the leftPanel to use LeftPanelSizer (it's not lupus) as the sizer
self.leftPanel.SetScrollbars(0,66,0, 0) #Add the scrollbar, increment in 64 pixel bits plus the 2 spacer pixels
self.leftPanel.SetAutoLayout(True)
self.lps.Fit(self.leftPanel)
#Create a sizer for the contents of the right panel
self.rps = wx.BoxSizer(wx.VERTICAL)
self.rps.Add((960, 0)) #Set it's width and height to be the window's, for now, with a spacer
self.rightPanelSizer = wx.GridSizer(16, 16, 0, 0) # horizontal rows, vertical rows, vgap, hgap
self.rps.Add(self.rightPanelSizer) #Add the level grid to the right panel sizer
self.rightPanel.SetSizerAndFit(self.rps) #Set the rightPanel to use RightPanelSizer as the sizer
self.rightPanel.SetScrollbars(64,64,0, 0) #Add the scrollbar, increment in 64 pixel bits - vertical and horizontal
self.rightPanel.SetAutoLayout(True)
self.rps.Fit(self.rightPanel)
#Debugging purposes. Colours :)
self.leftPanel.SetBackgroundColour((0,255,0))
self.rightPanel.SetBackgroundColour((0,128,128))
#Add the left panel to the left vertical sizer, tell it to resize with the window (0 does not resize, 1 does). Do not expand the sizer on this side.
self.v_sizer_left.Add(self.leftPanel, 1)
#Add the right panel to the right vertical sizer, tell it to resize with the window (0 does not resize, 1 does) Expand the sizer to fit as much as possible on this side.
self.v_sizer_right.Add(self.rightPanel, 1, wx.EXPAND)
#Now add the 2 vertical sizers to the horizontal sizer.
self.h_sizer.Add(self.v_sizer_left, 0, wx.EXPAND) #First the left one...
self.h_sizer.Add((0,704)) #Add in a spacer between the two to get the vertical window size correct...
self.h_sizer.Add(self.v_sizer_right, 1, wx.EXPAND) #And finally the right hand frame.
After getting all the data, the app will then use it to generate the level layout with .png tiles from a specified directory but for testing purposes I'm just generating a 16x16 grid from 00 to FF, as mentioned above - via a menu option I call this method:
def populateLevelGrid(self, id):
#This debug method fills the level grid scrollbar with 256 example image tiles
levelTileset = self.levelTilesetLookup[id]
#levelWidth = self.levelWidthLookup[id]
#levelHeight = self.levelHeightLookup[id]
#levelTileTotal = levelWidth * levelHeight
levelTileTotal = 256 #debug line
self.imgPanelGrid = []
for i in range(levelTileTotal):
self.imgPanelGrid.append(ImgPanel.ImgPanel(self, False))
self.rightPanelSizer.Add(self.imgPanelGrid[i])
self.imgPanelGrid[i].set_image("tiles/"+ levelTileset + "/" + str(i) + ".png")
self.rightPanelSizer.Layout()
self.rightPanelSizer.FitInside(self.rightPanel)
This works, but pins the grid to the top left of the entire frame, not to the top left of the right half of the frame - that it should be on. There's similar code to do a 1x256 grid on the left frame but I've no way of telling if that's suffering the same issue for obvious reasons. Both have working scrollbars but have redrawing issues when scrolled, making me wonder if it's drawing the images over the entire application and just ignoring the application layout.
Is there something I've missed here? This is the first time I've done anything with gridsizers, images and GUIs in general in python, having only recently started with the language after wanting to write in something a little more cross-platform than VB6 - any thoughts? =/
That's a lot of code and a lot of sizers! But I think maybe your ImgPanels should have the rightPanel as their parent, and not self.
Also, do you ever call self.SetSizer(self.h_sizer)? Didn't see that anywhere.
I recommend creating portions of the layout in separate functions. Then you don't have to worry about your local namespace being polluted with all these wacky sizer names. So for each part of the sizer hierarchy you could have a function.
create_controls
create_left_panel
create_grid
create_right_panel
Also, I usually use SetMinSize on the child controls instead of adding dummy spacers to the sizers to setup size constraints. Then Fit will do it for you. Speaking of Fit, I didn't even know it could take arguments!

Set Max Width for Frame with ScrolledWindow in wxPython

I created a Frame object and I want to limit the width it can expand to. The only window in the frame is a ScrolledWindow object and that contains all other children. I have a lot of objects arranged with a BoxSizer oriented vertically so the ScrolledWindow object gets pretty tall. There is often a scrollbar to the right so you can scroll up and down.
The problem comes when I try to set a max size for the frame. I'm using the scrolled_window.GetBestSize() (or scrolled_window.GetEffectiveMinSize()) functions of ScrolledWindow, but they don't take into account the vertical scrollbar. I end up having a frame that's just a little too narrow and there's a horizontal scrollbar that will never go away.
Is there a method that will account compensate for the vertical scrollbar's width? If not, how would I get the scrollbar's width so I can manually add it to the my frame's max size?
Here's an example with a tall but narrow frame:
class TallFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title='Tall Frame')
self.scroll = wx.ScrolledWindow(parent=self) # our scroll area where we'll put everything
scroll_sizer = wx.BoxSizer(wx.VERTICAL)
# Fill the scroll area with something...
for i in xrange(10):
textbox = wx.StaticText(self.scroll, -1, "%d) Some random text" % i, size=(400, 100))
scroll_sizer.Add(textbox, 0, wx.EXPAND)
self.scroll.SetSizer(scroll_sizer)
self.scroll.Fit()
width, height = self.scroll.GetBestSize()
self.SetMaxSize((width, -1)) # Trying to limit the width of our frame
self.scroll.SetScrollbars(1, 1, width, height) # throwing up some scrollbars
If you create this frame you'll see that self.SetMaxSize is set too narrow. There will always be a horizontal scrollbar since self.scroll.GetBestSize() didn't account for the width of scrollbar.
This is a little ugly, but seems to work on Window and Linux. There is difference, though. The self.GetVirtualSize() seems to return different values on each platform. At any rate, I think this may help you.
width, height = self.scroll.GetBestSize()
width_2, height_2 = self.GetVirtualSize()
print width
print width_2
dx = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
print dx
self.SetMaxSize((width + (width - width_2) + dx, -1)) # Trying to limit the width of our frame
self.scroll.SetScrollbars(1, 1, width, height) # throwing up some scrollbars

Categories

Resources