Embed Separate WX GUIs into Notebook Tabs - python

I basically have 3 separate wxPython GUIs that I would like to combine into a single application, giving each GUI its own notebook tab. I'm not really sure how to do this though and am looking for some advice. I appreciate any comments or thoughts I can get.
My idea is that it should look something like this:
import wx
import OtherFrame
class PageOne(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "This is a PageOne object", (20,20))
panel=OtherFrame.Panel(parent)
box = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(self,box)
panel.Layout(self, parent)
class PageTwo(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "This is a PageTwo object", (40,40))
class PageThree(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "This is a PageThree object", (60,60))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Simple Notebook Example")
p = wx.Panel(self)
nb = wx.Notebook(p)
page1 = PageOne(nb)
page2 = PageTwo(nb)
page3 = PageThree(nb)
nb.AddPage(page1, "Page 1")
nb.AddPage(page2, "Page 2")
nb.AddPage(page3, "Page 3")
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App()
MainFrame().Show()
app.MainLoop()
But this gives a few errors and crashes. Without the sizers under PageOne, it just puts a gray square in the top right corner, with them, it crashes.
Thanks in advance!

I assume each of your frame's have panels in them with the code you want. Just take the code for the widgets from each of those frames and put those in each of the Page classes. You'll probably have to change the parents of the widgets to "self" instead of "panel" or whatever you call them.
Then you won't be opening a frame in each of the page code base. You don't want to open a frame there. You want the page to be the parent of the widgets, not open something else up.

Thanks to your help, I got this to work for me. Since each of my other wx App had Panel classes with all the widgets I wanted, I didn't need to create classes for each page. Here's the code:
import wx
import OtherFrame1
import OtherFrame2
import OtherFrame3
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Simple Notebook Example")
p = wx.Panel(self)
nb = wx.Notebook(p)
page1 = OtherFrame1.Panel(nb)
page2 = OtherFrame2.Panel(nb)
page3 = OtherFrame3.Panel(nb)
nb.AddPage(page1, "Page 1")
nb.AddPage(page2, "Page 2")
nb.AddPage(page3, "Page 3")
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App(redirect=False)
MainFrame().Show()
app.MainLoop()
Thanks for the help!

Related

How to switch panels in python using wxPython

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!

wxPython append and pop message box inside panel serving like a notification center

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.

wxpython set sizer background

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

wxNotebook and wxBoxSizer behaviour

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()

Python attempting to create a simple gui, getting "AttributeError: 'MainMenu' object has no attribute 'intro_screen'"

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.

Categories

Resources