How to use CEFPython to add a WebBrowser widget in a Tkinter window?
So, there are 2 core issues with your main code:
- You didn't initialize
cefpython
- Your
browser_frame
was not configured to fit the whole area
In short, the answer is:
import tkinter as tkfrom tkinter import *from cefpython3 import cefpython as cefimport ctypesdef main(): win = Tk() cef.Initialize() win.minsize(600,600) win.grid_columnconfigure(0, weight=1) win.grid_rowconfigure(0, weight=1) #Create Frame frame = Frame(win, bg='black') frame.grid(row=0, column=0, sticky=('NSWE')) # Create Browser Frame browser_frame = BrowserFrame(frame) browser_frame.pack(fill=tk.BOTH, expand=tk.YES) win.mainloop() cef.Shutdown()class BrowserFrame(tk.Frame): def __init__(self, mainframe, navigation_bar=None): self.navigation_bar = navigation_bar self.closing = False self.browser = None tk.Frame.__init__(self, mainframe) self.mainframe = mainframe self.bind("<FocusIn>", self.on_focus_in) self.bind("<FocusOut>", self.on_focus_out) self.bind("<Configure>", self.on_configure) """For focus problems see Issue #255 and Issue #535. """ self.focus_set() #URLURLURL def embed_browser(self): window_info = cef.WindowInfo() rect = [0, 0, self.winfo_width(), self.winfo_height()] window_info.SetAsChild(self.get_window_handle(), rect) self.browser = cef.CreateBrowserSync(window_info, url="file:///calculo.html") assert self.browser #self.browser.SetClientHandler(LifespanHandler(self)) #self.browser.SetClientHandler(LoadHandler(self)) #self.browser.SetClientHandler(FocusHandler(self)) self.message_loop_work() def get_window_handle(self): if self.winfo_id() > 0: return self.winfo_id() else: raise Exception("Couldn't obtain window handle") def message_loop_work(self): cef.MessageLoopWork() self.after(10, self.message_loop_work) def on_configure(self, _): if not self.browser: self.embed_browser() def on_root_configure(self): # Root <Configure> event will be called when top window is moved if self.browser: self.browser.NotifyMoveOrResizeStarted() def on_mainframe_configure(self, width, height): if self.browser: if WINDOWS: ctypes.windll.user32.SetWindowPos( self.browser.GetWindowHandle(), 0, 0, 0, width, height, 0x0002) self.browser.NotifyMoveOrResizeStarted() def on_focus_in(self, _): #logger.debug("BrowserFrame.on_focus_in") if self.browser: self.browser.SetFocus(True) def on_focus_out(self, _): #logger.debug("BrowserFrame.on_focus_out") """For focus problems see Issue #255 and Issue #535. """ pass def on_root_close(self): #logger.info("BrowserFrame.on_root_close") if self.browser: #logger.debug("CloseBrowser") self.browser.CloseBrowser(True) self.clear_browser_references() else: #logger.debug("tk.Frame.destroy") self.destroy() def clear_browser_references(self): # Clear browser references that you keep anywhere in your # code. All references must be cleared for CEF to shutdown cleanly. self.browser = Noneclass LifespanHandler(object): def __init__(self, tkFrame): self.tkFrame = tkFrame def OnBeforeClose(self, browser, **_): #logger.debug("LifespanHandler.OnBeforeClose") self.tkFrame.quit()class LoadHandler(object): def __init__(self, browser_frame): self.browser_frame = browser_frame def OnLoadStart(self, browser, **_): if self.browser_frame.master.navigation_bar: self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())class FocusHandler(object): """For focus problems see Issue #255 and Issue #535. """ def __init__(self, browser_frame): self.browser_frame = browser_frame def OnTakeFocus(self, next_component, **_): pass#logger.debug("FocusHandler.OnTakeFocus, next={next}".format(next=next_component)) def OnSetFocus(self, source, **_): return True def OnGotFocus(self, **_): #logger.debug("FocusHandler.OnGotFocus") passif __name__ == '__main__': main()
So to fix issue № 1, within the tkinter main loop you have to type cef.Initialize()
and then put cef.Shutdown()
afterwards to close it. The problem is we still can't see the frame because you didn't do the grid_columnconfigure
nor grid_rowconfigure
so even though you did sticky='nsew'
it didn't do anything as there was no weight added to the row and column, to rectify this I used pack instead.
On windows this created for me:
Annoyingly this doesn't work very well on on cefpython
because the MathJax isn't loaded, to rectify this you need to change your calculo.html
to read:
<!DOCTYPE html><html><head><title>MathJax TeX Test Page</title><script type="text/x-mathjax-config" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"> MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});</script><script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script></head><body>When $a \ne 0$, there are two solutions to \(ax^2 + bx + c = 0\) and they are$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ \[\\ yep \\x = {-b \pm \sqrt{b^2-4ac} \over 2a}.\]</body></html>
Because the inline type text/x-mathjax-config
is not supported by cefpython
.
This creates:
Just as a quick pointer this does take a few seconds before the mathjax loads in, so it may be an idea to host a local MathJax config because this will allow for faster loading times most likely.
In relation to having a label above this, it would be rather simple to code this in by simply move the variable frame to grid(row=1, column=0...
(and changing the row weights) and then adding a label in grid(row=0, column=0...
.