简单的二维平面漫游
2016-05-27 16:54
190 查看
效果如下:鼠标中间拖动,滚轮缩放
这是一个MFC基于对话框的应用程序,这里只讲述绘制(如设备坐标逻辑坐标换算及人机互动的消息映射)不讲图元储存等数据结构的问题,计算不牵涉线性代数,只是利用小学数学的知识,所以可能看起来杂乱,核心部分如下:
1. 创建的工程名为Demo160526
2. 添加图元类头文件"Primitive.h"(这里就以点为例了)
3. CDemo160526Dlg.h中包含Primitive.h,并为了geo::POINT和POINT不混淆,方便起见来个typedef
4. class CDemo160526Dlg的声明中添加以下成员红色
5. 接下来是具体的方法实现,请看CDemo160526Dlg.cpp文件,先看构造函数和析构函数
析构函数用来删除我的样本,在我这里有时没有正确回收内存空间win10下可以执行,但是关闭后会报告产生垃圾
6. 在OnInitDialog()中设置窗体位置,大小(这步无所谓)
7. 在OnPaint()中调用m_Draw()
8. 定义m_Draw(),这里我开启了双重缓冲消除闪烁来改善体验
9. 逻辑坐标转设备坐标,与10可以认为是互为正反算的关系
10. 设备坐标转逻辑坐标
geoPOINT CDemo160526Dlg::ToLogic(const POINT & device_pt) const
{
int width = m_rc.right - m_rc.left;
int height = m_rc.bottom - m_rc.top;
geoPOINT logic_pt = {(device_pt.x - m_delta_x * m_k - width / 2) / m_k, (height / 2 + m_delta_y * m_k - device_pt.y) / m_k};
return logic_pt;
}
11. 绘制格网及格网刻度有点繁琐
12. 鼠标中键按下事件
13. 鼠标中键抬起事件
14. 鼠标拖动事件
14. 鼠标滚轮事件
至此完成。
ps: 一些小技巧
1. 屏蔽对话框应用的回车和escape键退出
2. 双重缓冲
void CDemo160526Dlg::m_Draw()
{
GetClientRect(&m_rc);
CClientDC dc(this);
CDC mdc;
mdc.CreateCompatibleDC(&dc);
CBitmap bm;
int width = m_rc.right - m_rc.left;
int height = m_rc.bottom - m_rc.top;
bm.CreateCompatibleBitmap(&dc, width, height);
mdc.SelectObject(&bm);
//此处内存mdc的具体绘制方法
dc.BitBlt(0, 0, width, height, &mdc, 0, 0,SRCCOPY);
bm.DeleteObject();
mdc.DeleteDC();
}
这是一个MFC基于对话框的应用程序,这里只讲述绘制(如设备坐标逻辑坐标换算及人机互动的消息映射)不讲图元储存等数据结构的问题,计算不牵涉线性代数,只是利用小学数学的知识,所以可能看起来杂乱,核心部分如下:
1. 创建的工程名为Demo160526
2. 添加图元类头文件"Primitive.h"(这里就以点为例了)
#ifndef PRIMITIVE_H_ #define PRIMITIVE_H_ namespace geo { struct POINT { double x; double y; }; } #endif
3. CDemo160526Dlg.h中包含Primitive.h,并为了geo::POINT和POINT不混淆,方便起见来个typedef
#include "Primitive.h" typedef geo::POINT geoPOINT;
4. class CDemo160526Dlg的声明中添加以下成员红色
// CDemo160526Dlg 对话框 class CDemo160526Dlg : public CDialogEx { // 构造 public: CDemo160526Dlg(CWnd* pParent = NULL); // 标准构造函数 <span style="color:#ff0000;"> virtual ~CDemo160526Dlg(); </span>// 对话框数据 enum { IDD = IDD_DEMO160526_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 <span style="color:#ff0000;">private: RECT m_rc;//客户区矩形 double m_k;//1米在屏幕上对应m_k个像素 double m_delta_x;//窗体准心水平偏移量 double m_delta_y;//窗体准心垂直偏移量 CPoint m_down_point;//鼠标按下点 CPoint m_up_point;//鼠标抬起点 void m_Draw();//全部元素的绘制 double m_grid_len;//格网宽度 void DrawGrid(CDC & mdc);//绘制格网 POINT ToScreen(const geoPOINT & logic_pt) const;//逻辑坐标点转设备坐标 geoPOINT ToLogic(const POINT & device_pt) const;//设备坐标转逻辑坐标 geoPOINT * m_test_pts;//演示用的点,至于要储存图元的话,可以自己写链表,也可以用容器,比如std::vector<Primitive *> //各类开关 bool m_midle_mouse_down;//判断鼠标中键书否按下 </span>// 实现 protected: HICON m_hIcon; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: virtual BOOL PreTranslateMessage(MSG* pMsg); afx_msg void OnMButtonDown(UINT nFlags, CPoint point); afx_msg void OnMButtonUp(UINT nFlags, CPoint point); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); };
5. 接下来是具体的方法实现,请看CDemo160526Dlg.cpp文件,先看构造函数和析构函数
CDemo160526Dlg::CDemo160526Dlg(CWnd* pParent /*=NULL*/) : CDialogEx(CDemo160526Dlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); <span style="color:#ff0000;"> m_k = 10.0;//逻辑坐标系中的1m将在设备坐标系中用10像素来表示 m_delta_x = 0.0; m_delta_y = 0.0; m_grid_len = 20.0;//格网20m长,根据自己需要 m_down_point.SetPoint(0, 0); m_up_point.SetPoint(0, 0); m_midle_mouse_down = false; //给样本赋值 m_test_pts = new geoPOINT [4]; m_test_pts[0].x = 0; m_test_pts[0].y = 0; m_test_pts[1].x = 10; m_test_pts[1].y = 0; m_test_pts[2].x = 10; m_test_pts[2].y = 162.11; m_test_pts[3].x = 5; m_test_pts[3].y = 200; </span>}
析构函数用来删除我的样本,在我这里有时没有正确回收内存空间win10下可以执行,但是关闭后会报告产生垃圾
CDemo160526Dlg::~CDemo160526Dlg() { delete [] m_test_pts; }
6. 在OnInitDialog()中设置窗体位置,大小(这步无所谓)
BOOL CDemo160526Dlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 <span style="color:#ff0000;"> SetWindowPos(NULL, 0, 0, 800, 600, 0); </span> return TRUE; // 除非将焦点设置到控件,否则返回 TRUE }
7. 在OnPaint()中调用m_Draw()
void CDemo160526Dlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使图标在工作区矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } <span style="color:#ff0000;"> m_Draw(); </span>}
8. 定义m_Draw(),这里我开启了双重缓冲消除闪烁来改善体验
void CDemo160526Dlg::m_Draw() { GetClientRect(&m_rc); CClientDC dc(this); CDC mdc; mdc.CreateCompatibleDC(&dc); CBitmap bm; int width = m_rc.right - m_rc.left; int height = m_rc.bottom - m_rc.top; bm.CreateCompatibleBitmap(&dc, width, height); mdc.SelectObject(&bm); CBrush brush_white(WHITE); mdc.FillRect(&m_rc, &brush_white);//清除 DrawGrid(mdc);//网格 CPen pen(PS_SOLID, 1, BLACK); mdc.SelectObject(&pen); mdc.MoveTo(ToScreen(m_test_pts[0]));//演示绘制样本,实际做应用的时候应该是遍历储存图元容器里的东西并绘制出来 mdc.LineTo(ToScreen(m_test_pts[1])); mdc.LineTo(ToScreen(m_test_pts[2])); mdc.LineTo(ToScreen(m_test_pts[3])); mdc.LineTo(ToScreen(m_test_pts[0])); //准心 CPen pen_cross(PS_SOLID, 1, RGB(255, 0, 0)); mdc.SelectObject(&pen_cross); mdc.MoveTo(width / 2 - 10, height / 2); mdc.LineTo(width / 2 + 10, height / 2); mdc.MoveTo(width / 2, height / 2 - 10); mdc.LineTo(width / 2, height / 2 + 10); dc.BitBlt(0, 0, width, height, &mdc, 0, 0,SRCCOPY); bm.DeleteObject(); mdc.DeleteDC(); }
9. 逻辑坐标转设备坐标,与10可以认为是互为正反算的关系
POINT CDemo160526Dlg::ToScreen(const geoPOINT & logic_pt) const { int width = m_rc.right - m_rc.left; int height = m_rc.bottom - m_rc.top; POINT device_pt = {width / 2 + logic_pt.x * m_k + m_delta_x * m_k, height / 2 - logic_pt.y * m_k + m_delta_y * m_k}; return device_pt; }
10. 设备坐标转逻辑坐标
geoPOINT CDemo160526Dlg::ToLogic(const POINT & device_pt) const
{
int width = m_rc.right - m_rc.left;
int height = m_rc.bottom - m_rc.top;
geoPOINT logic_pt = {(device_pt.x - m_delta_x * m_k - width / 2) / m_k, (height / 2 + m_delta_y * m_k - device_pt.y) / m_k};
return logic_pt;
}
11. 绘制格网及格网刻度有点繁琐
void CDemo160526Dlg::DrawGrid(CDC & mdc) { int width = m_rc.right - m_rc.left; int height = m_rc.bottom - m_rc.top; POINT screen_center = {width / 2, height / 2}; geoPOINT grid_seed = ToLogic(screen_center); grid_seed.x = floor(grid_seed.x / m_grid_len) * m_grid_len; grid_seed.y = floor(grid_seed.y / m_grid_len) * m_grid_len; mdc.SetBkMode(TRANSPARENT); mdc.SetTextColor(RGB(100, 100, 125)); CPen pen_grid(PS_DOT, 1, RGB(200, 200, 225)); mdc.SelectObject(&pen_grid); for(int i = 0; i * m_grid_len < 0.6 * width; i++) { //右竖线 mdc.MoveTo(ToScreen(grid_seed).x + i * m_grid_len * m_k, 0); mdc.LineTo(ToScreen(grid_seed).x + i * m_grid_len * m_k, height); int iw = (int)((grid_seed.x - 0) / m_grid_len); CString label; label.Format(_T("%d"), (i + iw) * (int)m_grid_len); mdc.TextOut(ToScreen(grid_seed).x + i * m_grid_len * m_k, 0, label); //左竖线 mdc.MoveTo(ToScreen(grid_seed).x - i * m_grid_len * m_k, 0); mdc.LineTo(ToScreen(grid_seed).x - i * m_grid_len * m_k, height); label.Format(_T("%d"), (-i + iw) * (int)m_grid_len); mdc.TextOut(ToScreen(grid_seed).x - i * m_grid_len * m_k, 0, label); //上横线 mdc.MoveTo(0, ToScreen(grid_seed).y + i * m_grid_len * m_k); mdc.LineTo(width, ToScreen(grid_seed).y + i * m_grid_len * m_k); int ih = (int)((grid_seed.y - 0) / m_grid_len); label.Format(_T("%d"), (-i + ih) * (int)m_grid_len); mdc.TextOut(0, ToScreen(grid_seed).y + i * m_grid_len * m_k, label); //下横线 mdc.MoveTo(0, ToScreen(grid_seed).y - i * m_grid_len * m_k); mdc.LineTo(width, ToScreen(grid_seed).y - i * m_grid_len * m_k); label.Format(_T("%d"), (i + ih) * (int)m_grid_len); mdc.TextOut(0, ToScreen(grid_seed).y - i * m_grid_len * m_k, label); } }
12. 鼠标中键按下事件
void CDemo160526Dlg::OnMButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 <span style="color:#ff0000;"> m_midle_mouse_down = true; m_down_point = point; </span> CDialogEx::OnMButtonDown(nFlags, point); }
13. 鼠标中键抬起事件
void CDemo160526Dlg::OnMButtonUp(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 <span style="color:#ff0000;"> m_midle_mouse_down = false; m_up_point = point; m_Draw(); </span> CDialogEx::OnMButtonUp(nFlags, point); }
14. 鼠标拖动事件
void CDemo160526Dlg::OnMouseMove(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 <span style="color:#ff0000;"> if(m_midle_mouse_down) { m_delta_x += (point.x - m_down_point.x) / m_k; m_delta_y += (point.y - m_down_point.y) / m_k; m_down_point = point; m_Draw(); } </span> CDialogEx::OnMouseMove(nFlags, point); }
14. 鼠标滚轮事件
BOOL CDemo160526Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { // TODO: 在此添加消息处理程序代码和/或调用默认值 <span style="color:#ff0000;"> if(zDelta > 0) { m_k *= 1.2; } else { if(m_k < 1)//防止太小 return TRUE; m_k *= 0.8; } m_Draw(); </span> return CDialogEx::OnMouseWheel(nFlags, zDelta, pt); }
至此完成。
ps: 一些小技巧
1. 屏蔽对话框应用的回车和escape键退出
BOOL CDemo160526Dlg::PreTranslateMessage(MSG* pMsg) { // TODO: 在此添加专用代码和/或调用基类 <span style="color:#ff0000;"> if(pMsg->message == WM_KEYDOWN) { switch(pMsg->wParam) { case VK_RETURN: return TRUE; case VK_ESCAPE: return TRUE; default: break; } } </span> return CDialogEx::PreTranslateMessage(pMsg); }
2. 双重缓冲
void CDemo160526Dlg::m_Draw()
{
GetClientRect(&m_rc);
CClientDC dc(this);
CDC mdc;
mdc.CreateCompatibleDC(&dc);
CBitmap bm;
int width = m_rc.right - m_rc.left;
int height = m_rc.bottom - m_rc.top;
bm.CreateCompatibleBitmap(&dc, width, height);
mdc.SelectObject(&bm);
//此处内存mdc的具体绘制方法
dc.BitBlt(0, 0, width, height, &mdc, 0, 0,SRCCOPY);
bm.DeleteObject();
mdc.DeleteDC();
}
相关文章推荐
- 100 个最佳 Ubuntu 应用(中)
- 在 AppImage、Flathub 和 Snapcraft 平台上搜索 Linux 应用
- 24 个必备的 Linux 应用程序
- 注册表趣味应用小集
- 远程控制技术的应用
- 路由器访问列表的应用
- xDSL技术及其应用
- 基于XML的桌面应用
- SQL Server 2008 R2 应用及多服务器管理
- Node.js 应用跑得更快 10 个技巧
- Visual C++中MFC消息的分类
- MFC中Radio Button的用法详解
- MFC对话框中添加状态栏的方法
- MFC创建右键弹出菜单的方法
- MFC中动态创建控件以及事件响应实现方法
- C++ 关于MFC多线程编程的注意事项
- MFC程序对文件的处理方法
- MFC自定义消息的实现方法
- MFC实现在文件尾追加数据的方法
- MFC之ComboBox控件用法实例教程