[Java] 自定义一个自己的窗口

[Java] 自定义UI窗口

0 目录

1 前言

​ 在经历过很多课设之后,我发现每次设计UI都需要占用我一大半的时间。为了之后的便利,索性写了几个窗口的封装模板,期末写课设的时候调用即可。本篇博客将手把手教会读者如何创建一个自己自定义的窗口,还附带很多拓展(例如:窗口组件随窗口变化而调整、JFrame的窗口偏移)。

警告:本片内容需要读者拥有一定的Java的awt包使用经验;本篇文章可能存在版本差异;本篇文章并非严格的设计模式。

2 准备阶段

​ 我写过很多次的封装模板,但是每一次几乎都不太合格,也就在最近,通过老师的点评和自己的努力,已经总结出一些最重要的步骤:

2.1 思考需求

​ 对于这一点,你需要知道你想写这个窗口干嘛、或者分为几个部分。我这里举个例子:在我做完课设的时候,我发现一般的课设都包含三个重要的面板:展示、信息、other。那么我将我的需求设定为:展示,信息,其他。那么我会将这三个模板设计为show、message、other面板,然后在窗口中调用。

JPanel show;
JPanel message;
JPanel other;

2.2 设计UI

​ 通过在纸上或者在PPT里面设计一个窗口UI,此处UI不需要太过于细分,因为这是一个模板。之后使用的时候也可能需要调整其中面板的大小。所以,仅仅需要将面板的大致关系写出来即可。其实就跟使用HTML一样,先将面板的具体位置关系设计出来。下面是我设计的基本UI:

UI

2.3 设计变量

​ 因为我们仅仅需要模板,之后的窗口比例是用户来自定义,则窗口的一些变量设置很重要。例如左右的占比大小,上下的占比大小,这些都可以让之后的使用和扩展更为方便。因为上面被分为了三个部分,那么可以使用下面的变量来控制组件的位置和大小:

int verticalProportion;
int horizontalProportion;
int gapPx;

JPanel content;

​ 其中gapPx代表组件间隙单位是px;content面板用来装左边的面板,方便调整左右比例。

2.4 设计代码实现

​ 我们可以确定以下的属性变量:

public class MyFrame extend JFrame {
	private JPanel show;
	private JPanel message;
	private JPanel other;
	private JPanel content;
	
	private int verticalProportion;
	private int horizontalProportion;
	private int gapPx;
}

​ 最后不要忘记添加构造器和访问器(content除外)。

3 具体实现

3.1 用户使用

​ 如何让用户使用这个窗口类?我思考了两个方案:

  1. 构造方法传参。用户通过组合的方式,将自定义三个面板,然后通过构造方法传参的方式,将面板放入指定的MyFrame中。
  2. 重写自定义三个方法——initShow、initMessage、initOther。用户通过继承的方式,重写面板的初始化方法达到用户可自定义效果。

​ 但实际想,若使用第二方案——继承,那么用户没有通过方法的方式修改参数,而是重写的方式修改了父类的参数,这与private属性相违背。简单来说就是违背了封装的设计特性。使得类的参数暴露在用户中。因为笔者暂时还没有学习设计模式,无法想到更加优秀的方法。

​ 在此篇文章使用第一个方法——构造方法传参。

3.2 默认值

​ 设置默认值,提供给用户一个无参构造,然后用户通过无参构造就可以看到默认的MyFrame窗口了。为了方便设置默认值,我们阶梯式设置四个构造方法:

public MyFrame() {
	this(new JPanel(), new JPanel(), new JPanel());
}

public MyFrame(JPanel show, JPanel message, JPanel other) {
	this(show, message, other, 1500, 900);
}

public MyFrame(JPanel show, JPanel message, JPanel other, int frameWidth, int frameHeight) {
	this(show, message, other, frameWidth, frameHeight, 80, 80, 10);
}

public MyFrame(JPanel show, JPanel message, JPanel other, 
               int frameWidth, int frameHeight, int verticalProportion, int horizontalProportion, int gapPx) {
}

​ 然后通过对最后一个构造方法设计即可。

3.3 实现UI

3.3.1 将构造方法分为不同的子方法

​ 为了便利和分模块写代码,我们将构造方法分开写,这样可以避免出错和方便Debug。

  • initPane——初始化面板组件
  • initData——初始化数据
  • initUI——初始化窗口和组件属性
  • initLocation——初始化组件位置
  • initListener——初始化监听器
public MyFrame(JPanel show, JPanel message, JPanel other,
               int frameWidth, int frameHeight, int verticalProportion, int horizontalProportion, int gapPx) {
    initPane(show, message, other);
    initData(verticalProportion, horizontalProportion, gapPx);
    initUI(frameWidth, frameHeight);
    initLocation();
    initListener();
}
private void initPane(JPanel show, JPanel message, JPanel other) {}
private void initData(int verticalProportion, int horizontalProportion, int gapPx) {}
private void initUI(int frameWidth, int frameHeight) {}
private void initLocation() {}
private void initListener() {}

