Keyboard interface to wxPython listbox - python

I'm using a wxPython listbox on Windows to get a choice from the user, and I would like them to be able to select an item using the ENTER key, as if they had double-clicked. I know how to do this in C or C++ using the Windows API directly, but can't seem to find how to do it using wxPython. Anyone know how? It seems like an obvious thing to want to do.

Maybe I'm missing some nuance, there wasn't much info to go on, but it sounds like you could accomplish this by catching the keydown event, matching for enter and then calling your on_doubleclick function. Unless there's an implicit double-click handling you should be good to go.

The simple answer is that the wx.ListBox doesn't support that. Try using a one column wx.ListCtrl (in Report mode) instead.

This works for me, the key is to Bind EVT_KEY_UP, rather than EVT_KEY_DOWN:
import wx
class myListBoxDemo(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent, title="testing ListBox right-click")
self.panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
self.myListBox = wx.ListBox(self.panel,style=wx.LB_HSCROLL|wx.LB_SINGLE)
self.myListBox.Bind(wx.EVT_KEY_UP, self.OnKeyDownSelectedList)
self.myListBox.Append('item 1')
self.myListBox.Append('item 2')
self.myListBox.Append('item 3')
self.myListBox.Append('item 4')
sizer.AddF(self.myListBox, wx.SizerFlags(1).Expand())
self.panel.SetSizer(sizer)
def OnKeyDownSelectedList(self, event):
print "KeyCode: %d" % event.GetKeyCode()
print "ListBox Item Index: %d" % event.GetEventObject().GetSelection()
import wx.lib.mixins.inspection as wit
class AppWInspection(wx.App, wit.InspectionMixin):
def OnInit(self):
self.Init() # enable Inspection tool
return True
if __name__ == "__main__":
app = AppWInspection()
f = myListBoxDemo(None)
f.Show()
app.MainLoop()

Related

Global event handlers in wxPython (Phoenix)

I'm having a problem trying to handle focus events in wxPython 3, the phoenix fork.
I'm trying to clear the value in a TextCtrl when it's focused if it still has its default value, and then restore that default value on leaving focus if the user hasn't entered a different value.
This is the handler I have:
def on_enter(self, event):
control = wx.Window.FindFocus()
if control.Id in TEXT_MAPPING.keys():
if control.Value == TEXT_MAPPING[control.Id]:
control.SetValue('')
exit_control = event.GetWindow()
if exit_control.Id in (TEXT_MAPPING.keys()):
if not exit_control.GetValue():
exit_control.SetValue(TEXT_MAPPING[exit_control.Id])
event.Skip()
The handler itself works fine, though it's a little clunky. My issue is that I would like to bind this on a global level, such that whenever any wx.EVT_SET_FOCUS event is fired, I can have it be handled by this function.
I found this:
import wx
class MyApp(wx.App):
def FilterEvent(self, evt):
print evt
return -1
app = MyApp(redirect=False)
app.SetCallFilterEvent(True)
frm = wx.Frame(None, title='Hello')
frm.Show()
app.MainLoop()
but I'm confused on how I would pass the event down to the children of MyApp.
What you can do is bind focus kill and focus select to functions, like this:
self.user_text.Bind(wx.EVT_SET_FOCUS, self.on_focus_username)
self.user_text.Bind(wx.EVT_KILL_FOCUS, self.on_deselect_username)
where self is the panel where the TextCtrl "user_text" is located".
The functions might look like this:
def on_focus_username(self, event):
if self.user_text.GetForegroundColour() != wx.BLACK:
self.user_text.SetForegroundColour(wx.BLACK)
# Clear text when clicked/focused
self.user_text.Clear()
def on_deselect_username(self, event):
if self.user_text.GetLineLength(0) is 0:
self.user_text.SetForegroundColour(wx.Colour(192, 192, 192))
# Put a default message when unclicked/focused away
self.user_text.SetValue("Enter Username")
Hope this helped, if I didn't answer something clearly/correctly let me know.

binding packages with wxpython

