Using accelerator table without menuitem - python

I have a wxpython desktop application and I am using python 2.7 and wxpython 2.8.
I know how to add an accelerator table to a menuitem but I would like to fire an event when a user press a certain combination of keys without having a menuitem.
The user could have the focus on any field in my UI but when he press (for instance) CTRL-L an event should be fired. How to do this ?
Thanks for any help

You always need to bind your accelerator table items to wx.EVT_MENU, but wxPython doesn't require that you use a menu item object. Here's a simple example:
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
randomId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onKeyCombo, id=randomId)
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('Q'), randomId )])
self.SetAcceleratorTable(accel_tbl)
text = wx.TextCtrl(panel)
text.SetFocus()
#----------------------------------------------------------------------
def onKeyCombo(self, event):
""""""
print "You pressed CTRL+Q!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
In this example, we just create a random id, bind that id to an event handler and then create an accelerator that will fire that handler, which in this case is CTRL+Q. To make things more interesting, I added a text control widget and set the focus to that. Then if you press CTRL+Q, you should see the event handler fire and some text appear in your console window.
You can read more about accelerators here:
http://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/

Related

Dialog is not modal when ProgressDialog and Dialog are opened in sequence

I'm running wxPython 4.0.1 msw (phoenix) with Python 3.6.5 on a Windows7 machine, as well as wxPython 2.9.4 with Python 2.7.
I'm observing an issue with a modal dialog, which doesn't block the access to its parent window behind. This only occurs if I run a progress dialog followed by a modal dialog. This behavior is somehow related to custom dialogs. Integrated dialogs like wx.MessageDialog doesn't seem to have this issue.
To isolate the issue, I've written an example. The first two buttons open either the progress or the modal dialog and work properly. The third button opens both dialogs in sequence. In this case the modal functionality of the custom dialog doesn't work and I'm able to access and close the mainframe. Which causes multiple issues.
Dialog is not modal, the main window can be accessed and closed
import wx
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, title='SomeDialog',
style=wx.DEFAULT_DIALOG_STYLE)
self.button_ok = wx.Button(self, wx.ID_OK, size=(120,-1))
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.Add(self.button_ok, 0, wx.ALL|wx.ALIGN_CENTER, 10)
self.SetSizer(hsizer)
self.SetSize(self.BestSize)
self.Layout()
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(400, 400))
self.button_progress = wx.Button(self, -1, 'Show Progress')
self.button_modal = wx.Button(self, -1, 'Show Modal')
self.button_both = wx.Button(self, -1, 'Show Both')
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_progress)
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_modal)
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_both)
sizer = wx.BoxSizer()
sizer.Add(self.button_progress)
sizer.Add(self.button_modal)
sizer.Add(self.button_both)
self.SetSizer(sizer)
def on_button(self, event):
if event.EventObject is self.button_progress:
self._show_progress_dialog()
elif event.EventObject is self.button_modal:
self._show_modal_dialog()
else:
self._show_progress_dialog()
self._show_modal_dialog()
def _show_progress_dialog(self):
max = 10
dlg = wx.ProgressDialog('Progress dialog example', 'Some message',
maximum=max, parent=self,
style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE)
keepGoing = True
count = 0
while keepGoing and count < max:
count += 1
wx.MilliSleep(250)
wx.Yield()
(keepGoing, skip) = dlg.Update(count)
dlg.Destroy()
def _show_modal_dialog(self):
with SomeDialog(self) as dlg:
dlg.CenterOnParent()
dlg.ShowModal()
if __name__ == '__main__':
app = wx.App()
frame = TestFrame()
frame.Show()
app.MainLoop()
In case this is an issue in the wxpython framework and not an issue with my implementation, it would be great if someone could provide me a workaround to show such dialogs in sequence.
This looks like a bug to me. I'm not sure why its happening, but one workaround would be to use wx.CallLater
changing _show_modal_dialog to:
def _show_modal_dialog(self):
def _make_dialog():
with SomeDialog(self) as dlg:
dlg.CenterOnParent()
dlg.ShowModal()
wx.CallLater(50, _make_dialog) # 50 mils is arbitrary
Seems to resolve the issue of the dialog not acting modalish. The problem with this workaround is that it will be non-blocking, meaning that any code that has to wait for the dialog to return has to be moved into the dialog class or passed to the dialog as a callback.
In the meantime I found a workaround myself which I like to share.
I added a handler catching the windows close event.
class TestFrame(wx.Frame):
def __init__(self):
#...
self.Bind(wx.EVT_CLOSE, self.on_close)
This event function checks if some child dialog is open and modal and performs a veto.
def on_close(self, event):
# In case any modal dialog is open, prevent the frame from closing.
for children in (c for c in self.Children if isinstance(c, wx.Dialog)):
if children.IsModal():
event.Veto()
return
event.Skip()
This is also only a workaround, but I seems to work for my use cases.

