QT integration CEF05 - embedded into QT form

Review how the browser window of CEF was created:

  • SimpleApp implements the CefBrowserProcessHandler interface

  • There is an OnContextInitialized callback in the CefBrowserProcessHandler interface, which is called back after the context created by CEF is initialized.

  • SimpleApp implements the OnContextInitialized callback. The code is as follows:

    void SimpleApp::OnContextInitialized() {
      CEF_REQUIRE_UI_THREAD();
      CefRefPtr<SimpleHandler> handler(new SimpleHandler(false));
      // Browser configuration,
      CefBrowserSettings browser_settings;
      // URL to open
      std::string url= "https://www.baidu.com";
      // Browser window information
      CefWindowInfo window_info;
      window_info.SetAsPopup(NULL, "cefsimple");
        // Create the first browser window.
        CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
                                      nullptr, nullptr);
    }
    

    You can see that this window is created through CefBrowserHost::CreateBrowser. During creation, you need to pass the following parameter:

    • Const cefwindowinfo & windowinfo: cefwindowinfo object, that is, window information
    • Cefrefptr < CefClient > client: CefClient object. We implemented the SimpleHandler class ourselves, which inherits from CefClient
    • Const cefstring & url: url
    • Const cefbrowsersettings & settings: browser settings
    • CefRefPtr<CefDictionaryValue> extra_ Info: additional information. NULL is posted here,
    • CefRefPtr<CefRequestContext> request_ Context: request context. NULL is also passed here

    The SetAsPopup method of CefWindowInfo makes this window pop up. It has two parameters. The first parameter is the parent Windows window handle, which passes NULL, because in the previous example, this window is a top-level window, and the second parameter is the window title.

CefWindowInfo

It represents browser window information in three ways:

  • void SetAsChild(CefWindowHandle parent, RECT windowRect) makes the browser window an embedded child window of the parent.

  • Void setaspopup (cefwindowhandle parent, const cefstring & windowname) makes the browser window a pop-up window. You can specify the parent window handle, but it is a separate pop-up window

  • Void setas windowless (cefwindowhandle parent) browser window is a windowless (off screen) rendering window. It can be understood as a browser window that exists in memory and is not displayed on the screen. For example, when making a crawler application or testing tool, you need to get the content in the browser, but there is no need to display the scene on the screen.

Idea of embedding into QT form

With the above analysis, we have the general idea of embedding into the QT form: when the CEF context is initialized, get the handle of the QT form and give it to CefWindowInfo, and then give CefWindowInfo to CefBrowserHost::CreateBrowser to create the browser window. CefWindowInfo calls the SetAsChild method to embed the browser window into the QT form.

There is a winId() method in the QWidget of QT to obtain the form handle.

MainWindow inherits from QMainWindow. It has a central widget. We can give the handle of the central widget to CefWindowInfo

Think about a question:

Settings.multi in main.cpp_ threaded_ message_ loop = true; This configuration causes CEF to run the Browser interface on a separate thread, not on the main thread. Therefore, when SimpleApp::OnContextInitialized is executed, it is in a separate thread. However, QT form MainWindow is executed in the main UI thread. If you create a Browser window in OnContextInitialized and embed the window into MainWindow, that is, operate the QT UI mainline in the sub thread, there will be a problem:

In QT, the child thread cannot directly operate the UI main thread, and an error will be reported when running

At this time, you can use QT's signal slot mechanism to let the sub thread send a signal to the thread where the UI is located, and then create a browser form in the slot function of MainWindow and complete the work embedded in the centralWidget.

step

Next, the SimpleApp, MainWindow and main functions are modified to complete the embedding of the browser into the QT main form.

SimpleApp join signal

  • To make SimpleApp support QT signal slot mechanism, SimpleApp should inherit from QObject and add Q_OBJECT macro.

  • In simple_ The signal is defined in the app. H header file

  • Emit signal in OnContextInitialized

During inheritance, QObject should be written in the front, and it is public inheritance

// simple_app.h file
#include "include/cef_app.h"
#include "QObject"
// 01: add public QObject parent class
class SimpleApp : public QObject, public CefApp, public CefBrowserProcessHandler {
    // 02: because the signal slot needs to be supported, Q should be added_ Object macro
    Q_OBJECT
 public:
  SimpleApp();
  CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() OVERRIDE {
    return this;
  }
  void OnContextInitialized() OVERRIDE;
// 03: add onCefOnctextInitialized signal
signals:
    void onCefOnctextInitialized();

 private:
  IMPLEMENT_REFCOUNTING(SimpleApp);
};

// simple_app.cc file
#include "simple_app.h"
#include <string>
#include "include/cef_browser.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_helpers.h"
#include "simple_handler.h"
SimpleApp::SimpleApp()
{
}
// 04: transmit onCefOnctextInitialized signal in OnContextInitialized
void SimpleApp::OnContextInitialized() {
  CEF_REQUIRE_UI_THREAD();
  // Signal
  emit onCefOnctextInitialized();
}

Connection signal in MainWindow

  • Because the signals defined in SimpleApp are connected in MainWindow, SimpleApp * m is defined in mainwindow.h_ The cefapp member is used to hold the SimpleApp object.
  • M in MainWindow constructor_ Cefapp initialization
  • MainWindow defines the slot function createBrowserWindow(), implements the creation of browser window in mainwindow.cpp, and embeds it into the central widget in MainWindow
  • Connect the slot function createBrowserWindow() with the onCefOnctextInitialized signal in SimpleApp in the MainWindow constructor