I'm kind of new with wxpython and python itself, sorry for the basic question.
I'm trying to organize my code in a more easy way to manage it. I created this simple example that kind of resume my problem. Basically it is just a window with a button that print a message. I separated it in three simple packages
ef_Main.py - This is the main packages it will import the UI and the application itself.
ef_Tool.py - it is the application that will run all the important code, right now it is just a print statment but will have all the application code.
ef_UI.py - a very basic interface using wxpython.
How it should work:
Run ef_Main.py it will import the interface (ef_UI.py) and the main code (ef_Tool.py). When something is click in the interface it will be ready by the ef_Main and send to ef_Tool to be executed.
My problem is:
I'm not sure how to use bind function to connect this three packages. I believe that it should be in the ef_Main but how it will get the info from the interface and send it to the ef_Tool.py.
And if I want to get some output from the ef_Tool and send it back to the interface. How should I do that.
Here is my code.
#ef_Main.py
import wx
import ef_UI as eU
import ef_Tool as eT
''' Here is where I don't know how to make it works,
if I should put a class here or not, and how to bind
this function with the impTool and impUI'''
#class MyMain(self):
def uiControls(self):
self.Bind(wx.EVT_BUTTON, eU.OnClick(self), eT.MyTools(self))
def main():
app = wx.App(False)
frame = eU.MyFrame()
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
=======================
#ef_Tool.py
import wx
'''just a simple as possible function to be execute when it is called '''
class MyTools():
def OnClick(self, event):
#Value = self.MyTextCtrl.GetValue()
print "it is working! "
=======================
#ef_UI.py
import wx
''' very simple interface with only a button and a TextCtrl '''
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Menu Test")
self.panel = wx.Panel(self)
self.MyButton = wx.Button(self.panel, -1, "Button_test", (0, 0))
self.MyTextCtrl = wx.TextCtrl(self.panel, -1, value="just a test", pos=(100, 0))
Thanks in advance!
Emerson
Here is a simple example of fulfilling your requirements. Supposing all your application logic is done in ef_Tool.py and the input for these logic is from ef_UI and the output is also sent to ef_UI.py.
You need to call a method in ef_Tool.py when a button click event occurs in ef_UI. You can invoke this method from MyFrame's method. But you need an obj of MyTools to do this.
so first, create an obj for MyTools in ef_Main.py and pass this object to MyFrame
#ef_Main.py
import wx
import ef_UI as eU
import ef_Tool as eT
def main():
efToolObj = eT.MyTools() # object of MyTools class
app = wx.App(False)
frame = eU.MyFrame(efToolObj) # Pass this to MyFrame so that it can make use of it
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
Store this MyTools's object in you MyFrame class. And then use this object to call the corresponding method inside MyTools
#ef_UI.py
import wx
''' very simple interface with only a button and a TextCtrl '''
class MyFrame(wx.Frame):
def __init__(self, efToolObj):
wx.Frame.__init__(self, None, title="Menu Test")
self.panel = wx.Panel(self)
self.efToolObj = efToolObj # save the MyTools object to be used later
self.MyButton = wx.Button(self.panel, -1, "Button_test", (0, 0))
self.MyButton.Bind(wx.EVT_BUTTON, self.onClickEvent) # Bind the click event to an event handling method
self.MyTextCtrl = wx.TextCtrl(self.panel, -1, value="just a test", pos=(100, 0))
def onClickEvent(self,event): #this is called when a button is clicked
res = self.efToolObj.OnClickPrinting(self.MyTextCtrl.GetValue()) #Use the mytools object to call its method to apply logic,also get result values
self.MyTextCtrl.SetValue(res) #use the result values in your UI
You can pass the info you want to send to the application logic in its arguments and get results as return values.
#ef_Tool.py
class MyTools:
def OnClickPrinting(self,textvalue):
#Value = self.MyTextCtrl.GetValue()
print "it is working! ",textvalue
resultstr = "test successful"
return resultstr
Hope this helped.

Invoking a wxPython EVT_BUTTON event programmatically

