I have the following code:
self.sliderR.Bind(wx.EVT_SCROLL,self.OnSlide)
In the function OnSlide I have the inserted the code pdb.set_trace() to help me debug.
In the pdb prompt if I type event.GetEventType() it returns a number (10136) but I have no idea which event that corresponds to.
Does the 10136 refer to the wx.EVT_SCROLL or another event that also triggers the wx.EVT_SCROLL event? If the latter is true, how do I find the specific event?
Thanks.
There isn't a built-in way. You will need to build an event dictionary. Robin Dunn has some code here that will help: http://osdir.com/ml/wxpython-users/2009-11/msg00138.html
Or you can check out my simple example:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Tutorial")
self.eventDict = {}
for name in dir(wx):
if name.startswith('EVT_'):
evt = getattr(wx, name)
if isinstance(evt, wx.PyEventBinder):
self.eventDict[evt.typeId] = name
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
btn = wx.Button(panel, wx.ID_ANY, "Get POS")
btn.Bind(wx.EVT_BUTTON, self.onEvent)
panel.Bind(wx.EVT_LEFT_DCLICK, self.onEvent)
panel.Bind(wx.EVT_RIGHT_DOWN, self.onEvent)
def onEvent(self, event):
"""
Print out what event was fired
"""
evt_id = event.GetEventType()
print self.eventDict[evt_id]
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
Related
I have a virtual ListCtrl with single-selection mode. Now I want to customize clicks, shift-clicks and control-clicks on listitems. To do that I need to GetModifiers() during the event handler of wx.EVT_LIST_ITEM_SELECTED. How do I do that?
I tried the following:
1) Instantiate self.keyboard = wx.KeyboardState() during init() of my custom ListCtrl. Then call self.keyboard.GetModifiers() during the event handling.
2) Instantiate the KeyboardState-class during the event handling.
To test if it works I just print all the modifiers that are captured. But it is always 0 (i.e. MOD_NONE).
I also tried to use wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP instead of wx.EVT_LIST_ITEM_SELECTED. The former has the same problems, the latter isn't even fired, when I click on a listitem.
This assigns both a mouse and an item selected event to the same thing, then uses event.Skip() to ensure that we see both.
Perhaps this might give you an idea on how to progress.
Use with Shift, Ctrl etc and combinations, mods will give you a different number depending on what is pressed.
import wx
class Myframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
pan = wx.Panel(self)
self.listC = wx.ListCtrl(pan, style=wx.LC_REPORT)
self.listC.InsertColumn(0, 'Column1', width=50)
self.listC.InsertColumn(1, 'Column2', width=50)
self.listC.InsertColumn(2, 'Column3', width=50)
self.listC.Bind(wx.EVT_LEFT_DOWN, self.ListClick)
self.listC.Bind(wx.EVT_LIST_ITEM_SELECTED, self.ListSelected)
self.listC.InsertStringItem(0,"Item1")
self.listC.SetStringItem(0, 1,"Col 1 Item")
self.listC.SetStringItem(0, 2,"Col 2 Item")
self.listC.InsertStringItem(1,"Item2")
self.listC.SetStringItem(1, 1,"Col 1 Item")
self.listC.SetStringItem(1, 2,"Col 2 Item")
def ListClick(self, event):
mods = event.GetModifiers()
print mods
event.Skip()
def ListSelected(self, event):
print "selected"
if __name__ == "__main__":
App = wx.App()
Myframe().Show()
App.MainLoop()
I would like to have two (I will add more later) panels that occupy the same space within the frame and for them to be shown/hidden when the respective button is pressed on the toolbar, "mListPanel" should be the default. Currently the settings panel is shown when the application is launched and the buttons don't do anything. I've searched and tried lots of stuff for hours and still can't get it to work. I apologise if it's something simple, I've only started learning python today.
This is what the code looks like now:
import wx
class mListPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
#wx.StaticText(self, -1, label='Search:')#, pos=(10, 3))
#wx.TextCtrl(self, pos=(10, 10), size=(250, 50))
class settingsPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
class bifr(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Title")
self.listPanel = mListPanel(self)
self.optPanel = settingsPanel(self)
menuBar = wx.MenuBar()
fileButton = wx.Menu()
importItem = wx.Menu()
fileButton.AppendMenu(wx.ID_ADD, 'Add M', importItem)
importItem.Append(wx.ID_ANY, 'Import from computer')
importItem.Append(wx.ID_ANY, 'Import from the internet')
exitItem = fileButton.Append(wx.ID_EXIT, 'Exit')
menuBar.Append(fileButton, 'File')
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.Quit, exitItem)
toolBar = self.CreateToolBar()
homeToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'Home', wx.Bitmap('icons/home_icon&32.png'))
importLocalToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'Import from computer', wx.Bitmap('icons/comp_icon&32.png'))
importToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'Import from the internet', wx.Bitmap('icons/arrow_bottom_icon&32.png'))
settingsToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'settings', wx.Bitmap('icons/wrench_plus_2_icon&32.png'))
toolBar.Realize()
self.Bind(wx.EVT_TOOL, self.switchPanels(), settingsToolButton)
self.Bind(wx.EVT_TOOL, self.switchPanels(), homeToolButton)
self.Layout()
def switchPanels(self):
if self.optPanel.IsShown():
self.optPanel.Hide()
self.listPanel.Show()
self.SetTitle("Home")
elif self.listPanel.IsShown():
self.listPanel.Hide()
self.optPanel.Show()
self.SetTitle("Settings")
else:
self.SetTitle("Error")
self.Layout()
def Quit(self, e):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = bifr()
frame.Show()
app.MainLoop()
first off, i would highly suggest that you learn about wxpython sizers and get a good understanding of them (they're really not that hard the understand) as soon as possible before delving deeper into wxpython, just a friendly tip :).
as for your example, a few things:
when your'e not using sizers, you have to give size and position for every window or else they just wont show, so you'd have to change your panel classes to something like this (again this is only for demonstration, you should be doing this with wx.sizers, and not position and size):
class mListPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent,pos=(0,100),size=(500,500))
class settingsPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent,pos=(0,200),size (1000,1000))
further more, when binding an event it should look like this:
self.Bind(wx.EVT_TOOL, self.switchPanels, settingsToolButton)
self.Bind(wx.EVT_TOOL, self.switchPanels, homeToolButton)
notice how I've written only the name of the function without the added (), as an event is passed to it, you cant enter your own parameters to a function emitted from an event (unless you do it with the following syntax lambda e:FooEventHandler(paramaters))
and the event handler (function) should look like this:
def switchPanels(self, event):
if self.optPanel.IsShown():
self.optPanel.Hide()
self.listPanel.Show()
self.SetTitle("Home")
elif self.listPanel.IsShown():
self.listPanel.Hide()
self.optPanel.Show()
self.SetTitle("Settings")
else:
self.SetTitle("Error")
self.Layout()
there should always be a second parameter next to self in functions that are bind to event as the event object is passes there, and you can find its associated methods and parameters in the documentation (in this example it is the wx.EVT_TOOL).
I'm trying to create a gui application using wxpython and I got some issues with the TextCtrl element. The effect that I am trying to achieve is that the user will enter a command to a text field (command) and the command might pop a message that appears in the `(out) field. After some time (0.7 seconds on this example) the message will get back to a default message ("OutPut"). I have two problems:
The message doesn't always appear.
The program sometime crashes due to a segmentation fault and I don't get any Error message to handle that.
I guess that the two related in some way, but I don't know why. In the following example, I only type "test" and wait until the original message appear. Both problems happen on that scenario.
I post here two files that serve as smallest working example. File number 1, creates the GUI,
import wx
import os.path
import os
from threading import Thread
from time import sleep
from MsgEvent import *
class MainWindow(wx.Frame):
def __init__(self):
super(MainWindow, self).__init__(None, size=(400,200),)
#style=wx.MAXIMIZE)
self.CreateInteriorWindowComponents()
self.CreateKeyBinding()
self.command.SetFocus()
self.Layout()
def Test(self):
self.command.SetValue('open')
self.ParseCommand(None)
def PostMessage(self,msg):
'''For its some reason, this function is called twice,
the second time without any input. I could'nt understand why.
For that, the test :if msg == None'''
if msg == None: return
worker = MessageThread(self,msg,0.7,'OutPut')
worker.start()
def CreateKeyBinding(self):
self.command.Bind(wx.EVT_CHAR,self.KeyPressed)
def KeyPressed(self,event):
char = event.GetUniChar()
if char == 13 and not event.ControlDown(): #Enter
if wx.Window.FindFocus() == self.command:
self.ParseCommand(event)
else:
event.Skip()
def ParseCommand(self,event):
com = self.command.GetValue().lower() #The input in the command field
self.PostMessage(com)
def CreateInteriorWindowComponents(self):
''' Create "interior" window components. In this case it is just a
simple multiline text control. '''
self.panel = wx.Panel(self)
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(12)
self.vbox = wx.BoxSizer(wx.VERTICAL)
#Out put field
self.outBox = wx.BoxSizer(wx.HORIZONTAL)
self.out = wx.TextCtrl(self.panel, style=wx.TE_READONLY|wx.BORDER_NONE)
self.out.SetValue('OutPut')
self.out.SetFont(font)
self.outBox.Add(self.out,proportion=1,flag=wx.EXPAND,border=0)
self.vbox.Add(self.outBox,proportion=0,flag=wx.LEFT|wx.RIGHT|wx.EXPAND,border=0)
#Setting the backgroudn colour to window colour
self.out.SetBackgroundColour(self.GetBackgroundColour())
#Commands field
self.commandBox = wx.BoxSizer(wx.HORIZONTAL)
self.command = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER)
self.command.SetFont(font)
self.commandBox.Add(self.command, proportion=1, flag=wx.EXPAND)
self.vbox.Add(self.commandBox, proportion=0, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=0)
self.panel.SetSizer(self.vbox)
return
#Close the window
def OnExit(self, event):
self.Close() # Close the main window.
app = wx.App()
frame = MainWindow()
frame.Center()
frame.Show()
app.MainLoop()
And file number 2, called MsgThread.py handle the events.
import wx
import threading
import time
myEVT_MSG = wx.NewEventType()
EVT_MSG = wx.PyEventBinder(myEVT_MSG,1)
class MsgEvent(wx.PyCommandEvent):
""" event to signal that a message is ready """
def __init__(self,etype,eid,msg='',wait=0,msg0=''):
""" create the event object """
wx.PyCommandEvent.__init__(self,etype,eid)
self._msg = unicode(msg)
self._wait_time = wait
self._reset_message = unicode(msg0)
def GetValue(self):
""" return the value from the event """
return self._msg
class MessageThread(threading.Thread):
def __init__(self,parent,msg='',wait=0,msg0=''):
"""
parent - The gui object that shuold recive the value
value - value to handle
"""
threading.Thread.__init__(self)
if type(msg) == int:
msg = unicode(msg)
self._msg = msg
self._wait_time = wait
self._reset_message = msg0
self._parent = parent
print self._msg
def run(self):
""" overide thread.run Don't call this directly, its called internally when you call Thread.start()"""
self._parent.out.SetValue(unicode(self._msg))
time.sleep(self._wait_time)
self._parent.out.SetValue(self._reset_message)
self._parent.MessageFlag = False
event = MsgEvent(myEVT_MSG,-1,self._msg)
wx.PostEvent(self._parent,event)
What is faulty?
WX Python is not thread safe except for 3 functions (wx.CallAfter, wx.CallLater, wx.PostEvent)
So basically what you have to do is ensure you never call any widget directly from within the subthread. You may use events or CallAfter.
You also might want to check this:
Nice writeup about threads and wxpython
This might help as well
Lokla
I'm struggling to find a way to use function from within a wxPython event handler function. Say I have a button that when clicked it runs a function called OnRun using an event handler. However, the user forgot to click a RadionButton before the OnRun button and I want to pop-up a MessageDialog telling them they forgot a step. I'm going to reuse this MessageDialog several times, thus rather than doing a copy/paste of the same code I would like to just have this MessageDialog in a function and call this MessageDialog function if the user forgets to check a RadioButton.
If this wasn't a function used in an Event Handler I know I could simply put the function as an argument but I'm not seeing a way I can do this with these. Any help here would be appreciated.
The following code shows how to create a little method that you can reuse to show custom dialogs and tells the user that they need to accept the agreement. You can change the conditionals to do whatever you want, of course. And you can change the "showMsg" method so that the icon changes too with just a little tweaking.
import wx
########################################################################
class TestFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Test")
panel = wx.Panel(self)
self.radios = wx.RadioBox(panel, label="Choices",
choices = ["None", "Accept", "Reject"])
button = wx.Button(panel, label="Run")
button.Bind(wx.EVT_BUTTON, self.onBtn)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.radios, 0, wx.ALL, 5)
sizer.Add(button, 0, wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onBtn(self, event):
""""""
btn = event.GetEventObject()
btn.SetLabel("Running")
radio_value = self.radios.GetStringSelection()
if radio_value == "None":
self.showMsg("Error", "Please Choose 'Accept' or 'Reject'!")
elif radio_value == "Accept":
self.showMsg("Message", "Thank you for accepting!")
else:
self.showMsg("Message", "We're sorry, but you cannot continue the install")
#----------------------------------------------------------------------
def showMsg(self, title, msg):
""""""
dlg = wx.MessageDialog(None, msg, title, wx.OK | wx.ICON_QUESTION)
dlg.ShowModal()
dlg.Destroy()
if __name__ == "__main__":
app = wx.App(False)
frame = TestFrame()
frame.Show()
app.MainLoop()
I will make a stab at this, even if the answer seems too direct. I would set a property in the enclosing frame that flags whether the Radio Button has been clicked or not. Then when OnRun is called check that property. Should it be in the wrong state, call the MessageDialog and abort/pause/modify the OnRun.
EDIT Here is what I mean, a trivial example with two buttons, neither of which will lead to further action unless a user agreement is clicked.
import wx
class ButtonFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Button Example',
size=(300, 100))
panel = wx.Panel(self, -1)
self.radio = wx.RadioButton(panel, -1, "Accept user agreement", pos=(50, 10))
self.button = wx.Button(panel, -1, "Run", pos=(50, 30))
self.Bind(wx.EVT_BUTTON, self.OnRun, self.button)
self.button.SetDefault()
self.btn2 = wx.Button(panel, -1, "Walk", pos=(150, 30))
self.Bind(wx.EVT_BUTTON, self.OnWalk, self.btn2)
def OnRun(self, event):
if not self.CheckRadio():
return
self.button.SetLabel("Running")
def OnWalk(self, event):
if not self.CheckRadio():
return
self.btn2.SetLabel("Walking")
def CheckRadio(self):
accepted = self.radio.GetValue()
if not accepted:
dlg = wx.MessageDialog(None, 'First accept the user agreement',
'MessageDialog', wx.OK | wx.ICON_QUESTION)
result = dlg.ShowModal() # result not used in this demo
dlg.Destroy()
return False
else:
return True
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = ButtonFrame()
frame.Show()
app.MainLoop()
Code is adapted from Listing 7.11 of wxPython in Action. I hope this helps, if you have not already solved this n the time that has passed.
You can create your own MessageDialog (inheriting), or you can use functools.partial/lambda to pass an additional argument to the event handler:
self.Bind(wx.MY_EVENT, lambda evt: self.OnEventX(evt, handler=foo), id=12)
I have a text box in wxpython (see below) that stores the name of the value as a variable.
I am trying to do two things:
After the answer is entered, I want to display another question, and assign the new answer to another variable, using the same or an idential TextEntryDialog window.
Ideally, from a user standpoint, they just see a prompt, type an answer (or select from a list), and then after hitting OK, the prompt will change, and they will type in a new answer (which will be assigned to a new variable).
So why am I trying to do this? So that after the end of this Q & A session, I can write all of the variables to a database using pyodbc (which I dont need to know about right now).
So could you please tell me how I can automatically generate new prompts once an answer has been entered without closing the app and losing the variable data? And is there anyway to automatically backup this variable data while the user is answering in case the app crashes? My question list is about 250 questions long, and I dont want all those variables lost if my application crashes (which they tend to do)
Thanks!
import wx
class applicationName(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
#create panel and button
panel = wx.Panel(self)
test = wx.TextEntryDialog(None, "What's your name?", 'Title', 'Enter name')
if test.ShowModal() == wx.ID_OK:
apples = test.GetValue()
wx.StaticText(panel, -1, apples, (10,10))
if __name__ =='__main__':
app = wx.PySimpleApp()
frame = applicationName(parent=None, id=-1)
frame.Show()
app.MainLoop()
I don't recommend creating and destroying 250 dialogs like the other fellow did. I would probably create a list or dict at the beginning of my program that would get appended to whenever the user enters an answer. Also in that event handler, I would reset the StaticText control with a new question. You might need to refresh the screen if your questions vary a lot in length, but I think that would be a lot better than showing hundreds of dialogs in a row.
EDIT - Added some example code below:
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.answers = {}
self.questions = ["What is your age?", "What is your weight?",
"Which of the following computer languages is the best ever: C++, PHP, Fortran, COBOL, Python?"]
self.nextQuestion = 0
self.question = wx.StaticText(panel, label="What is your name?")
self.answer = wx.TextCtrl(panel, value="")
submitBtn = wx.Button(panel, label="Submit")
submitBtn.Bind(wx.EVT_BUTTON, self.onSubmit)
sizer = wx.BoxSizer(wx.VERTICAL)
self.panelSizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.question, 0, wx.ALL, 5)
sizer.Add(self.answer, 0, wx.ALL|wx.EXPAND, 5)
sizer.Add(submitBtn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
self.panelSizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(self.panelSizer)
#----------------------------------------------------------------------
def onSubmit(self, event):
""""""
self.answers[self.question.GetLabel()] = self.answer.GetValue()
self.question.SetLabel(self.questions[self.nextQuestion])
self.answer.SetValue("")
self.nextQuestion += 1
print self.answers
self.panelSizer.Fit(self)
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
Write a function that an entry dialog and displays and returns the value the user entered
Put it in a for loop
You can even do something like:
answers = [getanswer(q) for q in questions]
getanswer could look like:
def getanswer(q):
test = wx.TextEntryDialog(None, *q)
if test.ShowModal() == wx.ID_OK:
return test.GetValue() # returns None the user didn't select OK.
questions can contain lists or tuples of the stuff you want to pass to the constructor of wx.TextEntryDialog.