I am new in wxPython and can't solve one problem. I need to continuously update panel with clock value. I have a solution, but in this case I can't normally close window (alt+f4 not works).
Also I do not unsderstand what is the difference between .Update .Refresh and when .Destroy should be called?
Can some one reccomend a good book, how to program in wxPython?
Thanks for any help.
class TimeDatePanel(wx.Panel):
def __init__(self, parent, ID=ID_TIMEDATE, pos=wx.DefaultPosition, size=(50, 50), controller=None):
wx.Panel.__init__(self, parent, ID, pos, size, wx.RAISED_BORDER)
self.controller = controller
transCoded = controller.transCodes
layout = wx.GridSizer(5,2,0,10)
layout.Add(wx.StaticText(self, wx.ID_ANY, transCoded.get("Time & Date")))
layout.Add(wx.StaticText(self, wx.ID_ANY, ""), 0,flag=wx.ALL)
layout.Add(wx.StaticText(self, wx.ID_ANY, transCoded.get("Local time")), 0,flag=wx.ALL|wx.ALIGN_RIGHT)
self.LT = wx.StaticText(self, wx.ID_ANY, "")
layout.Add(self.LT)
layout.Add(wx.StaticText(self, wx.ID_ANY, transCoded.get("UTC")), 0,flag=wx.ALL|wx.ALIGN_RIGHT)
self.UTC = wx.StaticText(self, wx.ID_ANY, "")
layout.Add(self.UTC)
layout.Add(wx.StaticText(self, wx.ID_ANY, transCoded.get("Julian day")), 0,flag=wx.ALL|wx.ALIGN_RIGHT)
self.JD = wx.StaticText(self, wx.ID_ANY, "")
layout.Add(self.JD)
layout.Add(wx.StaticText(self, wx.ID_ANY, transCoded.get("Local sidereal time")), 0,flag=wx.ALL|wx.ALIGN_RIGHT)
self.LST = wx.StaticText(self, wx.ID_ANY, "")
layout.Add(self.LST)
self.SetSizer(layout)
self.updateTimeDate()
self.Fit()
wx.EVT_PAINT(self, self.onPaint)
def onPaint(self, event=None):
self.updateTimeDate()
def updateTimeDate(self):
mechanics = self.controller.mechanics
self.LT.SetLabel(str(mechanics.getLT()))
self.UTC.SetLabel(str(mechanics.getUTC()))
self.JD.SetLabel(str(mechanics.getYD()))
self.LST.SetLabel(str(mechanics.getLST()))
If you need the clock updated every so often, why not use the AnalogClock, LEDNumberCtrl or maybe the TimeCtrl that's updated with a wx.Timer? The following tutorial will help you with the timer part: http://www.blog.pythonlibrary.org/2009/08/25/wxpython-using-wx-timers/
The first two widgets update themselves. You should have to call Update, Refresh or Layout when you rest a value of a StaticText control or other normal widget. Just use SetValue or SetLabel instead.
Robin Dunn has an older book called "wxPython in Action" that is still great for the most part. There's also a wxPython Cookbook by Cody Precord that came out this year.
Related
I am a humanities teacher, trying to adapt a simple app to help teachers manage classroom interaction - it takes attendance, then allows for calling on random attending students in class, or breaking them into groups, or recording an excused absence, and so on. I've long had a working version in PHP / MySQL running locally on my laptop, but I want to make it a portable python / sqlite app, with an eye toward releasing into the wild for other teachers to use and improve.
But I'm totally new to wxPython, and not really strong on object-oriented programming generally. I have spent hours reading (or skimming through) a fair number of tutorials and introductions and StackOverflow questions, and I've played with wxFormBuilder, but I do not feel like I'm making progress - I'm still quite confused about panels and sizers and layouts, and which bits should belong to what parents.
I think if I could just get this minimal version working, that would go a long way toward my real app. The toolbar-like buttons along the top seem to work fine, but I'd like a "display" area below with a minimal (but expandable) vertical size, a centered text area, and a row of 2-3 buttons in the display area below that text. This display area should change and clear depending on the button pushed above. Here's what I've got:
#!/usr/bin/env python3
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(-1,-1))
# self.panel = wx.Panel(self, size=(-1,300))
# buttons bar
self.top_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.btn_left = wx.Button(self, -1, label="left")
self.btn_right = wx.Button(self, -1, label="right")
self.btn_left.Bind(wx.EVT_BUTTON, self.OnLeft)
self.btn_right.Bind(wx.EVT_BUTTON, self.OnRight)
self.top_button_sizer.Add(self.btn_left, 1, wx.EXPAND)
self.top_button_sizer.Add(self.btn_right, 1, wx.EXPAND)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.display_sizer = wx.BoxSizer(wx.VERTICAL)
self.text_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.display_text = wx.StaticText(self, label="Push a button!")
self.display_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.text_sizer.Add(self.display_text, 0, wx.ALIGN_CENTER, 5)
self.display_sizer.Add(self.text_sizer, 1, wx.ALIGN_CENTER, 5)
self.display_sizer.Add(self.display_buttons_sizer, 0, wx.EXPAND)
self.sizer.Add(self.top_button_sizer, 0, wx.TOP | wx.EXPAND)
# self.sizer.Add(self.panel)
self.sizer.Add(self.display_sizer, 1, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
self.Show()
def OnLeft(self,e):
# for child in self.display_buttons_sizer.GetChildren():
# child.Destroy()
# ^ my attempt to "clear" causes a SegFault
for child in self.display_buttons_sizer.GetChildren():
child.Destroy()
self.display_text.SetLabel("Hey lefty!")
self.btn_hey = wx.Button(self, -1, label="Hey yourself lefty.")
self.btn_whoa = wx.Button(self, -1, label="Whoa there lefty.")
self.display_buttons_sizer.Add(self.btn_hey, 1, wx.EXPAND)
self.display_buttons_sizer.Add(self.btn_whoa, 1, wx.EXPAND)
self.sizer.Layout()
def OnRight(self,e):
# for child in self.display_buttons_sizer.GetChildren():
# child.Destroy()
# ^ my attempt to "clear" causes a SegFault!
self.display_text.SetLabel("Hey righty!")
self.btn_hey = wx.Button(self, -1, label="Hey yourself righty.")
self.btn_whoa = wx.Button(self, -1, label="Whoa there righty.")
self.display_buttons_sizer.Add(self.btn_hey, 1, wx.EXPAND)
self.display_buttons_sizer.Add(self.btn_whoa, 1, wx.EXPAND)
self.sizer.Layout()
app = wx.App(False)
frame = MainWindow(None, "MWE")
app.MainLoop()
(I'm sure this is abhorrent code in lots of ways.) Should I put in a panel somewhere? Where, and how? Would it be easier to learn about "notebooks" instead of using my top buttons? How do I properly clear what's in the "display" area below for the next button push?
Bonus points: the most complicated of the top buttons on my real app is for taking attendance. It would display many lines of text (the students in that class, which I get from sqlite; I've worked that part out I think) with radio buttons for present / absent next to each line, and then record in the database. Hints about this would also be appreciated.
Thanks in advance for your patience.
You could do this sort of thing several different ways. You can swap out panels when a button is pushed or you could just use a wx.Notebook which is basically the same idea. I wrote up a tutorial on panel switching here that you might find helpful:
https://www.blog.pythonlibrary.org/2010/06/16/wxpython-how-to-switch-between-panels/
It uses a menu instead of buttons, but that wouldn't be hard to change. Here's a simple example that doesn't do panel switching, but does show how to add a multiline text control that clears itself when a button is pressed:
import wx
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
left_button = wx.Button(self, label='Left')
left_button.Bind(wx.EVT_BUTTON, self.on_left)
btn_sizer.Add(left_button, 0, wx.ALL, 5)
right_button = wx.Button(self, label='Right')
right_button.Bind(wx.EVT_BUTTON, self.on_right)
btn_sizer.Add(right_button, 0, wx.ALL, 5)
self.text_ctrl = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.main_sizer.Add(btn_sizer, 0, wx.CENTER)
self.main_sizer.Add(self.text_ctrl, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(self.main_sizer)
def on_left(self, event):
self.text_ctrl.Clear()
self.text_ctrl.SetValue('Left')
def on_right(self, event):
self.text_ctrl.Clear()
self.text_ctrl.SetValue('Right')
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(-1,-1))
panel = MainPanel(self)
self.Show()
app = wx.App(False)
frame = MainWindow(None, "MWE")
app.MainLoop()
This also demonstrates how to add a panel, which is recommended as panels give you the right "look" on all platforms and it also enables tabbing between controls.
You could easily add another set of buttons to the bottom by using the same concepts shown here and then just adding the second set of button's sizer to the main sizer.
If you want to do lines of text with some kind of radio or check button, I would recommend using a wx.ListCtrl or (better), ObjectListView (https://objectlistview-python-edition.readthedocs.io/en/latest/recipes.html#recipe-checkbox) (see also https://www.blog.pythonlibrary.org/2009/12/23/wxpython-using-objectlistview-instead-of-a-listctrl/)
You can tie an event to the checkbox that can then update your database appropriately.
As someone pretty new to wxpython, I'm attempting to write a login script for an imaginary program. On startup, buttons ask if you want to create a new account or register a new one. When either one is clicked, I want all the widgets on the page to disappear, leaving a blank frame for other widgets to be imposed on. However I'm not sure how to .Hide() specific widgets -- my existing widgets are not being recognized as variables. Here's my relevant code:
class Welcome(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, "ImageViewer", size=(500,350))
panel = wx.Panel(self)
text = wx.StaticText(panel, -1, "Welcome to ImageViewer. Do you have an account?", (50,10))
font = wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
text.SetFont(font)
yesbutton = wx.Button(panel, label="Yes, I wish to log in", pos=(50,150), size=(150,60))
self.Bind(wx.EVT_BUTTON, self.loginwindow, yesbutton)
nobutton = wx.Button(panel, label="No, I wish to register", pos=(270,150), size=(150,60))
self.Bind(wx.EVT_BUTTON, self.registerwindow, nobutton)
def loginwindow(self, event):
self.Hide(self.text) #Error occurs here
AttributeError: 'Welcome' object has no attribute 'text'
I'm not sure if there is a better way of doing this (if there is please let me know) but for now I'm just not sure why I can't access these variables.
text isn't made an attribute of of the Welcome class, so when you try and call it in your loginwindow function it's out of scope.
When you declare it in your init method make it self.text
Edit: This code works.
class Welcome(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, None, id, "ImageViewer", size=(500,350))
panel = wx.Panel(self)
self.text = wx.StaticText(panel, -1, "Welcome to ImageViewer. Do you have an account?", (50,10))
font = wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
self.text.SetFont(font)
yesbutton = wx.Button(panel, label="Yes, I wish to log in", pos=(50,150), size=(150,60))
self.Bind(wx.EVT_BUTTON, self.loginwindow, yesbutton)
def loginwindow(self, event):
self.text.Hide() #Error occurs here
I uses a WrapSizer in order to have an automatic layout (as thumbnail gallery) like this (see screenshot on the left) :
I would like that if there are two many elements, a (vertical only)-ScrollBar is added on the panel (see right screenshot). How to add such a vertical scrollbar to a panel using a WrapSizer?
I tried by mixing WrapSizer and ScrolledPanel, but I cannot get the desired layout.
class MyPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent)
self.SetBackgroundColour('#f8f8f8')
sizer = wx.WrapSizer()
self.SetupScrolling()
# add some widgets btn1, btn2, etc. in the WrapSizer
sizer.Add(btn1, 0, wx.ALL, 10)
sizer.Add(btn2, 0, wx.ALL, 10)
Solution:
reset the width of the scroll panel virtual size to the displayable size.
import wx
import wx.lib.scrolledpanel as scrolled
class MyPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent, style=wx.VSCROLL)
self.SetBackgroundColour('#f8f8f8')
self.sizer = wx.WrapSizer()
self.SetupScrolling(scroll_x = False)
self.parent = parent
self.addButton(self.sizer , 10)
self.SetSizer(self.sizer )
self.Bind(wx.EVT_SIZE, self.onSize)
def onSize(self, evt):
size = self.GetSize()
vsize = self.GetVirtualSize()
self.SetVirtualSize((size[0], vsize[1]))
evt.Skip()
def addButton(self, sizer, num):
for i in range(1, num):
btn =wx.Button( self, wx.ID_ANY, "btn"+str(i), wx.DefaultPosition, wx.DefaultSize, 0 )
sizer.Add(btn, 0, wx.ALL, 10)
if __name__=='__main__':
app = wx.App(redirect=False)
frame = wx.Frame(None)
MyPanel(frame)
frame.Show()
app.MainLoop()
It looks like you just forgot to include
self.SetSizer(sizer)
Since the WrapSizer takes the whole frame, I think that will work. Also, instead of SetupScrolling, you can use
self.SetScrollRate(horiz, vert)
to specify the increment (in pixels, i think) of the scroll, and that should work.
I can't test it here right now though, and WrapSizers are a little weird - they sometimes have trouble figuring out their proper size. You may need to wrap it in a BoxSizer going the other direction.
I have created a pop up window, but the TextCtrl is not fully expanded to fill up the window. It works great if I use StaticText instead, (but if content too large then I would need the scroll bar, that is why I am using TextCtrl now). Please provide some guidance.
self.description = WindowPopup(self, wx.SIMPLE_BORDER, content)
btn = event.GetEventObject()
dw = wx.DisplaySize()[0]
width = self.description.GetSize()[0]
y = btn.ClientToScreen((0,0))[1]
height = btn.GetSize()[1]
x = dw - width - 20 - 10
self.description.Position((x, y), (0, height))
self.description.Show(True)
class WindowPopup(wx.PopupWindow):
""" Pops up a window to provide description for the selection """
def __init__(self, parent, style, content):
wx.PopupWindow.__init__(self, parent, style)
self.SetSize((700, 287))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
st = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_READONLY)
st.SetValue(content)
sizer.Add(st, 0, wx.EXPAND)
panel.SetSizer(sizer)
I suspect your problem is that the panel is not as big as the popupwindow ... so even though the textfield is expanding to fill its sizer area it is not filling the popup its self.
try using something like
def __init__(...):
...
self.SetMinSize((700,287))
sizer2 = wx.BoxSizer()
sizer2.Add(panel)
self.SetSizer(sizer2)
also make sure that you are calling layout on it at some point (note this is totally untested... so it may need some tweeks, or even worse just be wrong...)
The actual answer is:
sizer = wx.BoxSizer(wx.VERTICAL)
st = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_READONLY, size = (500, 174))
st.SetValue(content)
self.SetSize((500, 174))
sizer.Add(st, 0, wx.EXPAND)
self.SetSizer(sizer)
self.Layout()
self.Show(True)
Credits to Joran for noticing Layout().
PopupWindow does not require an additional panel, because the window itself can have sizer set to it. This has been realized by using the wxPython Widget Inspection Tool.
Make sure TextCtrl and PopupWindow have the same size.
Hi at all friends :)
I have a problem with a control inside a wx.Panel.
With my code the wx.GenericDirCtrl inside a wx.Panel don't fit in all directions in the Panel (or fit only in a direction if I use wx.BoxSizer).
I use an istance of MyPanel in a wx.Frame.
How I can solve it? Thanks
The code is:
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, wx.ID_ANY)
resizeBox = wx.BoxSizer(wx.VERTICAL)
self.dir1 = wx.GenericDirCtrl(self, wx.ID_ANY)
resizeBox.Add(self.dir1, wx.EXPAND | wx.ALL)
self.SetSizerAndFit(resizeBox)
and the code where I instancing Panel in wx.Framec is:
# controls
self.splitterMain = wx.SplitterWindow(self, wx.ID_ANY) # create a vertical splitter
self.panel1 = MyPanel(self.splitterMain)
self.panel1.SetBackgroundColour(wx.BLACK)
self.panel2 = wx.Panel(self.splitterMain, wx.ID_ANY)
self.panel2.SetBackgroundColour(wx.WHITE)
self.splitterMain.SplitVertically(self.panel1, self.panel2)
You're using the Add method wrong. Its signature is
Add(self, item, proportion=0, flag=0, border=0, userData=None)
You've passed the "flag" parameter as the proportion parameter.