I have some wxpython code that behave strangely.. this is OKAY code:
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title,size=(500, 300))
self.CreateStatusBar()
panel = wx.Panel(self)
self.srcSizer = wx.BoxSizer(wx.HORIZONTAL)
srcButton = wx.Button(panel, wx.ID_ANY, "src")
srcButton.Bind(wx.EVT_BUTTON, self.onSrcButton)
self.srcSizer.Add(srcButton, 0)
self.srcTxt = wx.TextCtrl(panel, wx.ID_ANY)
self.srcSizer.Add(self.srcTxt, 1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.srcSizer, 0 , flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=10)
panel.SetSizer(self.sizer)
self.Show(True)
now when I swap the two lines of creating statusbar and the panel so they become
panel = wx.Panel(self)
self.CreateStatusBar()
Then the button and the textctrl are overlapped when the window is loaded, and they are brought back to normal position when resizing the window manually !!
Does self.CreatStatusBar() have always to be before creating panels or something ?
Thanks
CreateStatusBar() triggers a resize event on the frame to make room for the status bar. If the panel was already created, it is resized to fit the client
area of the frame. Resizing the panel triggers a resize event on the panel which will then recalulate its layout (the sizer) if applicable.
Creating controls (to be added to the panel's sizer) simply places them at the default position (0,0) with their default size. They will require
a layout (sizer) update to be moved to their correct position. (that's why there's a pile of controls at the upper left corner when the problem occurs.)
When the frame is shown, a resize event is fired (again). However, if the panel already fits the client area of the frame, the panel's resize event is not triggered,
therefore its layout is not updated.
You can observe that effect by creating the panel with the size of the client area even without the status bar:
panel = wx.Panel(self,size=self.GetClientSize())
#self.CreateStatusBar()
Likewise, you can trigger the update by setting the size of the panel to something else (once the frame is shown, it will resize the panel again):
# at the end of __init__
panel.SetSize(0,0)
However, this would create an unnecessary resize: first when you manually SetSize() and again in frame.Show(). There's a better way (shown below).
In wx 2.8, this issue applies to CreateToolBar() as well. wx 2.9.4 appears to handle the toolbar correctly.
As a workaround, you can:
create toolbar/statusbar before you create the panel or after you set the panel's sizer (that requires to add the controls to the sizer first)
recalculate the panel's layout after you set the sizer:
panel.SetSizer(sizer)
panel.Layout()
Related
I'm taking the first steps to move from .NET to Python but I'm already having a few headaches regarding the GUI design.
For some reason, passing the size attribute to a wx.Button seems to be kind of ignored. And I say "kind of" because the actual space seems to change but the actual button keeps occupying the same space:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
self.SetSize((800, 600))
self.SetTitle('Main Menu')
self.Centre()
self.Show(True)
''' Fill the form '''
self.lblUsername = wx.StaticText(self, size=(80, -1), pos=(20,20), label="Username:" )
self.txtUsername = wx.TextCtrl(self, size=(140, -1), pos=(100,20), style=wx.TE_PROCESS_ENTER)
self.lblPassword = wx.StaticText(self, size=(80, -1), pos=(20,50), label="Password:" )
self.txtPassword = wx.TextCtrl(self, size=(140, -1), pos=(100,50), style=wx.TE_PROCESS_ENTER)
self.btnOK = wx.Button( self, label="OK", pos=(260, 16), size=(50,50))
self.btnOK.Bind(wx.EVT_BUTTON, self.onClickOK)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Ready')
def onClickOK(self, e):
print "Button triggered"
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
No matter what size I set, the Button won't stretch (it will be centered as if all the space was actually being used, but will still be small).
Can anyone spot what am I doing wrong?
This is a limit imposed by OSX. The way the native button widget is drawn only allows it to be stretched horizontally, and the vertical size is fixed. Or rather, as you've discovered, the widget itself can be larger than normal vertically, but it will only draw itself at a fixed height within that space. It seems less neccessary with modern versions of OSX, but if you look at buttons in OSX from a few years ago you can probably see why this is so. The esthetic graphical effect of the "tic-tack" or "capsule" buttons would be totally ruined if they were a non-standard vertical size, causing the images used to draw the buttons to be stretched. wxWidgets follows the native plaform look and feel standards where possible, in this case it happens that Apple's standard is imposed upon us and wx can't offer the same level of flexibility that it usually does.
You do have some options however if you really want taller than normal buttons. The native widgets have a few different standard sizes, which you can select using the SetWindowVariant method, although I don't think the variants would get as tall as you want. Or you could use a generic button widget instead of a native one, such as wx.lib.buttons.ThemedGenButton.
Same problem in my little Software EventSoundControl.
Just a workaround: Use a multiline label and sizes of wxButton will work as desired!
If you want the button to stretch when you resize the frame, then you cannot use static sizes and positioning. You will need to put your widgets in a sizer. Then the sizer will manage the position / size of the widget(s) as you change the size of the frame. There are many examples on the wxPython wiki that demonstrate how to use sizers. You might also find the following tutorial helpful:
http://zetcode.com/wxpython/layout/
Is it possible to clone a wxPanel across a pair of frames in wxPython?
I have tried using the same wxID, which resulted in of significance.
I have tried using the same instance of the control which results in only one being drawn.
I am ultimately trying to display the output of LibVLC (which renders to a wxPanel via it's hwnd) on two frames simultaneously. One frame is inside a control window to provide a "preview" of the video while the other is displayed fullscreen on a second monitor.
Here's a reduced version of the code I'm using to display the video output in the preview window:
class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.video_panel = wx.Panel(self, wx.ID_ANY)
self.video_panel.SetBackgroundColour(wx.Colour(0, 0, 0))
sizer_video = wx.BoxSizer(wx.VERTICAL)
sizer_video.Add(self.video_panel, 1, wx.ALL | wx.EXPAND, 2)
self.SetSizer(sizer_video)
self.Layout()
self.Instance = Libraries.vlc.Instance()
self.player = self.Instance.media_player_new()
def mediaLoad(self, path):
self.Media = self.Instance.media_new(unicode(path))
self.player.set_media(self.Media)
if sys.platform.startswith('win'):
self.player.set_hwnd(self.video_panel.GetHandle())
elif sys.platform.startswith('linux'):
self.player.set_xwindow(self.video_panel.GetHandle())
else:
self.player.set_nsobject(self.video_panel.GetHandle())
You can't do that in wxPython. Each widget has a single parent. I think the best course would be to create a subclass of wx.Panel and have both frames instantiate the class. Then you might use pubsub to communicate between the two frames. I don't think you would be able to get both instances to sync up exactly, but you should be able to get it pretty close.
How do I change the size of wxPython widget after its creation? I am trying to dynamically change the width of a ComboBoxCtrl widget based on the elements in the popup. For the sake of simplicity lets say we create a button and try to resize it:
btn = wx.Button(self, wx.ID_ANY, 'Start', size=(25,-1))
btn.SetSize(wx.Size(25,25))
self.Layout()
In the above case, the new 25x25 size does not take. I can only get SetSize to work for a panel. I am assuming there is another call I should be using. How can I change the size of widget after creation?
The SetMinSize command does the trick.
btn = wx.Button(self, wx.ID_ANY, 'Start', size=(25,-1))
btn.SetMinSize(wx.Size(25,25))
self.Layout()
wxpython 2.8.11.0, python 2.7
If i put some Sizer with some controls directly into a Frame like
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(frm))
sizer.Add(wx.SpinCtrl(frm))
frm.SetSizerAndFit(sizer)
frm.Show()
app.MainLoop()
the Frame will automagically have a correct minimum size to contain the Sizer and it is not possible to make it smaller.
If there is a Panel in between (as needed for tabbing between controls) this does not work, the window can be made too small.
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizerAndFit(sizer)
frm.Show()
app.MainLoop()
Additionally frm.Fit() and frm.SetMinSize(frm.GetEffectiveMinSize()) are required to get the same behavior. Full Code:
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizerAndFit(sizer)
frm.Fit()
frm.SetMinSize(frm.GetEffectiveMinSize())
frm.Show()
app.MainLoop()
I am okay with frm.Fit(), but i dislike frm.SetMinSize(frm.GetEffectiveMinSize()). Isn't there a better solution so that the Frame automatically considers the minimum size of the Panel just like with the Sizer before? I'm considered with what happens if the EffectiveMinSize changes after another control is added to the sizer.
Edit:
Apparently
panel.SetSizerAndFit(sizer)
frm.Fit()
frm.SetMinSize(frm.GetEffectiveMinSize())
should be replaced by
panel.SetSizer(sizer)
sizer.SetSizeHints(frm)
which looks somewhat cleaner. So in total this looks like
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizer(sizer)
sizer.SetSizeHints(frm)
frm.Show()
app.MainLoop()
Is this the preferred way to do this?
If widgets are added later on even the first approach with the Sizer directly on the Frame doesn't get it right without intervention, so i think that is a totally different question.
The most elegant way (IMHO) is given in phineas' answer. It is slightly inefficient to have an extra otherwise unneeded sizer but I don't think it can really be noticeable.
Some people do call SetSizeHints() manually, as in your last example, and this works as well and might be more clear (whenever I use an extra sizer I feel the need to leave a comment explaining why it shouldn't be removed).
Unfortunately there is no better way and I am not sure if we can even add one because you need to do something with all of the panel, the sizer and the frame and this means that you can't do it with a single method call without passing unrelated parameters to it. I.e. we could have something like
panel.SetSizerAndFitParent(sizer, frame);
but it's not really clear whether this would be better.
What about introducing a sizer that manages the frame content (e.g. the panel)? Since you didn't include a minimal example, I haven't tested it yet.
frameSizer = wx.BoxSizer(wx.VERTICAL)
frameSizer.Add(panel)
frm.SetSizerAndFit(frameSizer)
How do I set the size of a multi-line TextCtrl to always fill its parent panel?
Use a boxSizer.
When you add your textCtrl to the sizer set the proportion to 1 and pass the wx.EXPAND flag, that way your textCtrl should fill the panel even when the panel is resized
bsizer = wx.BoxSizer()
bsizer.Add(yourTxtCtrl, 1, wx.EXPAND)
Put the following at the end of your panels initialization to set the layout
self.SetSizerAndFit(bsizer)