pythonpyqt5chromium-embeddedcefpython

CEFPython unable to embed inside pyqt5 application


I am new to Cefpython and PyQt5 both. I have tried to follow the tutorial in the cefpython repository. I was trying to embed cefpython inside a pyqt application and haven't achieved any success, what's wrong here?

import sys

from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QWindow
from PyQt5.QtWidgets import *
from cefpython3 import cefpython as cef

from navbar import NavigationBar


class ChromiumApplication(QApplication):
    def __init__(self):
        super().__init__([])
        self.timer = self.create_timer()

    def create_timer(self):
        timer = QTimer()
        timer.timeout.connect(self.on_timeout)
        timer.start(10)
        return timer

    def on_timeout(self):
        cef.MessageLoopWork()


class ChromiumBrowserWindow(QMainWindow):
    DEFAULT_TITLE = "Chromium Browser"
    DEFAULT_WIDTH = 800
    DEFAULT_HEIGHT = 600

    def __init__(self):
        super().__init__()
        self.chrome = None
        self.web_view = None
        self.setWindowTitle(self.DEFAULT_TITLE)
        self.init_window()
        self.show()

    def init_window(self):
        self.resize(self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)

        self.web_view = WebViewWidget(parent=self)
        self.chrome = NavigationBar(parent=self, browser=self.web_view.browser)

        layout = QGridLayout()
        layout.addWidget(self.chrome, 0, 0)
        layout.setColumnStretch(0, 1)

        layout.addWidget(self.web_view, 1, 0)
        layout.setRowStretch(1, 2)

        layout.setContentsMargins(0, 0, 0, 0)

        frame = QFrame()
        frame.setLayout(layout)

        self.setCentralWidget(frame)

        self.web_view.init_browser()

    def closeEvent(self, event):
        if self.web_view.browser is not None:
            self.web_view.browser.CloseBrowser(True)  # force=True
            self.web_view.browser = None  # required to close cleanly


class WebViewWidget(QWidget):
    DEFAULT_URL = "https://www.google.com"
    HANDLERS = []

    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.browser = None
        self.browser_window = None
        self.timer = None

    def init_browser(self):
        self.browser_window = QWindow()
        window_config = cef.WindowInfo()
        rect_pos_and_size = [0, 0, self.width(), self.height()]
        window_config.SetAsChild(self.get_window_handle(), rect_pos_and_size)
        self.browser = cef.CreateBrowserSync(window_config, url=self.DEFAULT_URL)
        self.set_handlers()

    def get_window_handle(self):
        return int(self.browser_window.winId())

    def set_handlers(self):
        for handler in self.HANDLERS:
            self.browser.SetClientHanlder(handler(self))


if __name__ == "__main__":
    sys.excepthook = cef.ExceptHook
    cef.Initialize()
    app = ChromiumApplication()
    window = ChromiumBrowserWindow()
    app.exec()
    app.timer.stop()
    cef.Shutdown()

I know the issue is with my code because the example provided by cefpython works perfectly on my machine. I have no clue what I did wrong here, any suggestions will help!

EDIT: (code for navigation bar)

from PyQt5.QtWidgets import (
    QApplication,
    QFrame,
    QHBoxLayout,
    QPushButton,
    QLineEdit,
)


class NavigationBar(QFrame):
    def __init__(self, parent=None, browser=None):
        super().__init__()
        self.parent = parent
        self.browser = browser

        self.back_btn = self.create_button("Back")
        self.back_btn.clicked.connect(self.on_back)

        self.forward_btn = self.create_button("Forward")
        self.forward_btn.clicked.connect(self.on_forward)

        self.refresh_btn = self.create_button("Refresh")
        self.refresh_btn.clicked.connect(self.on_refresh)

        self.url_bar = self.create_url_bar()
        self.url_bar.returnPressed.connect(self.on_search)

        self.frame_layout = QHBoxLayout()
        self.init_layout()

    def create_button(self, name):
        button_icon_path = f"./{name}.svg"
        button = QPushButton()
        button.setStyleSheet(f"""
        QPushButton {{
            background-image: url("{button_icon_path}");
            background-repeat: no-repeat;
            background-position: center;
            background-color: rgba(0, 0, 0, 0.1);
            border: 10px;
            border-radius: 8px;
            padding: 10px;
        }}
        QPushButton:hover {{
            background-color: rgba(0, 0, 0, 0.5);
        }}
        
        QPushButton:pressed {{
            background-color: none;
        }}
        """)
        return button

    def create_url_bar(self):
        search = QLineEdit()
        search.setStyleSheet("""QLineEdit {
           min-width: 300px;
           padding: 10px;
           margin-left: 50px;
           margin-right: 30px;

           border-width: 10px;
           border-radius: 8px;

           background-color: rgba(0, 0, 0, 0.2);
           color: white;
       }

       QLineEdit:hover {
           background-color: #454549;
       }
       """)
        return search

    def init_layout(self):
        self.setStyleSheet("""
        background: #2A292E;
        max-height: 40px;
        """)
        self.frame_layout.addWidget(self.back_btn, 0)
        self.frame_layout.addWidget(self.forward_btn, 0)
        self.frame_layout.addWidget(self.refresh_btn, 0)
        self.frame_layout.addWidget(self.url_bar, 1)
        self.setLayout(self.frame_layout)

    def on_back(self):
        if self.browser is not None:
            self.browser.GoBack()

    def on_forward(self):
        if self.browser is not None:
            self.browser.GoForward()

    def on_refresh(self):
        if self.browser is not None:
            self.browser.Reload()

    def on_search(self):
        if self.browser is not None:
            url = self.url_bar.text()
            self.browser.LoadUrl(url)


