QT作为C++下著名的跨平台软件开发框架,实现了一套代码可以在所有的操作系统、平台和屏幕类型上部署。我们前几篇文章讲解了如何构建一款基于CEF的简单的样例,但这些样例的GUI都是使用的原生的或者是控件功能不强大的CEF视图框架。本文将会重新开始,使用VS2019编写一款基于QT的并嵌入原生窗体的文章。

环境搭建

在本文中,我没有使用QtCreator进行项目搭建的工作,而是使用VS配合QT VS Tools类来完成项目的环境。在本文,假设你已经安装了QT,并且了解QT的相关知识。

安装Qt VS Tools插件

在VS中,我们通过在扩展(Extension)搜索对应的QT插件,完成安装工作,安装完成后,需要重启VS。

配置Qt环境

找到Extensions - Qt VS Tools - Options

找到Qt - Versions,进行QT - VS编译的配置:

Qt项目创建

在经过配置以后,此时使用VS进行项目创建的时候,会发现创建的向导页面会出现Qt的相关项目模板:

接下来创建一个名为QtCefDemo的样例,此时会弹出Qt的创建向导:

然后,Qt会自动帮我们配置好Debug和Release:

最后,我们再调整下项目的文件:

点击Finish,我么就得到了如下的在VS IDE下的QT项目大致结构:

当我们运行该项目以后,就可以看到目前的一个简单的QT窗体:

当然,本文的目的不仅仅是创建一个Qt窗体那样的简单,还需要进行CEF的简单集成。所以,接下来我们继续配置CEF的环境。

配置CEF环境

在前一篇文章,我们已经了解如何编译libcef_dll_wrapper这个库,所以,本文假设你已经编译出了libcef_dll_wrapper.lib(Debug和Release版本,并且对应版本的程序集类型分别是:MDd和MD):

接下来,我们需要在我们的解决方案下,创建对应的文件夹,用来存放CEF在编译和运行时会使用到的头文件、库文件以及资源文件。

拷贝头文件以及资源文件

首先,我们在解决方案同级目录下创建一个名为CefFiles的文件夹,将cef文件中的Release和Include拷贝进来:

拷贝二进制库文件

接下来,我们在CefFiles文件夹中创建一个bin目录,用于存放libcef.lib相关文件以及ibcef_dll_wrapper.lib库文件,但需要注意的是,我们需要按照Debug和Release进行分类:

对于拷贝libcef_dll_wrapper.lib文件,我们也拷贝到对应的bin/版本目录下:

Release的同理:

此时,我们的CefFiles文件结构如下:

CefFiles
├─bin
│ ├─Debug
│ │ │ ...
│ │ │ libcef.dll
│ │ │ libcef.lib
│ │ │ libcef_dll_wrapper.lib
│ │ │ ...
│ │ │
│ │ └─swiftshader
│ │ ...
│ │
│ └─Release
│ │ ...
│ │ libcef.dll
│ │ libcef.lib
│ │ libcef_dll_wrapper.lib
│ │ ...
│ │
│ └─swiftshader
│ ...

├─include
│ 各种.h头文件
│ ...
└─Resources
│ cef.pak
│ ..
└─locales
...
zh-CN.pak
zh-TW.pak

编写manifest文件

在Windows上使用CEF的时候,需要配置将manifest文件打入exe可执行程序中,这个manifest文件我们直接手工创建,在项目目录下创建一个名为app.manifest的文件:

内容如下:

<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates application support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- 10.0 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

配置VS中的头文件以及库文件的加载地址

首先是配置头文件include的目录:

由于头文件不存在Debug和Release的差别,所以Release相同配置,不再赘述。

接下来是配置链接库的文件路径,由于Debug和Release下,库文件内容存在不同,所以需要分别配置,但我们看可以使用$(Configuration)宏来完成根据环境自动配置。

在Release下,只需要同样的配置,但是会自动定位。

配置manifest文件

当然,由于manifest文件不涉及Debug还是Release,所以配置一致即可。

至此,我们的使用VS作为IDE,基于QT的框架的,集成CEF的环境完全搭建完成了,在文章的末尾,我会附上在环境搭建完成下的初始状态的项目。

集成CEF的编码

在CEF编码的时候,我们直接将cefsimple中的相关代码迁移到我们的项目中,但是会进行一定的删改。

编写simple_handler

simple_handler.h

#pragma once

#include "include/cef_client.h"

#include <list>

