I have a WXnotebook that has different number of tabs depending on the amount of information that the program pulls. My goal is to take a screenshot of the information displayed by each tab and store those images. Im having a problem with the program going through the tabs. I was thinking maybe something like
for i in range(numOfTabs):
self.waferTab.ChangeSelection(i)
time.sleep(3)
but this only shows me the last tab in the wxnotebook. If anybody knows anyway of getting this i'd really appreciate it.
EDIT
so I tried the following like suggested below but the GUI shows up but when it shows up It looks like it already iterated through the whole loop and displays the selection is the last tab I still cant see the screen actually going through the tabs
for i in range(numOfTabs):
self.waferTab.SetSelection(i)
Refresh
wx.SafeYield()
time.sleep(10)
I don't know why you would want to do this as it seems like a confusing interface for a user to use, but here's an example using a wx.Timer:
import random
import wx
class TabPanel(wx.Panel):
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent)
colors = ["red", "blue", "gray", "yellow", "green"]
self.SetBackgroundColour(random.choice(colors))
btn = wx.Button(self, label="Press Me")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 10)
self.SetSizer(sizer)
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Notebook Tutorial",
size=(600,400)
)
panel = wx.Panel(self)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.change_tabs, self.timer)
self.timer.Start(1000)
self.notebook = wx.Notebook(panel)
tabOne = TabPanel(self.notebook)
self.notebook.AddPage(tabOne, "Tab 1")
tabTwo = TabPanel(self.notebook)
self.notebook.AddPage(tabTwo, "Tab 2")
tabThree = TabPanel(self.notebook)
self.notebook.AddPage(tabThree, 'Tab 3')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
def change_tabs(self, event):
current_selection = self.notebook.GetSelection()
print(current_selection)
pages = self.notebook.GetPageCount()
if current_selection + 1 == pages:
self.notebook.ChangeSelection(0)
else:
self.notebook.ChangeSelection(current_selection + 1)
if __name__ == "__main__":
app = wx.App(True)
frame = DemoFrame()
app.MainLoop()
You could also use a Thread and use something like wx.CallAfter to update your UI, but I think a timer makes more sense in this case.
Related
I'm trying to create an App which allows the user to switch the information show using buttons. The basic idea of the code is that the user sees buttons on the left side of the screen and when the user presses "button 1", the code shows Panel1. I've made 2 buttons so far and the code for 2 panels is written as well but i can't figure out how to update my MainFrame so it show a different panel when one of the buttons is pressed.
Code:
import wx
TabNumber = 1
class ButtonPanel(wx.Panel):
def __init__(self, parent):
global TabNumber
super(ButtonPanel, self).__init__(parent, -1)
self.Tab1Button = wx.Button(self, label="TAB 1")
self.Tab1Button.Bind(wx.EVT_BUTTON, self.SwitchTab(1))
self.Tab2Button = wx.Button(self, label="TAB 2")
self.Tab2Button.Bind(wx.EVT_BUTTON, self.SwitchTab(2))
self.Sizer = wx.BoxSizer(wx.VERTICAL)
self.Sizer.Add(self.Tab1Button, wx.CENTER,0)
self.Sizer.Add(self.Tab2Button, wx.CENTER, 0)
self.SetSizer(self.Sizer)
def SwitchTab(self, tab):
def OnClick(event):
print(f"Switch to tab {tab} started")
TabNumber = tab
print(TabNumber)
return OnClick
class Panel1(wx.Panel):
def __init__(self, parent):
super(Panel1, self).__init__(parent, -1)
self.panel = wx.Panel(self)
self.text = wx.StaticText(self.panel, label="1")
class Panel2(wx.Panel):
def __init__(self, parent):
super(Panel2, self).__init__(parent, -1)
self.panel = wx.Panel(self)
self.text = wx.StaticText(self.panel, label="2")
class MainFrame(wx.Frame):
def __init__(self):
super(MainFrame, self).__init__(None, -1, "Test Application")
self.Panels = {
"Panel1": Panel1(self),
"Panel2": Panel2(self)
}
self.MySizer = wx.BoxSizer(wx.HORIZONTAL)
self.tabpanel = ButtonPanel(self)
self.MySizer.Add(self.tabpanel,wx.CENTER,0)
self.InfoPanel = self.Panels["Panel"+str(TabNumber)]
self.MySizer.Add(self.InfoPanel, wx.CENTER,0)
self.SetSizer(self.MySizer)
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
I was also wondering how I can adjust the ratio for the space that is given to my ButtonPanel and my InfoPanel.
As far as I can see, you are trying to do something that works like a Wizard... On the one hand, you can use wx.adv.Wizard. On the other hand, you can look at this tutorial that does something very similar and adapt it to what you need:
WXPython: How to create a generic wizard
Good luck!
I would like to make a wxpython program that has a notification center just like the one on windows or mac. Whenever I have a message, the message will show inside the the notification panel, and the user could close that message afterwards.
I have a sample code for illustration as follows:
import wx
import wx.lib.scrolledpanel as scrolled
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
topPanel = wx.Panel(self)
panel1 = wx.Panel(topPanel, -1)
button1 = wx.Button(panel1, -1, label="generate message")
self.panel2 = scrolled.ScrolledPanel(
topPanel, -1, style=wx.SIMPLE_BORDER)
self.panel2.SetAutoLayout(1)
self.panel2.SetupScrolling()
button1.Bind(wx.EVT_BUTTON, self.onAdd)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(panel1,-1,wx.EXPAND|wx.ALL,border=10)
sizer.Add(self.panel2,-1,wx.EXPAND|wx.ALL,border=10)
self.sizer2 = wx.BoxSizer(wx.VERTICAL)
topPanel.SetSizer(sizer)
self.panel2.SetSizer(self.sizer2)
def onAdd(self, event):
new_text = wx.TextCtrl(self.panel2, value="New Message")
self.sizer2.Add(new_text,0,wx.EXPAND|wx.ALL,border=1)
self.panel2.Layout()
self.panel2.SetupScrolling()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'frame')
frame.Show(True)
return True
app = MyApp(0)
app.MainLoop()
In the above I code, the right panel (i.e. panel2) serves as a notification center that all the messages should shown inside it. On the left panel (i.e. panel1) I have a button to generate message just to mimic the notification behavior. Ideally the message on the right panel should be a message box that you could close (maybe a frame? Or a MessageDialog?)
Any hint or advice is much appreciated, and an example would be the best!
Thanks!
Finally figured out myself, it was easier than I initially thought.
Here is the code:
import wx
import wx.lib.scrolledpanel as scrolled
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.number_of_panels = 0
topPanel = wx.Panel(self)
panel1 = wx.Panel(topPanel, -1)
button1 = wx.Button(panel1, -1, label="generate message")
self.panel2 = scrolled.ScrolledPanel(
topPanel, -1, style=wx.SIMPLE_BORDER)
self.panel2.SetAutoLayout(1)
self.panel2.SetupScrolling()
button1.Bind(wx.EVT_BUTTON, self.onAdd)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(panel1,0,wx.EXPAND|wx.ALL,border=5)
sizer.Add(self.panel2,1,wx.EXPAND|wx.ALL,border=5)
self.sizer2 = wx.BoxSizer(wx.VERTICAL)
topPanel.SetSizer(sizer)
self.panel2.SetSizer(self.sizer2)
def onAdd(self, event):
self.number_of_panels += 1
panel_label = "Panel %s" % self.number_of_panels
panel_name = "panel%s" % self.number_of_panels
new_panel = wx.Panel(self.panel2, name=panel_name, style=wx.SIMPLE_BORDER)
self.closeButton = wx.Button(new_panel, label='Close %s' % self.number_of_panels)
self.closeButton.panel_number = self.number_of_panels
self.closeButton.Bind(wx.EVT_BUTTON, self.OnClose)
self.sizer2.Add(new_panel,0,wx.EXPAND|wx.ALL,border=1)
self.panel2.Layout()
self.panel2.SetupScrolling()
def OnClose(self, e):
if self.panel2.GetChildren():
e.GetEventObject().GetParent().Destroy()
self.number_of_panels -= 1
self.panel2.Layout() # Reset layout after destroy the panel
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'frame')
frame.Show(True)
return True
app = MyApp(0)
app.MainLoop()
Basically I can destroy the newly created panel. I just need to know which panel it is when I click the close button. This should work very similar to the Notification Center.
I want my wxPython application to trigger an event when a text entry widget loses focus. I've followed the tutorial here, which describes using wx.EVT_KILL_FOCUS. However, I'm getting unexpected behavior.
The following code works fine:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1a")
panel = wx.Panel(self, wx.ID_ANY)
txt = wx.TextCtrl(panel, wx.ID_ANY, "")
txt.Bind(wx.EVT_KILL_FOCUS, self.onTextKillFocus)
"""
This next line seems to be important for working correctly,
but I don't understand why:
"""
txt.Bind(wx.EVT_SET_FOCUS, self.onTextFocus)
btn = wx.Button(panel, wx.ID_ANY, "Test")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.ALL, 5)
sizer.Add(btn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def onTextFocus(self, event):
print "text received focus!"
def onTextKillFocus(self, event):
print "text lost focus!"
if __name__ == '__main__':
app = wx.App()
frame = MyForm().Show()
app.MainLoop()
When I tab back and forth from the text control to the button, or click in or out of the text control, I get the focus messages I expect.
However, when I make the following (reasonable?) edit, things go south:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1a")
panel = wx.Panel(self, wx.ID_ANY)
txt = wx.TextCtrl(panel, wx.ID_ANY, "")
txt.Bind(wx.EVT_KILL_FOCUS, self.onTextKillFocus)
"""
This next line seems to be important for working correctly,
but I don't understand why:
"""
## txt.Bind(wx.EVT_SET_FOCUS, self.onTextFocus)
btn = wx.Button(panel, wx.ID_ANY, "Test")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.ALL, 5)
sizer.Add(btn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def onTextFocus(self, event):
print "text received focus!"
def onTextKillFocus(self, event):
print "text lost focus!"
if __name__ == '__main__':
app = wx.App()
frame = MyForm().Show()
app.MainLoop()
The unexpected behavior is that when I try to change focus from the text box to anything else (tabbing or mouse clicks), the 'text lost focus!' message prints once, and never again, and I can no longer edit the contents of the text control.
Is this expected behavior? If not, what am I doing wrong?
Python version 2.7, wxPython version 3.0.0.0, Windows 7 64-bit
As explained in wxEvent::Skip() documentation, you should almost invariably call it for non-command events as you don't want to prevent the default handling from taking place.
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()
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()