Python wx session timeoute

i am trying to write a simple code for timeout a session. my idea is that if user dont interact with the application for 5min, then a function will fireup and kill the application. But if user is active and interact with the application the kill function won't be able to run. Anyone help
my simple wx here
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Timeout",
size=(500, 500))
panel = wx.Panel(self, wx.ID_ANY)
self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Enter")
self.toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
def onToggle(self, event):
print("you have a action")
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
wx.Timer is what you need.
Start a timer with 60000 ms interval (5 minutes). Bind the timer event to some handler. When the timer shots, close your app in that handler.
If the user makes some action (e.g. moving the mouse, or hitting the keyboard) the timer must be stopped and restarted.
For this you need to bind mouse and keyboard events (and let them to be processed as usual by calling Skip). In these handlers is where you restart the timer.
See, for example, this wiki

wxPython: How can I listen to EVT_CHAR events on a multiline TextCtrl?

I am able to successfully listen to EVT_CHAR events on a TextCtrl but when I change the TextCtrl to use TE_MULTILINE then the binding seems to stop working.
self.input = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.input.Bind(wx.EVT_CHAR, self.OnChar)
I am using 3.0.3.dev1820+49a8884 osx-cocoa (phoenix).
How can I listen to EVT_CHAR events on a multiline TextCtrl?
The code exhibited in the question should work, and does if you're not on a Mac. The fact that it doesn't work on Macs is a bug - I reported it at https://github.com/wxWidgets/Phoenix/issues/804 and it's confirmed by the maintainers.
The bug will be fixed in wxPython 4.1, which at the time of writing is not yet released.
If you have to use a version of wxPython that doesn't have the fix, then depending upon your objectives it may be adequate to bind to wx.EVT_TEXT or wx.EVT_KEY_DOWN instead, although neither has quite the same behaviour as wx.EVT_CHAR.
I could not reproduce your problem. Here's the working code that I modified from the Mouse vs the Python blog. I only changed the style of text control to wx.TE_MULTILINE and it still works on Windows7 (wxPython2.8.12.1). Could it be a Phoenix bug?
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Char Event Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
btn = wx.TextCtrl(panel, value="", style=wx.TE_MULTILINE)
btn.Bind(wx.EVT_CHAR, self.onCharEvent)
def onCharEvent(self, event):
keycode = event.GetKeyCode()
controlDown = event.CmdDown()
altDown = event.AltDown()
shiftDown = event.ShiftDown()
print keycode
if keycode == wx.WXK_SPACE:
print "you pressed the spacebar!"
elif controlDown and altDown:
print keycode
event.Skip()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
app.MainLoop()

How do I programmatically pull down a wx.Menu in wxPython

If I have a wx.Menu (in a wx.MenuBar, at the top of a frame, like normal) - how can I cause that menu to drop down and take focus, without clicking on it. I want the behavior to be as if the user had pressed the keyboard accelerator shortcut for that menu (so Alt+F for example, for the &File menu)
Try with wx.PostEvent:
event = wx.MenuEvent(wx.wxEVT_LEFT_DOWN, menuitem.GetId(), menu)
wx.PostEvent(frame, event)
Other wx mouse events: http://www.wxpython.org/docs/api/wx.MouseEvent-class.html
Found in google groups thread
I had the same requirement and found the simple way of using the PopupMenu function. It is not called from the menu object but from the parent of the menu (the window, frame, etc..)
To make sure that the menu appears at a specific position, regardless of your mouse, supply to the PopupMenu function a position parameter as well.
In the example bellow, I turned a platebtn that was opening the menu only when clicked in the right side, in the small area of the down arrow, into a button that opens the same menu in the same way when you click it anywhere on its surface.
Example:
import wx
import wx.lib.platebtn as platebtn
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title, size=(300, 250))
wx.Panel(self,-1, style=wx.SUNKEN_BORDER)
droparrow = platebtn.PB_STYLE_DROPARROW | platebtn.PB_STYLE_SQUARE | platebtn.PB_STYLE_GRADIENT
self.btn1 = platebtn.PlateButton(self, wx.ID_ANY, label=" File ", style=droparrow)
self.btn1.SetPressColor(wx.LIGHT_GREY)
self.menu1 = wx.Menu()
self.menu1.Append(1, "New")
self.menu1.Append(2, "Open")
self.menu1.Append(3, "Exit")
sm = wx.Menu()
sm.Append(8, "sub item 1")
sm.Append(9, "sub item 1")
self.menu1.AppendMenu(7, "Test Submenu", sm)
self.btn1.SetMenu(self.menu1)
self.Bind(wx.EVT_BUTTON, self.OnFile, self.btn1)
def OnFile(self, event):
self.btn1.PopupMenu(self.menu1, pos=(1, self.btn1.GetSize()[1]))
app = wx.App(False)
frame = MyFrame(None, -1, "PopupMenu example")
frame.Show()
app.MainLoop()
To define accelerators for menu in your program,Understand through the given example
example:
file_menu=wx.Menu()
menubar=wx.MenuBar()
menubar.Append(file_menu,"&File")
self.SetMenuBar(menubar)
Now we can access the File menu (here) by pressing ALT+F.
If we have other menus too,on pressing ALT ,it will point to the first Letter of menu bar,from which you can press the next key according to the name of menu_item.

