Buttons on a gradient bar to access different panels - python

The following code makes a window with a grey gradient bar.
import wx
class GradientFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Center()
self.Show()
def OnSize(self, event):
event.Skip()
self.Refresh()
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self)
rect = self.GetClientRect()
dc.SetBackground(wx.Brush("white"))
dc.Clear()
rect.SetHeight(30)
dc.GradientFillLinear(rect, '#fbfbfb', '#efefef', wx.SOUTH)
rect.SetTop(30)
rect.SetHeight(2)
dc.GradientFillLinear(rect, '#dbdbdb', '#c1c1c1', wx.SOUTH)
app = wx.App(0)
frame = GradientFrame(None, 'Test')
app.MainLoop()
I would like to add toogle buttons like the following screenshot, that allows to access to different pages / panels of the GUI (each of them containing their own widgets, etc.)
What is the good framework for that : should these buttons be created manually in OnPaint (this would be very 'low-level') or somewhere else? Is there a ready-to-use way to use buttons linked to different pages ?

There is no good framework for creating custom widgets. However, there are some good recipes out there:
https://stackoverflow.com/questions/1351448/how-to-make-custom-buttons-in-wx
http://wiki.wxpython.org/CreatingCustomControls
Those two links should get you started. You can also take a look at the source for GenericButtons, AquaButton or PlateButton for additional ideas.
Alternatively, you could also just create a panel that's a specific size and put some of the custom buttons or just regular buttons on it instead.
Here's an example of how to use PlateButtons that should get you started:
import wx
import wx.lib.platebtn as platebtn
class GradientFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title)
self.panel = wx.Panel(self)
self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
self.panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
self.panel.Bind(wx.EVT_SIZE, self.OnSize)
# add plate buttons
top_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
labels = ["Morceaux", "Albums", "Artistes", "Genres"]
style = platebtn.PB_STYLE_GRADIENT
for label in labels:
btn = platebtn.PlateButton(self.panel, label=label, style=style)
btn.SetPressColor(wx.Colour(208,208,208))
btn_sizer.Add(btn, 0, wx.RIGHT|wx.LEFT|wx.CENTER, 5)
top_sizer.Add(btn_sizer, 0, wx.ALL|wx.CENTER, 5)
top_sizer.Add((1,1), 1, wx.EXPAND)
self.panel.SetSizer(top_sizer)
self.Center()
self.Show()
def OnSize(self, event):
event.Skip()
self.Refresh()
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self.panel)
rect = self.panel.GetClientRect()
dc.SetBackground(wx.Brush("white"))
dc.Clear()
rect.SetHeight(30)
dc.GradientFillLinear(rect, '#fbfbfb', '#efefef', wx.SOUTH)
rect.SetTop(30)
rect.SetHeight(2)
dc.GradientFillLinear(rect, '#dbdbdb', '#c1c1c1', wx.SOUTH)
app = wx.App(0)
frame = GradientFrame(None, 'Test')
app.MainLoop()

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.

align wx.DrawCircle to the center of the frame

Hi there I wanted to draw a circle at the center of the Frame.
Is there anything like wx.Align_Center I can use with wx.DrawCircle Below is my code.
#!/usr/bin/python
# points.py
import wx
import random
class Points(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(250, 150))
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Centre()
self.Show(True)
def OnPaint(self, event):
dc = wx.PaintDC(self)
dc.SetBrush(wx.Brush('RED'))
dc.DrawCircle(20,20)
app = wx.App()
Points(None, -1, 'Test')
app.MainLoop()
Just get the size of the frame and then divide the width and the height by 2.
import wx
import random
class Points(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(250, 150))
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Centre()
self.Show(True)
def OnPaint(self, event):
w, h = self.GetClientSize()
dc = wx.PaintDC(self)
dc.SetBrush(wx.Brush('RED'))
dc.DrawCircle(w/2,h/2, 20)
app = wx.App()
Points(None, -1, 'Test')
app.MainLoop()
That will get you pretty close. You might need to tweak the height a bit as I don't think GetSize accounts for the width of the System Menu (the top bar on all windows).

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

Placing two wx Grids side by side