I've seen another question regarding this topic, but I couldn't quite get the information to work for me, so I thought I'd give my specifics - I suspect I'm just being short-sighted.
I'm trying to exercise my GUI from a test framework, which involves manually invoking an event (in this case a button press) within a test script. So far, in addition to other irrelevant guff, I have:
# In GUI class:
self.button_1 = wx.Button(self, id=wx.ID_ANY, label="Button 1")
self.button_1.Bind(wx.EVT_BUTTON, self.button_1)
# In GUI Test class:
event = wx.PyCommandEvent(X, Y)
wx.PostEvent(get_gui_instance(), event)
My problem is that I don't know what X and Y should be (assuming that the rest is ok). Any help is greatly appreciated.
btnInfo = wx.Button(self,-1,"Some Button")
evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId,btnInfo.GetId())
wx.PostEvent(self, evt) #attach event to self ... alternatively maybe attach to btnInfo
should work
So it turns out that because I've re-jigged my GUI to run in a worker thread from GUI Test, I can communicate with it directly. I should have realised this earlier, but nonetheless the result is that I don't need to bother with posting events from GUI Test to GUI since they're running in the same process.
Instead, I can now call the effects of events directly. For example, I can call on_button_press(), bypassing the actual clicking of the button, which would normally fire off the event in wxPython. This allows me to simulate user interaction and test workflows and input ranges, which is exactly what I wanted to do.
Of course, this only works because I'm running my GUI in the same process as the test suite. Posting events seems to be the way forward otherwise, and in answer to my own question, it seems custom events are the way to invoke button presses cross-process. This implies the use of some sort of "test agent" within the GUI to handle those events that are specific for testing.
import wx
class MessageDialog(wx.Dialog):
def __init__(self, message, title, tiempo = 2000):
style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
super(MessageDialog, self).__init__(None, -1, title, style=style)
text = wx.StaticText(self, -1, message)
fuente = wx.Font(pointSize = 20,
family = wx.DEFAULT,
style = wx.NORMAL,
weight = wx.LIGHT,
underline=False,
faceName="",
encoding=wx.FONTENCODING_DEFAULT)
text.SetFont(fuente)
self.ok = wx.Button(self, wx.ID_OK, "OK")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(text,0,wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL,5)
sizer.Add(self.ok, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizerAndFit(sizer)
color = "WHEAT"
self.SetBackgroundColour(color)
self.Center()
self.Refresh()
wx.FutureCall(tiempo,self.salir_ok)
def salir_ok(self):
par_btn = getattr(self, "ok")
evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId, par_btn.GetId())
wx.PostEvent(self, evt)
return
if __name__ == '__main__':
app = wx.App()
dialog = MessageDialog( 'Teclee el nombre del proveedor', 'Proveedor')
dialog.ShowModal()
dialog.Destroy()
app.MainLoop()

wxPython: call a wxApp from another wxApp

Is it possible to run a wxApp from another wxApp?
I am trying to simply call a program I wrote (called DataDeck) from a method of another wxApp, like it was a plugin.
something like:
def on_datadeck_btn_click(self, event):
import datadeck.main
datadeck.main.run()
event.Skip()
where datadeck.main.run() is a classic start of a wxApp:
def run():
app = DataDeck(0)
app.SetAppName("DataDeck")
app.MainLoop()
Right now, it correctly opens DataDeck the first time and it works, but it won't reopen DataDeck a second time after I close it. This would freeze everything.
Update: based on #Mike Driscoll answer, I documented myself more and came to the following solution:
I added an "entry point" in datadeck
def run_as_plugin():
#[do some stuff related to XRC layouts and sysout redirection]
MainGUI = datadeck.gui.maingui.MainGUI()
Where the constructor of MainGUI() automatically shows the wxFrame. Now my application behaves like it was a component of the caller wxApp.
Therefore, I modify the application method as follows:
def on_datadeck_btn_click(self, event):
import datadeck.main
datadeck.main.run_as_plugin()
event.Skip()
It was very simple, indeed! I just had to modify my objects that deal with stdout redirection (not part of this question, I omit the details), and everything worked fine.
There should only be on wx.App. From what I've read online, you can't have two wx.App objects running in one script. You could probably do it using the subprocess module to open a new process though. Take a look at Editra to see some examples for how to do plugins. It is included with wxPython or you can download it separately.
It is perfectly feasible. Not sure why it doesnt work for you.
This example works perfectly:
--main.py--
import wx
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='Main', size=(353,270))
button= wx.Button(self, -1, 'call app', pos=(10,10), size=(-1,30))
self.Bind(wx.EVT_BUTTON, self.capp, button)
def capp(self, event):
import datadeck
datadeck.run()
if __name__ == '__main__':
app = wx.App(0)
frame = MainFrame(None)
frame.Show()
app.MainLoop()
--datadeck.py--
import wx
class DDFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='DDFrame', size=(353,270))
button = wx.Button(self, -1, 'print something', pos=(100,100), size=(-1,30))
self.Bind(wx.EVT_BUTTON, self.say_hello, button)
def say_hello(self, event):
print 'something'
class DataDeck(wx.App):
def OnInit(self):
frame = DDFrame(None)
frame.Show()
return True
def run():
app = DataDeck(1)
app.SetAppName("DataDeck")
app.MainLoop()
if you press the 'call app' button you get the new frame open. And you can open as many as you want.
Created aplications/frames are independent of each other. You can close any of them without affecting the others. And the system doesnt freeze.

