当前位置: 首页 > news >正文

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)                 │    ││  │                       │    ││  │   实际可见的图         │    ││  └───────────────────────┘    │└───────────────────────────────┘
http://www.dtcms.com/a/356491.html

相关文章:

  • C/C++---预定义常量
  • Fast-LIVO2算法与其他激光雷达-惯性-视觉里程计算法相比有何优势?
  • 【LeetCode 热题 100】62. 不同路径——(解法二)递推
  • Spring Security 传统 web 开发场景下开启 CSRF 防御原理与源码解析
  • “我店 + RWA”来袭:重构商业价值,解锁消费投资新密码
  • 大模型入门学习微调实战:基于PyTorch和Hugging Face电影评价情感分析模型微调全流程(附完整代码)手把手教你做
  • C++基础(④链表反转(链表 + 迭代 / 递归))
  • Linux - 中文显示乱码问题解决方法(编码查看及转换)- 学习/实践
  • 对于牛客网—语言学习篇—编程初学者入门训练—函数类型:BC156 牛牛的数组匹配及BC158 回文数解析
  • Total PDF Converter多功能 PDF 批量转换工具,无水印 + 高效处理指南
  • docker 搭建zookper集群,快照虚拟机多机模拟
  • 2025数学建模国赛AI提示词模板
  • 如何解决网关断网后时间不再统计的问题?无RTC子设备如何打通主网关的时间同步功能?
  • 法律审查prompt收集
  • 高并发内存池(19)-用基数树优化
  • IDA-pro-mcp 的核心功能 常用的prompt
  • Mybatis的常用标签
  • word去空格去空行_word辅助工具 word批量处理
  • 【C++】类与对象(上)
  • Matlab实现基于CPO-QRCNN-BiGRU-Attention注意力多变量时间序列区间预测
  • FPGA实现1553B BC控制器IP方案
  • 【AOSP】Android Dump 开发与调试指南
  • Replay – AI音乐伴奏分离工具,自动分析音频内容、提取主唱、人声和伴奏等音轨
  • 栈和队列OJ习题
  • 【物联网】关于 GATT (Generic Attribute Profile)基本概念与三种操作(Read / Write / Notify)的理解
  • 如何在mysql中执行创建数据库的脚本文件?
  • Spring Boot 使用 RestTemplate 调用 HTTPS 接口时报错:PKIX path building failed 解决方案
  • Linux下的网络编程SQLITE3详解
  • 神经语言学视角:脑科学与NLP深层分析技术的交叉融合
  • Java的CAS机制:无锁并发控制及其高频面试题