swing中JFrame的各个面板关系&截图工具开发

官网解析:https://docs.oracle.com/javase/tutorial/uiswing/components/rootpane.html

一 JFrame层次

官方图片

二 源码浅析

JFrame

JFrame类中保存了一个JRootPane的变量,该变量值来自一个createRootPane()方法。创建JFrame时会调用createRootPane();赋值。

JFrame调用几个面板的细节如下:

root pane   

只有一个类叫做JRootPane  所以本文的rootpane、RootPane 都是指JRootPane

黄色部分圈出来的三个方法如下:(还是在JRootPanel.class中)

Glass pane

 并没有一个专门的类叫做GlassPane类,从上面的代码可知,它其实是个JPanel。本文的glasspanel Glass Panel都是一个意思。其它类似。

LayeredPane

有一个类叫做JLayeredPane

Content Pane

并没有一个专门的类ContentPane或者JContentPane。(本文的content Pane ContentPane都是一个意思) 在结构体系中,ContentPane属于LayeredPane。

它是由可以由JFrame或者RootPane调用setContentPane设置。最终都是调用JRootPane的setContentPane方法,所以直接看这个方法:

发现它的确被添加进了JFrame的RootPane的LayeredPane中。怪不得说

LayeredPane包含ContentPane。接着打开:JLayeredPane.FRAME_CONTENT_LAYER对应源码

真相大白:ContentPane是被位于LayeredPane中负30000层的一个JPanel对象。

MenuBar 可选的。默认没有。

综上可知:创建一个JFrame时,其实默认已经创建了RootPane、LayeredPane、ContentPane、GlassPane四种面板。

 疑问:JFrame为什么要设置这么多层面板?都有什么应用场景?

三 应用场景

RootPane

 一般不用设置。也不能往里面添加组件。

例如以下操作试图往JFrame中的RootPane中添加一个按钮。实际上并不会展示出来。

它是虚拟的容器。

当然我见过有人这么用的:简化代码如下

就是创建RootPane,然后将RootPane设置为JFrame的ContentPane。不是很理解为什么不调用setRootPanel而是调用setContentPane来设置rp。

GlassPane

 默认情况下是隐藏的,可以设置它显示出来。如果展示出来则位于RootPane的最顶层(上述图可看出来)。牢牢的记住它是属于RootPane的,因此只能通过RootPane对象setGlassPane()去设置它。同时不需要为它设置尺寸大小。

显示玻璃面板前后对比。

What?这能证明什么?证明它在JFrame的最上层?有什么用?

别急,官网有介绍:

好吧,通读全文,找出关键作用:拦截事件。

也就是说:可以将玻璃面板设置为透明的,由于它遮罩在JFrame体系结构中的最上层。因此如果需要拦截事件。是非常方便的。因为透明之后,用户感知不到它的存在。但是事件触发(例如:鼠标、按键事件)确实发生在玻璃面板上的。进而起到拦截事件的作用。

好吧,就算证明它可以拦截事件。那实际开发中到底有什么应用场景?

实在是太多了:五子棋、拼图、截图工具。都可以在玻璃面板上添加点击事件。不过按键事件无法拦截。

LayeredPane

官网是这么介绍的:

官网还配了一个例子:

关键意思如下:

每个根面板(root pane)都会将它的菜单条(menubar)和内容面板(content pane)放在一个JLayeredPane实例中。Z属性的顺序保证了他们展示的层次优先级。数值大的会展示在上层。你可以选择将组件放入根面板的layered面板中。即:

f.getRootPane().getLayeredPane().add(new JButton("按钮"), new Integer(1));

这里给出的案例例子如下:

有几个注意的点:

1组件的深度(或者叫层次)是通过Z属性来设置的(和CSS的z-index一样的意思)。体现在add方法的第二个参数。

2如果不设置第二个参数,添加进去的所有组件的层次都是相同的。都没有获得焦点的情况下,最先添加的会展示在最上面。哪个组件获得焦点哪个组件就展示在上层。

3添加的组件要设置尺寸和位置(一般都设置)否则展示不出来。

4可以直接使用rootPane的LayeredPane,来实现多容器深度的效果,可以新创建一个LayeredPane对象。将add进rootPane的contentPane中。再往LayeredPane中添加组件。

5不允许:先创建JPanel jp,作为ContentPanel。再创建LayeredPanel。add到jp中。

问题:能不能创建一个JPanel对象,然后将其设置为JFrame的root panel的 layered pane ?

答:不行。不管是JFrame还是rootpane 的set LayeredPane方法,接受的参数都是JLayeredPane类型的。它和JPane没有继承关系。只是使用方式有些相同。

综上:可以得出LayeredPane的使用方法和场景。

使用方法:

①使用rootPanl默认的layered pane来添加组件。f.getRootPane().getLayeredPane().add(new JButton("按钮"), new Integer(1));

② 手动创建一个layeredpane对象,设置为rootPane的ContentPane。

JLayeredPane jr = new JLayeredPane();

f.setContentPanel( jr );

ContentPane

现在对Content已经没有可神秘的了。它无非就是JFrame的RootPane的LayeredPane中负30000层的一个JPanel对象。(特意重复强调)如果说还有一点小疑惑,那就是:以下三种方式底层是一样的操作吗?

方式一

f.add(new JButton("按钮"));

方式二

f.getContentPane().add(new JButton("按钮"));

方式三

JPanel jcp = new JPanel();

jcp.add(new JButton());

f.setContentPane(jcp);

答案:一样的。方式二和方式三很容易证明。只是修改了ContentPane 。方式一,查看源码有点麻烦,不过有更简单的检测办法:

结果证明,直接调用JFrame的add方法添加的组件其实都是add到ContentPane中。

四 铁锅大乱炖

如果一个JFrame中,即存在GlassPane、又存在。。抱歉。口误。因为平时使用JFrame时感知不到有这么多面板存在,JFrame中本来就存在这么多面板它们没有展示出来或者被我们忽略了。

我应该这么问,如果JFrame中的GlassPane、RootPane、LayeredPane、ContentPane、MenuBar同时发挥作用展示出来,会是一个什么样的场景?群魔乱舞?铁锅大乱炖?

好的!我们决定使用上全部的面板。来实现一个截图工具。

好了开始动工吧。

步骤一

在玻璃面板上添加截屏按钮。按下截屏按钮时做步骤二。

步骤二

隐藏截屏按钮、将当前窗口隐藏。200ms后。截屏。展示窗口。将图片设置到ContentPanel中。隐藏遮罩层清空位置信息。隐藏选择层。

步骤三

再玻璃面板上开始拖动鼠标。显示遮罩层,尤其时位置要还原。显示选择层。记录拖动起点。

步骤四

鼠标拖动过程中。动态计算截图的部分,展示在选择层中。

步骤五

拖动结束。图片不能再动。展示玻璃面板中的保存按钮。

步骤六

点击保存按钮。打开文件保存框。等待用户填写保存路径。

步骤七

如果保存路径不为空。将选中图片通过ImageIO写入到指定的File中。

步骤八

还原到步骤一。

代码,下篇博客发。

猜你喜欢

转载自my.oschina.net/lightled/blog/1823254