mxGraph 如何实现图形拖动

4,678 阅读2分钟

在《mxgraph 系列【3】: 底层状态树 mxCell》,有同学提到一个疑问:

image.png

大致上,是需要修改拖拽功能,从下图左边样式更改为右边样式:


简单翻了一下代码,找到解决方案:

1. 拖拽的实现

首先,需要简单了解一下mxGraph是如何实现拖拽的。在 mxGraph 内部有一个 mxGraphHandler 类专门用于监听鼠标 keydown、mousemove、keyup 等事件,当用户选中某个图形并执行鼠标拖拽后,会触发 mxGraphHandler.prototype.mouseMove 回调,函数内部执行一堆判断逻辑,包括:

  1. 是否在复制元素
  2. 是否在将元素drop到其他容器上
  3. 是否实时预览
  4. ...


逻辑太复杂了,总结下来就是,拖拽预览功能有两种模式,一是创建一个与拖拽原始宽高位置相同的矩形,在该矩形上施加位置变化,姑且称为“矩形预览模式”;二是直接在原始元素上施加位置变化,姑且称为“实时预览”。两者主要通过 mxGraphHandler 实例的 livePreviewUsed 属性进行判断。
判断的位置,一是在 mouseMove 回调中:

mxGraphHandler.prototype.mouseMove = function(sender, me){ 
  ...
  // https://github.com/jgraph/mxgraph/blob/master/javascript/src/js/handler/mxGraphHandler.js#L901
  if (!this.livePreviewUsed && this.shape == null) {
    this.shape = this.createPreviewShape(this.bounds);
  }
  ...
}


二是在更新预览图形的处理函数中:

// https://github.com/jgraph/mxgraph/blob/master/javascript/src/js/handler/mxGraphHandler.js#L1036
mxGraphHandler.prototype.updatePreview = function(remote) {
	if (this.livePreviewUsed && !remote) {
		if (this.cells != null)
		{
			this.setHandlesVisibleForCells(this.graph.getSelectionCells(), false);
			this.updateLivePreview(this.currentDx, this.currentDy);
		}
	}
	else
	{
		this.updatePreviewShape();
	}
};

那么,理论上在mouseMove回调之前设置 this.livePreviewUsed = true 就能强制切换为实时预览效果。

2. 修改代码


在上面理论基础上,我们只需要扩展原有的 mxGraphHandler.prototype.mouseMove 函数就可以达到目标:

const oldFunc = mxGraphHandler.prototype.mouseMove;

mxGraphHandler.prototype.mouseMove = function (sender, me) {
  var graph = this.graph;

  if (
    !me.isConsumed() &&
    graph.isMouseDown &&
    this.cell != null &&
    this.first != null &&
    this.bounds != null &&
    !this.suspended
  ) {
    this.livePreviewUsed = true;
  }
  oldFunc.call(this, sender, me);
};

注意

这段代码需要放在创建mxGraph实例之前执行才能生效。

修改后,运行效果:

emmm,效果不太理想,因为拖拽过程中,原型的操作点(绿色的点)没有随鼠标移动,继续翻代码,发现上面说的 updatePreview 函数只是修改了model状态,还没有推送到页面渲染,实际执行渲染的位置是 mxGraphHandler 文件的第 1214 行:

mxGraphHandler.prototype.mouseMove = function (sender, me) {
  ...
  // https://github.com/jgraph/mxgraph/blob/master/javascript/src/js/handler/mxGraphHandler.js#L1214
  this.graph.view.validate();
  this.redrawHandles(states);
  this.resetPreviewStates(states);
  ...
}

那么,只在渲染之前执行 this.setHandlesVisibleForCells(this.graph.getSelectionCells(), false); 调用,更新 handlers 数组的位置就可以实现操作点随拖拽移动的效果:

嗯,效果不错。但是这里只能更改 javascript/src/js/handler/mxGraphHandler.js 源码文件才能实现,请根据项目情况决定是否执行此变更吧。修改后代码应该入:

mxGraphHandler.prototype.mouseMove = function (sender, me) {
  ...
  // https://github.com/jgraph/mxgraph/blob/master/javascript/src/js/handler/mxGraphHandler.js#L1214
  this.setHandlesVisibleForCells(this.graph.getSelectionCells(), false);
  this.graph.view.validate();
  this.redrawHandles(states);
  this.resetPreviewStates(states);
  ...
}