基于mxCell的value对象属性构建自定义Swing画布、事件处理可行性及空指针异常问题问询
解决mxCell自定义Swing画布的空指针问题与事件处理方案
咱们先从你遇到的空指针异常问题入手,再一步步实现自定义画布的事件处理~
一、搞定空指针异常
你在drawVertex里直接强转mxCell的value导致空指针,大概率是因为有些单元格的value没有被正确设置为你的Pattern对象,或者cell本身的类型不符合预期。给你几个具体的修复步骤:
1. 先加层层空值检查,避免强转翻车
修改你的drawVertex方法,先判断cell和value的类型,再执行绘制逻辑,同时兼容非自定义单元格的情况:
@Override public void drawVertex(mxCellState state, String label) { Object cell = state.getCell(); // 先判断是不是mxCell类型 if (cell instanceof mxCell) { mxCell mxCell = (mxCell) cell; Object value = mxCell.getValue(); // 再判断是不是咱们的Pattern对象 if (value instanceof Pattern) { Pattern p = (Pattern) value; // 你的按钮绘制逻辑 for (int i = 0; i < p.length; i++) { int btnX = (int) (state.getX() + translate.getX() + i * state.getWidth()/p.length); int btnY = (int) (state.getY() + translate.getY()); int btnWidth = (int) state.getWidth()/p.length; int btnHeight = (int) state.getHeight(); rendererPane.paintComponent(g, new JButton("hi"), graphComponent, btnX, btnY, btnWidth, btnHeight, true); } return; } } // 不是自定义单元格的话,调用父类方法绘制默认样式 super.drawVertex(state, label); }
2. 确保所有自定义单元格都正确设置了value
创建单元格的时候,一定要把Pattern对象作为value传入,比如:
// 假设Pattern类有带length参数的构造方法 Pattern pattern = new Pattern(3); // 要绘制3个按钮 mxCell customCell = (mxCell) graph.insertVertex( graph.getDefaultParent(), null, pattern, 100, 100, 300, 50 // x,y,width,height );
3. 检查重写的drawState和convertValueToString方法
这两个方法不要修改cell的value,只负责绘制状态和文本转换就行,比如:
@Override public void drawState(mxICanvas canvas, mxCellState state, boolean drawLabel) { // 只处理自定义单元格的绘制,其他交给父类 Object value = ((mxCell) state.getCell()).getValue(); if (value instanceof Pattern) { // 你的自定义绘制逻辑(如果需要在这里补充的话) } else { super.drawState(canvas, state, drawLabel); } } @Override public String convertValueToString(Object cell) { if (cell instanceof mxCell) { Object value = ((mxCell) cell).getValue(); if (value instanceof Pattern) { return "按钮数量:" + ((Pattern) value).length; } } return super.convertValueToString(cell); }
二、自定义Swing画布的事件处理当然可以实现!
不过要注意:你用rendererPane.paintComponent绘制的按钮只是“画出来的样子”,并没有真正加入Swing的组件树,所以默认没有事件响应。这里给你两种实现思路:
思路1:模拟事件处理(适合简单场景)
直接在mxGraphComponent中重写鼠标事件方法,判断点击位置是否落在某个自定义按钮的区域内:
graphComponent = new mxGraphComponent(graph) { @Override public void mouseClicked(MouseEvent e) { super.mouseClicked(e); // 获取点击位置对应的单元格 mxCell clickedCell = (mxCell) getCellAt(e.getX(), e.getY()); if (clickedCell != null && clickedCell.getValue() instanceof Pattern) { Pattern p = (Pattern) clickedCell.getValue(); mxCellState state = getGraph().getView().getState(clickedCell); int btnWidth = (int) state.getWidth() / p.length; // 遍历每个按钮的区域,判断点击位置是否命中 for (int i = 0; i < p.length; i++) { int btnX = (int) (state.getX() + getTranslate().getX() + i * btnWidth); int btnY = (int) (state.getY() + getTranslate().getY()); Rectangle btnArea = new Rectangle(btnX, btnY, btnWidth, (int) state.getHeight()); if (btnArea.contains(e.getPoint())) { // 这里处理按钮点击事件,比如打印日志 System.out.println("点击了第" + (i+1) + "个按钮"); break; } } } } };
思路2:真正添加Swing组件(适合复杂交互)
如果需要按钮的悬停、焦点等原生Swing交互效果,就把JButton真正加到mxGraphComponent的组件树里,然后监听单元格的位置变化实时更新按钮布局:
// 用Map存储单元格和对应按钮的映射,方便后续管理 private Map<mxCell, JButton[]> cellButtonMap = new HashMap<>(); // 创建自定义单元格时,同时创建并添加按钮 Pattern pattern = new Pattern(3); mxCell cell = (mxCell) graph.insertVertex(graph.getDefaultParent(), null, pattern, 100, 100, 300, 50); JButton[] buttons = new JButton[3]; for (int i = 0; i < 3; i++) { JButton btn = new JButton("按钮" + (i+1)); // 给按钮加原生事件监听器 btn.addActionListener(evt -> { JOptionPane.showMessageDialog(null, "你点击了" + btn.getText()); }); graphComponent.add(btn); buttons[i] = btn; } cellButtonMap.put(cell, buttons); // 监听视图变化,实时更新按钮的位置和大小 graph.getView().addListener(mxEvent.CHANGE, (sender, evt) -> { for (Map.Entry<mxCell, JButton[]> entry : cellButtonMap.entrySet()) { mxCell cell = entry.getKey(); mxCellState state = graph.getView().getState(cell); if (state != null) { JButton[] buttons = entry.getValue(); int btnWidth = (int) state.getWidth() / buttons.length; int baseX = (int) (state.getX() + graphComponent.getTranslate().getX()); int baseY = (int) (state.getY() + graphComponent.getTranslate().getY()); int height = (int) state.getHeight(); for (int i = 0; i < buttons.length; i++) { buttons[i].setBounds(baseX + i*btnWidth, baseY, btnWidth, height); } } } }); // 记得在单元格删除时清理按钮,避免内存泄漏 graph.addListener(mxEvent.CELLS_REMOVED, (sender, evt) -> { Object[] removedCells = (Object[]) evt.getProperty("cells"); for (Object cellObj : removedCells) { if (cellObj instanceof mxCell) { JButton[] buttons = cellButtonMap.remove(cellObj); if (buttons != null) { for (JButton btn : buttons) { graphComponent.remove(btn); } } } } });
三、额外注意点
- 你的
Pattern类要实现Serializable接口,不然mxGraph保存/加载的时候会序列化失败。 - 如果用思路2,一定要处理单元格删除的情况,把按钮从组件树和映射中移除,避免内存泄漏。
- 绘制自定义内容时,注意绘制顺序,避免按钮被默认的顶点样式覆盖。
内容的提问来源于stack exchange,提问作者Guglielmo Fratticioli
相关产品推荐
相关产品推荐

