您的位置:首页 > 其它

简单的二维平面漫游

2016-05-27 16:54 190 查看
效果如下:鼠标中间拖动,滚轮缩放



这是一个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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mfc 应用 二维