I am using python 2.7 and the latest version of wxpython.
I need to place 2 grids side by side and to be able to add rows dynamically. Ideally, the sizes of grids should grow dynamically, but "big enough" size should be ok for the time being.
How should I change (and simplify) my code to make this happen?
class MatricesFrame(wx.BoxSizer):
def __init__(self, parentPanel):
super(MatricesFrame, self).__init__(wx.HORIZONTAL)
self.outputsMatrix = self.addEmptyGrid(parentPanel)
self.inputsMatrix = self.addEmptyGrid(parentPanel)
addRowsButton = wx.Button(parentPanel, -1, " Add Rows")
addRowsButton.Bind(wx.EVT_BUTTON, self.addRows)
self.Add(addRowsButton)
def initResize(self, ev = None):
self.inputsMatrix.SetSize((500, 500))
self.outputsMatrix.SetSize((500, 500))
def addEmptyGrid(self, parentPanel):
panel_ = wx.Panel(parentPanel)
sizer_ = wx.BoxSizer(wx.VERTICAL)
panel_.SetSizer(sizer_)
panel_.SetSize((500, 500))
matrix_ = wx.grid.Grid(panel_)
matrix_.SetRowLabelSize(0)
matrix_.SetColLabelSize(0)
matrix_.CreateGrid(1,1)
sizer_.Add(matrix_)
self.Add(panel_)
return matrix_
def addRows(self, ev=None):
self.inputsMatrix.AppendRows(1)
self.outputsMatrix.AppendRows(1)
class TestFrame(wx.Frame):
def __init__(self, parent):
super(TestFrame, self).__init__(parent, title='test', size=(1280, 950))
panel = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
self.matricesFrame = MatricesFrame(panel)
box.Add(self.matricesFrame)
panel.SetSizer(box)
self.matricesFrame.initResize()
self.Centre()
self.Show()
self.matricesFrame.initResize()
wx.EVT_IDLE(wx.GetApp(), wx.WakeUpIdle())
def main():
app = wx.App(False)
t= TestFrame(None)
app.MainLoop()
if __name__ == '__main__':
main()
For starters, if you convert your custom Sizer into a custom Panel I think it makes your code a lot easier to control. Take a look at what I came up with
class MatricesPanel(wx.Panel):
def __init__(self, parent):
super(MatricesPanel, self).__init__(parent)
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.outputsMatrix = self.addEmptyGrid(sizer)
self.inputsMatrix = self.addEmptyGrid(sizer)
addRowsButton = wx.Button(self, -1, " Add Rows")
addRowsButton.Bind(wx.EVT_BUTTON, self.addRows)
sizer.Add(addRowsButton)
self.SetSizer(sizer)
self.SetAutoLayout(True)
self.Layout()
def initResize(self, ev = None):
self.inputsMatrix.SetSize((500, 500))
self.outputsMatrix.SetSize((500, 500))
self.Layout()
def addEmptyGrid(self, sizer):
matrix_ = wx.grid.Grid(self)
matrix_.SetRowLabelSize(0)
matrix_.SetColLabelSize(0)
matrix_.CreateGrid(1,1)
sizer.Add(matrix_)
return matrix_
def addRows(self, ev=None):
self.inputsMatrix.AppendRows(1)
self.outputsMatrix.AppendRows(1)
self.Layout() #refresh the frame
Working with a Panel instead of a Sizer you greatly simplify your "addEmptyGrid" method as well as now you can try setting the size of your matricies using the size of the panel, not the size of the matrices themselves. Also, this allows you the flexibility to change from wx.Panel to wx.lib.scrolledpanel.ScrolledPanel if you wanted to add scroll bars (for if you add a lot of rows).
You can then init your new panel as below:
class TestFrame(wx.Frame):
def __init__(self, parent):
super(TestFrame, self).__init__(parent, title='test', size=(1280, 950))
self.matricesPanel = MatricesPanel(self)
sizer = wx.BoxSizer()
sizer.Add(self.matricesPanel, flag=wx.EXPAND)
self.SetSizer(sizer)
self.Centre()
self.Show()
self.matricesPanel.initResize()
wx.EVT_IDLE(wx.GetApp(), wx.WakeUpIdle())
Finally, so far as I can tell, columns in a Grid have a fixed width, so your matrix.SetSize((500, 500)) calls arent' doing much. If you can find a way to set the width of the column then I suggest you rewrite initResize() to set the width relative to the width of the panel.

Categories

Resources