pythontkinterchromium-embeddedtkinter-layoutcefpython

How to use CEFPython to add a WebBrowser widget in a Tkinter window?


I want to display a WebBrowser inside my tkinter app. Per the example below:

##############
#  label     #
#————————————#
# webbrowser #
##############

Here is what I have tried: https://github.com/iCarlosCode/Problematika/blob/88a0f13cbdc4ee48ac9690b3ae26cf231ce3c340/calculator/cef3.py

I tried to copy the cefpython example and use the BrowserFrame, but it does not work. The cefpython tkinter example: https://github.com/cztomczak/cefpython/blob/master/examples/tkinter_.py

Anyone can help me?


Solution

  • So, there are 2 core issues with your main code:

    1. You didn't initialize cefpython
    2. Your browser_frame was not configured to fit the whole area

    In short, the answer is:

    import tkinter as tk
    from tkinter import *
    from cefpython3 import cefpython as cef
    import ctypes
    
    def 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 = None
    
    class 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")
            pass
    
    
    if __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:

    Image

    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:

    CefPython with MathJax

    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....