​ 前两个都是初始化属性变量,最后一个则是添加监听,是为了之后的扩展。我们仅需要正对initUI和initLocation即可。下面笔者将介绍UI的一些awt的特性。

3.3.2 JPanel的常用方法和JFrame的特性

​ JPanel的常用方法:

JPanel jp1 = new JPanel();
JPanel jp2 = new JPanel();
jp1.add(jp2);
jp1.setBackground(new Color(0xFFFFFF));
jp1.setLayout(null);
jp1.setBounds(x, y, width, height);
  • add——jp2添加进jp1;

  • setBackGround——设置背景颜色;

  • setLayout——设置布局,null为自由布局;

  • setBounds——x、y为父面板的起始点坐标;width、height为横跨和竖跨像素大小;

​ JFrame的特性:

  1. JFrame只能存放最多三个面板。超过添加也不会显示在窗口中。
  2. JFrame和JPanel的默认布局的设定为BorderLayout,该布局无法自由设置组件的大小和位置。
  3. JFrame的默认关闭方式为窗口关闭不会结束程序。
  4. JFrame存在XY轴偏移。Y轴偏移:程序设置的窗口大小包括了窗口的控制栏内容;X轴偏移:不清楚也可能不存在。

​ 对于JFrame的特性问题,笔者给出对应的解决方案:

  1. 设置一个JPanel名叫box,然后将box添加进入窗口。之后的所有窗口都添加进入box即可;

  2. 我们仅需要将box的布局设置为null,就可以实现内部组件的自定义大小和位置;

  3. 可以使用下面方法,设置关闭窗口后程序结束:

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    
  4. 通过笔者测试,X轴偏移量为16个像素,Y轴偏移量是39个元素。

3.3.3 initUI方法
private void initUI(int frameWidth, int frameHeight) {
    setSize(frameWidth, frameHeight);

    JPanel box = new JPanel();
    content = new JPanel();
    add(box);
    box.add(content);
    box.add(message);
    content.add(show);
    content.add(other);

    box.setLayout(null);
    content.setLayout(null);
    message.setLayout(null);
    show.setLayout(null);
    other.setLayout(null);

    setBackground(new Color(0xf2f2f2));//grey
    content.setBackground(new Color(0xf2f2f2));
    message.setBackground(new Color(0xFFFFFF));//white
    show.setBackground(new Color(0xFFFFFF));
    other.setBackground(new Color(0xFFFFFF));
}
3.3.4 initLocation方法

​ 通过减法可以严格地扩充面板。

private void initLocation(){
    
    //offsetX/offsetY
    int frameWidth = getFrameWidth() - 16;
    int frameHeight = getFrameHeight() - 39;

    //get the px size after calculating the proportion
    double verticalRatio = verticalProportion / 100.0;
    double horizontalRatio = horizontalProportion / 100.0;
    int contentWidth = (int) ((frameWidth - 3 * gapPx) * verticalRatio);
    int messageWidth = frameWidth - contentWidth - 3 * gapPx;
    int boxHeight = frameHeight - 2 * gapPx;
    int showHeight = (int) ((boxHeight - gapPx) * horizontalRatio);
    int otherHeight = boxHeight - gapPx - showHeight;

    content.setBounds(gapPx, gapPx, contentWidth, boxHeight);
    message.setBounds(contentWidth + gapPx * 2, gapPx,messageWidth, boxHeight);
    show.setBounds(0, 0, contentWidth, showHeight);
    other.setBounds(0, showHeight + gapPx, contentWidth, otherHeight);
}
3.3.5 initPane和initData方法

​ 这两个方法很简单,目的就是为了传递参数。因为不同类型存在不同的判定,为了后续的发展,这里分开两边写,当然也可以一起写。

private void initPane(JPanel show, JPanel message, JPanel other) {
    this.show = show;
    this.message = message;
    this.other = other;
}

private void initData(int verticalProportion, int horizontalProportion, int gapPx) {
    this.verticalProportion = verticalProportion;
    this.horizontalProportion = horizontalProportion;
    this.gapPx = gapPx;
}
3.3.6 测试

​ 最后在该类里面写一个main方法测试一下:

public static void main(String[] args) {
    new MyFrame().setVisible(true);
}

​ 效果如下:

Result

4 效果扩展

4.1 动态窗口

​ 我们可以通过添加监听,实现动态窗口。主要思路为:

  1. 使用窗口监听,获取窗口大小是否被调整了。
  2. 若窗口大小被重新调整了,我们使用initLocation方法重置位置。

​ 具体实现如下代码:

private void initListener() {
    addComponentListener(new ComponentAdapter() {
        @Override
        public void componentResized(ComponentEvent e) {
            initLocation();
        }
    });
}

​ 在main中创建两个相同的窗口,移动效果如下:

ChangedFrame