// mainwindow.h file
#include <QtWidgets/QMainWindow>
#include "ui_mainwindow.h"
#include "cef/simple_app.h"
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    // 01 pass in SimpleApp to connect to its onCefOnctextInitialized signal
    MainWindow(SimpleApp* cefApp,QWidget* parent = Q_NULLPTR);
// 02 slot function to complete the creation and embedding of browser window
private slots:
    void createBrowserWindow();
private:
     SimpleApp*  m_cefApp=NULL;
    Ui::MainWindowClass ui;
};

// mainwindow.cpp file
#include "mainwindow.h"
#include "cef/simple_handler.h"
MainWindow::MainWindow(SimpleApp* cefApp,QWidget *parent)
    : QMainWindow(parent),m_cefApp(cefApp)
{
    ui.setupUi(this);
    // When OnctextInitialized is called back in SimpleApp, notify the main form to create a browser window and embed it in the main window
    connect(m_cefApp, &SimpleApp::onCefOnctextInitialized, this, &MainWindow::createBrowserWindow);
}

/// <summary>
///Create browser form slot function
/// </summary>
void MainWindow::createBrowserWindow() {
    CefRefPtr<SimpleHandler> handler(new SimpleHandler(false));
  // Browser configuration,
  CefBrowserSettings browser_settings;
  // URL to open
  std::string url= "https://www.baidu.com";
  // Browser window information
  CefWindowInfo window_info;
  
  //window_info.SetAsPopup(NULL, "cefsimple");
  // Gets the handle to the embedded form
  HWND wnd = (HWND)this->centralWidget()->winId();
  CefWindowInfo cefWndInfo;
  RECT winRect;
  QRect qtRect = this->rect();
  winRect.left = qtRect.left();
  winRect.top = qtRect.top();
  winRect.right = qtRect.right();
  winRect.bottom = qtRect.bottom();

  window_info.SetAsChild(wnd, winRect);
    // Create the first browser window.
    CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
                                  nullptr, nullptr);
}

main.cpp entry

Create a SimpleApp object before creating a MainWindow object, and then pass it into the MainWindow constructor

int main(int argc, char *argv[])
{
    CefEnableHighDPISupport();
    HINSTANCE hInstance = GetModuleHandle(nullptr);
    CefMainArgs main_args(hInstance);
	int exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
    if (exit_code >= 0) {
        return exit_code;
    }
    CefSettings settings;
    settings.no_sandbox = true;
    // This setting will cause CEF to run the Browser interface on a separate thread, not on the main thread.
    settings.multi_threaded_message_loop = true;
    // Create SimpleApp object
    SimpleApp* cefApp=new SimpleApp;
    QApplication a(argc, argv);
    // Pass in SimpleApp for MainWindow constructor
    MainWindow w(cefApp, nullptr);
    w.show();
    
    CefRefPtr<SimpleApp> app(cefApp);
    CefInitialize(main_args, settings, app.get(), nullptr);
    int ret = a.exec();
    // Shut down CEF.
    CefShutdown();
    return ret;
}

Compile run

Problem: embedded browser window size problem

When the size of the parent form changes, the embedded form does not change with the parent form

The idea to solve this problem is:

  • Listen for the resizeEvent event of MainWindow
  • When the window size changes, get the browser window handle
  • Call the Windows API function (:: MoveWindow) to change the position of the browser window and keep it consistent with the MainWindow window window.

The new problem is how to get the window handle of the browser window?

Find the SimpleHandler class, which implements the browser declaration cycle interface GetLifeSpanHandler, in which there is an onaftercreated (cefrefptr < CefBrowser > browser) callback method, indicating that it is called back after the browser window is created, and the CefBrowser object is passed during the callback.

In the implementation of SimpleHandler:

void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
    CEF_REQUIRE_UI_THREAD();
    // Was added to the browser_list_  In collection
    browser_list_.push_back(browser);
}

Our current program only creates a CefBrowser object, so you can write another public method in the SimpleHandler class to obtain the window handle of the browser window object:

// simple_handler.h 
// ... other parts omitted
class SimpleHandler : public CefClient,
                      public CefLifeSpanHandler {
  // ... other parts omitted
  // For convenience, the implementation is directly written in simple_handler.h is in the header file.
  HWND getBrowserWindowHandle() {
      if (!browser_list_.empty()) { //If the collection is not empty
          // Get the first CefBrowser element in the collection, get its CefBrowserHost object, and then get the WindowHandle in the CefBrowserHost object, that is, the window handle
          return  browser_list_.front()->GetHost()->GetWindowHandle();
      }
      return NULL;
  }
  // ... other parts omitted                             
}

Listen for the resizeEvent event of MainWindow:

// mainwindow.cpp file
// ... other parts omitted
void MainWindow::resizeEvent(QResizeEvent* event)
{
    if (SimpleHandler::GetInstance()) {
        HWND wnd = SimpleHandler::GetInstance()->getBrowserWindowHandle();
        if (wnd) {
            QRect qtRect = this->centralWidget()->rect();
            ::MoveWindow(wnd, qtRect.x(), qtRect.y(), qtRect.width(), qtRect.height(), true);
        }
    }
}

A static method is provided in SimpleHandler to get its sample object. If it is not empty, after obtaining the object, call the getBrowserWindowHandle() method just defined to obtain the window handle of the browser window.

If the window handle is not empty, get the rectangular area of the centralWidget in the current QT form, and then move the browser window.

Recompile and run again. It is found that the browser window can follow the change of QT window size.

Tags: Qt cef

Posted on Mon, 06 Dec 2021 17:50:23 -0500 by smokeydj