请通过目录,选择感兴趣的部分阅读。
【java】本地客户端内嵌浏览器1 - Swing、SWT、DJNativeSwing、javaFX
【java】本地客户端内嵌浏览器2 - chrome/chromium/cef/jcef
【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码
CEF
。哎呀呵,
Google Chromium
、多平台支持
、有其他语言的移植版
、支持Webkit & Chrome中实现的HTML5的特性
,这不正是我想要的么!
???
google.com
???这,能打开吗?
事实证明,打不开!
非常详细的获取 JCEF 相关 jar 包的教程
、比较简单的获取 JCEF 相关 jar 包的教程
本节接上节末提到的两篇文章后开始
在这里说明一下,jcef 是基于 Swing 的,不过本文不需要多么懂 Swing,因为我就不是很懂。
junittests
需要 junit 相关依赖,这里不介绍此部分相关内容,因此这个文件夹(包)删掉,后面不再提及)加号
,找到 lib\jcef\lib\win64
文件夹。上述内容,如果第 5 步之后不做,则根本无法启动程序,因为 jar 包都没有添加依赖嘛。
上述内容,如果第 6 步之后不做,则启动程序失败,因为会报错:Exception in thread "main" java.lang.UnsatisfiedLinkError: no chrome_elf in java.library.path
new MainFrame("http://www.google.com", useOsr, false);
变成new MainFrame("https://www.baidu.com", useOsr, false);
虽然用的百度翻译,但好歹是中文,,,有需要的可以直接拿走。
// Copyright (c) 2014 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.
package tests.simple;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import org.cef.CefApp;
import org.cef.CefApp.CefAppState;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.OS;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefAppHandlerAdapter;
import org.cef.handler.CefDisplayHandlerAdapter;
import org.cef.handler.CefFocusHandlerAdapter;
/**
* This is a simple example application using JCEF.
* It displays a JFrame with a JTextField at its top and a CefBrowser in its
* center. The JTextField is used to enter and assign an URL to the browser UI.
* No additional handlers or callbacks are used in this example.
*
* The number of used JCEF classes is reduced (nearly) to its minimum and should
* assist you to get familiar with JCEF.
*
* For a more feature complete example have also a look onto the example code
* within the package "tests.detailed".
*/
public class MainFrame extends JFrame {
private static final long serialVersionUID = -5570653778104813836L;
private final JTextField address_;
private final CefApp cefApp_;
private final CefClient client_;
private final CefBrowser browser_;
private final Component browerUI_;
private boolean browserFocus_ = true;
/**
* To display a simple browser window, it suffices completely to create an
* instance of the class CefBrowser and to assign its UI component to your
* application (e.g. to your content pane).
* 要显示一个简单的浏览器窗口,只需创建一个类 CefBrowser 的实例并将其 UI 组件
* 分配给应用程序(例如,分配给内容窗格)就足够了。
*
* But to be more verbose, this CTOR keeps an instance of each object on the
* way to the browser UI.
* 但是,为了更详细,这个 CTOR 将每个对象的一个实例保存在通往浏览器 UI 的路上。
*/
private MainFrame(String startURL, boolean useOSR, boolean isTransparent) {
// (1) The entry point to JCEF is always the class CefApp. There is only one
// instance per application and therefore you have to call the method
// "getInstance()" instead of a CTOR.
// JCEF 的入口点总是类 CefApp。每个应用程序只有一个实例,因此必须调用
// 方法"getInstance()"而不是一个 CTOR。
// CefApp is responsible for the global CEF context. It loads all
// required native libraries, initializes CEF accordingly, starts a
// background task to handle CEF's message loop and takes care of
// shutting down CEF after disposing it.
// CefApp 负责全局 CEF 上下文。它加载所有必需的本地库,相应地初始化 CEF,
// 启动后台任务来处理 CEF 的消息循环,并在处理完后关闭 CEF。
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
// Shutdown the app if the native CEF part is terminated
// 如果本机 CEF 部分终止,则关闭应用程序
if (state == CefAppState.TERMINATED) System.exit(0);
}
});
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = useOSR;
cefApp_ = CefApp.getInstance(settings);
// (2) JCEF can handle one to many browser instances simultaneous. These
// browser instances are logically grouped together by an instance of
// the class CefClient. In your application you can create one to many
// instances of CefClient with one to many CefBrowser instances per
// client. To get an instance of CefClient you have to use the method
// "createClient()" of your CefApp instance. Calling an CTOR of
// CefClient is not supported.
// JCEF 可以同时处理一到多个浏览器实例。这些浏览器实例按类 CefClient 的实例在逻辑上分组在一起。
// 在您的应用程序中,您可以创建一到多个 CefClient 实例,每个客户端有一到多个 CefBrowser 实例。
// 要获取 CefClient 的实例,必须使用 CefApp 实例的方法"createClient()"。不支持调用 CefClient 的 CTOR。
//
// CefClient is a connector to all possible events which come from the
// CefBrowser instances. Those events could be simple things like the
// change of the browser title or more complex ones like context menu
// events. By assigning handlers to CefClient you can control the
// behavior of the browser. See tests.detailed.MainFrame for an example
// of how to use these handlers.
// CefClient 是连接来自 CefBrowser 实例的所有可能事件的连接器。
// 这些事件可以是诸如更改浏览器标题之类的简单事件,也可以是诸如上下文菜单事件之类的更复杂事件。
// 通过将处理程序分配给 CefClient,您可以控制浏览器的行为。有关如何使用这些处理程序的示例,请参见 tests.detailed.MainFrame。
client_ = cefApp_.createClient();
// (3) One CefBrowser instance is responsible to control what you'll see on
// the UI component of the instance. It can be displayed off-screen
// rendered or windowed rendered. To get an instance of CefBrowser you
// have to call the method "createBrowser()" of your CefClient
// instances.
// 一个 CefBrowser 实例负责控制在该实例的 UI 组件上看到的内容。
// 它可以显示屏幕外渲染或窗口渲染。要获取 CefBrowser 实例,必须调用 CefClient 实例的方法"createBrowser()"。
//
// CefBrowser has methods like "goBack()", "goForward()", "loadURL()",
// and many more which are used to control the behavior of the displayed
// content. The UI is held within a UI-Compontent which can be accessed
// by calling the method "getUIComponent()" on the instance of CefBrowser.
// The UI component is inherited from a java.awt.Component and therefore
// it can be embedded into any AWT UI.
// CefBrowser 有"goBack()"、"goForward()"、"loadURL()"等方法,这些方法用于控制显示内容的行为。
// 该 UI 保存在 UI 组件中,可以通过调用 CefBrowser 实例上的方法"getUIComponent()"来访问该 UI 组件。
// UI 组件继承自java.awt.Component,因此可以嵌入到任何 AWT UI 中。
browser_ = client_.createBrowser(startURL, useOSR, isTransparent);
browerUI_ = browser_.getUIComponent();
// (4) For this minimal browser, we need only a text field to enter an URL
// we want to navigate to and a CefBrowser window to display the content
// of the URL. To respond to the input of the user, we're registering an
// anonymous ActionListener. This listener is performed each time the
// user presses the "ENTER" key within the address field.
// If this happens, the entered value is passed to the CefBrowser
// instance to be loaded as URL.
// 对于这个最小的浏览器,我们只需要一个文本字段来输入我们要导航到的 url,以及一个 CefBrowser 窗口来显示 url 的内容。
// 为了响应用户的输入,我们注册了一个匿名 ActionListener。每当用户在地址字段中按“回车”键时,就会执行此侦听器。
// 如果发生这种情况,则将输入的值传递给要作为 url 加载的 CefBrowser 实例。
address_ = new JTextField(startURL, 100);
address_.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
browser_.loadURL(address_.getText());
}
});
// Update the address field when the browser URL changes.
// 当浏览器 URL 更改时更新地址字段。
client_.addDisplayHandler(new CefDisplayHandlerAdapter() {
@Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
address_.setText(url);
}
});
// Clear focus from the browser when the address field gains focus.
// 当地址字段获得焦点时,从浏览器中清除焦点。
address_.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (!browserFocus_) return;
browserFocus_ = false;
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
address_.requestFocus();
}
});
// Clear focus from the address field when the browser gains focus.
// 当浏览器获得焦点时,从地址字段中清除焦点。
client_.addFocusHandler(new CefFocusHandlerAdapter() {
@Override
public void onGotFocus(CefBrowser browser) {
if (browserFocus_) return;
browserFocus_ = true;
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
browser.setFocus(true);
}
@Override
public void onTakeFocus(CefBrowser browser, boolean next) {
browserFocus_ = false;
}
});
// (5) All UI components are assigned to the default content pane of this
// JFrame and afterwards the frame is made visible to the user.
// 所有 UI 组件都被分配给这个 JFrame 的默认内容窗格,然后这个框架对用户可见。
getContentPane().add(address_, BorderLayout.NORTH);
getContentPane().add(browerUI_, BorderLayout.CENTER);
pack();
setSize(800, 600);
setVisible(true);
// (6) To take care of shutting down CEF accordingly, it's important to call
// the method "dispose()" of the CefApp instance if the Java
// application will be closed. Otherwise you'll get asserts from CEF.
// 要相应地关闭 CEF,如果 Java 应用程序将被关闭,那么调用 CefApp 实例的方法"dispose()"非常重要。否则你会得到 CEF 的指控。
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
CefApp.getInstance().dispose();
dispose();
}
});
}
public static void main(String[] args) {
// Perform startup initialization on platforms that require it.
// 在需要的平台上执行启动初始化。
if (!CefApp.startup()) {
System.out.println("Startup initialization failed!");
return;
}
// The simple example application is created as anonymous class and points
// to Google as the very first loaded page. Windowed rendering mode is used by
// default. If you want to test OSR mode set |useOsr| to true and recompile.
// 这个简单的示例应用程序被创建为匿名类,并指向 Google 作为第一个加载的页面。默认情况下使用窗口渲染模式。如果要测试OSR模式,请将 |useOsr| 设置为 true 并重新编译。
boolean useOsr = false;
new MainFrame("http://www.google.com", useOsr, false);
}
}
很简单,就是把源代码中的 address_
变量相关语句全部删掉,以及 Focus 相关的代码也删掉。这里直接分享源代码(为节省篇幅,我将注释全部删除了)
:
package tests.simple;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import org.cef.CefApp;
import org.cef.CefApp.CefAppState;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefAppHandlerAdapter;
import org.cef.handler.CefDisplayHandlerAdapter;
import org.cef.handler.CefFocusHandlerAdapter;
public class MainFrame extends JFrame {
private static final long serialVersionUID = -5570653778104813836L;
private final CefApp cefApp_;
private final CefClient client_;
private final CefBrowser browser_;
private final Component browerUI_;
private boolean browserFocus_ = true;
private MainFrame(String startURL, boolean useOSR, boolean isTransparent) {
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
if (state == CefAppState.TERMINATED) System.exit(0);
}
});
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = useOSR;
cefApp_ = CefApp.getInstance(settings);
client_ = cefApp_.createClient();
browser_ = client_.createBrowser(startURL, useOSR, isTransparent);
browerUI_ = browser_.getUIComponent();
getContentPane().add(browerUI_, BorderLayout.CENTER);
pack();
setSize(800, 600);
setVisible(true);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
CefApp.getInstance().dispose();
dispose();
}
});
}
public static void main(String[] args) {
if (!CefApp.startup()) {
System.out.println("Startup initialization failed!");
return;
}
boolean useOsr = false;
new MainFrame("https://www.baidu.com", useOsr, false);
}
}
pack();
setSize(800, 600);
setMinimumSize(new Dimension(1366, 738)); // 设置最小窗口大小
setExtendedState(JFrame.MAXIMIZED_BOTH); // 默认窗口全屏
很简单,就一条语句
setTitle("MyBrowser");
setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/images/icon.png")));
System.out.println("Startup initialization failed!");
替换成如下代码(以下代码用到了一个图片文件,请自行放置,或删除相关代码)JFrame jFrame = new JFrame("MyBrowser");
jFrame.setMinimumSize(new Dimension(1366, 738)); // 设置最小窗口大小
jFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); // 默认窗口全屏
JLabel error = new JLabel(" 在启动这个应用程序时,发生了一些错误,请关闭并重启这个应用程序。
There is something wrong when this APP start up, please close and restart it.");
error.setFont(new Font("宋体/Arial", Font.PLAIN, 28));
error.setIcon(new ImageIcon(jFrame.getClass().getResource("/images/error.png")));
error.setForeground(Color.red);
error.setHorizontalAlignment(SwingConstants.CENTER);
jFrame.getContentPane().setBackground(Color.white);
jFrame.getContentPane().add(error, BorderLayout.CENTER);
jFrame.setVisible(true);
addWindowListener
方法的入参中的 windowClosing
方法:addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int i;
String language = "en-us";
if (language.equals("en-us"))
i = JOptionPane.showOptionDialog(null, "Do you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"Yes", "No"}, "Yes");
else if (language.equals("zh-cn"))
i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的", "不"}, "是的");
else
i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?\nDo you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的(Yes)", "不(No)"}, "是的(Yes)");
if (i == JOptionPane.YES_OPTION) {
CefApp.getInstance().dispose();
dispose();
System.exit(0);
}
}
});
上一部分是直接在 simple/MainFrame 上面改的,本部分重新建包,从头干起。(之前的文件可以不必删,删了也行,建议看完本文再删。。。)
my.client.browser
和 my.client.main
,并新建两个 class 叫做 MyBrowser
和 Main
。package my.client.browser;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.handler.CefAppHandlerAdapter;
import java.awt.*;
public class MyBrowser {
private final CefApp cefApp_;
private final CefClient client_;
private final CefBrowser browser_;
private final Component browserUI_;
public MyBrowser(String startURL, boolean useOSR, boolean isTransparent) {
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
if (state == CefApp.CefAppState.TERMINATED) System.exit(0);
}
});
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = useOSR;
cefApp_ = CefApp.getInstance(settings);
client_ = cefApp_.createClient();
browser_ = client_.createBrowser(startURL, useOSR, isTransparent);
browserUI_ = browser_.getUIComponent();
}
public CefApp getCefApp() {
return cefApp_;
}
public CefClient getClient() {
return client_;
}
public CefBrowser getBrowser() {
return browser_;
}
public Component getBrowserUI() {
return browserUI_;
}
}
package my.client.main;
public class Main {
public static void main(String[] args) {
init();
}
private static void init() {}
}
EventQueue.invokeLater 的作用及其相关知识请自行了解。
这里的很多代码和上一部分中的修改是相关的
package my.client.main;
import my.client.browser.MyBrowser;
import org.cef.CefApp;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Main {
public static void main(String[] args) {
init();
}
private static void init() {
EventQueue.invokeLater(() -> {
JFrame jFrame = new JFrame("MyBrowser");
jFrame.setMinimumSize(new Dimension(1366, 738)); // 设置最小窗口大小
jFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); // 默认窗口全屏
jFrame.setIconImage(Toolkit.getDefaultToolkit().getImage(jFrame.getClass().getResource("/images/icon.png")));
if (!CefApp.startup()) { // 初始化失败
JLabel error = new JLabel(" 在启动这个应用程序时,发生了一些错误,请关闭并重启这个应用程序。
There is something wrong when this APP start up, please close and restart it.");
error.setFont(new Font("宋体/Arial", Font.PLAIN, 28));
error.setIcon(new ImageIcon(jFrame.getClass().getResource("/images/error.png")));
error.setForeground(Color.red);
error.setHorizontalAlignment(SwingConstants.CENTER);
jFrame.getContentPane().setBackground(Color.white);
jFrame.getContentPane().add(error, BorderLayout.CENTER);
jFrame.setVisible(true);
return;
}
MyBrowser myBrowser = new MyBrowser("https://www.baidu.com", false, false);
// // // // // // // // // // // // // // // // // // // // // // // // // // // //
// TODO: 后面的步骤不再 po 全部代码了,如果说“在 init() 方法中插入”,则全是插入在这里 //
// // // // // // // // // // // // // // // // // // // // // // // // // // // //
jFrame.getContentPane().add(myBrowser.getBrowserUI(), BorderLayout.CENTER);
jFrame.setVisible(true);
jFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int i;
String language = "en-us";
if (language.equals("en-us"))
i = JOptionPane.showOptionDialog(null, "Do you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"Yes", "No"}, "Yes");
else if (language.equals("zh-cn"))
i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的", "不"}, "是的");
else
i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?\nDo you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的(Yes)", "不(No)"}, "是的(Yes)");
if (i == JOptionPane.YES_OPTION) {
myBrowser.getCefApp().dispose();
jFrame.dispose();
System.exit(0);
}
}
});
});
}
}
实例化出 MyBrowser 对象,加载完网页之后,就可以向网页上执行 js 代码了。为保证加载完网页,我们将相关代码写到一个新线程中,并 sleep 一秒。直接分享源代码:
MyBrowser myBrowser = new MyBrowser("https://www.baidu.com", false, false);
new Thread(new Runnable() {
@Override
public void run() {
try {
// 让线程 sleep 一秒保证 executeJavaScript 方法能够执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 第一个参数是 js 代码,第二、三个参数是控制台打印所附带的信息,并不是指向网页执行代码。
// 第二个参数是 url,一旦报错,就会打印其相关信息,以供开发人员阅读。
myBrowser.getBrowser().executeJavaScript("console.log(123)", "http://whatever", 123);
// 第三个参数是行号,一旦报错,就会打印其相关信息,以供开发人员阅读。
myBrowser.getBrowser().executeJavaScript("document.write(123456)", "http://whatever", 1);
}
}).start();
jcef 这个东西,很多表现功能都要求自己去实现,比如下载功能,需要主动写一个类继承其特定的 Adapter,然后重写相关方法,才能完成这个功能。
CefClient client = myBrowser.getClient();
client.addContextMenuHandler(new DownloadHandler());
my.client
中创建一个 Package 叫做 handler
,创建一个 DownloadHandler
类,继承自 CefDownloadHandlerAdapter
,重写 onBeforeDownload
和 onDownloadUpdated
方法。此处可以参考 jcef 提供的 detailed 实例,里面有相关代码。
onBeforeDownload()
方法,添加很重要的一条语句:callback.Continue(fileName, true);
package my.client.handler;
import org.cef.browser.CefBrowser;
import org.cef.callback.CefBeforeDownloadCallback;
import org.cef.callback.CefDownloadItem;
import org.cef.callback.CefDownloadItemCallback;
import org.cef.handler.CefDownloadHandlerAdapter;
public class DownloadHandler extends CefDownloadHandlerAdapter {
@Override
public void onBeforeDownload(CefBrowser browser, CefDownloadItem item, String fileName, CefBeforeDownloadCallback callback) {
callback.Continue(fileName, true); // 通过此方法让下载正常进行
}
@Override
public void onDownloadUpdated(CefBrowser browser, CefDownloadItem item, CefDownloadItemCallback callback) {
// 判断当前状态正在进行中、没有被取消、没有完成状态
if (item.isInProgress() && !item.isCanceled() && !item.isComplete()) {
// 如果没有开始下载(选择下载存放路径时),item.getPercentComplete() 返回值是 -1
int percent = item.getPercentComplete() == -1 ? 0 : item.getPercentComplete();
StringBuilder sb = new StringBuilder();
// 判断当前网址是“英文网址” 还是“中文网址”
if (browser.getURL().contains("en-us"))
sb.append("It is downloading, ").append(percent).append("% completed.");
else
sb.append("正在下载,完成度:").append(percent).append("%。");
// 下载完毕让网页的下载窗口 dom 元素出现,并修改其中的文本信息
browser.executeJavaScript("$download.show(); pDownload.innerText='" + sb + "';", item.getURL(), 1);
} else {
// 下载完毕让网页的下载窗口 dom 元素隐藏
browser.executeJavaScript("setTimeout(() => $download.fadeOut('fast'), 1000);", item.getURL(), 2);
}
}
}
client.addContextMenuHandler(new MenuHandler());
,其中的 MenuHandler 类,由下一步创建。my.client.handler
中,创建一个 MenuHandler
类,继承自 CefContextMenuHandlerAdapter
,重写 onBeforeContextMenu
和 onContextMenuCommand
方法。此处参考 jcef 提供的 detailed 实例,里面有相关代码。
package my.client.handler;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.handler.CefContextMenuHandlerAdapter;
public class MenuHandler extends CefContextMenuHandlerAdapter {
@Override
public void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {
}
@Override
public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {
}
}
onBeforeContextMenu
方法中写一条语句就可以了:model.clear();
package my.client.handler;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefMenuModel.MenuId;
import org.cef.handler.CefContextMenuHandlerAdapter;
public class MenuHandler extends CefContextMenuHandlerAdapter {
private final static int MENU_ID_MORE = 10001;
@Override
public void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {
// 清除菜单项
model.clear();
//剪切、复制、粘贴
model.addItem(MenuId.MENU_ID_COPY, "copy");
model.addItem(MenuId.MENU_ID_CUT, "cut");
model.addItem(MenuId.MENU_ID_PASTE, "paste");
model.setEnabled(MenuId.MENU_ID_PASTE, false);
model.addSeparator();
CefMenuModel more = model.addSubMenu(MENU_ID_MORE, "more");
more.addItem(MenuId.MENU_ID_PRINT,"print");
more.addItem(MenuId.MENU_ID_VIEW_SOURCE,"view source");
model.addSeparator();
model.addItem(MenuId.MENU_ID_RELOAD, "reload");
}
@Override
public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {
switch (commandId) {
case MenuId.MENU_ID_RELOAD:
browser.reload();
return true;
}
return false;
}
}
onBeforeContextMenu 方法:
* model.clear(); // 清除菜单项。
* MenuId.MENU_ID_COPY // 是 MenuId 中定义好的一个值,使用特定值会触发默认的特定事件,也可以自定义,建议不要与 MenuId 类中已定义的值冲突。
* model.setEnabled(MenuId.MENU_ID_PASTE, false); // 是将这个按钮禁用,因为每次右键单击的时候都会触发这个方法,所以可以通过一些变量控制其是否被禁用。
* model.addSeparator(); // 是在菜单栏中添加一条分割线。
* model.addSubMenu(MENU_ID_MORE, "more"); // 创建下级菜单,返回值是一个 CefMenuModel 对象,通过这个对象继续 addItem 添加下级菜单项目。
onContextMenuCommand 方法:
* switch (commandId) // 可以通过 commandId 获取点击项目设置的 Id,然后去匹配,去实现相关功能。
* return true; // 阻止默认事件。
* return false; // 默认事件可以触发,如 print、copy、cut、paste 等都有默认事件,见名思意即可。
package my.client.handler;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.handler.CefContextMenuHandlerAdapter;
public class MenuHandler extends CefContextMenuHandlerAdapter {
private final static int MENU_ID_SAVE_PICTURE = 10001;
@Override
public void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {
//清除菜单项
model.clear();
if (params.hasImageContents() && params.getSourceUrl() != null) {
model.addItem(MENU_ID_SAVE_PICTURE, "图片另存为/save picture as...");
model.addSeparator();
}
}
@Override
public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {
switch (commandId) {
case MENU_ID_SAVE_PICTURE:
browser.startDownload(params.getSourceUrl());
return true;
}
return false;
}
}
params.hasImageContents()
方法能够返回 true,但是 params.getSourceUrl()
返回的是空字符串,所以执行 browser.startDownload("")
时不会发生任何事情。my.client
中创建一个 Package 叫做 dialog
,创建一个 DevToolsDialog
类,继承自 JDialog
,直接分享源代码:package my.client.dialog;
import org.cef.browser.CefBrowser;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class DevToolsDialog extends JDialog {
private final CefBrowser devTools_;
// 一般使用这个构造方法
public DevToolsDialog(Frame owner, String title, CefBrowser browser) {
this(owner, title, browser, null);
}
public DevToolsDialog(Frame owner, String title, CefBrowser browser, Point inspectAt) {
super(owner, title, false);
setLayout(new BorderLayout()); // 设置布局
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // 拿到屏幕尺寸
setSize(screenSize.width / 2,screenSize.height / 2); //设置大小为屏幕尺寸的一半,可以自定大小
setLocation(owner.getLocation().x + 20, owner.getLocation().y + 20); // 设置左上角点的位置,是指定 Frame 的左上角点的偏移 20px 位置
devTools_ = browser.getDevTools(inspectAt); // 获取到 browser 的 DevTools
add(devTools_.getUIComponent()); // 将其 UIComponent 添加上去
// 添加相关监听
addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
dispose();
}
});
}
@Override
public void dispose() {
devTools_.close(true); // 关闭的时候触发此方法,关闭 DevTools
super.dispose();
}
}
package my.client.handler;
import my.client.dialog.DevToolsDialog;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefMenuModel.MenuId;
import org.cef.handler.CefContextMenuHandlerAdapter;
import java.awt.*;
public class MenuHandler extends CefContextMenuHandlerAdapter {
private final Frame owner;
public MenuHandler(Frame owner) {
this.owner = owner;
}
private final static int MENU_ID_SHOW_DEV_TOOLS = 10000;
@Override
public void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {
//清除菜单项
model.clear();
model.addItem(MENU_ID_SHOW_DEV_TOOLS, "开发者选项");
}
@Override
public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {
switch (commandId) {
case MENU_ID_SHOW_DEV_TOOLS:
// 打开开发者选项
DevToolsDialog devToolsDlg = new DevToolsDialog(owner, "开发者选项", browser);
devToolsDlg.setVisible(true);
return true;
}
return false;
}
}
client.addContextMenuHandler(new MenuHandler(jFrame));
此处参考 jcef 提供的 detailed 实例,里面有相关代码。
my.client.handler
中,创建一个 MessageRouterHandler
类,继承自 CefMessageRouterHandlerAdapter
,重写 onQuery
。这里直接分享源代码:package my.client.handler;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandlerAdapter;
public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent, CefQueryCallback callback) {
// 请求信息以 "click:" 开头
if (request.indexOf("click:") == 0) {
String msg = request.substring(6).trim();
callback.success(msg + " create new message(cnm)"); // 返回对应信息到前端 success 回调函数
return true;
}
// 请求信息以 "custom:" 开头
if (request.indexOf("custom:") == 0) {
// 将后面的字符串按 ,:- 切割
String[] method = request.substring(7).trim().split("[,:\\-]");
switch (method[0].trim()) {
case "search":
callback.success("This is the result of search."); // 返回对应信息到前端 success 回调函数
break;
case "connect":
System.out.println(method[1].trim());
callback.success("This is the result of connect."); // 返回对应信息到前端 success 回调函数
break;
default:
callback.failure(404, "This is the result of failure."); // 返回对应信息到前端 failure 回调函数
break;
}
return true;
}
// Not handled.
return false; // 如果返回 false 则会自动执行一个 alert 弹出框提示没有 handled
}
@Override
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long query_id) {
}
}
// 这里的 cef 和 cefCancel 是自定义字符串,前端通过调用这两个字符串表示的方法来访问 client,
// 即对应的 onQuery 和 onQueryCanceled 方法。
CefMessageRouter cmr = CefMessageRouter.create(new CefMessageRouter.CefMessageRouterConfig("cef", "cefCancel"));
cmr.addHandler(new MessageRouterHandler(), true);
client.addMessageRouter(cmr);
function sendMessage() {
// 这里的 cef 就是 client 创建 CefMessageRouter 对象的入参涉及到的字符串
window.cef({
request: 'click:' + document.getElementById("message").value,
onSuccess(response) {
console.log(response);
},
onFailure(error_code, error_message) {
console.log(error_code, error_message);
}
});
}
function sendCustom() {
// 这里的 cef 就是 client 创建 CefMessageRouter 对象的入参涉及到的字符串
window.cef({
request: 'custom: connect-192.168.1.1',
onSuccess(response) {
console.log(response);
},
onFailure(error_code, error_message) {
console.log(error_code, error_message);
}
});
window.cef({
request: 'custom: search-' + JSON.stringify({a: 1, b: "str"}),
onSuccess(response) {
console.log(response);
},
onFailure(error_code, error_message) {
console.log(error_code, error_message);
}
});
}
★ 前端用 JSON.stringify() 将对象转换成字符串传输到 client,同样,后台接收过来的 response 数据用 JSON.parse() 转换成对象。
★ 后台则使用 net.sf.json 的 JSONObject.from()、JSONArray.from()、JSONArray.from().toString() 等方法将字符串转换成对象,将对象转换成字符串。