I'm creating a small wxPython utility for the first time, and I'm stuck on a problem.
I would like to add components to an already created frame. To do this, I am destroying the frame's old panel, and creating a new panel with all new components.
1: Is there a better way of dynamically adding content to a panel?
2: Why, in the following example, do I get a a strange redraw error in which in the panel is drawn only in the top left hand corner, and when resized, the panel is drawn correctly?
(WinXP, Python 2.5, latest wxPython)
Thank you for the help!
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'TimeTablr')
#Variables
self.iCalFiles = ['Empty', 'Empty', 'Empty']
self.panel = wx.Panel(self, -1)
self.layoutElements()
def layoutElements(self):
self.panel.Destroy()
self.panel = wx.Panel(self, -1)
#Buttons
self.getFilesButton = wx.Button(self.panel, 1, 'Get Files')
self.calculateButton = wx.Button(self.panel, 2, 'Calculate')
self.quitButton = wx.Button(self.panel, 3, 'Quit Application')
#Binds
self.Bind(wx.EVT_BUTTON, self.Quit, id=3)
self.Bind(wx.EVT_BUTTON, self.getFiles, id=1)
#Layout Managers
vbox = wx.BoxSizer(wx.VERTICAL)
#Panel Contents
self.ctrlsToDescribe = []
self.fileNames = []
for iCalFile in self.iCalFiles:
self.ctrlsToDescribe.append(wx.TextCtrl(self.panel, -1))
self.fileNames.append(wx.StaticText(self.panel, -1, iCalFile))
#Add Components to Layout Managers
for i in range(0, len(self.ctrlsToDescribe)):
hboxtemp = wx.BoxSizer(wx.HORIZONTAL)
hboxtemp.AddStretchSpacer()
hboxtemp.Add(self.fileNames[i], 1, wx.EXPAND)
hboxtemp.AddStretchSpacer()
hboxtemp.Add(self.ctrlsToDescribe[i], 2, wx.EXPAND)
hboxtemp.AddStretchSpacer()
vbox.Add(hboxtemp)
finalHBox = wx.BoxSizer(wx.HORIZONTAL)
finalHBox.Add(self.getFilesButton)
finalHBox.Add(self.calculateButton)
finalHBox.Add(self.quitButton)
vbox.Add(finalHBox)
self.panel.SetSizer(vbox)
self.Show()
def Quit(self, event):
self.Destroy()
def getFiles(self, event):
self.iCalFiles = ['Example1','Example1','Example1','Example1','Example1','Example1']
self.layoutElements()
self.Update()
app = wx.App()
MainFrame()
app.MainLoop()
del app
1) I beleive the Sizer will let you insert elements into the existing ordering of them. That would probably be a bit faster.
2) I don't see the behavior you're describing on OSX, but at a guess, try calling self.Layout() before self.Show() in layoutElements?
I had a similar problem where the panel would be squished into the upper-right corner. I solved it by calling panel.Fit().
In your example, you should call self.panel.Fit() after self.panel.SetSizer(vbox)
Related
I've just gotten into wxpython for Python 3.6 and I've hit a roadblock. I just can't work out how to make elements/widgets scale with the screen! I know you have to use sizers but that's about it, I'm still fairly new to programming so just reading the documentation didn't help. If someone could just sample some code that works I'd be very thankful as I could then read through it and work out what I was doing wrong. The code that draws out the GUI I want to scale with window size is below, the key idea is that the TextCtrl scales, other elements don't really need scaling.
def createGUI(self):
panel = wx.Panel(self)
menuBar = wx.MenuBar()
menuButton = wx.Menu()
newItem = wx.MenuItem(menuButton, wx.ID_NEW, 'New Note\tCtrl+N')
delItem = wx.MenuItem(menuButton, wx.ID_DELETE, 'Delete Note\tCtrl+Backspace')
saveItem = wx.MenuItem(menuButton, wx.ID_SAVE, 'Save\tCtrl+S')
exitItem = wx.MenuItem(menuButton, wx.ID_EXIT, 'Quit\tCtrl+Q')
menuButton.Append(newItem)
menuButton.Append(saveItem)
menuButton.Append(delItem)
menuButton.Append(exitItem)
menuBar.Append(menuButton, 'Menu')
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.new, newItem)
self.Bind(wx.EVT_MENU, self.delete, delItem)
self.Bind(wx.EVT_MENU, self.save, saveItem)
self.Bind(wx.EVT_MENU, self.onExit, exitItem)
self.noteText = wx.TextCtrl(panel)
self.noteText.AppendText(self.notecontent)
self.Bind(wx.EVT_CLOSE, self.onExit)
self.SetTitle(f'Welcome {self.username}! You are working on {self.notepath}')
self.Centre()
self.Show(True)
To begin with it's best to equate sizers to something familiar and I usually think of storage boxes or a chest of drawers.
When we define widgets, they are all dumped into a container, the parent object, often the ubiquitous self or self.panel.
If we do not assign a size and pos to each item, it's just a jumbled mess, a pile of widgets.
The sizer, there are many types, are the virtual drawers in our chest of drawers, that herds this pile of widgets into order.
The widgets are assigned to the appropriate sizer, note sizers can go into other sizers, and eventually when everything has been assigned a place or drawer in our chest of drawers, the sizers do their magic, arranging and sizing all of widgets into a coherent screen for display or arranging the drawers contents and relative positions in the chest of drawers.
Below, I've used the simplest sizer a boxsizer.
One will arrange things vertically and the other horizontally.
The horizontal sizer is for the buttons and the vertical sizer, will be the main sizer, into which I place not only the TectCtrl but also the buttons, prearranged in their horizontal sizer.
For a better and more comprehensive description of sizers and their controls see: https://docs.wxpython.org/sizers_overview.html and for details on the actual sizers available see the detailed documenation on each.
A loose approximation of your code:
#!/usr/bin/python
import wx
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title,
size=(450, 350))
self.panel = wx.Panel(self, -1)
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.noteText = wx.TextCtrl(self.panel, -1, style=wx.TE_MULTILINE)
self.Button_close = wx.Button(self.panel, -1, label="Quit")
self.Button_1 = wx.Button(self.panel, -1, label="Btn1")
self.Button_2 = wx.Button(self.panel, -1, label="Btn2")
self.Bind(wx.EVT_CLOSE, self.onExit)
self.Button_close.Bind(wx.EVT_BUTTON, self.onExit)
self.Button_1.Bind(wx.EVT_BUTTON, self.onButton)
self.Button_2.Bind(wx.EVT_BUTTON, self.onButton)
# Place buttons within their own horizontal sizer
self.button_sizer.Add(self.Button_close,proportion=0, flag=wx.ALL, border=10)
self.button_sizer.Add(self.Button_1,proportion=0, flag=wx.ALL, border=10)
self.button_sizer.Add(self.Button_2,proportion=0, flag=wx.ALL, border=10)
# Add textctrl and the button sizer to the main sizer (vertical)
self.main_sizer.Add(self.noteText,proportion=1, flag=wx.EXPAND|wx.ALL, border=10)
self.main_sizer.Add(self.button_sizer, 0, 0, 0)
self.panel.SetSizer(self.main_sizer)
self.Show()
def onExit(self, event):
self.Destroy()
def onButton(self, event):
print("A button was pressed")
if __name__ == '__main__':
app = wx.App()
Example(None, title="Example")
app.MainLoop()
Edit:
Here's the same code without using sizers, although they are recommended.
#!/usr/bin/python
import wx
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title,
size=(450, 350))
self.panel = wx.Panel(self, -1)
self.noteText = wx.TextCtrl(self.panel, -1, pos=(10,10), size=(400,280), style=wx.TE_MULTILINE)
self.Button_close = wx.Button(self.panel, -1, label="Quit", pos=(10,290), size=(50,30))
self.Button_1 = wx.Button(self.panel, -1, label="Btn1", pos=(70,290), size=(50,30))
self.Button_2 = wx.Button(self.panel, -1, label="Btn2", pos=(130,290), size=(50,30))
self.Bind(wx.EVT_CLOSE, self.onExit)
self.Button_close.Bind(wx.EVT_BUTTON, self.onExit)
self.Button_1.Bind(wx.EVT_BUTTON, self.onButton)
self.Button_2.Bind(wx.EVT_BUTTON, self.onButton)
self.Show()
def onExit(self, event):
self.Destroy()
def onButton(self, event):
print("A button was pressed")
if __name__ == '__main__':
app = wx.App()
Example(None, title="Example")
app.MainLoop()
I'm creating a wx.agw.SuperToolTip. I'm updating the message in the tip every few seconds, and if the tip is showing when the message updates the tip is redrawn in a different position.
The new position seems to be relative to the original position's relation to the top left corner of the screen, but that could just be coincidence.
Also, if I modify wx.lib.agw.supertooltip.ToolTipWindowBase.Invalidate() by commenting out the call to self.CalculateBestSize() the problem goes away. Of course then the window won't resize, so that's no solution.
I'm using wxPython 2.8.12.1.
Here's an app that demonstrates the problem:
class MyFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title,
pos=(150, 150), size=(350, 225))
panel = wx.Panel(self)
btn = wx.Button(panel, -1, "Hover over this")
self._superTip = SuperToolTip("")
self._superTip.SetHeader("Heyo!")
self._superTip.SetTarget(btn)
self._superTip.EnableTip(True)
self._superTip.SetDrawHeaderLine(True)
self._superTip.SetDrawFooterLine(True)
self._superTip.SetStartDelay(1)
self._superTip.SetEndDelay(60)
currentFooterFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
currentFooterFont.SetPointSize(6)
currentFooterFont.SetWeight(wx.NORMAL)
self._superTip.SetFooterFont(currentFooterFont)
self._superTip.SetFooter('(Click to close)')
self._superTip.ApplyStyle("Blue Glass")
self._superTip.SetDropShadow(True)
self.ttTimer = wx.Timer(self)
self.ttText = 'What the?'
self.Bind(wx.EVT_TIMER, self.onTimer, self.ttTimer)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 10)
panel.SetSizer(sizer)
self.ttTimer.Start(2000)
panel.Layout()
def onTimer(self, evt):
self._superTip.SetMessage(self.ttText)
self.ttText += '?'
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, "STT error demo")
self.SetTopWindow(frame)
frame.Show(True)
return True
app = MyApp(redirect=True)
app.MainLoop()
Any thoughts on how I can update a visible tooltip without its location changing?
Thanks a lot.
I wanted to improve my GUI by using StaticBoxes. But since I added one I can't call my layout function without crashing python (Segementation fault; no other Error messages).
The following lines reproduce this error exactly. Did I use StaticBoxes correctly?
What do I need to get it to run properly? I use the nested Sizers frequently
so the layout looks nice ;).
import wx
class MainWindow(wx.Frame):
'''test frame'''
def __init__(self,*args,**kwargs):
'''Constructor'''
super(MainWindow,self).__init__(*args,**kwargs)
self.panel = wx.Panel(self)
self.box = wx.StaticBox(self.panel, wx.ID_ANY, u'input value1')
self.button_calc = wx.Button(self.panel, wx.ID_ANY, label=u'calc_xy')
self.Bind(wx.EVT_BUTTON, self.calculate, self.button_calc)
self._layout()
def _layout(self):
box_sizer = wx.StaticBoxSizer(self.box, wx.VERTICAL)
sizer = wx.GridBagSizer()
inside_the_box = wx.GridBagSizer()
box_sizer.Add(inside_the_box, 5, wx.ALL, 5)
sizer.Add(box_sizer, (0, 0), (2, 2), wx.EXPAND)
sizer.Add(self.button_calc, (2, 0))
self.panel.SetSizerAndFit(sizer)
def calculate(self, event):
print '5'
self._layout()
if __name__ == '__main__':
app = wx.App()
frame = MainWindow(None, -1, 'test window')
frame.Show()
app.MainLoop()
You are calling self._layout() in your __init__ method, and it works corectly for me in there.
Then when you click the button you call self._layout again and at that point you are trying to assign self.box to a new box_sizer but it is already assigned to a sizer therefore the segfault.
I am writing a text editor using wxpython. I want to display the line & column numbers based on the position of the text pointer(when changed using keyboard or mouse), in the statusbar.
I tried to do it using the below code:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_DOWN, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
self.StatusBar.SetStatusText(str(l)+","+str(c), number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()
I tried the below 2 ways to achieve that:
1.) lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
2.) l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
But, both, always give the line & column values of previous position of the pointer instead of current.
Is there a way to display the current line & column values?
I think you just need to bind to EVT_KEY_UP instead of EVT_KEY_DOWN. That way the event handler isn't called until after you have finished typing. I also updated the content written to the statusbar to make it a bit clearer which value was which:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_UP, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
stat = "col=%s, row=%s" % (l,c)
self.StatusBar.SetStatusText(stat, number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()
Help!
I am a non-GUI programmer who is trying to write a simple (!) program using wxPython.
I have read everything I can online, but my overall lack of GUI experience is presumably causing me to not see the problem.
In a nutshell, I want to have a window with a wxNotebook with several tabs. Each tab, of course, will have its own child widgets. I envision having either a wxListCtrl (as in my code) or possibly a wxGrid control, along with several buttons.
Here is my "EmployeesPanel" class. When I run this, I see a tiny square that must represent the listctrl, but for the life of me I cannot figure out how to make it look correct. Of course, it is possible that I am way off base in some other area(s) as well.
Any help as to what I am doing wrong would be greatly appreciated.
Here is the code:
import wx
import sys
employees = [('Earl Boffo', 'Software'), ('Mildred Plotka', 'Software'), ('Sugar Kane', 'QA')]
classes = [('Python'), ('Java'), ('C#')]
class EmployeesPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
grid = wx.GridBagSizer(hgap=5, vgap=5)
#hSizer = wx.BoxSizer(wx.HORIZONTAL|wx.EXPAND)
hSizer = wx.BoxSizer(wx.HORIZONTAL)
panel = wx.Panel(self, -1)
self.list = wx.ListCtrl(panel, size=(100,100), style=wx.LC_REPORT)
self.list.InsertColumn(0, 'Name')
self.list.InsertColumn(1, 'Group')
for i in employees:
index = self.list.InsertStringItem(sys.maxint, i[0])
self.list.SetStringItem(index, 1, i[1])
# A button
self.button = wx.Button(self, label="Exit")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
self.list.Show(True)
# add the listctrl widget to the grid
grid.Add(self.list, pos=(0,0), flag=wx.EXPAND|wx.ALL)
# add the button to the grid
grid.Add(self.button, pos=(1,0))
# add a spacer to the sizer
grid.Add((10, 40), pos=(1,1))
# add grid to hSizer
hSizer.Add(grid, 0, wx.ALL, 5)
# add hSizer to main (v) sizer
mainSizer.Add(hSizer, 0, wx.ALL, 5)
self.SetSizerAndFit(mainSizer)
self.Show()
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
def OnClick(self,event):
sys.exit(3)
app = wx.App(False)
frame = wx.Frame(None, title="Training Tracker", size=(700,500))
nb = wx.Notebook(frame)
nb.AddPage(EmployeesPanel(nb), "Employees")
frame.Show()
app.MainLoop()
Welcome to wxPython! It's actually a lot of fun once you get the hang of it. I almost never use the grid sizers as they're just a pain for simple layouts like this. If you have a grid like interface and you don't have controls that are going to stretch across cells, then it's great. Otherwise, I almost always use BoxSizers nested in each other. I simplified your code quite a bit to show the two widgets. Currently the list control only stretches horizontally. If you need it to go vertically too, then change the proportion from 0 to 1 in the sizer.Add part.
import wx
import sys
employees = [('Earl Boffo', 'Software'), ('Mildred Plotka', 'Software'), ('Sugar Kane', 'QA')]
classes = [('Python'), ('Java'), ('C#')]
class EmployeesPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.list = wx.ListCtrl(self, size=(100,100), style=wx.LC_REPORT)
self.list.InsertColumn(0, 'Name')
self.list.InsertColumn(1, 'Group')
for i in employees:
index = self.list.InsertStringItem(sys.maxint, i[0])
self.list.SetStringItem(index, 1, i[1])
# A button
self.button = wx.Button(self, label="Exit")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
mainSizer.Add(self.list, 0, wx.EXPAND|wx.ALL, 5)
mainSizer.Add(self.button, 0, wx.ALL, 5)
self.SetSizer(mainSizer)
self.Show()
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
def OnClick(self,event):
sys.exit(3)
app = wx.App(False)
frame = wx.Frame(None, title="Training Tracker", size=(700,500))
nb = wx.Notebook(frame)
nb.AddPage(EmployeesPanel(nb), "Employees")
frame.Show()
app.MainLoop()
I also think these articles might help you:
wxPython: wx.ListCtrl Tips and Tricks
wxPython: Using ObjectListView instead of a ListCtrl
http://wiki.wxpython.org/ListControls