Generate wxPython widget groups from lists - python

This is one of those questions that is really frustrating for me to ask because I'm sure the answer is out there, I just haven't been able to word the searches correctly. Basically I am new to GUI programming (done a decent amount of embedded C/C++) and learning wxPython to start out.
I'm making an app to read and write to a config file. So I have a StaticText to display the name of the parameter to be read/written, a TextCtrl to display the value and allow user input, and then a Get button and a Set button. I'll call all of these together a "group" of widgets. For this app obviously this group will be repeated several times. Rather than write and maintain all that code by hand, I thought it would be easier to simply have a list of the config parameters I want to be able to edit, and then iterate through the list and generate an instance of this "group" of widgets for each item in the list. I made it work except for one thing: I had to bind all of the Get buttons to the same function. Same thing with the Set buttons. Is there any way from within those functions to know which Get or Set button was pushed, and thus which parameter to find and edit in the config file? I'm sure there's a way to do this with parent or IDs or something but I'm just too new to OOP.

I assume that the get button reads the parameter value from the config file and displays the value.
Why do you need one get button for each parameter? I would have just one get button for tham all. When the user clicks the get button, every parameter is read from the file and all the displays are updated.
A similar approach for the set button. One set button for them all - when the button is pressed every parameter that has a new value entered is updated in the config file. Parameters where the user has not entered a new value remain with their previous values.
This scheme is easier to code, and easier for the user also.
However, I really suggest you look at the wxPropertyGrid widget. This has the potential to make your life a lot easier! Here's a screenshot showing one in action

In your button's event handler, you can do something like this:
btn = event.GetEventObject()
btn.GetId()
btn.GetName()
Then you just use an If statement to decide what to do based on whatever info you want to use. Note: you can set the button's name when you create the button like this:
setBtn = wx.Button(self, label="Set", name="SetX")
You might find this article on wxPython and ConfigObj helpful too: http://www.blog.pythonlibrary.org/2010/01/17/configobj-wxpython-geek-happiness/

