javafx treeview菜单千层渲染算法

通过接口自动构建TreeView树形菜单

进行javafx开发时候,渲染树形菜单代码比较繁琐,本文将通过查询到的sql结果集,自动装配TreeView。其中装配算法通过java的引用重定向,递归回调等实现,,代码算法逻辑难道较大,小白慎入。且该算法可以升级到一切父子结构菜单,通过返回json串,只需要一次调用,即可渲染10层,100层菜单树。算法效率很高。复杂度仅仅为O(N)(N是所有节点个数)。

下例展示5层菜单的无延迟极快渲染

sql建表,预装数据


CREATE TABLE IF NOT EXISTS `menu0` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='0级菜单';
INSERT INTO `menu0` (`id`, `name`) VALUES
	(1, '世界');

CREATE TABLE IF NOT EXISTS `menu1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='一级菜单';
INSERT INTO `menu1` (`id`, `name`, `parent_id`) VALUES
	(1, '亚洲', 1),
	(2, '美洲', 1),
	(3, '欧洲', 1);


CREATE TABLE IF NOT EXISTS `menu2` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='二级菜单';
INSERT INTO `menu2` (`id`, `name`, `parent_id`) VALUES
	(1, '中国', 1),
	(2, '韩国', 1),
	(4, '美国', 2),
	(3, '日本', 1),
	(5, '加拿大', 2),
	(6, '墨西哥', 2),
	(7, '巴拿马', 2),
	(8, '英国', 3),
	(9, '法国', 3);

CREATE TABLE IF NOT EXISTS `menu3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='三级菜单';
INSERT INTO `menu3` (`id`, `name`, `parent_id`) VALUES
	(1, '澳门', 1),
	(2, '香港', 1),
	(3, '首尔', 2),
	(4, '釜山', 2),
	(5, '纽约', 4),
	(6, '华盛顿', 4),
	(7, '安徽', 1),
	(8, '长崎', 3),
	(9, '东京', 3),
	(10, '多伦多', 5),
	(11, '阿瓜斯卡连特斯', 6),
	(12, '阿瓜斯卡连特斯1', 6),
	(13, '巴黎', 9),
	(14, '伦敦', 8),
	(15, '伯明翰', 8);
	
CREATE TABLE IF NOT EXISTS `menu4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `parent_name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COMMENT='四级菜单';
INSERT INTO `menu4` (`id`, `name`, `parent_id`, `parent_name`) VALUES
	(1, '澳门半岛', 1, '澳门'),
	(2, '九龙', 2, '香港'),
	(3, '江南区', 3, '首尔'),
	(4, '西临沙上区', 4, '釜山'),
	(5, '布朗克斯', 5, '纽约'),
	(6, '奥林匹亚市', 6, '华盛顿'),
	(7, '安庆', 7, '安徽'),
	(8, '半岛', 8, '长崎'),
	(9, '千代田区 ', 9, '东京'),
	(10, '士嘉堡', 10, '多伦多'),
	(11, 'a区', 11, '阿瓜斯卡连特斯'),
	(12, 'a1区', 12, '阿瓜斯卡连特斯1'),
	(13, '第1区', 13, '巴黎'),
	(14, '伦敦城', 14, '伦敦'),
	(15, 'a', 15, '伯明翰'),
	(16, '氹仔岛', 1, '澳门'),
	(17, '江东区', 3, '首尔'),
	(19, '布朗克斯', 5, '纽约'),
	(20, '布鲁克林', 5, '纽约'),
	(21, '斯波坎', 6, '华盛顿'),
	(22, '合肥', 7, '安徽'),
	(23, '蚌埠', 7, '安徽'),
	(24, '港区', 9, '东京'),
	(25, '中央区', 9, '东京'),
	(26, '第2区 ', 13, '巴黎'),
	(27, '第3区 ', 13, '巴黎'),
	(28, '西伦敦', 14, '伦敦');


查询结果集

select
		cast(t0.id as char) f_f_1_menuId,
		t0.name f_f_1_menuName,

		cast(t1.id as char) f_f_2_menuId,
		t1.name f_f_2_menuName,

		cast(t2.id as char) f_f_3_menuId,
		t2.name f_f_3_menuName,

		cast(t3.id as char) f_f_4_menuId,
		t3.name f_f_4_menuName,
		
		cast(t4.id as char) f_f_5_menuId,
		t4.name f_f_5_menuName

		from  menu0 t0 
		left join menu1 t1 on t0.id=t1.parent_id

		left join menu2 t2 on t1.id=t2.parent_id

		left join menu3 t3 on t2.id=t3.parent_id
		left join menu4 t4 on t3.id=t4.parent_id

如下sql结果为:
在这里插入图片描述