class SimpleHandler : public CefClient,
public CefLifeSpanHandler,
public CefLoadHandler
{
public:
explicit SimpleHandler();
~SimpleHandler(); // Provide access to the single global instance of this object.
static SimpleHandler* GetInstance(); virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE
{
return this;
} virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE { return this; } // CefLifeSpanHandler methods:
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE; // CefLoadHandler methods:
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) OVERRIDE; // Request that all existing browser windows close.
void CloseAllBrowsers(bool force_close); bool IsClosing() const { return is_closing_; } private:
// List of existing browser windows. Only accessed on the CEF UI thread.
typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
BrowserList browser_list_; bool is_closing_; // Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleHandler);
};

simple_handler.cpp

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file. #include "simple_handler.h" #include <sstream>
#include <string> #include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "include/cef_parser.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h" namespace
{
SimpleHandler* g_instance = nullptr; // Returns a data: URI with the specified contents.
std::string GetDataURI(const std::string& data, const std::string& mime_type)
{
return "data:" + mime_type + ";base64," +
CefURIEncode(CefBase64Encode(data.data(), data.size()), false)
.ToString();
}
} // namespace SimpleHandler::SimpleHandler(): is_closing_(false)
{
DCHECK(!g_instance);
g_instance = this;
} SimpleHandler::~SimpleHandler()
{
g_instance = nullptr;
} // static
SimpleHandler* SimpleHandler::GetInstance()
{
return g_instance;
} void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD(); // Add to the list of existing browsers.
browser_list_.push_back(browser);
} bool SimpleHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD(); // Closing the main window requires special handling. See the DoClose()
// documentation in the CEF header for a detailed destription of this
// process.
if (browser_list_.size() == 1)
{
// Set a flag to indicate that the window close should be allowed.
is_closing_ = true;
} // Allow the close. For windowed browsers this will result in the OS close
// event being sent.
return false;
} void SimpleHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD(); // Remove from the list of existing browsers.
BrowserList::iterator bit = browser_list_.begin();
for (; bit != browser_list_.end(); ++bit)
{
if ((*bit)->IsSame(browser))
{
browser_list_.erase(bit);
break;
}
} if (browser_list_.empty())
{
// All browser windows have closed. Quit the application message loop.
CefQuitMessageLoop();
}
} void SimpleHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl)
{
CEF_REQUIRE_UI_THREAD(); // Don't display an error for downloaded files.
if (errorCode == ERR_ABORTED)
return; // Display a load error message using a data: URI.
std::stringstream ss;
ss << "<html><body bgcolor=\"white\">"
"<h2>Failed to load URL "
<< std::string(failedUrl) << " with error " << std::string(errorText)
<< " (" << errorCode << ").</h2></body></html>"; frame->LoadURL(GetDataURI(ss.str(), "text/html"));
} void SimpleHandler::CloseAllBrowsers(bool force_close)
{
if (!CefCurrentlyOn(TID_UI))
{
// Execute on the UI thread.
CefPostTask(TID_UI, base::Bind(&SimpleHandler::CloseAllBrowsers, this,
force_close));
return;
} if (browser_list_.empty())
return; BrowserList::const_iterator it = browser_list_.begin();
for (; it != browser_list_.end(); ++it)
(*it)->GetHost()->CloseBrowser(force_close);
}

编写simple_app

simple_app.h

#pragma once
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file. #include "include/cef_app.h" // Implement application-level callbacks for the browser process.
class SimpleApp : public CefApp, public CefBrowserProcessHandler
{
public:
SimpleApp(); // CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE
{
return this;
} // CefBrowserProcessHandler methods:
virtual void OnContextInitialized() OVERRIDE; private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleApp);
};

simple_app.cpp

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file. #include "simple_app.h" #include <string>
#include "include/views/cef_window.h"
#include "include/wrapper/cef_helpers.h"
#include "simple_handler.h" SimpleApp::SimpleApp()
{
} void SimpleApp::OnContextInitialized()
{
CEF_REQUIRE_UI_THREAD();
}

编写入口代码处理函数集成CEF

main.cpp

对于入口函数,目前只是进行QT相关代码的编写,我们还需要对CEF进行初始化操作,对于该文件整体如下:

#include <cef_app.h>

#include "qtcefwindow.h"
#include "stdafx.h"
#include <QtWidgets/QApplication> #include "simple_app.h" /**
* 初始化QT以及CEF相关
*/
int init_qt_cef(int& argc, char** argv)
{
const HINSTANCE h_instance = static_cast<HINSTANCE>(GetModuleHandle(nullptr)); const CefMainArgs main_args(h_instance);
const CefRefPtr<SimpleApp> app(new SimpleApp); //CefApp实现,用于处理进程相关的回调。 const int exit_code = CefExecuteProcess(main_args, app.get(), nullptr);
if (exit_code >= 0)
{
return exit_code;
} // 设置配置
CefSettings settings;
settings.multi_threaded_message_loop = true; //多线程消息循环
settings.log_severity = LOGSEVERITY_DISABLE; //日志
settings.no_sandbox = true; //沙盒 CefInitialize(main_args, settings, app, nullptr); return -1;
} int main(int argc, char* argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // 解决高DPI下,界面比例问题
QApplication a(argc, argv);
const int result = init_qt_cef(argc, argv);
if (result >= 0)
{
return result;
} QtCefWindow w;
w.show();
a.exec(); CefShutdown(); // 关闭CEF,释放资源 return 0;
}

