I'm building an app that should contain a resizable sidebar area on the left (starting at 200px width) and a main area on the right that should expand to fill the remaining area. I've gone with the SplitterWindow method as it's the only one I think offers manual resize on panels. I'm experiencing some black borders around the individual panels AND the entire frame that I can't seem to get rid of. The borders on the individual panels disappear when I comment out the make_canvas calls, but the border on the frame is still there. Strangely enough if I resize the entire app window the borders flicker on and off. I suspect it's not actually a border issue but a BoxSizing issue, but I'm not sure how to take care of it.
Here's the code:
#! /usr/bin/python
# -*- coding: utf-8 -*-
import wx, random
class TDTaskBarIcon(wx.TaskBarIcon):
def __init__(self, parent):
wx.TaskBarIcon.__init__(self)
self.parentApp = parent
self.icon = wx.Icon("images/icon_glasses.png", wx.BITMAP_TYPE_PNG)
self.SetIconImage()
def SetIconImage(self):
self.SetIcon(self.icon)
class Sidebar(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# tiled background
self.bgimage = wx.Bitmap('images/noise.png')
wx.FutureCall(50, self.make_canvas)
wx.EVT_SIZE(self, self.make_canvas)
self.SetBackgroundColour((229,226,218))
def make_canvas(self, event=None):
dc = wx.ClientDC(self)
brush_bmp = wx.BrushFromBitmap(self.bgimage)
dc.SetBrush(brush_bmp)
w, h = self.GetClientSize()
dc.DrawRectangle(0, 0, w, h)
class Main(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# tiled background
self.bgimage = wx.Bitmap('images/noise.png')
wx.FutureCall(50, self.make_canvas)
wx.EVT_SIZE(self, self.make_canvas)
self.SetBackgroundColour((229,226,218))
self.SetBackgroundColour('WHITE')
def make_canvas(self, event=None):
dc = wx.ClientDC(self)
brush_bmp = wx.BrushFromBitmap(self.bgimage)
dc.SetBrush(brush_bmp)
w, h = self.GetClientSize()
dc.DrawRectangle(0, 0, w, h)
# Create Tapedeck class
class Tapedeck(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.tbicon = TDTaskBarIcon(self)
self.tbicon.Bind(wx.EVT_MENU, self.OnQuit, id=wx.ID_EXIT)
splitter = wx.SplitterWindow(self)
self.Sidebar = Sidebar(splitter)
self.Main = Main(splitter)
splitter.SplitVertically(self.Sidebar, self.Main)
splitter.SetSashPosition(200)
splitter.SetMinimumPaneSize(200)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(splitter, 1, wx.EXPAND)
self.SetSizerAndFit(sizer)
self.SetAutoLayout(True)
self.InitUI()
self.SetSize((800, 600))
self.SetTitle('Tapedeck')
self.Center()
self.Show(True)
def InitUI(self):
panel = wx.Panel(self)
# font styles
header = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD, False, u'Helvetica')
# create a menubar at the top of the user frame
menuBar = wx.MenuBar()
# create menus
fileMenu = wx.Menu()
helpMenu = wx.Menu()
# export
export = fileMenu.Append(wx.NewId(), "&Export", "Export Playlist",
wx.ITEM_NORMAL)
export.SetBitmap(wx.Bitmap('images/men_playlist.png'))
fileMenu.AppendSeparator()
# quit
quit = fileMenu.Append(wx.NewId(), "&Quit\tCtrl+Q", "Quit the program",
wx.ITEM_NORMAL)
quit.SetBitmap(wx.Bitmap('images/men_quit.png'))
self.Bind(wx.EVT_MENU, self.OnQuit, quit)
# put the file menu on the menubar
menuBar.Append(fileMenu, "&File")
# about tapedeck
about = helpMenu.Append(wx.NewId(), "&About TapeDeck",
"About TapeDeck", wx.ITEM_NORMAL)
about.SetBitmap(wx.Bitmap('images/men_skull.png'))
self.Bind(wx.EVT_MENU, self.OnAbout, about)
# put the help menu on the menubar
menuBar.Append(helpMenu, "&Help")
# set menu bar
self.SetMenuBar(menuBar)
# create a status bar at the bottom of the frame
self.CreateStatusBar()
def OnQuit(self, e):
self.tbicon.RemoveIcon()
self.tbicon.Destroy()
self.Close()
def OnAbout(self, e):
self.SetStatusText("Here's your help!")
# Run the application
def main():
deck = wx.App()
Tapedeck(None)
deck.MainLoop()
if __name__ == '__main__':
main()
And screenshots:
Before resize (source):
After resize (source):
Suggestions?
You are drawing those lines with the call to DrawRectangle.
If you want to eliminate the lines and still draw the rectangle you can do:
dc.SetPen(wx.Pen("WHITE",1))
dc.DrawRectangle(0, 0, w, h)
in the two make_canvas methods. It works in windows.
Related
I am trying to create a wxPython frame that is just the size of the toolbar I created.
I have successfully created the wx.Frame and empty toolbar but the window is far too big, how do I make it just fit the toolbar.
Code I tried:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
vbox = wx.BoxSizer(wx.VERTICAL)
toolbar1 = wx.ToolBar(self)
toolbar1.SetToolBitmapSize(wx.Size(64, 64))
toolbar1.Realize()
vbox.Add(toolbar1, 0, wx.EXPAND)
self.SetSizer(vbox)
self.SetTitle('Toolbars')
self.Centre()
vbox.Fit(self)
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
Is it not possible to set the size of frame to size of tool bar.
Something like.
self.size = toolbar1.size
I'm would assume that the reason that the toolbar size on linux/mac is (0, 0) is b/c there are no tools attached to it. To workaround that you can set a minimum size to the toolbar and then calculate the frame size by adding the frame caption and borders to the toolbar size.
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
vbox = wx.BoxSizer(wx.VERTICAL)
toolbar1 = wx.ToolBar(self)
toolbar1.SetBackgroundColour(wx.RED)
toolbar1.SetToolBitmapSize(wx.Size(64, 64))
# prevent the sizer from setting the size to (0, 0) when there are no tools attached
toolbar1.SetMinSize((64, 64))
toolbar1.Realize()
vbox.Add(toolbar1, 0, wx.EXPAND)
self.SetSizer(vbox)
self.SetTitle('Toolbars')
self.Centre()
vbox.Fit(self)
self.Layout()
# calculate the frame caption height
caption_height = wx.SystemSettings.GetMetric(wx.SYS_CAPTION_Y, self)
# *2 for the left and right border
border_width = wx.SystemSettings.GetMetric(wx.SYS_BORDER_X, self) * 2
# get the size of the toolbar
sx, sy = toolbar1.GetSize()
# set the frame size by adding the toolbar size to the border/caption size
self.SetSize((sx + border_width, sy + caption_height))
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
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'm trying to figure out how to align a ListBox properly. As soon as i insert the lines of ListBox, the layout transforms into a mess.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
oplist=[]
with open("options.txt","r") as f:
for line in f:
oplist.append(line.rstrip('\n'))
print(oplist)
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title = title, size=(200,300))
self.InitUI()
self.Centre()
self.Show()
def InitUI(self):
p = wx.Panel(self)
vbox= wx.BoxSizer(wx.VERTICAL)
self.l1 = wx.StaticText(p, label="Enter number", style=wx.ALIGN_CENTRE)
vbox.Add(self.l1, -1, wx.ALIGN_CENTER_HORIZONTAL, 200)
self.b1 = wx.Button(p, label="Buton 1")
vbox.Add(self.b1, -1, wx.ALIGN_CENTER_HORIZONTAL,100)
self.flistbox= wx.ListBox(self,choices=oplist, size=(100,100), name="Field", wx.ALIGN_CENTER_HORIZONTAL)
vbox.Add(self.flistbox, -1, wx.CENTER, 10)
p.SetSizer(vbox)
app = wx.App()
Example(None, title="BoxSizer")
app.MainLoop()
Here the outputs with and without:
The listbox is being parented to the frame by using self.
self.flistbox= wx.ListBox(
self,choices=oplist, size=(100,100), name="Field", wx.ALIGN_CENTER_HORIZONTAL)
It should be parented to the panel by using p like the other controls.
self.flistbox= wx.ListBox(
p,choices=oplist, size=(100,100), name="Field", wx.ALIGN_CENTER_HORIZONTAL)
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()
I would like to be able to display Notebook and a TxtCtrl wx widgets in a single frame. Below is an example adapted from the wxpython wiki; is it possible to change their layout (maybe with something like wx.SplitterWindow) to display the text box below the Notebook in the same frame?
import wx
import wx.lib.sheet as sheet
class MySheet(sheet.CSheet):
def __init__(self, parent):
sheet.CSheet.__init__(self, parent)
self.SetLabelBackgroundColour('#CCFF66')
self.SetNumberRows(50)
self.SetNumberCols(50)
class Notebook(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(600, 600))
menubar = wx.MenuBar()
file = wx.Menu()
file.Append(101, 'Quit', '' )
menubar.Append(file, "&File")
self.SetMenuBar(menubar)
wx.EVT_MENU(self, 101, self.OnQuit)
nb = wx.Notebook(self, -1, style=wx.NB_BOTTOM)
self.sheet1 = MySheet(nb)
self.sheet2 = MySheet(nb)
self.sheet3 = MySheet(nb)
nb.AddPage(self.sheet1, "Sheet1")
nb.AddPage(self.sheet2, "Sheet2")
nb.AddPage(self.sheet3, "Sheet3")
self.sheet1.SetFocus()
self.StatusBar()
def StatusBar(self):
self.statusbar = self.CreateStatusBar()
def OnQuit(self, event):
self.Close()
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(450, 400))
self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE)
self.Center()
class MyApp(wx.App):
def OnInit(self):
frame = Notebook(None, -1, 'notebook.py')
frame.Show(True)
frame.Center()
frame2 = MyFrame(None, -1, '')
frame2.Show(True)
self.SetTopWindow(frame2)
return True
app = MyApp(0)
app.MainLoop()
Making two widgets appear on the same frame is easy, actually. You should use sizers to accomplish this.
In your example, you can change your Notebook class implementation to something like this:
class Notebook(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(600, 600))
menubar = wx.MenuBar()
file = wx.Menu()
file.Append(101, 'Quit', '' )
menubar.Append(file, "&File")
self.SetMenuBar(menubar)
wx.EVT_MENU(self, 101, self.OnQuit)
nb = wx.Notebook(self, -1, style=wx.NB_BOTTOM)
self.sheet1 = MySheet(nb)
self.sheet2 = MySheet(nb)
self.sheet3 = MySheet(nb)
nb.AddPage(self.sheet1, "Sheet1")
nb.AddPage(self.sheet2, "Sheet2")
nb.AddPage(self.sheet3, "Sheet3")
self.sheet1.SetFocus()
self.StatusBar()
# new code begins here:
# add your text ctrl:
self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE)
# create a new sizer for both controls:
sizer = wx.BoxSizer(wx.VERTICAL)
# add notebook first, with size factor 2:
sizer.Add(nb, 2)
# then text, size factor 1, maximized
sizer.Add(self.text, 1, wx.EXPAND)
# assign the sizer to Frame:
self.SetSizerAndFit(sizer)
Only the __init__ method is changed. Note that you can manipulate the proportions between the notebook and text control by changing the second argument of the Add method.
You can learn more about sizers from the official Sizer overview article.
You can use a splitter, yes.
Also, it makes sense to create a Panel, place your widgets in it (with sizers), and add this panel to the Frame.