if __name__ == "__main__":
    app = QApplication([])
    nav = NavigationBar()
    nav.show()
    app.exec()

Solution

  • The problem is that the QWindow used to render the browser is hidden. The solution is to create a QWidget using QWidget::createWindowContainer() and add it using a layout.

    import sys
    
    from cefpython3 import cefpython as cef
    
    
    from PyQt5.QtCore import QTimer
    from PyQt5.QtGui import QWindow
    from PyQt5.QtWidgets import QApplication, QFrame, QGridLayout, QMainWindow, QVBoxLayout, QWidget
    
    from navbar import NavigationBar
    
    
    class ChromiumApplication(QApplication):
        def __init__(self):
            super().__init__([])
            self.timer = self.create_timer()
    
        def create_timer(self):
            timer = QTimer()
            timer.timeout.connect(self.on_timeout)
            timer.start(10)
            return timer
    
        def on_timeout(self):
            cef.MessageLoopWork()
    
    
    class ChromiumBrowserWindow(QMainWindow):
        DEFAULT_TITLE = "Chromium Browser"
        DEFAULT_WIDTH = 800
        DEFAULT_HEIGHT = 600
    
        def __init__(self):
            super().__init__()
            self.chrome = None
            self.web_view = None
            self.setWindowTitle(self.DEFAULT_TITLE)
            self.init_window()
            self.show()
    
        def init_window(self):
            self.resize(self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
    
            self.web_view = WebViewWidget()
            self.chrome = NavigationBar(parent=self, browser=self.web_view.browser)
    
            frame = QFrame()
            self.setCentralWidget(frame)
            layout = QGridLayout(frame)
            layout.addWidget(self.chrome, 0, 0)
            layout.setColumnStretch(0, 1)
    
            layout.addWidget(self.web_view, 1, 0)
            layout.setRowStretch(1, 2)
    
            layout.setContentsMargins(0, 0, 0, 0)
    
        def closeEvent(self, event):
            if self.web_view.browser is not None:
                self.web_view.browser.CloseBrowser(True)
                del self.web_view.browser
    
    
    class WebViewWidget(QWidget):
        DEFAULT_URL = "https://www.google.com"
        HANDLERS = []
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self._browser = None
            self._browser_widget = None
            lay = QVBoxLayout(self)
            lay.setContentsMargins(0, 0, 0, 0)
            self.init_browser()
    
        @property
        def browser(self):
            return self._browser
    
        @browser.deleter
        def browser(self):
            self._browser = None
    
        def init_browser(self):
            browser_window = QWindow()
            window_config = cef.WindowInfo()
            window_config.SetAsChild(
                int(browser_window.winId()), list(self.rect().getRect())
            )
            self._browser = cef.CreateBrowserSync(window_config, url=self.DEFAULT_URL)
            self._browser_widget = QWidget.createWindowContainer(browser_window)
            self.layout().addWidget(self._browser_widget)
            self.set_handlers()
    
        def set_handlers(self):
            for handler in self.HANDLERS:
                self.browser.SetClientHanlder(handler(self))
    
        def resizeEvent(self, event):
            if self.browser and self._browser_widget:
                self.browser.SetBounds(*self._browser_widget.geometry().getRect())
                self.browser.NotifyMoveOrResizeStarted()
    
    
    if __name__ == "__main__":
        sys.excepthook = cef.ExceptHook
        cef.Initialize()
        app = ChromiumApplication()
        window = ChromiumBrowserWindow()
        app.exec()
        app.timer.stop()
        cef.Shutdown()
    

    enter image description here