修改qtcefwindow窗体代码

qtcefwindow.h

为窗体添加私有成员:CefRefPtr<SimpleHandler>

#pragma once

#include <QtWidgets/QMainWindow>

#include "simple_handler.h"
#include "ui_qtcefwindow.h" class QtCefWindow : public QMainWindow
{
Q_OBJECT public:
QtCefWindow(QWidget *parent = Q_NULLPTR); private:
Ui::QtCefWindowClass ui;
CefRefPtr<SimpleHandler> simple_handler_; // 这里是新增的CefRefPtr<SimpleHandler>成员
};

qtcefwindow.cpp

在构造函数中,处理关联qtcefwindow和SimpleHandler:

#include "qtcefwindow.h"

#include <cef_request_context.h>

#include "simple_handler.h"
#include "stdafx.h" QtCefWindow::QtCefWindow(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this); // 以下是将 SimpleHandler 与窗体进行关联的代码
CefWindowInfo cef_wnd_info;
QString str_url = "https://www.cnblogs.com/w4ngzhen";
RECT win_rect;
QRect rect = this->geometry();
win_rect.left = rect.left();
win_rect.right = rect.right();
win_rect.top = rect.top();
win_rect.bottom = rect.bottom(); cef_wnd_info.SetAsChild((HWND)this->winId(), win_rect); //将cef界面嵌入qt界面中
CefBrowserSettings cef_browser_settings;
simple_handler_ = CefRefPtr<SimpleHandler>(new SimpleHandler());
CefBrowserHost::CreateBrowser(cef_wnd_info,
simple_handler_,
str_url.toStdString(),
cef_browser_settings,
nullptr,
CefRequestContext::GetGlobalContext());
}

运行代码

终于,项目搭建完成以后,我们走到了最后一步,看看我们在Qt中集成CEF的效果吧。

运行程序,会发现报错:

---------------------------
QtCefDemo.exe - 系统错误
---------------------------
由于找不到 libcef.dll,无法继续执行代码。重新安装程序可能会解决此问题。
---------------------------
确定
---------------------------

对于这个问题,其实我们就是缺少运行时候的相关库文件,这里我们暂时先手动进行拷贝工作,以Debug环境为例,我们将资源文件拷贝到输出目录中:

然后将CefFiles\bin\Debug中所有的文件拷贝到输出目录中:

当然,我们可以通过配置自动化脚本的方式,让IDE帮助我们拷贝这些文件,但本文不讨论这个问题。在手动拷贝了文件以后,我们再次尝试运行。

终于,我们看到了我们想要的页面,不过似乎渲染显示还有点问题,不过在本文我们暂且不讨论。在后续,我会单独写一篇文章,来谈一谈使用CEF以及QT集成CEF的过程中会遇到的各种问题以及解决方案。

附录:源码以及相关文件

本文所涉及的项目源码在:w4ngzhen/QtCefDemo (github.com)

其中,会有两个提交:

  1. project init
  2. integrate CEF code

读者可以自行创建分支,回退到指定的提交查看对应状态的代码。

此外,本Demo还需要我们创建的CefFiles文件夹以及其中的文件。由于Github对于大文件的处理不太方便。本人将其上传到了网盘,读者只需要从网盘下载CefFiles.zip文件,并将其解压到解决方案同级目录即可。

网盘地址:链接:https://pan.baidu.com/s/1BylLcETsFAJ5-TnmzpRxeA

提取码:bydn