main函数渲染结果

public class Main extends Application {
	private MysqlUtils mysqlUtils = new MysqlUtils();
	private MenuNode menuNode = new MenuNode();
	@Override
	public void start(Stage primaryStage) {
		try {
			Pane p = new Pane();
			p.setPrefHeight(500);
			p.setPrefWidth(400);
			Scene scene = new Scene(p,800,800);
			
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			
			
			//@1 sql获取菜单集合,只需要查询菜单列表,调用一个接口,就自动获取treeview,你不需要去写任何sql以外的代码。
			String sql ="select\r\n" + 
					"		cast(t0.id as char) f_f_1_menuId,\r\n" + 
					"		t0.name f_f_1_menuName,\r\n" + 
					"\r\n" + 
					"		cast(t1.id as char) f_f_2_menuId,\r\n" + 
					"		t1.name f_f_2_menuName,\r\n" + 
					"\r\n" + 
					"		cast(t2.id as char) f_f_3_menuId,\r\n" + 
					"		t2.name f_f_3_menuName,\r\n" + 
					"\r\n" + 
					"		cast(t3.id as char) f_f_4_menuId,\r\n" + 
					"		t3.name f_f_4_menuName,\r\n" + 
					"		\r\n" + 
					"		cast(t4.id as char) f_f_5_menuId,\r\n" + 
					"		t4.name f_f_5_menuName\r\n" + 
					"\r\n" + 
					"		from  menu0 t0 \r\n" + 
					"		left join menu1 t1 on t0.id=t1.parent_id\r\n" + 
					"\r\n" + 
					"		left join menu2 t2 on t1.id=t2.parent_id\r\n" + 
					"\r\n" + 
					"		left join menu3 t3 on t2.id=t3.parent_id\r\n" + 
					"		left join menu4 t4 on t3.id=t4.parent_id";
			List<Map<String, Object>> muneList = mysqlUtils.getList(sql);
			
			//@2 调用公共接口,一键渲染得到treeview对象;
			TreeView<String> tree = menuNode.getMenuTreeview(muneList);
			tree.setPrefHeight(800);
			tree.setPrefWidth(800);
			
			//@3 写入scene
			p.getChildren().add(tree);
			
			
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}

运行代码得到结果如下:
在这里插入图片描述
在这里插入图片描述

核心算法源码

public static MenuNode getMenu(List<Map<String, Object>> muneList,MenuNode rootMenuNode, String... arg) throws Exception {
		MenuNode rootMenu = rootMenuNode;
		String menuStartWith = menuFieldRegDefalt;
		
		if(arg.length > 0) {//针对arg有参数,
			menuStartWith = arg[0];
			if(!RegexpTool.isMatch(menuStartWith, menuFieldRegLeft)) {
				throw new Exception(String.format("arg[0] pattern is not '%s'", menuFieldRegLeft) );
			}
		}
		
		/**
		 * 1、获取菜单层次
		 */
		
		int maxLevel = 0;//菜单最大层数
		boolean f_1th_map = true;//第一次遍历muneList标记
		for(Map<String, Object> map : muneList) {//遍历数据行(f1_*,f1_*,f2_*,f2_*格式的数据)(后改成f_f_1_*)
			
			if(f_1th_map) {//第一次遍历记录maxLevel
				
				for(Entry<String, Object> entry : map.entrySet()) {
					if(!RegexpTool.isMatch(entry.getKey(), menuFieldRegLeft + ".*")) {
						continue;
					}else {
						if(!RegexpTool.isMatch(entry.getKey(), menuFieldReg)) {
							throw new Exception(String.format("muneList field about mune %s pattern is not '%s'", entry.getKey(), menuFieldReg));
						}
					}
					String levelStr = RegexpTool.getOneByReg(entry.getKey(), "(?<="+menuStartWith+")\\d+");
					int level = Integer.parseInt(levelStr == null ? "0" : levelStr);//获取当前菜单层级。

					if(maxLevel < level) {
						maxLevel = level;
					}
				}
				f_1th_map = false;
			}
			
			int i = 0;
			List<MenuNode> enuNodeList_1 = new ArrayList<>();
			MenuNode menuNode = null;
			String parentId = "";
			while(maxLevel > i) {//依次处理 f1,f2,f3,f4...........,该行各个层级的数据,放入rootMenu 对应的位置。
				if(i == 0) {
					enuNodeList_1 = rootMenu.getMenuSub();
				}else {
					enuNodeList_1 = menuNode.getMenuSub();
				}
				
				/**
				 * 获取map值。
				 */
				String parentID = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"parentID"));
				String menuId = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"menuId"));
				String menuName = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"menuName"));
				String orderById = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"orderById"));
				String remark = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"remark"));
				String icon = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"icon"));
				String style = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"style"));
				String show = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"show"));
				String path = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"path"));
				boolean expanded = (boolean) (map.get(String.format("%s%d_%s", menuStartWith,i+1,"expanded")) == null ? false :map.get(String.format("%s%d_%s", menuStartWith,i+1,"expanded")));
				if(menuId == null) {
					break;
				}
				if(enuNodeList_1.size() == 0) {
					menuNode = new MenuNode(parentID == null ? "-1" : parentID, menuId == null ? "0" : menuId, menuName == null ? "0" : menuName, orderById == null ? "" : orderById,
							remark == null ? "" : remark, icon == null ? "" : icon, style == null ? "" : style, StringUtils.isBlank(show) ? "1" : show, expanded, path);
					enuNodeList_1.add(menuNode);
				}else {
					//如果size>0,且相等于当前,则menuNode重新指向;如果size=0且不相等,则新建。
					boolean f_sizem0e = false;//flag
					for(MenuNode m : enuNodeList_1) {
						if(m.getMenuId().equals(menuId)) {
							menuNode = m;
							f_sizem0e = true;
							break;
						}
					}
					
					if(!f_sizem0e) {
						menuNode = new MenuNode(parentID == null ? "-1" : parentID, menuId == null ? "0" : menuId, menuName == null ? "0" : menuName, orderById == null ? "" : orderById,
								remark == null ? "" : remark, icon == null ? "" : icon, style == null ? "" : style, StringUtils.isBlank(show) ? "1" : show, expanded, path);
						enuNodeList_1.add(menuNode);
					}
					
				}
				
				enuNodeList_1 = menuNode.getMenuSub();//重新指向下一个menuNode
				i++;
			}
		}
		return rootMenu;
	}

