gui设置可编辑文本框的回调函数_从零开始写文本编辑器(十九):代码树组织...

9fb05527c6b9bd048204ba81f4e3f4f0.png

前言

在第十六篇,我把一个Edit.java重构成多个单独的类文件。

il0vec:从零开始写文本编辑器(十六):从Editor.java分化成多个单独类文件​zhuanlan.zhihu.com
2bfc40798a7432a75c3556c3b7929ecd.png

但是结果仍然不理想,类的列表超过了一个显示器屏幕的范围。本来是没有打算分类成代码树组织的。但考虑到新功能的不断添加,所以从本篇起就要正式用代码树组织类文件了。

重构后的代码树结构

卷 文档与开发软件 的文件夹 PATH 列表
卷序列号为 AAA3-83B6
D:.
└─editor
    └─frame
        └─main
            ├─dialog
            │  ├─hexviewer
            │  └─preference
            │      ├─apply_close
            │      ├─category
            │      └─content
            ├─menubar
            ├─statusbar
            ├─tabbedpanel
            │  └─scrollpanel
            │      ├─list
            │      │  └─linenum
            │      └─textpanel
            └─token
                └─keywords_java

我的命名很随意,但方向是向“GUI界面结构”收敛的。即GUI的窗口可以联想出源代码的类层级路径,反之亦然。

保持这个重构方向不变,这样代码越迭代,组织结构就越趋近GUI观察层级包含结果。

例如:

观察下面目录层级

            │  └─scrollpanel
            │      ├─list
            │      │  └─linenum
            │      └─textpanel

可以对应出GUI界面的滚动面板,它的行头视图是JList列表控件,它的视口控件是JTextPane。

bcfe0ebf6b0fbee618763a4011f2ba65.png

这样就很方便维护了。

代码树结点的命名

命名主要以GUI控件类名为主,比如:frame dialog menubar,只是我去掉了 swing的默认 J 前缀。

命名还有部分是非GUI类,比如:词法分析 TokenReader 模块,由于它的上游数据是来自代码编辑器的文本框(JTextPane),所以我把 TokenReader 也放到 JTextPane 附近。目前是放到 TabbedPanel 的同一级,后续会小调整,但距离 JTextPane 不会太远。

访问权限的微调

  • 一阶段:任意访问权限

是写在同一个Editor.java类中,所有权限都是随意访问的,即使写成私有 private,仍然在同一个源文件中可以互相访问,所以在初始时,没有考虑访问权限的设计。

  • 二阶段:包级私有权限(缺省)

重构成多个在editor同一目录下的几十个类,相访问对方的方法,只需把private 提升为 protected,用eclipse自动修改为缺省。

  • 三阶段:就是本篇使用的代码树组织

对于逻辑是调用JDK静态方法的类,就直接写成静态方法。

比如:偏好 Preference 和 全局资源 Res 模块,

package editor;
import java.util.prefs.Preferences;

public class PreferenceEditor  {
	
	private static Preferences preferences;

	public static Preferences getPreferences() {
		if (preferences == null) {
			preferences = Preferences.userNodeForPackage(Editor.class);
		}
		return preferences;
	}
}
package editor;

import javax.swing.KeyStroke;

public class Res {

	private static HashMapStringsEn hashMapStringsEn;
	private static HashMapStringsZh hashMapStringsZh;
	private static HashMapAccelerator hashMapAccelerators;

	private static Lang langCurrent;

	public static void init() {
		langCurrent = Lang.chinese;
		hashMapStringsEn = new HashMapStringsEn();
		hashMapStringsZh = new HashMapStringsZh();
		hashMapAccelerators = new HashMapAccelerator();
	}

	public static String getString(String stringId) {
		try {
			if (langCurrent == Lang.chinese) {
				return hashMapStringsZh.get(stringId);
			} else {
				return hashMapStringsEn.get(stringId);
			}
		} catch (NullPointerException e) {
			e.printStackTrace();
			System.out.printf("Error: %s.init() not invokedn", Res.class.getName());
			return null;
		}
	}

	public static KeyStroke getAccelerator(String stringId) {
		try {

			return hashMapAccelerators.get(stringId);
		} catch (NullPointerException e) {
			e.printStackTrace();
			System.out.printf("Error: %s.init() not invokedn", Res.class.getName());
			return null;
		}
	}

}

对于实体类,它需要创建实例来使用。

多数是设计为私有成员和公有get-set-add-remove等方法,这些是按源码的风格来写。也没什么多讲的。

有一个耗时的重构内容:匿名类的重新组织,主要是各种事件侦听回调。这个还真不好讲清楚。我试试打个比方。

在一阶段也写了很多匿名事件侦听类,比如:

  1. 菜单项“偏好”,显示“个人偏好”对话框。
  2. “个人偏好”对话框点击“应用”按钮,会记录偏好的字体设置,并立即更新编辑框的字体。

其中1)是好重构的,因为它是单向关联。然而2)是双向关联,有点麻烦。所以重构为添加多个单独的回调。

首先,在偏好对话框内部,仍然匿名类完成记录偏好字体设置的逻辑。

