|
| 1 | +# Example of embedding CEF Python browser using wxPython library. |
| 2 | +# This example has a top menu and a browser widget without navigation bar. |
| 3 | + |
| 4 | +# To install wxPython on Linux type "sudo apt-get install python-wxtools". |
| 5 | + |
| 6 | +# Tested with wxPython 2.8 on Linux, wxPython 3.0 on Windows/Mac |
| 7 | +# and CEF Python v55.3. |
| 8 | + |
| 9 | +import wx |
| 10 | +from cefpython3 import cefpython as cef |
| 11 | +import platform |
| 12 | +import sys |
| 13 | +import os |
| 14 | + |
| 15 | +# Constants |
| 16 | +LINUX = (platform.system() == "Linux") |
| 17 | +WINDOWS = (platform.system() == "Windows") |
| 18 | +WIDTH = 800 |
| 19 | +HEIGHT = 600 |
| 20 | + |
| 21 | + |
| 22 | +def main(): |
| 23 | + check_versions() |
| 24 | + sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error |
| 25 | + cef.Initialize() |
| 26 | + app = CefApp(False) |
| 27 | + app.MainLoop() |
| 28 | + del app # Must destroy before calling Shutdown |
| 29 | + cef.Shutdown() |
| 30 | + |
| 31 | + |
| 32 | +def check_versions(): |
| 33 | + print("[wxpython.py] CEF Python {ver}".format(ver=cef.__version__)) |
| 34 | + print("[wxpython.py] Python {ver}".format(ver=sys.version[:6])) |
| 35 | + print("[wxpython.py] wx {ver}".format(ver=wx.version())) |
| 36 | + # CEF Python version requirement |
| 37 | + assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" |
| 38 | + |
| 39 | + |
| 40 | +class MainFrame(wx.Frame): |
| 41 | + |
| 42 | + def __init__(self): |
| 43 | + wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, |
| 44 | + title='wxPython example', size=(WIDTH, HEIGHT)) |
| 45 | + self.browser = None |
| 46 | + |
| 47 | + self.setup_icon() |
| 48 | + self.create_menu() |
| 49 | + self.Bind(wx.EVT_CLOSE, self.OnClose) |
| 50 | + |
| 51 | + # Set wx.WANTS_CHARS style for the keyboard to work. |
| 52 | + # This style also needs to be set for all parent controls. |
| 53 | + self.browser_panel = wx.Panel(self, style=wx.WANTS_CHARS) |
| 54 | + self.browser_panel.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) |
| 55 | + self.browser_panel.Bind(wx.EVT_SIZE, self.OnSize) |
| 56 | + |
| 57 | + # Must show so that handle is available when embedding browser |
| 58 | + self.Show() |
| 59 | + self.embed_browser() |
| 60 | + |
| 61 | + def setup_icon(self): |
| 62 | + icon_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), |
| 63 | + "resources", "wxpython.png") |
| 64 | + if os.path.exists(icon_file): |
| 65 | + icon = wx.IconFromBitmap(wx.Bitmap(icon_file, wx.BITMAP_TYPE_PNG)) |
| 66 | + self.SetIcon(icon) |
| 67 | + |
| 68 | + def create_menu(self): |
| 69 | + filemenu = wx.Menu() |
| 70 | + filemenu.Append(1, "Open") |
| 71 | + exit_ = filemenu.Append(2, "Exit") |
| 72 | + self.Bind(wx.EVT_MENU, self.OnClose, exit_) |
| 73 | + aboutmenu = wx.Menu() |
| 74 | + aboutmenu.Append(1, "CEF Python") |
| 75 | + menubar = wx.MenuBar() |
| 76 | + menubar.Append(filemenu, "&File") |
| 77 | + menubar.Append(aboutmenu, "&About") |
| 78 | + self.SetMenuBar(menubar) |
| 79 | + |
| 80 | + def embed_browser(self): |
| 81 | + window_info = cef.WindowInfo() |
| 82 | + window_info.SetAsChild(self.browser_panel.GetHandle()) |
| 83 | + self.browser = cef.CreateBrowserSync(window_info, |
| 84 | + url="https://www.google.com/") |
| 85 | + self.browser.SetClientHandler(FocusHandler()) |
| 86 | + |
| 87 | + def OnSetFocus(self, _): |
| 88 | + if not self.brower: |
| 89 | + return |
| 90 | + if WINDOWS: |
| 91 | + # noinspection PyUnresolvedReferences |
| 92 | + cef.WindowUtils.OnSetFocus(self.GetHandleForBrowser(), 0, 0, 0) |
| 93 | + self.browser.SetFocus(True) |
| 94 | + |
| 95 | + def OnSize(self, _): |
| 96 | + if not self.browser: |
| 97 | + return |
| 98 | + if WINDOWS: |
| 99 | + # noinspection PyUnresolvedReferences |
| 100 | + cef.WindowUtils.OnSize(self.GetHandleForBrowser(), 0, 0, 0) |
| 101 | + elif LINUX: |
| 102 | + (x, y) = (0, 0) |
| 103 | + (width, height) = self.browser_panel.GetSizeTuple() |
| 104 | + # noinspection PyUnresolvedReferences |
| 105 | + self.browser.SetBounds(x, y, width, height) |
| 106 | + |
| 107 | + def OnClose(self, event): |
| 108 | + # In cefpython3.wx.chromectrl example calling browser.CloseBrowser() |
| 109 | + # and/or self.Destroy() in OnClose is causing crashes when |
| 110 | + # embedding multiple browser tabs. The solution is to call only |
| 111 | + # browser.ParentWindowWillClose. Behavior of this example |
| 112 | + # seems different as it extends wx.Frame, while ChromeWindow |
| 113 | + # from chromectrl extends wx.Window. Calling CloseBrowser |
| 114 | + # and Destroy does not cause crashes, but is not recommended. |
| 115 | + # Call ParentWindowWillClose and event.Skip() instead. See |
| 116 | + # also Issue #107: https://github.com/cztomczak/cefpython/issues/107 |
| 117 | + self.browser.ParentWindowWillClose() |
| 118 | + event.Skip() |
| 119 | + |
| 120 | + # Clear all browser references for CEF to shutdown cleanly |
| 121 | + del self.browser |
| 122 | + |
| 123 | + |
| 124 | +class FocusHandler(object): |
| 125 | + |
| 126 | + def __init__(self): |
| 127 | + pass |
| 128 | + |
| 129 | + def OnTakeFocus(self, **kwargs): |
| 130 | + # print("[wxpython.py] FocusHandler.OnTakeFocus, next={next}" |
| 131 | + # .format(next=kwargs["next_component"]])) |
| 132 | + pass |
| 133 | + |
| 134 | + def OnSetFocus(self, **kwargs): |
| 135 | + # source_enum = {cef.FOCUS_SOURCE_NAVIGATION: "navigation", |
| 136 | + # cef.FOCUS_SOURCE_SYSTEM: "system"} |
| 137 | + # print("[wxpython.py] FocusHandler.OnSetFocus, source={source}" |
| 138 | + # .format(source=source_enum[kwargs["source"]])) |
| 139 | + # return False |
| 140 | + pass |
| 141 | + |
| 142 | + def OnGotFocus(self, browser, **_): |
| 143 | + # Temporary fix for focus issues on Linux (Issue #284). |
| 144 | + # If this is not applied then when switching to another |
| 145 | + # window (alt+tab) and then back to this example, keyboard |
| 146 | + # focus becomes broken, you can't type anything, even |
| 147 | + # though a type cursor blinks in web view. |
| 148 | + print("[wxpython.py] FocusHandler.OnGotFocus:" |
| 149 | + " keyboard focus fix (#284)") |
| 150 | + browser.SetFocus(True) |
| 151 | + |
| 152 | + |
| 153 | +class CefApp(wx.App): |
| 154 | + |
| 155 | + def __init__(self, redirect): |
| 156 | + self.timer = None |
| 157 | + self.timer_id = 1 |
| 158 | + super(CefApp, self).__init__(redirect=redirect) |
| 159 | + |
| 160 | + def OnInit(self): |
| 161 | + self.create_timer() |
| 162 | + frame = MainFrame() |
| 163 | + self.SetTopWindow(frame) |
| 164 | + frame.Show() |
| 165 | + return True |
| 166 | + |
| 167 | + def create_timer(self): |
| 168 | + # See also "Making a render loop": |
| 169 | + # http://wiki.wxwidgets.org/Making_a_render_loop |
| 170 | + # Another way would be to use EVT_IDLE in MainFrame. |
| 171 | + self.timer = wx.Timer(self, self.timer_id) |
| 172 | + self.timer.Start(10) # 10ms |
| 173 | + wx.EVT_TIMER(self, self.timer_id, self.on_timer) |
| 174 | + |
| 175 | + def on_timer(self, _): |
| 176 | + cef.MessageLoopWork() |
| 177 | + |
| 178 | + def OnExit(self): |
| 179 | + self.timer.Stop() |
| 180 | + |
| 181 | + |
| 182 | +if __name__ == '__main__': |
| 183 | + main() |
0 commit comments