MFC视图中绘制图形缩放和滚动条的处理
MFC的视图类是支持绘制图形的,有一个微软官方的例子:scribble,仅仅能够手绘图形,而没有缩放功能。
本文提供一个例子,从geojson文件中读取路网信息,在view进行绘制,并支持缩放,且可以在放大后通过滚动条看到全图。
研究了半天才搞清楚各个坐标系之间的关系。
例子如下:绘制图形并缩放
运行程序后打开debug目录下的road.json即可看到实际显示,如图:
放大后:
技术点总结如下:
四种坐标的含义
────────────────────
名称 | 范围 | 原点 | 典型单位 | 说明 |
---|---|---|---|---|
世界坐标 | 任意 | 左下角(0,0) | 米、度… | GeoJSON 里的真实值 |
逻辑坐标 | ≥0 | 左上角(0,0) | 像素 | ScrollView 内部坐标 |
设备坐标 | ≥0 | 左上角(0,0) | 像素 | 当前客户区左上角 |
屏幕坐标 | ≥0 | 设备左上角 | 像素 | 包含滚动偏移 |
由于MFC帮忙处理了逻辑坐标映射为设备坐标和屏幕坐标
因此在程序中只需要关心世界坐标和逻辑坐标之间的转换。
世界坐标是我们真实世界的坐标,坐标原来在左下角
而逻辑坐标以及实际显示用的设备坐标和屏幕坐标都是左上角的,且只有一个象限,就是都是大于等于0的。
世界坐标和逻辑坐标之间关系就是一个变比加平移。
世界坐标 (wx, wy)
│
│ ① 缩放 + 平移
▼
逻辑坐标 (lx, ly) = (wx·scale + offset.x, -wy·scale + offset.y)
│
│ ② 减去滚动条位置
▼
设备坐标 (dx, dy) = (lx - scroll.x, ly - scroll.y)
│
│ ③ SetViewportOrg(-scroll.x,-scroll.y)
▼
屏幕坐标 (sx, sy) = (dx, dy)
世界 → 逻辑(函数 ToScr)
lx = wx * scale + offset.x
ly = -wy * scale + offset.y
逻辑 → 世界(函数 ScreenToWorld)
已知逻辑坐标 lx, ly,反推世界坐标:
wx = (lx - offset.x) / scale
wy = -(ly - offset.y) / scale
滚轮缩放时保持鼠标点不动
设鼠标在 设备坐标 中的点为 (mx, my),对应世界坐标 (wx0, wy0):
wx0 = (mx + scroll.x - offset.x) / scale_old
wy0 = -(my + scroll.y - offset.y) / scale_old
缩放后 scale 变为 scale_new = scale_old * factor,为了让 (wx0, wy0) 仍然落在 (mx, my) 上,需要重新计算 offset:
offset_new.x = mx + scroll.x - wx0 * scale_new
offset_new.y = my + scroll.y + wy0 * scale_new // 注意符号
滚动范围
先算出所有世界点映射后的 逻辑坐标 的最小/最大值:
left = min(lx_i) - margin
top = min(ly_i) - margin
right = max(lx_i) + margin
bottom = max(ly_i) + margin
但 MFC 要求 逻辑坐标不能为负,因此整体平移,使左上角为 (0,0):
width = right - left
height = bottom - top
SetScrollSizes(MM_TEXT, CSize(width, height))
offset.x -= left
offset.y -= top
ScrollToPosition(CPoint(-left, -top))
┌───────────────────────────────┐│ ScrollView 逻辑坐标系 ││ (0,0) 左上角 (W,H) 右下角 ││ ││ ┌───────────────────────┐ ││ │ 客户区 (设备坐标) │ ││ │ (0,0) │ ││ │ │ ││ │ 实际可见的图 │ ││ └───────────────────────┘ │└───────────────────────────────┘