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.
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/
I have a simple wxPython application. It basically has a image and a text-entry field (a wx.TextCtrl).
I want to be able to immediately be able to start entering text as soon as the window opens. Right now, I have to first click in the text control, and then I can start entering text.
Here is a minimal app that demonstrates the issue:
import wx
class MyFrame(wx.Frame):
""" We simply derive a new class of Frame. """
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(200, 100))
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
mainSizer.Add(self.control, 1, wx.EXPAND)
self.SetSizer(mainSizer)
self.Show(True)
app = wx.App(False)
frame = MyFrame(None, 'Small editor')
app.MainLoop()
I've poked around with wx.SetInsertionPoint, but that does not seem to have any effect.
Ah, derp. I had to look further up the inheritance chain.
You can simply call SetFocus() on the control (in this case, self.control.SetFocus()).
SetFocus() is a member function of wxWindow. I was only looking at the docs for wxTextCtrl.
Of course, I didn't think to look up the inheritance chain until I had already asked the question.
I'm leaving this here, as this is a pretty hard to google issue. Hopefully this will help someone else.
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()
Is it possible to make a wxPython window only re-sizable to a certain ratio? I know you can disable resizing; however, I'd like it so when the window was resized it stuck to a certain width to height ratio.
One obvious way to do this would be to bind wx.EVT_SIZE to a function that constrains the aspect ratio. I'm not certain this is The Right Way to do this, but it works:
import wx
class SizeEvent(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Centre()
self.SetSizeWH(400, 300)
self.Show(True)
def OnSize(self, event):
hsize = event.GetSize()[0] * 0.75
self.SetSizeHints(minW=-1, minH=hsize, maxH=hsize)
self.SetTitle(str(event.GetSize()))
app = wx.App()
SizeEvent(None, 1, 'sizeevent.py')
app.MainLoop()
(The boilerplate is borrowed from here.)
I'm not too familiar with wxPython, but can't you just reset the window size to your max/min size that you want once the user pass that? Preferably in the event that detects resizing?
import wx
class TestDraw(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id)
self.SetBackgroundColour("#FFFFFF")
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self, event):
event.Skip()
dc=wx.PaintDC(self)
dc.BeginDrawing()
width=dc.GetSize()[0]
height=dc.GetSize()[1]
if height<width:
self.drawTestRects(dc)
else:
dc.Clear()
dc.EndDrawing()
def drawTestRects(self,dc):
dc.SetBrush(wx.Brush("#000000",style=wx.SOLID))
dc.DrawRectangle(50,50,50,50)
dc.DrawRectangle(100,100,100,100)
class TestFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=TestDraw(self,-1)
self.Show(True)
app = wx.App(False)
frame = TestFrame(None,"Test App")
app.MainLoop()
This code should draw the test rectangles only when the height is less than the width, and otherwise the window should remain clear. However, if you mess with resizing the window, the panel isn't actually redrawn unless it is moved off the window. What am I doing wrong?
You can bind a method to handle wx.EVT_SIZE or the panel and invalidate it there. Alternatively simply use the wx.FULL_REPAINT_ON_RESIZE for the panel.
The documentation for a SizeEvent claims that there may be some complications when drawing depends on the dimensions of the window. I do not know exactly what is going on behind the scenes. I followed the suggestion on the link and added the call self.Refresh() to the top of onPaint() and this seems to give the desired behavior. See mghie's answer for a more efficient example of working code.