You can derive your own class from the wx.Button and add one or more attributes to it to make the button remember any information you want.
You can use this stored information during a callback function call. Something like:
import wx
L = [("1", "One"), ("2", "Two"), ("3", "Three")]
# =====================================================================
class MemoryButton(wx.Button):
def __init__(self, memory, *args, **kwargs):
wx.Button.__init__(self, *args, **kwargs)
self.memory = memory
# =====================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.buttons = []
for description in L:
button = MemoryButton(memory=description[1], parent=self.panel,
label=description[0])
button.Bind(wx.EVT_BUTTON, self.OnMemoryButton)
self.buttons.append(button)
self.sizer = wx.BoxSizer()
for button in self.buttons:
self.sizer.Add(button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# -----------------------------------------------------------------
def OnMemoryButton(self, e):
print("Clicked '%s'" % e.GetEventObject().memory)
# =====================================================================
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
or alternatively:
import wx
L = [("1", "One"), ("2", "Two"), ("3", "Three")]
# =====================================================================
class MemoryButton(wx.Button):
def __init__(self, memory, *args, **kwargs):
wx.Button.__init__(self, *args, **kwargs)
self.memory = memory
def OnButton(self, e):
print("Clicked '%s'" % self.memory)
# =====================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.buttons = []
for description in L:
button = MemoryButton(memory=description[1], parent=self.panel,
label=description[0])
button.Bind(wx.EVT_BUTTON, button.OnButton)
self.buttons.append(button)
self.sizer = wx.BoxSizer()
for button in self.buttons:
self.sizer.Add(button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# =====================================================================
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Related

Setting button signals for widget created via wrapper function

I am trying to create a GUI (see attached screenshot) where it has about 6 sets of widgets where each set comprises of QLabel, QLineEdit and QPushButton.
Instead of writing the widgets and the layout again and again, I thought of writing up a wrapper function such that I will only need to write up 6 lines for the creation.
However, while this works efficiently for me in terms of the widgets creation, I realized that I am not able to set the Signal for the button(s) as I do not know the widget's names.
What will be the best way to set the signals if I decided to use this wrapper methodology?
The following is a screenshot of what I am trying to achieve as well as my code:
class MyDevTool(QtGui.QWidget):
def __init__(self, parent=None):
super(MyDevTool, self).__init__(parent)
self._create_ui()
def _create_hbox_layout(self, label):
h_layout = QtGui.QHBoxLayout()
label = QtGui.QLabel(label)
label.setFixedWidth(80)
line_edit = QtGui.QLineEdit()
push_btn = QtGui.QPushButton("<<<")
h_layout.addWidget(label)
h_layout.addWidget(line_edit)
h_layout.addWidget(push_btn)
return h_layout
def _create_ui(self):
main_layout = QtGui.QVBoxLayout()
main_layout.addLayout(
self._create_hbox_layout("Input Directory")
)
main_layout.addLayout(
self._create_hbox_layout("Output Directory")
)
self.setLayout(main_layout)

How to call a function from within a class in PYQT4

def home(self):
btn = QtGui.QPushButton("Log in", self)
self.show()
if btn.clicked:
btn.clicked.connect(btn.deleteLater)
self.Page()
else:
pass
def Page(self):
btn2 = QtGui.QPushButton("Exit", self)
self.show()
Sorry if the indenting isn't correct here, but it is in my python file:
So the btn does delete when it is pressed, but Page function isn't correctly being run because the btn2 doesn't appear.
This is only the relevant code snippet pasted.
TIA for help as to why the Page function isn't being run. I am using python 2,7 and pyqt4
It looks like btn variable is local inside home method. This means it is only visible within this method (unless it's defined outside in a higher-level scope.)
If you want to share a variable in multiple methods of the class, you should store it as an object property - that's why you'll need some OOP. For example (assuming the made-up rest of your class definition):
class YourApp(object):
def __init__(self):
# All the preparations should go here.
# If self.btn is created later dynamically,
# it's still recommended to declare it here
# and assign `None` to it
self.btn = QtGui.QPushButton("Log in", self)
def home(self):
# Do stuff with self.btn
self.btn.spam()
pass
def page(self):
# Do other stuff with self.btn
self.btn.eggs()

Automating a wxPython main menu setup?

I am trying to find a way to condense and automate the construction of a main menu (underneath the title bar, with file, edit, help, etc.) in wxPython.
Writing out each and every menu item is direct, but I notice I repeat myself a lot, between Appending, sorting IDs, etc. Followed by other unique pits like if I want to add an icon to a specific menu, or if I have submenus and they may have submenus, etc. Without one consistent way to itemize everything, simply by adding information to maybe a list or dictionary, or a combo of the two, my wx.Frame object will get very dense.
I can't see a clean an organized way of doing that, short of a 3-dimensional array. And even then, I don't know how to organize that 3D array uniformly so every item is ready to go.
Here is what I have so far (pardon any indentation errors; it works fine on me):
class frameMain(wx.Frame):
"""The main application frame."""
def __init__(self,
parent=None,
id=-1,
title='TITLE',
pos=wx.DefaultPosition,
size=wx.Size(550, 400),
style=wx.DEFAULT_FRAME_STYLE):
"""Initialize the Main frame structure."""
wx.Frame.__init__(self, parent, id, title, pos, size, style)
self.Center()
self.CreateStatusBar()
self.buildMainMenu()
def buildMainMenu(self):
"""Creates the main menu at the top of the screen."""
MainMenu = wx.MenuBar()
# Establish menu item IDs.
menuID_File = ['exit']
menuID_Help = ['about']
menuID_ALL = [menuID_File,
menuID_Help]
# Make a dictionary of the menu item IDs.
self.menuID = {}
for eachmenu in menuID_ALL:
for eachitem in eachmenu:
self.menuID[eachitem] = wx.NewId()
# Create the menus.
MM_File = wx.Menu()
FILE = {}
MM_File.AppendSeparator()
FILE['exit'] = MM_File.Append(self.menuID['exit'],
'Exit',
'Exit application.')
self.Bind(wx.EVT_MENU, self.onExit, FILE['exit'])
MainMenu.Append(MM_File, 'File')
MM_Help = wx.Menu()
HELP = {}
MM_Help.AppendSeparator()
HELP['about'] = MM_Help.Append(self.menuID['about'],
'About',
'About the application.')
self.Bind(wx.EVT_MENU, self.onAbout, HELP['about'])
MainMenu.Append(MM_Help, 'Help')
# Install the Main Menu.
self.SetMenuBar(MainMenu)
I tried using the list-to-dictionary thing to make it so I don't need a specific index number when referring to an ID, just write in a keyword and it gets the ID. I write it once, and it's applied across the rest of the function.
Notice how I have to make a whole new variable and repeat itself, like MM_File, MM_Edit, MM_Help, and each time I do I put in similar information to append and bind. And keep in mind, some of the menus may need Separators, or have menus in menus, or I may want to use a sprite next to any of these menu items, so I'm trying to figure how to organize my arrays to do that.
What is the appropriate way to organize this into a concise system so it doesn't bloat this class?
There are several approaches you can take with this. You can put the menu generation code into a helper function if you like. Something like this should work:
def menu_helper(self, menu, menu_id, name, help, handler, sep=True):
menu_obj = wx.Menu()
if sep:
menu_obj.AppendSeparator()
menu_item = menu_obj.Append(menu_id, name, help)
self.Bind(wx.EVT_MENU, handler, menu_item)
self.MainMenu.Append(menu_obj, menu)
Here's a complete example:
import wx
class frameMain(wx.Frame):
"""The main application frame."""
def __init__(self,
parent=None,
id=-1,
title='TITLE',
pos=wx.DefaultPosition,
size=wx.Size(550, 400),
style=wx.DEFAULT_FRAME_STYLE):
"""Initialize the Main frame structure."""
wx.Frame.__init__(self, parent, id, title, pos, size, style)
self.Center()
self.CreateStatusBar()
self.buildMainMenu()
def buildMainMenu(self):
"""Creates the main menu at the top of the screen."""
self.MainMenu = wx.MenuBar()
# Establish menu item IDs.
menuID_File = 'exit'
menuID_Help = 'about'
menuID_ALL = [menuID_File,
menuID_Help]
# Make a dictionary of the menu item IDs.
self.menuID = {item: wx.NewId() for item in menuID_ALL}
# Create the menus.
self.menu_helper('File', self.menuID['exit'], 'Exit',
'Exit application', self.onExit)
self.menu_helper('Help', self.menuID['about'], 'About',
'About the application.', self.onAbout)
# Install the Main Menu.
self.SetMenuBar(self.MainMenu)
def menu_helper(self, menu, menu_id, name, help, handler, sep=True):
"""
"""
menu_obj = wx.Menu()
if sep:
menu_obj.AppendSeparator()
menu_item = menu_obj.Append(menu_id, name, help)
self.Bind(wx.EVT_MENU, handler, menu_item)
self.MainMenu.Append(menu_obj, menu)
#----------------------------------------------------------------------
def onExit(self, event):
pass
def onAbout(self, event):
pass
if __name__ == '__main__':
app = wx.App(False)
frame = frameMain()
frame.Show()
app.MainLoop()
Or you could create a class that handles all the menu creation. You could also create a config file that has all this information in it that you read to create your menu. Another alternative would be to use XRC, although I personally find that a bit limiting.

wxPython EVT_CHAR not called when a panel is added to a frame

I need to bind the EVT_CHAR event for a GUI application I am developing using wxPython. I tried the following and I cann understand the beahviour of the code.
import wx
import wx.lib.agw.flatnotebook as fnb
class DemoApp(wx.App):
def __init__(self):
wx.App.__init__(self, redirect=False)
self.mainFrame = DemoFrame()
self.mainFrame.Show()
def OnInit(self):
return True
class DemoFrame(wx.Frame):
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"FlatNotebook Tutorial",
size=(600,400)
)
panel = wx.Panel(self)
button = wx.Button(panel, label="Close", pos=(125, 10), size=(50, 50))
self.Bind(wx.EVT_CHAR, self.character)
def character(self, event):
print "Char keycode : {0}".format(event.GetKeyCode())
if __name__ == "__main__":
app = DemoApp()
app.MainLoop()
The character function never gets called. However, when I comment out the two lines call to the Frame constructor, I character function is called. Adding a panel to the frame seems to interfere with the binding of the frame's EVT_CHAR.
How do I address this problem? Am I doing something wrong in my code?
The problem is that you are catching events that happen to the frame, but the frame is not in focus. The button is. In wxPython, events are sent to the widget in focus. If you add this to the end of your init, it works:
self.SetFocus()
However, if you change the focus to the button, then it will stop working again. See also:
wxpython capture keyboard events in a wx.Frame
http://www.blog.pythonlibrary.org/2009/08/29/wxpython-catching-key-and-char-events/
http://wxpython-users.1045709.n5.nabble.com/Catching-key-events-from-a-panel-and-follow-up-to-stacked-panels-td2360109.html
I appreciate that this question was answered 2 years ago but this issue catches us all out at some point or other.
It is the classic binding to wx.Event or wx.CommandEvent problem.
In this case simply changing the self.Bind(wx.EVT_CHAR, self.character) line to read button.Bind(wx.EVT_CHAR, self.character) will solve the problem, detailed above.
The issue of wx.Event - wx.CommandEvent is covered in full here:
http://wiki.wxpython.org/EventPropagation
and here:
http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind

Run __init__ after panel is created?

Is there a way, from a button to reinitialize a specific panel? I have a section of an app that looks for specific files in the OS and then creates some checkboxes and textctrls and then adds them to the sizer dynamically:
for db in numDB:
if xIndex >= 12:
pass
xIndex += 1
else:
check = wx.CheckBox(self, -1, db)
sizer.Add(check, pos=(xIndex,0), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=10)
label = wx.StaticText(panel, label="")
sizer.Add(label, pos=(xIndex,1), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=10)
name = wx.TextCtrl(panel)
#Set Temp Name
if db.endswith('.db'):
name.Value = db[:-3]
sizer.Add(name, pos=(xIndex,2), span=(1,3),flag=wx.EXPAND|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
xIndex +=1
I have a refresh button, in the event the user adds a new db, or removes one. The simplest concept for me, at least, is to try to find a way to rerun the init.
I'm trying to reference the init by:
def onRefreshClick(self, event):
self.__init__
That def happens within the Class itself, but it doesn't seem to do anything. And I'm super new to the whole wxPython and Python in general, so it's probably something simple that I cannot quite figure out.
Edited to try and incorporate super():
class LaunchPanel(wx.Panel):
"""
Launch Tab for finding and launching .db files
"""
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
super (LaunchPanel)
self.initialize()
def initialize(self):
panel = self
sizer = wx.GridBagSizer(11, 3)
<snip...>
This results in my panel being empty. When I hit refresh, it adds (I think) all my elements to the upper left portion of the panel...? I'm sure it's easy, whatever I am doing wrong...?
You're missing parentheses:
self.__init__() # now, this is a method call
A simple solution is to have your __init__ do nothing but call another method, then you can call that method whenever you want:
def __init__(self, ...):
super(...)
self.initialize()
def initialize(self):
<actual intialization code here>
I think that's a lot cleaner than directly calling __init__. Plus, this lets you separate stuff that must truly be done only at object instantiation (such as calling the init of the superclass) from the code you want to call multiple times.

Categories

Resources