使用CEF(四)— 在QT中集成CEF(1):基本集成的更多相关文章

  1. Qt中使用CEF(Windows下)

    最近项目中要在Qt中使用CEF(Chromium Embedded Framework),在这里总结下其中的几个要点. 下载合适的CEF版本 关于CEF的简介我们这里就不做介绍了,下载CEF可以有2种 ...

  2. Qt中图元对象的多重集成

    在继承自定义QGraphicsItem图元对象时,有时需要用到信号/槽机制,由于QGraphicsItem非QObject的子类 所以需要多重继承QObject,有一点需要特别注意:就是继承的顺序,一 ...

  3. qt中使用sqlite存储数据

    一.sqilte的安装 在Windows上安装SQLite: 请访问 SQLite 下载页面,从 Windows 区下载预编译的二进制文件. 您需要下载 sqlite-tools-win32-*.zi ...

  4. 解决Duilib集成CEF浏览器在Win10无法向客户区拖拽文件

    在Duilib中集成CEF浏览器项目实际开发中,遇到一个问题. 一个需求从资源管理器(桌面)拖拽文件到客户端,窗口捕获WM_DROPFILES消息然后进行消息处理,但客户区是集成的CEF浏览器,浏览器 ...

  5. Qt中事件处理的方法(三种处理方法,四种覆盖event函数,notify函数,event过滤,事件处理器。然后继续传递给父窗口。可观察QWidget::event的源码,它是虚拟保护函数,可改写)

    一.Qt中事件处理的方式   1.事件处理模式一 首先是事件源产生事件,最后是事件处理器对这些事件进行处理.然而也许大家会问, Qt中有这么多类的事件,我们怎么样比较简便的处理每个事件呢?设想,如果是 ...

  6. Qt中使用ActiveX(3篇)

    由于最近需要使用ActiveX,一般来说可以使用微软提供的MFC或者ATL框架来开发,由于我个人对这部分内容不是很熟悉,好在Qt也提供对于ActiveX的支持.本文主要记录个人学习ActiveX的一些 ...

  7. QT中的SOCKET编程(QT-2.3.2)

    转自:http://mylovejsj.blog.163.com/blog/static/38673975200892010842865/ QT中的SOCKET编程 2008-10-07 23:13 ...

  8. Qt中,当QDockWidget的父窗口是一个不可以拖动的QTabWidget的时候实现拖动的方法

    之前在做有关QDockWidget的内容时候遇到了瓶颈,那就是窗口弹出来之后拖动不了,也不可以放大和缩小,若是弹出来之后设置成了window的flags,也不可以拖动,而且也不是需要的效果. 1.弹出 ...

  9. Qt中设置widget背景颜色/图片的注意事项(使用样式表 setStyleSheet())

    在Qt中设置widget背景颜色或者图片方法很多种:重写paintEvent() , 调色板QPalette , 样式表setStyleSheet等等. 但是各种方法都有其注意事项,如果不注意则很容易 ...

随机推荐

  1. Spring Mvc原理分析(一)

    Servlet生命周期了解 Servlet的生命(周期)是由容器(eg:Tomcat)管理的,换句话说,Servlet程序员不能用代码控制其生命. 加载和实例化:时机取决于web.xml的定义,如果有 ...

  2. java和js中for循环的区别

    java中for循环,先执行语句后循环 for (int i=1;i<10;i++){ for(int b=1;b<=i;b++){ System.out.print(b+"*& ...

  3. js函数和封装

    $就是jquery对象,$()就是jQuery(),在里面可以传参数,作用就是获取元素 js对象与jQuery对象的区别:jQuery对象是一个数组,jQuery对象转为js对象:[0] 取第一个即可 ...

  4. MySQL高可用主从复制部署

    原文转自:https://www.cnblogs.com/itzgr/p/10233932.html作者:木二 目录 一 基础环境 二 实际部署 2.1 安装MySQL 2.2 初始化MySQL 2. ...

  5. PS-头发丝抠图

    [PS版本]Photoshop CS5 [主题]头发丝抠图 [操作步骤] 第一步:打开待处理图片,复制图层: 第二步:快速选择工具选择主体(也可用魔术棒用具选择背景色,然后反向): 第三步:点击&qu ...

  6. Three.js 中 相机的常用参数含义

    Three.js 中相机常用的参数有up.position和lookAt. position是指相机所在的位置,将人头比作相机的话,那么position就是人头的中心的位置: up类似于人的脖子可以调 ...

  7. 图神经网络-环境配置与PyG库

    环境配置与PyG中图与图数据集的表示和使用 一.引言 PyTorch Geometric (PyG)是面向几何深度学习的PyTorch的扩展库,几何深度学习指的是应用于图和其他不规则.非结构化数据的深 ...

  8. shell传参和变量赋值

    1.变量赋值方式 (1)方式1--直接赋值(=) (2)方式2--read交互式赋值 (3)方式3--脚本传参赋值 2.read read -p "请输入你的名字和年龄:" nam ...

  9. Selenium系列(十八) - Web UI 自动化基础实战(5)

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  10. HCNP Routing&Switching之IS-IS邻居建立、LSDB同步、拓扑计算和路由形成

    前文我们了解了IS-IS的报文结构和类型相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15260670.html:今天我们来聊一聊IS-IS建立邻居. ...