private ActionListener actionListenerApply = new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		for (int i = 0; i < panelPreferenceContent.getComponentCount(); i++) {
			Component component = panelPreferenceContent.getComponent(i);
			if (component instanceof PanelPreferenceFont) {
				PanelPreferenceFont panelPreferenceFont = (PanelPreferenceFont) component;
				panelPreferenceFont.apply();
			} else if (component instanceof PanelPreferenceMultiDocument) {
				PanelPreferenceMultiDocument panelPreferenceMultiDocument = (PanelPreferenceMultiDocument) component;
				panelPreferenceMultiDocument.apply();
			} else if (component instanceof PanelPreferenceRecentFiles) {
				PanelPreferenceRecentFiles panelRecentFiles = (PanelPreferenceRecentFiles) component;
				panelRecentFiles.apply();
			} else if (component instanceof PanelPreferenceDeveloper) {
				PanelPreferenceDeveloper panelPreferenceDeveloper = (PanelPreferenceDeveloper) component;
				panelPreferenceDeveloper.apply();
			} else {
				// more branch.
			}
		}
	}
};

然后 FrameMain 初始时,就绑定了处理更新编辑框字体更新的逻辑

public class FrameMain extends JFrame {
	...
	private void init() {
		...
		dialogPreference = new DialogPreference();
		dialogPreference.addActionListenerApply(actionListenerDialogPreferenceApply);
	}    
	...

	private ActionListener actionListenerDialogPreferenceApply = new ActionListener() {

		@Override
		public void actionPerformed(ActionEvent e) {
			Font font = PreferenceFont.fontRead();
			if (font != null) {
				tabbedPaneEditors.setFont(font);
			} // end if
		}
	};
	...
}

最后要配合实现修改字体的逻辑,很多小细节的设计,比如:

  1. 用包装器思想,来穿透多层更新到编辑器的字体;
  2. 用多态思想,重写标签面板的setFont()方法,来更新内部的所有标签下的文本编辑控件的字体;
  3. 用多态思想,重写滚动面板的setFont()方法,来同步更新行号和代码的字体。
  4. ...

这些细节都是迭代完成的,起始我也没想清楚。

但有一个结构是我一直很清楚的。就是前面提过的向“GUI界面结构”收敛。

比如:PanelPreferenceApplyAndClose 类

package editor.frame.main.dialog.preference.apply_close;

import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JPanel;

import editor.Res;

public class PanelPreferenceApplyAndClose extends JPanel {

	private static final long serialVersionUID = 1L;
	
	private JButton buttonApply;
	private JButton buttonClose;

	public PanelPreferenceApplyAndClose() {
		super();
		setLayout(new FlowLayout(FlowLayout.RIGHT));
		{
			JButton button = new JButton(Res.getString("apply"));
			buttonApply = button;
			add(button);
		}
		{
			JButton button = new JButton(Res.getString("cancel"));
			buttonClose = button;
			add(button);
		}
	}

	public JButton getButtonApply() {
		return buttonApply;
	}

	public JButton getButtonClose() {
		return buttonClose;
	}
}

它是对应偏好对话框底部的“应用”和“取消”按钮的父面板类。

74534ee26a9eb2f6c2a9c18edf90b5d5.png

在这里没有直接写任何按钮处理逻辑,因为应该把逻辑由上游实现。为了这个目的,要开放按钮的接口。当然也可以直接写几对addActionListener/removeActionListener,但是计划会变的,比如:将来可能有禁用按钮状态等功能。所以这个类就只完成布局的逻辑,不干别的事情。

另一处类似的重构是菜单项,直接上代码

private void initMenuItemActions() {
	{
		MenuFile menuFile = menuBarMain.getMenuFile();
		menuFile.getMenuItemNew().addActionListener(actionListenerNew);
		menuFile.getMenuItemOpen().addActionListener(actionListenerOpen);
		menuFile.getMenuItemSave().addActionListener(actionListenerSave);
		menuFile.getMenuItemSaveAs().addActionListener(actionListenerSaveAs);
		menuFile.getMenuItemExit().addActionListener(actionListenerExit);
	}
	{
		MenuView menuView = menuBarMain.getMenuView();
		menuView.getMenuItemHexView().addActionListener(actionListenerHexView);
	}
	{
		MenuTest menuTest = menuBarMain.getMenuTest();
		menuTest.getMenuItemDivZero().addActionListener(actionListenerDivZero);
		menuTest.getMenuItemMDI().addActionListener(actionListenerMDI);
		menuTest.getMenuItemToken().addActionListener(actionListenerToken);
		menuTest.getMenuItemCodeColor().addActionListener(actionListenerCodeColor);
	}
	{
		MenuWindow menuWindow = menuBarMain.getMenuWindow();
		menuWindow.getMenuItemPreference().addActionListener(actionListenerPreference);
	}
	{
		MenuHelp menuHelp = menuBarMain.getMenuHelp();
		//
	}
}

75329d78e31af77b8e7cdef71f1b6ffb.png
逻辑全写在FrameMain中

其实有一个好玩的比喻

一家里有两兄弟吵架分一个苹果,哥哥和弟弟互相不服,都要自己来分,想给自己多分一部分。这时让哥哥或弟弟分都不合适。所以应该有一个高于他们的人来处理,应该是爸爸。这样由爸爸来分苹果,就公平了,不管怎么分,都是爸爸的逻辑问题,不公平找爸爸,而不是哥哥弟弟之间互相扯皮(双向关联)。
哪么,爸爸是哪个类呢?

答:从源代码树中,向上级找类,找到一个公共的最近父类。

点到即止,就说到这里吧!

以上~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值