Tix ComboBox causes python2.7 to crash

I've been using Tix to create a comboBox and it causes an intermittent crash if the entry box is left empty.
I'm new to Python and very new to GUI programming so I've been using example to teach myself stuff.
When using the following example code, you should be able to enter a value into the entry box or select form the dropdown menu, however if you leave the entry field empty and press go it will cause the python to crash.
import Tix
import tkMessageBox
class App(object):
def __init__(self, window):
window.winfo_toplevel().wm_title("test")
self.window = window
self.combo = Tix.ComboBox(window)
self.combo.insert(Tix.END, 'thing1')
self.combo.insert(Tix.END, 'thing2')
self.combo.entry['state'] = "normal"
self.combo['editable'] = True
self.combo.pack()
button = Tix.Button(window)
button['text'] = "Go"
button['command'] = self.go
button.pack()
def go(self):
tkMessageBox.showinfo('info', self.combo['selection'])
if __name__ == '__main__':
root = Tix.Tk()
App(root)
root.mainloop()
=========================
CRASH DETAILS :
Problem signature:
Problem Event Name: APPCRASH
Application Name: python2.7.exe
Application Version: 0.0.0.0
Application Timestamp: 4cfbf049
Fault Module Name: ntdll.dll
Fault Module Version: 6.1.7600.16385
Fault Module Timestamp: 4a5bdb3b
Exception Code: c0000005
Exception Offset: 0002e23e
OS Version: 6.1.7600.2.0.0.256.48
Locale ID: 2057
Additional Information 1: 0a9e
Additional Information 2: 0a9e372d3b4ad19135b953a78882e789
Additional Information 3: 0a9e
Additional Information 4: 0a9e372d3b4ad19135b953a78882e789
So
A] is it something the example is doing wrong ?
B] is there a better way?
C] Should I be using Tix at all ?
I've noticed that some people seem to shy away from Tix and only use Tkinter. is there a reason for this? Should I not be using Tix ?
++++++++++++++++++++++++++++
Ok so I managed to monkeypatch this by adding value =' ' so the return string is not empty and then lstrip() the value when I need it.
self.combo = Tix.ComboBox(window, value =' ')
...
tkMessageBox.showinfo('info', self.combo['selection'].lstrip())
Very ugly but stops me from crashing for now; until someone wiser can give me a better answer.
If the user deletes the space and then presses 'go' it will of cause crash again!
I am not Tix expert, but this should fix the problem:
tkMessageBox.showinfo('info', self.combo.entry.get())
In between the lines, I would recommend using wxPython for learning GUI programming. It was great for me as a learning environment.
import wx
class MainWin(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.combo = wx.ComboBox(self, choices=["Thing 1", "Thing 2"])
self.button = wx.Button(self, label="Go")
self.button.Bind(wx.EVT_BUTTON, self.OnButton)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.combo, 0, wx.EXPAND)
self.sizer.Add(self.button, 0, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
self.Show()
def OnButton(self, e):
wx.MessageBox(self.combo.GetValue())
app = wx.App(False)
main_win = MainWin(None)
app.MainLoop()

Categories