11# Example of embedding CEF Python browser using Tkinter toolkit.
22# This example has two widgets: a navigation bar and a browser.
33#
4- # NOTE: This example doesn't work on Mac, details in Issue #306.
4+ # NOTE: This example often crashes on Mac (Tk 8.5) during initial
5+ # app loading with such message: "Segmentation fault: 11".
6+ # Reported as Issue #309.
57#
68# Tested configurations:
7- # - Tk 8.5 on Windows
9+ # - Tk 8.5 on Windows/Mac
810# - Tk 8.6 on Linux
911# - CEF Python v55.3+
1012#
3234
3335# Globals
3436logger = _logging .getLogger ("tkinter_.py" )
35- # Python 2.7 on Windows comes with Tk 8.5 which doesn't support PNG images
36- IMAGE_EXT = ".gif" if WINDOWS else ".png"
37+
38+ # Constants
39+ # Tk 8.5 doesn't support png images
40+ IMAGE_EXT = ".png" if tk .TkVersion > 8.5 else ".gif"
3741
3842
3943def main ():
@@ -47,25 +51,27 @@ def main():
4751 logger .info ("Tk {ver}" .format (ver = tk .Tcl ().eval ('info patchlevel' )))
4852 assert cef .__version__ >= "55.3" , "CEF Python v55.3+ required to run this"
4953 sys .excepthook = cef .ExceptHook # To shutdown all CEF processes on error
54+ root = tk .Tk ()
55+ app = MainFrame (root )
56+ # Tk must be initialized before CEF otherwise fatal error (Issue #306)
5057 cef .Initialize ()
51- app = MainFrame (tk .Tk ())
5258 app .mainloop ()
5359 cef .Shutdown ()
5460
5561
5662class MainFrame (tk .Frame ):
5763
58- def __init__ (self , master ):
64+ def __init__ (self , root ):
5965 self .browser_frame = None
6066 self .navigation_bar = None
6167
6268 # Root
63- master .geometry ("800x600" )
64- tk .Grid .rowconfigure (master , 0 , weight = 1 )
65- tk .Grid .columnconfigure (master , 0 , weight = 1 )
69+ root .geometry ("800x600" )
70+ tk .Grid .rowconfigure (root , 0 , weight = 1 )
71+ tk .Grid .columnconfigure (root , 0 , weight = 1 )
6672
6773 # MainFrame
68- tk .Frame .__init__ (self , master )
74+ tk .Frame .__init__ (self , root )
6975 self .master .title ("Tkinter example" )
7076 self .master .protocol ("WM_DELETE_WINDOW" , self .on_close )
7177 self .master .bind ("<Configure>" , self .on_root_configure )
@@ -135,6 +141,119 @@ def setup_icon(self):
135141 self .master .call ("wm" , "iconphoto" , self .master ._w , self .icon )
136142
137143
144+ class BrowserFrame (tk .Frame ):
145+
146+ def __init__ (self , master , navigation_bar = None ):
147+ self .navigation_bar = navigation_bar
148+ self .closing = False
149+ self .browser = None
150+ tk .Frame .__init__ (self , master )
151+ self .bind ("<FocusIn>" , self .on_focus_in )
152+ self .bind ("<FocusOut>" , self .on_focus_out )
153+ self .bind ("<Configure>" , self .on_configure )
154+ self .focus_set ()
155+
156+ def embed_browser (self ):
157+ window_info = cef .WindowInfo ()
158+ rect = [0 , 0 , self .winfo_width (), self .winfo_height ()]
159+ window_info .SetAsChild (self .get_window_handle (), rect )
160+ self .browser = cef .CreateBrowserSync (window_info ,
161+ url = "https://www.google.com/" )
162+ assert self .browser
163+ self .browser .SetClientHandler (LoadHandler (self ))
164+ self .browser .SetClientHandler (FocusHandler (self ))
165+ self .message_loop_work ()
166+
167+ def get_window_handle (self ):
168+ if self .winfo_id () > 0 :
169+ return self .winfo_id ()
170+ elif MAC :
171+ # On Mac window id is an invalid negative value (Issue #308).
172+ # This is kind of a dirty hack to get window handle using
173+ # PyObjC package. If you change structure of windows then you
174+ # need to do modifications here as well.
175+ # noinspection PyUnresolvedReferences
176+ from AppKit import NSApp
177+ # noinspection PyUnresolvedReferences
178+ import objc
179+ # Sometimes there is more than one window, when application
180+ # didn't close cleanly last time Python displays an NSAlert
181+ # window asking whether to Reopen that window.
182+ # noinspection PyUnresolvedReferences
183+ return objc .pyobjc_id (NSApp .windows ()[- 1 ].contentView ())
184+ else :
185+ raise Exception ("Couldn't obtain window handle" )
186+
187+ def message_loop_work (self ):
188+ cef .MessageLoopWork ()
189+ self .after (10 , self .message_loop_work )
190+
191+ def on_configure (self , _ ):
192+ if not self .browser :
193+ self .embed_browser ()
194+
195+ def on_root_configure (self ):
196+ # Root <Configure> event will be called when top window is moved
197+ if self .browser :
198+ self .browser .NotifyMoveOrResizeStarted ()
199+
200+ def on_mainframe_configure (self , width , height ):
201+ if self .browser :
202+ if WINDOWS :
203+ WindowUtils .OnSize (self .get_window_handle (), 0 , 0 , 0 )
204+ elif LINUX :
205+ self .browser .SetBounds (0 , 0 , width , height )
206+ self .browser .NotifyMoveOrResizeStarted ()
207+
208+ def on_focus_in (self , _ ):
209+ logger .debug ("BrowserFrame.on_focus_in" )
210+ if self .browser :
211+ self .browser .SetFocus (True )
212+
213+ def on_focus_out (self , _ ):
214+ logger .debug ("BrowserFrame.on_focus_out" )
215+ if self .browser :
216+ self .browser .SetFocus (False )
217+
218+ def on_root_close (self ):
219+ if self .browser :
220+ # Close browser and free reference by setting to None
221+ self .browser .CloseBrowser (True )
222+ self .browser = None
223+ self .destroy ()
224+
225+
226+ class LoadHandler (object ):
227+
228+ def __init__ (self , browser_frame ):
229+ self .browser_frame = browser_frame
230+
231+ def OnLoadStart (self , browser , ** _ ):
232+ if self .browser_frame .master .navigation_bar :
233+ self .browser_frame .master .navigation_bar .set_url (browser .GetUrl ())
234+
235+
236+ class FocusHandler (object ):
237+
238+ def __init__ (self , browser_frame ):
239+ self .browser_frame = browser_frame
240+
241+ def OnTakeFocus (self , next_component , ** _ ):
242+ logger .debug ("FocusHandler.OnTakeFocus, next={next}"
243+ .format (next = next_component ))
244+
245+ def OnSetFocus (self , source , ** _ ):
246+ logger .debug ("FocusHandler.OnSetFocus, source={source}"
247+ .format (source = source ))
248+ return False
249+
250+ def OnGotFocus (self , ** _ ):
251+ """Fix CEF focus issues (#255). Call browser frame's focus_set
252+ to get rid of type cursor in url entry widget."""
253+ logger .debug ("FocusHandler.OnGotFocus" )
254+ self .browser_frame .focus_set ()
255+
256+
138257class NavigationBar (tk .Frame ):
139258
140259 def __init__ (self , master ):
@@ -254,97 +373,5 @@ def __init__(self):
254373 # TODO: implement tabs
255374
256375
257- class BrowserFrame (tk .Frame ):
258-
259- def __init__ (self , master , navigation_bar = None ):
260- self .navigation_bar = navigation_bar
261- self .closing = False
262- self .browser = None
263- tk .Frame .__init__ (self , master )
264- self .bind ("<FocusIn>" , self .on_focus_in )
265- self .bind ("<FocusOut>" , self .on_focus_out )
266- self .bind ("<Configure>" , self .on_configure )
267- self .focus_set ()
268-
269- def embed_browser (self ):
270- window_info = cef .WindowInfo ()
271- window_info .SetAsChild (self .winfo_id ())
272- self .browser = cef .CreateBrowserSync (window_info ,
273- url = "https://www.google.com/" )
274- assert self .browser
275- self .browser .SetClientHandler (LoadHandler (self ))
276- self .browser .SetClientHandler (FocusHandler (self ))
277- self .message_loop_work ()
278-
279- def message_loop_work (self ):
280- cef .MessageLoopWork ()
281- self .after (10 , self .message_loop_work )
282-
283- def on_configure (self , _ ):
284- if not self .browser :
285- self .embed_browser ()
286-
287- def on_root_configure (self ):
288- # Root <Configure> event will be called when top window is moved
289- if self .browser :
290- self .browser .NotifyMoveOrResizeStarted ()
291-
292- def on_mainframe_configure (self , width , height ):
293- if self .browser :
294- if WINDOWS :
295- WindowUtils .OnSize (self .winfo_id (), 0 , 0 , 0 )
296- elif LINUX :
297- self .browser .SetBounds (0 , 0 , width , height )
298- self .browser .NotifyMoveOrResizeStarted ()
299-
300- def on_focus_in (self , _ ):
301- logger .debug ("BrowserFrame.on_focus_in" )
302- if self .browser :
303- self .browser .SetFocus (True )
304-
305- def on_focus_out (self , _ ):
306- logger .debug ("BrowserFrame.on_focus_out" )
307- if self .browser :
308- self .browser .SetFocus (False )
309-
310- def on_root_close (self ):
311- if self .browser :
312- # Close browser and free reference by setting to None
313- self .browser .CloseBrowser (True )
314- self .browser = None
315- self .destroy ()
316-
317-
318- class LoadHandler (object ):
319-
320- def __init__ (self , browser_frame ):
321- self .browser_frame = browser_frame
322-
323- def OnLoadStart (self , browser , ** _ ):
324- if self .browser_frame .master .navigation_bar :
325- self .browser_frame .master .navigation_bar .set_url (browser .GetUrl ())
326-
327-
328- class FocusHandler (object ):
329-
330- def __init__ (self , browser_frame ):
331- self .browser_frame = browser_frame
332-
333- def OnTakeFocus (self , next_component , ** _ ):
334- logger .debug ("FocusHandler.OnTakeFocus, next={next}"
335- .format (next = next_component ))
336-
337- def OnSetFocus (self , source , ** _ ):
338- logger .debug ("FocusHandler.OnSetFocus, source={source}"
339- .format (source = source ))
340- return False
341-
342- def OnGotFocus (self , ** _ ):
343- """Fix CEF focus issues (#255). Call browser frame's focus_set
344- to get rid of type cursor in url entry widget."""
345- logger .debug ("FocusHandler.OnGotFocus" )
346- self .browser_frame .focus_set ()
347-
348-
349376if __name__ == '__main__' :
350377 main ()
0 commit comments