how to create read only text ctrl but support copy paste event

In my application i have text control.
I want my text ctrl should be read only but when some one right click on this he is able to copy the value from that ctrl and he can paste that value in other text control.
If i made my text control read only with wx.TE_READONLY then copy/paste is not working.
Is there any requirement to handle other type of wx event or i have to set more type of wx style flags while creating the text control object.
Thanks in advance.
Hmm Im on windows 7 and don't have any problem copying from a textCtrl that has the wx.READ_ONLY style flag set.
You said you wanted to paste into another textCtrl -obviously you can't have the wx.READ_ONLY style flag set in the textCtrl you want to be able to paste into.
Try out the demo below, you should be able to copy from the textCtrl on the left(which is read only) to the one on the right(which doesn't have the read only style set).
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, id=-1):
wx.Frame.__init__(self,parent,id, size=(200,200))
self.panel = wx.Panel(self,wx.ID_ANY)
bsizer = wx.BoxSizer()
read_only_txtCtrl = wx.TextCtrl(self,-1,
"This textCtrl is read only",
style=wx.TE_MULTILINE|wx.TE_READONLY)
editable_txtCtrl = wx.TextCtrl(self,-1,
"This textCtrl is editable",
style=wx.TE_MULTILINE)
bsizer.Add(read_only_txtCtrl, 1, wx.EXPAND)
bsizer.Add(editable_txtCtrl, 1, wx.EXPAND)
self.SetSizerAndFit(bsizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MainWindow(None)
frame.SetSize((200,200))
frame.Show()
app.MainLoop()
We should bind wx.EVT_TEXT_COPY and wx.EVT_TEXT_PASTE with text control.
one can copy and paste data from text ctrl although text ctrl is read only mode.
Actually I had the same challenge. I needed a textbox where users could paste information (or open -> read file via a menu). The app would then analyze the info for correctness. But I didn't want to allow editing in the textbox - it would suggest that you could correct in the same textbox, while the app was for analysis only. Yeah, requirements, I know.
Weird thing was, I could make a readonly TextCtrl under MacOSX that would allow pasting (but not editing), but not on Windows.
In order to support both, I ended up creating a read/writesuper(MyWin, self).init(None, size=(800,600)) textbox (to allow pasting under windows) and binding wx.EVT_TEXT events to it (apart from wx.EVT_TEXT_PASTE for the obvious pasting). The EVT_TEXT handler that's triggered when the textctrl's contents change, simply shows a dialog that you aren't allowed to do this.
In the app a boolean this.painted plays the following role: when this.painted is true, then modifications of the textctrl aren't allowed. So, the onpaste handler first sets this.painted to false, then modifies the TextCtrl's contents, then sets this.painted to true (otherwise the dialog alert would also go off during the paste event, which I wanted to allow). Unfortunately when the EVT_TEXT handler goes off, the window's contents have already been modified by a user hitting some key. Therefore the app also needs a backup buffer to put back into the TextCtrl when such editing has been detected.
According to the Python docs, TextCtrl.ChangeValue() instead of SetValue() should not trigger the EVT_TEXT event (which would be handy) but I couldn't get that to work, might be my mistake due to not-enough-time-to-investigate-because-it-should-have-been-done-yesterday.
Not an elegant solution, but works.
import wx
class MyWin(wx.Frame):
def __init__(self):
super(MyWin, self).__init__(None, size=(800,600))
self.painted = True
self.backup = ''
self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.HSCROLL)
self.text.Bind(wx.EVT_TEXT_PASTE, self.onpaste)
self.text.Bind(wx.EVT_TEXT, self.ontextchange)
self.Show()
def onpaste(self, event):
if not wx.TheClipboard.IsOpened():
buf = wx.TextDataObject()
wx.TheClipboard.Open()
success = wx.TheClipboard.GetData(buf)
wx.TheClipboard.Close()
if success:
self.painted = False
self.backup = buf.GetText()
self.text.SetValue(self.backup)
self.painted = True
def ontextchange(self, event):
if self.painted:
dlg = wx.MessageDialog(self, 'Editing not allowed', '', wx.OK)
dlg.ShowModal()
dlg.Destroy()
self.painted = False
self.text.SetValue(self.backup)
self.painted = True
app = wx.App()
frame = MyWin()
app.MainLoop()

Categories

Resources