In Python 3.10.5 on Windows 11 employing the wxPython 4.2.0 package, I am using the wx.html2.WebView widget to display HTML content in a help dialog. Now, I want the dialog to close based on the Escape key press event. However, if I bind the wx.EVT_CHAR_HOOK event handlerr to the dialog object, the event does not get propagated and thus the handler is not executed when the keyboard focus is on the WebView widget, but if the focus is on other widget in the dialog, such as a close button I added there next to the WebView widget, the event is received properly. To better demonstrate it, here is the code I am using:
class HelpHTMLDialog(wx.Dialog):
def __init__(self, title, parent=None):
super(HelpHTMLDialog, self).__init__(parent=parent, title=title, size=(1000, 800))
self.Bind(wx.EVT_CHAR_HOOK, self.charHook)
self.addWidgets()
self.Centre()
self.ShowModal()
def addWidgets(self):
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
# HTML browser containing the help page
self.browser = wx.html2.WebView.New(self.panel)
html = "<p>Hello there! <a href='#'>Click me</a>.</p>"
self.browser.SetPage(html, "")
vbox.Add(self.browser, 1, wx.EXPAND | wx.ALL, 5)
# Close button
self.closeButton = wx.Button(self.panel, label="Close")
self.closeButton.Bind(wx.EVT_BUTTON, self.onCloseButtonClick)
vbox.Add(self.closeButton, 1, wx.EXPAND | wx.ALL, 5)
self.panel.SetSizer(vbox)
def close(self):
self.Destroy()
def charHook(self, event):
key = event.GetKeyCode()
if key == wx.WXK_ESCAPE:
self.close()
else:
event.Skip()
def onCloseButtonClick(self, event):
self.close()
The question therefore is - How can the key press or charhook event be propagated from the WebView widget to the dialog so that I can handle it? I also tried binding the event to the WebView widget instead of the dialog object, but that had the same effect.
Related
Q1: Is there a mouse single click event in wxpython. I didn't find a single click event. So I use Mouse_Down and Mouse_UP to implement that.
Q2: I also have a double click event. But double click event can emmit mouse up and down also. How can I distinguish them?
To distinguish click and double click you can use wx.Timer:
Start the timer in OnMouseDown
In OnDoubleClick event handler stop the timer
If timer wasn't stopped by OnDoubleClick you can handle single click in the timer handler.
Similar discussion in Google Groups.
Code example (needs polishing and testing of course but gives a basic idea):
import wx
TIMER_ID = 100
class Frame(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title, size=(350,200))
self.timer = None
self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
def OnDoubleClick(self, event):
self.timer.Stop()
print("double click")
def OnSingleClick(self, event):
print("single click")
self.timer.Stop()
def OnLeftDown(self, event):
self.timer = wx.Timer(self, TIMER_ID)
self.timer.Start(200) # 0.2 seconds delay
wx.EVT_TIMER(self, TIMER_ID, self.OnSingleClick)
app = wx.App(redirect=True)
top = Frame("Hello World")
top.Show()
app.MainLoop()
I'm implementing the help menu of an App done in wxPython. By now, I'm using a txt file opened in a frame. I would like to have hyperlinks in the help text in order to open other txt files in the same frame. However, I don't know how to do this. I don't even know if this is the most elegant way to implement a help menu. Any suggestion will be very useful.
Below you can find part of the code I'm using (you will need a txt file called "Help_Main_App.txt"):
import wx
class Help_Frame(wx.Frame):
title = "Help, I need somebody, help..."
def __init__(self):
wx.Frame.__init__(self, wx.GetApp().TopWindow, title=self.title, size=(450,500))
self.CreateStatusBar()
panel = wx.Panel(self, wx.ID_ANY)
panel.SetBackgroundColour('#ededed')
self.Centre()
vBox = wx.BoxSizer(wx.VERTICAL)
hBox = wx.BoxSizer(wx.HORIZONTAL)
self.textbox = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(-1, 295))
hBox.Add(self.textbox, 1, flag=wx.EXPAND)
vBox.Add(hBox, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
panel.SetSizer(hBox)
defaultdir, filename = './', 'Help_Main_App.txt'
self.filePath = '/'.join((defaultdir, filename))
self.textbox.LoadFile(self.filePath)
self.textbox.Disable()
class Main_Window(wx.Frame):
def __init__(self, parent, title):
#wx.Frame.__init__(self, parent, title = title, pos = (0, 0), size = wx.DisplaySize())
wx.Frame.__init__(self, parent, title=title, size=(1000,780))
self.Center()
# Setting up the menu.
filemenu = wx.Menu()
helpmenu = wx.Menu()
menuExit = filemenu.Append(wx.ID_EXIT,"&Exit"," Close window and exit program")
menuHelp = helpmenu.Append(wx.ID_HELP, "&Help"," Help of this program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
menuBar.Append(helpmenu,"&Help") # Adding the "helpmenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
# Set event handlers
self.Bind(wx.EVT_MENU, self.OnHelp, menuHelp)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
def OnHelp(self,e):
Help_Frame().Show()
def OnExit(self,e):
self.Close(True) # Close the frame.
def main():
app = wx.App(False)
frame = Main_Window(None, "Main App")
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()
I recommend using an HTMLWindow for something simple like that. It can handle only simple HTML, so don't try to make a website with it as HTMLWindow doesn't support CSS or javascript.
I wrote a simple About box using it. You can read about it here:
http://www.blog.pythonlibrary.org/2008/06/11/wxpython-creating-an-about-box/
The basic idea is to subclass HTMLWindow and override its OnLinkClicked method. Then you can use Python's webbrowser to open the user's default browser. Or you can try using subprocess, although that will be a lot less likely to work unless you always know what is installed on your target machines.
Further to Mikes answer if you are able to use wxPython 2.9.4 or above you can consider using the more advanced html2 webview which does support CSS and javascript. Using this you could make the help as a simple website that can be viewed in program.
http://wxpython.org/Phoenix/docs/html/html2.WebView.html
Its also worth mentioning that if (for some strange reason) you don't want to work with you could achieve a similar outcome with a StyledTxtCtrl.
Late to the party but just for the sake of completeness (seeing that the OP's code was using wx.TextCtrl to show the help text), here is an example on how to add and launch hyperlinks using wx.TextCtrl (I have attached any explanations on the code comments):
class HelpDialog(wx.Dialog):
"""Help Dialog."""
def __init__(self, parent, title, style):
"""Init."""
wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
title=title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=style)
# We need the 'wx.TE_AUTO_URL' style set.
self.help = wx.TextCtrl(self, wx.ID_ANY, '', DPOS, DSIZE,
wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2|wx.TE_WORDWRAP)
# Events - this is the interesting part,
# we catch the mouse on hovering the hyperlink:
self.help.Bind(wx.EVT_TEXT_URL, self.openHlpUrl)
# Show dialog
self.ShowModal()
def openHlpUrl(self, event):
"""Open help URL."""
# We get the starting and ending points on
# the text stored in our ctrl from this event
# and we slice it:
url = self.help.GetValue()[event.GetURLStart():event.GetURLEnd()]
# We want to capture the left up mouse event
# when hovering on the hyperlink:
if event.MouseEvent.LeftDown():
# Let's be wxpythion native and launch the browser this way:
wx.LaunchDefaultBrowser(url)
I'm trying to get my empty frame so that when I click the X it just hides the window, and then if I hit the dock icon it'll reveal the window. It's turning out to be more of a challenge than I expected.. I used http://wiki.wxpython.org/Optimizing%20for%20Mac%20OS%20X/ but I can't make ends of it.
Here's my code:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "title",style=wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.CAPTION, size=(300,300))
panel = wx.Panel(self)
def MacReopenApp(self, event):
print "Attempting to reveal the window."
def MacHideApp(self, event):
print "Attempting to hide the window."
if __name__ == '__main__':
app = wx.App()
frame = Frame()
frame.Show()
app.MainLoop()
The document you link to states that you need to add those event handlers on the App. You have currently defined them on a frame. So you need to extend wx.App and define those event handlers, and instantiate your own App instead of wx.App.
So (shortened example copied from your link):
class MyApp(wx.App):
def __init__(self, *args, **kwargs):
wx.App.__init__(self, *args, **kwargs)
# This catches events when the app is asked to activate by some other
# process
self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate)
#.....
def MacReopenApp(self):
"""Called when the doc icon is clicked, and ???"""
self.BringWindowToFront()
app = MyApp(False)
app.MainLoop()
I'm trying to create something like the categories panel in Wordpress, with wxPython.
What I'm trying to figure out, is how to add a widget when the user clicks a button (like "Add New Category")
Here is my code:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(300,200))
self.panel = wx.Panel(self, -1)
button = wx.Button(self.panel,-1,"Button")
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(button)
add_btn = wx.Button(self.panel,-1,"Add")
add_btn.Bind(wx.EVT_BUTTON, self.add)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(add_btn)
main_vbox = wx.BoxSizer(wx.VERTICAL)
main_vbox.Add(self.vbox)
main_vbox.Add(hbox)
self.panel.SetSizer(main_vbox)
self.Centre()
self.Show(True)
def add(self,event):
self.vbox.Add((wx.Button(self.panel,-1,"Button")))
if __name__ == "__main__":
app = wx.App()
MainWindow(None, -1, 'Add a Button')
app.MainLoop()
My problem is, the button gets added on top of the previous button. I'm rather mystified by this, because if I delete the event argument of the add() function, and then call it in the __init__ method, self.add(), it works fine. But that doesn't help me any because I need to add the widgets when the user clicks the button.
Any help is much appreciated.
Call self.panel.Layout() after adding the button. This function is called automatically when you resize a window with children (try it with your current code), but not when you add widgets to it.
I have a wxPython notebook, in this case a wx.aui.AuiNotebook. (but this problem has happened with other kinds of notebooks as well.) In my notebook I have a widget, in this case a subclass of ScrolledPanel, for which I am trying to do some custom event handling (for wx.EVT_KEY_DOWN). However, the events are not being handled. I checked my code outside of the notebook, and the event handling works, but when I put my widget in the notebook, the event handler doesn't seem to get invoked when the event happens.
Does the notebook somehow block the event? How do I solve this?
I tried reproducing your problem but it worked fine for me. The only thing I can think of is that there is one of your classes that also binds to wx.EVT_KEY_DOWN and doesn't call wx.Event.Skip() in its callback. That would prevent further handling of the event. If your scrolled panel happens to be downstream of such an object in the sequence of event handlers it will never see the event.
For reference, here's an example that worked for me (on Windows). Is what you're doing much different than this?
import wx
import wx.aui, wx.lib.scrolledpanel
class AppFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
# The notebook
self.nb = wx.aui.AuiNotebook(self)
# Create a scrolled panel
panel = wx.lib.scrolledpanel.ScrolledPanel(self, -1)
panel.SetupScrolling()
self.add_panel(panel, 'Scrolled Panel')
# Create a normal panel
panel = wx.Panel(self, -1)
self.add_panel(panel, 'Simple Panel')
# Set the notebook on the frame
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(self.sizer)
# Status bar to display the key code of what was typed
self.sb = self.CreateStatusBar()
def add_panel(self, panel, name):
panel.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.nb.AddPage(panel, name)
def on_key(self, event):
self.sb.SetStatusText("key: %d [%d]" % (event.GetKeyCode(), event.GetTimestamp()))
event.Skip()
class TestApp(wx.App):
def OnInit(self):
frame = AppFrame(None, -1, 'Click on a panel and hit a key')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = TestApp(0)
app.MainLoop()