​ 过程非常流畅,丝毫不卡顿。

5 最终代码

(别忘了设置packageimport javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

/**
 * @author ghsont
 */

public class MyFrame extends JFrame {

    private JPanel show;
    private JPanel message;
    private JPanel other;
    private JPanel content;

    private int verticalProportion;
    private int horizontalProportion;
    private int gapPx;

    public static void main(String[] args) {
        new MyFrame().setVisible(true);
    }

    public MyFrame() {
        this(new JPanel(), new JPanel(), new JPanel());
    }

    public MyFrame(JPanel show, JPanel message, JPanel other) {
        this(show, message, other, 1500, 900);
    }

    public MyFrame(JPanel show, JPanel message, JPanel other, int frameWidth, int frameHeight) {
        this(show, message, other, frameWidth, frameHeight, 80, 80, 10);
    }

    public MyFrame(JPanel show, JPanel message, JPanel other,int frameWidth, int frameHeight, int verticalProportion, int horizontalProportion, int gapPx) {
        initPane(show, message, other);
        initData(verticalProportion, horizontalProportion, gapPx);
        initUI(frameWidth, frameHeight);
        initLocation();
        initListener();
    }

    private void initPane(JPanel show, JPanel message, JPanel other) {
        this.show = show;
        this.message = message;
        this.other = other;
    }

    private void initData(int verticalProportion, int horizontalProportion, int gapPx) {
        this.verticalProportion = verticalProportion;
        this.horizontalProportion = horizontalProportion;
        this.gapPx = gapPx;
    }

    private void initUI(int frameWidth, int frameHeight) {
        setSize(frameWidth, frameHeight);

        JPanel box = new JPanel();
        content = new JPanel();
        add(box);
        box.add(content);
        box.add(message);
        content.add(show);
        content.add(other);

        box.setLayout(null);
        content.setLayout(null);
        message.setLayout(null);
        show.setLayout(null);
        other.setLayout(null);

        setBackground(new Color(0xf2f2f2));
        content.setBackground(new Color(0xf2f2f2));
        message.setBackground(new Color(0xFFFFFF));
        show.setBackground(new Color(0xFFFFFF));
        other.setBackground(new Color(0xFFFFFF));
    }

    private void initListener() {
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                initLocation();
            }
        });
    }

    private void initLocation() {
        int frameWidth = getWidth() - 16;
        int frameHeight = getHeight() - 39;

        double verticalRatio = verticalProportion / 100.0;
        double horizontalRatio = horizontalProportion / 100.0;
        int contentWidth = (int) ((frameWidth - 3 * gapPx) * verticalRatio);
        int messageWidth = frameWidth - contentWidth - 3 * gapPx;
        int boxHeight = frameHeight - 2 * gapPx;
        int showHeight = (int) ((boxHeight - gapPx) * horizontalRatio);
        int otherHeight = boxHeight - gapPx - showHeight;

        content.setBounds(gapPx, gapPx, contentWidth, boxHeight);
        message.setBounds(contentWidth + gapPx * 2, gapPx,messageWidth, boxHeight);
        show.setBounds(0, 0, contentWidth, showHeight);
        other.setBounds(0, showHeight + gapPx, contentWidth, otherHeight);
    }

    public JPanel getShow() {
        return show;
    }

    public void setShow(JPanel show) {
        this.show = show;
    }

    public JPanel getMessage() {
        return message;
    }

    public void setMessage(JPanel message) {
        this.message = message;
    }

    public JPanel getOther() {
        return other;
    }

    public void setOther(JPanel other) {
        this.other = other;
    }

    public int getVerticalProportion() {
        return verticalProportion;
    }

    public void setVerticalProportion(int verticalProportion) {
        this.verticalProportion = verticalProportion;
    }

    public int getHorizontalProportion() {
        return horizontalProportion;
    }

    public void setHorizontalProportion(int horizontalProportion) {
        this.horizontalProportion = horizontalProportion;
    }

    public int getGapPx() {
        return gapPx;
    }

    public void setGapPx(int gapPx) {
        this.gapPx = gapPx;
    }
}

6 个人感悟

​ 大一开始一直想写一个窗口类,然后大二上寒假写了好久,反反复复遇到很多问题。期间自己还不断地扩充内容,最后弄得不好扩展,代码写得像小说一样。最近终于对这个抱有了一些好奇,经历了两三天的时间,最后代码重写成功了。相比原来版本的代码更加符合代码的设计,但实际上肯定存在某些不足,例如content的设计比较别扭;其实我原本的代码还加入了frameWidth和frameHeight两个属性,这个其实完全没必要的,最后一次重写让我修改了;对于比例值还需要判定大小,我懒没写,读者可以实现。

​ 这次让我学到了:

  1. 分方法——具体需求具体分析;
  2. 窗口调整如此简单。

​ 之后,我还会根据类似的设计写出其他窗口。若读者需要,可以联系我邮箱。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值