How to center panel with controlled aspect ratio - python

In wxPython, I'm having trouble centering a sub-panel that I've created to have a fixed aspect ratio.
To control the aspect ratio, the sub-panel needs to capture the Size event and then do an explicit SetSize. So, I've done that and it works well. Unfortunately, when I embed this sub-panel into another panel (using a sizer), the wx.ALIGN_CENTER_HORIZONTAL flag doesn't work.
Apparently, the sizer tells my sub-panel that it has the whole width to fit within. When my window doesn't use the whole width, then the sizer doesn't adjust. Here is a simplified version of my code that shows the problem:
import wx
ROWS = 6
COLS = 25
class TestPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
label = wx.StaticText(self, -1, 'Label')
grid = TestGrid(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(label, 0, flag=wx.ALIGN_CENTER_HORIZONTAL)
sizer.Add(grid, 1, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND)
self.SetSizer(sizer)
class TestGrid(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.SetBackgroundColour(wx.BLUE)
self.Bind(wx.EVT_SIZE, self.OnSize)
def OnSize(self, event):
w, h = self.GetSizeTuple()
delta = min(w // COLS, h // ROWS)
self.SetSize((COLS*delta, ROWS*delta))
self.Refresh()
if __name__ == '__main__':
app = wx.App()
frame = wx.Frame(None, -1, "Test", size=(600, 200))
panel = TestPanel(frame)
frame.Show(True)
app.MainLoop()
This is a screen capture:

As VZ explained in his answer, your wxEVT_SIZE handler isn't a very good idea. Remove it. When using sizers, you have to work within the sizer infrastructure to achieve what you want.
You want your TestGrid to fill as much space as possible within its parent, while maintaining a certain aspect ratio; that's exactly what wx.SHAPED is for (combined with a proportion greater than 0, which you already have). So, you should add grid to the sizer like this:
sizer.Add(grid, 1, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.SHAPED)
The only other thing you need to do is tell the sizer what your desired aspect ratio is. The easiest way is to set an initial size for your TestGrid (before it's added to the sizer); for example, set the size of its wxPanel base:
wx.Panel.__init__(self, parent, -1, size=(COLS*7, ROWS*7))
That's all there is to it.
(I'm just guessing the Python syntax, so ignore any obvious errors :-) )

You can't, or at the very least shouldn't, resize a window from its own wxEVT_SIZE handler. I'm not sure what exactly are you trying to achieve, but if the goal is to encapsulate everything in TestGrid class, then you should let it have whichever size it has and create a nested window in it that you would resize to the appropriate size in your OnSize.

Related

wxPython grid formatting

I have a wxPython GUI with a grid in it. I can't predict the exact size of the grid before it is created. My problem is the way the grid scrollbars display when the user clicks on a cell to edit. This is particularly apparent the grid has only one row, which can happen occasionally; if you select a cell to edit, the scrollbars pop up in such a way that you can't see the row at all. I don't want the user to have to scroll within the grid at all (in my GUI, the grid is in a wx.ScrolledWindow).
I need to have some buttons and directions outside of the grid, so having the grid object 'stand alone' is not a good option for me.
Here is the code:
class Spacing(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Fix spacing")
self.panel = wx.ScrolledWindow(self)
self.InitUI()
def InitUI(self):
label = wx.StaticText(self.panel,label="hello")#, size=(500, 50))
grid = self.make_table(1, 5)
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(label, flag=wx.ALL, border=10)
vbox.Add(grid, flag=wx.BOTTOM|wx.EXPAND, border=20)
hbox_all= wx.BoxSizer(wx.HORIZONTAL)
hbox_all.AddSpacer(20)
hbox_all.AddSpacer(vbox)
hbox_all.AddSpacer(20)
self.panel.SetSizer(hbox_all)
self.panel.SetScrollbars(20, 20, 50, 50)
hbox_all.Fit(self)
self.Show()
def make_table(self, n_cols, n_rows):
grid = wx.grid.Grid(self.panel, -1)
grid.ClearGrid()
grid.CreateGrid(n_cols, n_rows)
for n in range(n_cols):
for num in range(n_rows):
grid.SetCellValue(n, num, (str(num) + str(n)))
return grid
if __name__ == "__main__":
app = wx.PySimpleApp()
app.frame = Spacing()
app.frame.Show()
app.frame.Center()
app.MainLoop()
I can imagine a few options. One, if I could add some "padding" to the grid window, the user would still be able to see what they were editing no matter what. Two, if I could override the scrolled window nature of the grid, that would work also. I haven't figured
out how to do either of these, and I'm open to other suggestions. I've tried messing with disabling grid.SetAutoLayout and manually sizing the grid, but neither of these has proven effective. I would really appreciate any suggestions!
The (very) hack-y solution I've devised is to make sure to have another widget in the container window that is wider than the grid and also set the grid to wx.EXPAND, which prevents the horizontal scrollbars from ever popping up.
That is, I replace this line of code:
label = wx.StaticText(self.panel,label="hello")
with this:
label = wx.StaticText(self.panel,label="hello", size=(500, 50))
and this line:
vbox.Add(grid, flag=wx.BOTTOM, border=20)
with this:
vbox.Add(grid, flag=wx.BOTTOM|wx.EXPAND, border=20)
This option looks a little weird too, but it doesn't obscure what the user is typing. Obviously this is not a good solution, but I haven't been able to figure out how to actually fix the problem.
Update:
If I create the grid with a set spacing it doesn't re-size:
grid = wx.grid.Grid(self.panel, -1, size = (500, 300))
But if I create the grid and then try to re-size it, it doesn't "stick" and I have the same objectionable behavior as before. In fact, grid.SetSize doesn't seem to work at all visually, even though when I call grid.GetSize() it reports being the size I have assigned.
grid.SetSize(wx.Size(500, 300))

ScrolledPanel with vertical scrollbar only and WrapSizer

I uses a WrapSizer in order to have an automatic layout (as thumbnail gallery) like this (see screenshot on the left) :
I would like that if there are two many elements, a (vertical only)-ScrollBar is added on the panel (see right screenshot). How to add such a vertical scrollbar to a panel using a WrapSizer?
I tried by mixing WrapSizer and ScrolledPanel, but I cannot get the desired layout.
class MyPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent)
self.SetBackgroundColour('#f8f8f8')
sizer = wx.WrapSizer()
self.SetupScrolling()
# add some widgets btn1, btn2, etc. in the WrapSizer
sizer.Add(btn1, 0, wx.ALL, 10)
sizer.Add(btn2, 0, wx.ALL, 10)
Solution:
reset the width of the scroll panel virtual size to the displayable size.
import wx
import wx.lib.scrolledpanel as scrolled
class MyPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent, style=wx.VSCROLL)
self.SetBackgroundColour('#f8f8f8')
self.sizer = wx.WrapSizer()
self.SetupScrolling(scroll_x = False)
self.parent = parent
self.addButton(self.sizer , 10)
self.SetSizer(self.sizer )
self.Bind(wx.EVT_SIZE, self.onSize)
def onSize(self, evt):
size = self.GetSize()
vsize = self.GetVirtualSize()
self.SetVirtualSize((size[0], vsize[1]))
evt.Skip()
def addButton(self, sizer, num):
for i in range(1, num):
btn =wx.Button( self, wx.ID_ANY, "btn"+str(i), wx.DefaultPosition, wx.DefaultSize, 0 )
sizer.Add(btn, 0, wx.ALL, 10)
if __name__=='__main__':
app = wx.App(redirect=False)
frame = wx.Frame(None)
MyPanel(frame)
frame.Show()
app.MainLoop()
It looks like you just forgot to include
self.SetSizer(sizer)
Since the WrapSizer takes the whole frame, I think that will work. Also, instead of SetupScrolling, you can use
self.SetScrollRate(horiz, vert)
to specify the increment (in pixels, i think) of the scroll, and that should work.
I can't test it here right now though, and WrapSizers are a little weird - they sometimes have trouble figuring out their proper size. You may need to wrap it in a BoxSizer going the other direction.

Determining selected image in wxPython

I have a set of images available. If I click on one of those images is there a way to determine which of the images has been clicked on in wxPython?
You will almost certainly have to calculate it for yourself. The most straight-forward method would be to use a mouse event like wx.EVT_LEFT_DOWN and grab the mouse's coordinates in the event handler. Then use that information to tell you where on your wxPython window you clicked. Each of your image widgets or DCs or whatever you're using can report it's size and position, so if the mouse coordinates are in X image's boundaries, you know it's been clicked on. You might also be able to use the HitTest() method, depending on what you're using to show the images.
EDIT: Here is how you would do it if you were using a wx.StaticBitmap, which actually lets you attach an wx.EVT_LEFT_DOWN to it:
import wx
class PhotoCtrl(wx.Frame):
def __init__(self):
size = (400,800)
wx.Frame.__init__(self, None, title='Photo Control', size=size)
self.panel = wx.Panel(self)
img = wx.EmptyImage(240,240)
self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.BitmapFromImage(img),
name="emptyImage")
imageCtrl2 = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.BitmapFromImage(img),
name="anotherEmptyImage")
self.imageCtrl.Bind(wx.EVT_LEFT_DOWN, self.onClick)
imageCtrl2.Bind(wx.EVT_LEFT_DOWN, self.onClick)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
mainSizer.Add(imageCtrl2, 0, wx.ALL, 5)
self.panel.SetSizer(mainSizer)
self.Show()
#----------------------------------------------------------------------
def onClick(self, event):
""""""
print event.GetPosition()
imgCtrl = event.GetEventObject()
print imgCtrl.GetName()
if __name__ == '__main__':
app = wx.App(False)
frame = PhotoCtrl()
app.MainLoop()
you dont tell us anything about how you are displaying your images? are you blitting them right on the dc? are you creating panels for them? etc... properly setting up your project is important. basically you give us zero information to help you with.
Keeping all that in mind, something like this would work fine (this is called a self contained code example, you should always provide one with your questions, to make it easier for people to help you)
import wx
a = wx.App(redirect=False)
f= wx.Frame(None,-1,"Some Frame",size = (200,200))
sz = wx.BoxSizer(wx.HORIZONTAL)
def OnClick(evt):
print "Clicked:",evt.GetId()-10023
for i,img in enumerate(["img1","img2","img3"]):
id = 10023+i
p = wx.Panel(f,-1)
sz.Add(p)
sz1 = wx.BoxSizer()
p.Bind(wx.EVT_LEFT_UP,OnClick)
bmp = wx.Image(img).ConvertToBitmap()
b = wx.StaticBitmap(p,-1,bmp)
sz1.Add(b)
p.SetSizer(sz1)
f.SetSizer(sz)
f.Layout()
f.Fit()
f.Show()
a.MainLoop()
Keep in mind I didnt test it... but theoretically it should work...

elementary wx sizing issues

I've got the following sizing-related code:
import wx
class TableSelectPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
self.title = wx.StaticText(self, label="Select Table")
self.tableList = wx.ListBox(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.title)
sizer.Add(self.tableList, flag=wx.EXPAND)
self.SetSizerAndFit(sizer)
class LobbyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.tableSelect = TableSelectPanel(self)
#window size
self.SetMinSize((800, 600))
self.SetMaxSize((800, 600))
self.SetSize((800, 600))
#sizers
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.tableSelect, flag=wx.EXPAND)
self.SetSizer(sizer)
self.Show(True)
What I expect is that I will have an 800x600 window with the wx.ListBox stretching vertically to fit the entire height of the table. However, while I do have an 800x600 window, the wx.ListBox does not expand to the entire height. Rather, it seems the panel does stretch out, but the list box does not:
What have I done wrong?
Set the proportion to 1:
sizer.Add(self.tableList, proportion=1, flag=wx.EXPAND)
Although the meaning of this parameter is undefined in wxSizer, it is used in wxBoxSizer to indicate if a child of a sizer can change its size in the main orientation of the wxBoxSizer - where 0 stands for not changeable and a value of more than zero is interpreted relative to the value of other children of the same wxBoxSizer. For example, you might have a horizontal wxBoxSizer with three children, two of which are supposed to change their size with the sizer. Then the two stretchable windows would get a value of 1 each to make them grow and shrink equally with the sizer's horizontal dimension.

Easiest way to create a scrollable area using wxPython?

Okay, so I want to display a series of windows within windows and have the whole lot scrollable. I've been hunting through the wxWidgets documentation and a load of examples from various sources on t'internet. Most of those seem to imply that a wx.ScrolledWindow should work if I just pass it a nested group of sizers(?):
The most automatic and newest way is to simply let sizers determine the scrolling area.This is now the default when you set an interior sizer into a wxScrolledWindow with wxWindow::SetSizer. The scrolling area will be set to the size requested by the sizer and the scrollbars will be assigned for each orientation according to the need for them and the scrolling increment set by wxScrolledWindow::SetScrollRate.
...but all the example's I've seen seem to use the older methods listed as ways to achieve scrolling. I've got something basic working, but as soon as you start scrolling you lose the child windows:
import wx
class MyCustomWindow(wx.Window):
def __init__(self, parent):
wx.Window.__init__(self, parent)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.SetSize((50,50))
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self)
dc.SetPen(wx.Pen('blue', 2))
dc.SetBrush(wx.Brush('blue'))
(width, height)=self.GetSizeTuple()
dc.DrawRoundedRectangle(0, 0,width, height, 8)
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.scrolling_window = wx.ScrolledWindow( self )
self.scrolling_window.SetScrollRate(1,1)
self.scrolling_window.EnableScrolling(True,True)
self.sizer_container = wx.BoxSizer( wx.VERTICAL )
self.sizer = wx.BoxSizer( wx.HORIZONTAL )
self.sizer_container.Add(self.sizer,1,wx.CENTER,wx.EXPAND)
self.child_windows = []
for i in range(0,50):
wind = MyCustomWindow(self.scrolling_window)
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.child_windows.append(wind)
self.scrolling_window.SetSizer(self.sizer_container)
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.PySimpleApp()
f = TestFrame()
f.Show()
app.MainLoop()
Oops.. turns out I was creating my child windows badly:
wind = MyCustomWindow(self)
should be:
wind = MyCustomWindow(self.scrolling_window)
..which meant the child windows were waiting for the top-level window (the frame) to be re-drawn instead of listening to the scroll window. Changing that makes it all work wonderfully :)

Categories

Resources