总结

通过一个sql查询结果集,调用该源码公共接口,直接渲染模型。该算法,解决了遍历的空间复杂度,时间复杂度问题,合理利用java的对象引用概念,递归模式,灵活指向父子对象,完成百层,千层菜单渲染。而且你可以升级我的源码,将可以通过sql直接得到前端web界面需要的父子结果菜单json串,从而快捷渲染树形菜单。且比你的传统获取菜单方式快捷,方便。而且关于该源码算法,也是研读了base64算法源码,得来的灵感。当然该算法对java引用的理解要求高。我相信你如果喜欢java,通过java实现复杂算法逻辑,实现复杂的业务逻辑,你可以下载该源码,执行完sql建表语句后,可以在jdk环境下直接运行。欢迎大家一起交流,你会发现java很美妙。最近也改造了不少javafx相关源码,你会发现这是一件有趣的事情。且在这个过程中你会体会到对象这个概念在java中是如何诠释地淋漓尽致。万物皆对象。且我的博客以后会发很多类似文章。
博主希望实现公司项目地复杂算法逻辑,对于java性能优化,sql优化有较多实践。欢迎各位,不吝赐教。其实我也是一个菜鸟,很多都不懂,只是喜欢java 对象,它很优雅。
算法java项目:

欢迎阅读源码,交流。正在搭建中网站:albagu.com;

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Java在窗口中添加树形菜单TreeView源代码,分享JAVA新手的一个例子,JTextField jtfInfo; //文本域,用于显示点击的节点名称   public JTreeDemo(){    super("森林状的关系图"); //调用父类构造函数       DefaultMutableTreeNode root = new DefaultMutableTreeNode("设置"); //生成根节点    DefaultMutableTreeNode node1=new DefaultMutableTreeNode("常规"); //生成节点一    node1.add(new DefaultMutableTreeNode("默认路径")); //增加新节点到节点一上    node1.add(new DefaultMutableTreeNode("保存选项"));    root.add(node1); //增加节点一到根节点上    root.add(new DefaultMutableTreeNode("界面"));    root.add(new DefaultMutableTreeNode("提示声音"));    root.add(new DefaultMutableTreeNode("打印"));       JTree tree = new JTree(root); //得到JTree的实例    DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)tree.getCellRenderer(); //得到JTree的Renderer    renderer.setLeafIcon(null); //设置叶子节点图标为空    renderer.setClosedIcon(null); //设置关闭节点的图标为空    renderer.setOpenIcon(null); //设置打开节点的图标为空       tree.addTreeSelectionListener(new TreeSelectionListener() { //选择节点的事件处理    public void valueChanged(TreeSelectionEvent evt) {    TreePath path = evt.getPath(); //得到选择路径    String info=path.getLastPathComponent().toString(); //得到选择的节点名称    jtfInfo.setText(info); //在文本域中显示名称    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ernest ZZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值