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!
Related
I'm trying to create a rectangle inside a Tkinter (Python 2.7) canvas that is the same dimension as the canvas. Here is the relevant part of the code:
self.canvas = Canvas(self, width=100, height=100, backround="yellow")
self.canvas.create_rectangle(0,0,100,100)
This draws a rectangle but I can't see the left and top border of the rectangle. If I start the rectangle from let's say 5,5 instead of 0,0 I can see the border of the rectangle. Any ideas as to why this happens, and how I can get around it?
Unfortunately, the canvas border is included in the drawable region. Try setting the borderwidth and highlightthickness attributes to zero on the canvas.
You'll also want to adjust the coordinates of your rectangle to end at 99, since counting starts at zero (if the width is 100, the coordinates go from 0 to 99).
Relevant code:
self.propertyListWrapper = ttk.Frame(self.propertyMenu)
self.propertyListWrapper.pack( fill = tk.BOTH, expand = tk.YES )
self.propertyListCanvas = tk.Canvas(self.propertyListWrapper)
self.propertyListCanvas.pack( fill = tk.BOTH, expand = tk.YES, side = tk.LEFT )
self.propertyGrid = ttk.Frame(self.propertyListCanvas)
self.propertyListScrollbar = ttk.Scrollbar(self.propertyListWrapper)
self.propertyListScrollbar.config(command = self.propertyListCanvas.yview)
self.propertyListCanvas.config(yscrollcommand = self.propertyListScrollbar.set)
self.propertyListScrollbar.pack(side = tk.RIGHT, fill = tk.Y)
...
# where stuff is added to self.propertyGrid
...
self.propertyListCanvas.config( scrollregion = (0, 0, self.propertyGrid.winfo_width(), self.propertyGrid.winfo_height()))
self.propertyListCanvas.create_window((0,0), window = self.propertyGrid, anchor='nw')
This is what is currently happening:
As you can see, the scrollbar doesn't have a bar. Not very useful.
What am I doing wrong?
Here's a simplified Github Gist that duplicates the problem. 20 labels should be there, and you should be able to scroll through them, but the scrollbar doesn't appear.
The default for pack is to put something along the top edge. When you packed the canvas you're using the default, so it takes up the top of the gui, leaving blank space below. When you packed the scrollbar, you told it to go to the right, so it is at the right edge of the empty space below the canvas. If you want the scrollbar and canvas side-by-side, pack both on the right, or pack one on the right and one on the left.
The problem with your gist is that you're trying to get the width and height of the frame before it has been drawn. Since the frame hasn't been mapped to the screen (because you haven't added it to the canvas yet) it's width and height is 1 (one). You must make a widget visible before you can measure its width and height. That means to add it to a canvas or pack/place/grid it to a visible window and then wait for a screen repaint.
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.
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!
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