I'm experiencing very different behavior when creating a wx.Panel depending on whether the main window's already called Show().
With the below code, the application opens quickly with MainPanel created & populated before MainFrame.Show(). Using "New" to re-create it has multi-second lag while it re-makes the 200 texts. Also the MainPanel doesn't expand to take up the whole size of the window until the window is resized.
This is definitely an issue with panel creation rather than panel destruction; if I remove the MainPanel creation in MainFrame's init, then the New action has the same speed & size issues.
Two questions:
Is there anything I can do to speed up panel creation after MainFrame.Show()?
What needs to be done when creating MainPanel after MainFrame.Show()ed to have the MainPanel expand to the size of its parent?
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import wx.lib.scrolledpanel
class MainPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self,parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent=parent)
self.SetupScrolling()
sizer = wx.BoxSizer(wx.VERTICAL)
for i in range(1,200):
sizer.Add(wx.StaticText(self, wx.ID_ANY, "I'm static text"))
self.SetSizer(sizer)
self.SetAutoLayout(True)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="FrameTest", size=(600,800))
self.InitMenu()
self.panel = None
self.panel = MainPanel(self)
def InitMenu(self):
self.menuBar = wx.MenuBar()
menuFile = wx.Menu()
menuFile.Append(wx.ID_NEW, "&New")
self.Bind(wx.EVT_MENU, self.OnNew, id=wx.ID_NEW)
self.menuBar.Append(menuFile, "&File")
self.SetMenuBar(self.menuBar)
def OnNew(self, evt):
if self.panel:
self.panel.Destroy()
self.panel = MainPanel(self)
if __name__ == "__main__":
app = wx.App(0)
frame = MainFrame()
frame.Show()
app.MainLoop()
UPDATE: joaquin's SendSizeEvent() definitely solves the first problem. For the second, I've found that hiding containers works out well. I'm guessing that after the window's been shown, it's trying to unnecessarily re-(display/layout/something) after every new widget and that's slowing it down. If I add Hide & Show to the panel's init, then there's no more lag and it works in both situations.
class MainPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self,parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent=parent)
self.SetupScrolling()
self.Hide()
sizer = wx.BoxSizer(wx.VERTICAL)
for i in range(1,200):
sizer.Add(wx.StaticText(self, wx.ID_ANY, "I'm static text"))
self.SetSizer(sizer)
self.SetAutoLayout(True)
self.Show()
The panel will get the correct size if the Frame feels a SizeEvent. So this works for your second question:
def OnNew(self, evt):
if self.panel:
self.panel.Destroy()
self.panel = MainPanel(self)
self.SendSizeEvent()
Control of windows and widgets becomes easier by using sizers. With sizers in your main frame you can use the sizer Layout method to fit widgets in place.
Could not find a way of speeding up panel rewrite. But you can diminish the bizarre visual effect by not deleting the panel but clearing the sizer instead (you dont need SendSizeEvent() for this case):
class MainPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self,parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent=parent)
self.SetupScrolling()
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.fill()
self.SetSizer(self.sizer)
def fill(self):
tup = [wx.StaticText(self, wx.ID_ANY, "I'm static text") for i in range(200)]
self.sizer.AddMany(tup)
self.Layout()
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="FrameTest", size=(600,800))
self.InitMenu()
self.panel = None
self.panel = MainPanel(self)
def InitMenu(self):
self.menuBar = wx.MenuBar()
menuFile = wx.Menu()
menuFile.Append(wx.ID_NEW, "&New")
self.Bind(wx.EVT_MENU, self.OnNew, id=wx.ID_NEW)
self.menuBar.Append(menuFile, "&File")
self.SetMenuBar(self.menuBar)
def OnNew(self, evt):
if self.panel:
self.panel.sizer.Clear()
self.panel.fill()
Related
i am using wxpython and trying to make an background to a sizer without any success, i searched in google without any results.
i try it with this boxsizer
wx.BoxSizer(wx.HORIZONTAL)
I just use panels for this sort of thing. You can set the color of the panel several different ways: you can use a named color, a wx.Color object, a predefined wx.Color object like wx.RED or a tuple of 3 integers.
Here's a simple example:
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.SetBackgroundColour('white')
main_sizer = wx.BoxSizer(wx.VERTICAL)
for number in range(5):
btn = wx.Button(self, label='Button {}'.format(number))
main_sizer.Add(btn, 0, wx.ALL, 5)
self.SetSizer(main_sizer)
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Background colors')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
I wrote a little about this topic here:
https://www.blog.pythonlibrary.org/2009/09/03/wxpython-resetting-the-background-color/
You might also the wxPython wiki helpful:
https://wiki.wxpython.org/GettingStarted
I have a GUI with two wxNotebook-elements like this:
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"My App",size=(800,600),pos=((wx.DisplaySize()[0]-800)/2,(wx.DisplaySize()[1]-600)/2),style= wx.SYSTEM_MENU | wx.CAPTION | wx.MINIMIZE_BOX | wx.CLOSE_BOX)
self.SetBackgroundColour((232,232,232))
self.p = wx.Panel(self,size=(800,6300),pos=(0,0))
self.SetPages()
def SetPages(self):
self.nb = wx.Notebook(self.p,style=wx.NB_BOTTOM)
page1 = PageOne(self.nb)
page2 = PageTwo(self.nb)
self.nb.AddPage(page1, "page1")
self.nb.AddPage(page2, "page2")
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.p.SetSizer(self.sizer)
Now I want to create a third Notebook-page & set focus on it at a certain event. But this does not work:
def CreateNewPageEvent(self, event):
self.CreateNewPage()
def CreateNewPage(self):
page3 = PageThree(self.nb)
self.nb.AddPage(page3, "page3")
I must admit that I'm not sure what a "BoxSizer" does =/
Any ideas to get this working?
Edit: OK, this works for an event inside my MainFrame-class. But I also want to create a new nb-page from an event of another class:
class ContinueApp(MainFrame):
def foo(self):
super(ContinueApp, self).CreateNewPage()
def continueapp(event):
cont = ContinueApp()
cont.foo()
The BoxSizer (and other sizers) are for laying out widgets so you don't have to position them yourself. They also help control which widgets expand or stretch when you make your application window larger or smaller. In your case, you should NOT add the same widget to the same sizer twice. You shouldn't add one widget to two different sizers either.
You need to remove this:
self.nb.AddPage(page1, "page3")
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.p.SetSizer(self.sizer)
Also note that you are adding page1 to the notebook again when you should be adding page3:
page3 = PageThree(self.nb)
self.nb.AddPage(page3, "page3")
If you want to switch between tabs programmatically, you should use the notebook's SetSelection method. I have an example app you can look at in the following tutorial (or the answer below it):
http://www.blog.pythonlibrary.org/2012/07/18/wxpython-how-to-programmatically-change-wx-notebook-pages/
wxpython: How to make a tab active once it is opened via an event handler?
Once you have switched tabs, you may want to set the focus on a widget within that tab. I find that using pubsub to send events is probably the cleanest way to communicate between classes. I have a couple of tutorials on that subject:
For early versions of wxPython 2.8 - http://www.blog.pythonlibrary.org/2010/06/27/wxpython-and-pubsub-a-simple-tutorial/
For later versions of wxPython 2.8 and all of 2.9 - http://www.blog.pythonlibrary.org/2013/09/05/wxpython-2-9-and-the-newer-pubsub-api-a-simple-tutorial/
This should help you, just click "GoTo Blue Panel" button.
import wx
import wx.lib
import wx.lib.flatnotebook as FNB
class MyFlatNotebook(FNB.FlatNotebook):
def __init__(self, parent):
mystyle = FNB.FNB_DROPDOWN_TABS_LIST|\
FNB.FNB_FF2|\
FNB.FNB_SMART_TABS|\
FNB.FNB_X_ON_TAB
super(MyFlatNotebook, self).__init__(parent, style=mystyle)
# Attributes
self.textctrl = wx.TextCtrl(self, value="edit me", style=wx.TE_MULTILINE)
self.blue = wx.Panel(self)
self.blue.SetBackgroundColour(wx.BLUE)
# Setup
self.AddPage(self.textctrl, "Text Editor")
self.AddPage(self.blue, "Blue Panel")
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
# Make some buttons
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
button = wx.Button(self, wx.ID_OK, "GoTo Blue Panel")
self.Bind(wx.EVT_BUTTON, self.OnButton, button)
hbox.Add(button, 0, wx.ALL, 5)
self.nb = MyFlatNotebook(self)
vbox.Add(hbox, 0, wx.EXPAND)
vbox.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(vbox)
def OnButton(self, event):
self.nb.SetSelection(1)
if __name__=='__main__':
app = wx.App(False)
frame = MyFrame(None, -1, "NoteTest")
frame.Show()
app.MainLoop()
complete newbie here to Python as I only started learning the language a few days ago (with no programming experience beforehand).
I'm basically bashing my skull against the desk here, trying to create one menu with a button that will lead you to another menu, which is supposed to replace/hide/destroy the previous menu (either works, so long as the process can be reversed).
What I've come up with so far:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.Centre()
self.main_menu = MainMenu(self)
self.intro_screen = IntroScreen(self)
self.intro_screen.Hide()
class MainMenu(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent)
self.main_menu = MainMenu
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
nextscreen = wx.Button(panel, label='Next Screen', size=(150,30))
nextscreen.Bind(wx.EVT_BUTTON, self.NextScreen)
sizer.Add(nextscreen, 0, wx.CENTER|wx.ALL, 5)
self.Show()
self.Centre()
def NextScreen(self, event):
self.main_menu.Hide(self)
self.intro_screen.Show()
class IntroScreen(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent)
self.intro_screen = IntroScreen
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
gobackscreen = wx.Button(panel, label='Go Back a Screen', size=(150,30))
gobackscreen.Bind(wx.EVT_BUTTON, self.GoBackScreen)
sizer.Add(gobackscreen, 0, wx.CENTER|wx.ALL, 5)
self.Show()
self.Centre()
def GoBackScreen(self, event):
self.intro_screen.Hide()
self.main_menu.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
#frame.Show()
app.MainLoop()
From what I can tell, the NextScreen button does not see the intro_screen class, and is therefore unable to show it. But I am clueless as to how to fix this.
Indeed, I have absolutely no idea if this is on the right way to do it. Any help is greatly appreciated
Using Python 2.7
intro_screen is an attribute of MainFrame instances; not of MainMenu instances.
Your MainMenu.__init__() method is passed in a MainFrame instance as parent. I am not certain if self.parent is set by the line wx.Frame.__init__(self, parent=parent), but if it is not, do add self.parent = parent in MainMenu.__init__(.
You can then refer to self.parent on MainMenu instances, and the following should work:
self.parent.intro_screen.Show()
I am not sure why you are setting the current class as an instance attribute:
self.main_menu = MainMenu
and
self.intro_screen = IntroScreen
Instead of self.main_menu.Hide(self) you can just call self.Hide(), the reference to the class is not needed.
So I have the following code set up to demonstrate the problem:
import wx
class testPanel(wx.Panel):
def __init__(self, parent):
super(testPanel, self).__init__(parent)
self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
self.txt = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.hsizer.Add(self.txt, proportion=1,
flag=wx.EXPAND)
self.SetSizer(self.hsizer)
self.hsizer.Fit(self)
self.Show(True)
class testFrame(wx.Frame):
def __init__(self, parent):
super(testFrame, self).__init__(parent)
self.mainPanel = wx.Panel(self)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.txt1 = testPanel(self)
self.txt2 = testPanel(self)
self.vsizer.Add(self.txt1, proportion=1,
flag=wx.EXPAND)
self.vsizer.Add(self.txt2, proportion=1,
flag=wx.EXPAND)
self.mainPanel.SetSizer(self.vsizer)
self.vsizer.Fit(self.mainPanel)
self.mainSizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(self.mainPanel, proportion=1,
flag=wx.EXPAND)
self.SetSizer(self.mainSizer)
self.mainSizer.Fit(self)
self.Show(True)
app = wx.PySimpleApp()
frame = testFrame(None)
frame.Show(True)
app.MainLoop()
When this is run, everything displays as expected, but the two wx.TextCtrls won't receive focus ever. This isn't the case when the extra layer of panel is removed, but I can't avoid having that extra panel.
Use:
self.txt1 = testPanel(self.mainPanel)
self.txt2 = testPanel(self.mainPanel)
and they will get focus.
In wxPython things go better when you design a straight line of inheritance, with branches but without crossovers. As your testPanels are in a sizer that belongs to mainPanel, the natural parent is not the Frame (self) but mainPanel,
This is a continuation from this question:
wxPython: Can a wx.PyControl contain a wx.Sizer?
The main topic here is using a wx.Sizer inside a wx.PyControl. I had problems Fit()ting my CustomWidget around its child widgets. That problem was solved by calling Layout() after Fit().
However, as far as I have experienced, the solution only works when the CustomWidget is a direct child of a wx.Frame. It breaks down when it becomes a child of a wx.Panel.
EDIT: Using the code below, the CustomWidget doesn't resize correctly to fit its children. I observed that this only happens when the CustomWidget (as a subclass of wx.PyControl) is a child of a wx.Panel; otherwise, if it is a direct child of a wx.Frame, it Fit()s perfectly.
Here is the code:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None)
panel = Panel(parent=self)
custom = CustomWidget(parent=panel)
self.Show()
class Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
self.SetSize(parent.GetClientSize())
class CustomWidget(wx.PyControl):
def __init__(self, parent):
wx.PyControl.__init__(self, parent=parent)
# Create the sizer and make it work for the CustomWidget
sizer = wx.GridBagSizer()
self.SetSizer(sizer)
# Create the CustomWidget's children
text = wx.TextCtrl(parent=self)
spin = wx.SpinButton(parent=self, style=wx.SP_VERTICAL)
# Add the children to the sizer
sizer.Add(text, pos=(0, 0), flag=wx.ALIGN_CENTER)
sizer.Add(spin, pos=(0, 1), flag=wx.ALIGN_CENTER)
# Make sure that CustomWidget will auto-Layout() upon resize
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Fit()
def OnSize(self, event):
self.Layout()
app = wx.App(False)
frame = Frame()
app.MainLoop()
.SetSizerAndFit(sizer) does the job. I'm not sure why a .SetSizer(sizer) then a .Fit() won't work. Any ideas?
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None)
panel = Panel(parent=self)
custom = CustomWidget(parent=panel)
self.Show()
class Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
self.SetSize(parent.GetClientSize())
class CustomWidget(wx.PyControl):
def __init__(self, parent):
wx.PyControl.__init__(self, parent=parent)
# Create the sizer and make it work for the CustomWidget
sizer = wx.GridBagSizer()
self.SetSizer(sizer)
# Create the CustomWidget's children
text = wx.TextCtrl(parent=self)
spin = wx.SpinButton(parent=self, style=wx.SP_VERTICAL)
# Add the children to the sizer
sizer.Add(text, pos=(0, 0), flag=wx.ALIGN_CENTER)
sizer.Add(spin, pos=(0, 1), flag=wx.ALIGN_CENTER)
# Set sizer and fit, then layout
self.SetSizerAndFit(sizer)
self.Layout()
# ------------------------------------------------------------
# # Make sure that CustomWidget will auto-Layout() upon resize
# self.Bind(wx.EVT_SIZE, self.OnSize)
# self.Fit()
#
#def OnSize(self, event):
# self.Layout()
# ------------------------------------------------------------
app = wx.App(False)
frame = Frame()
app.MainLoop()