You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 07:18:13