博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
研究:窗口映射
阅读量:6821 次
发布时间:2019-06-26

本文共 12394 字,大约阅读时间需要 41 分钟。

/**********************************************************************

*

*  这一篇文是以前写的,但现在看来好像还是没有透彻理解。一块内容真的需要反反复复好几遍才能理解。

*

************************************************************************/

 

对于我们来说,都学过笛卡尔坐标系,无论画什么图都首先考虑用笛卡尔坐标系来描述,比如说,我想在x=100,y=200的地方画一个点,我的脑中想像的是这样的:

可是,一旦把【想像】的东西画在程序的客户区中,就有变化了,如果想得到同样的点,前提是程序客户区的坐标系和这一样,也是笛卡尔坐标系,方向也相同,x轴向右为正,y轴向上为正。

一个点可能未必那么明显,假设想让别人看到三个点的图像,(这三个点的图像是想像的,也是想让别人看到原样的图像),比如:

三个点的坐标,假设为(100,200),(200,300),(300,400),(不改变设备任何属性)用API的设点函数,代码:

SetPixel(hdc,100,200,RGB(0,0,0));SetPixel(hdc,200,300,RGB(0,0,0));SetPixel(hdc,300,400,RGB(0,0,0));

(圈住的就是点),由这个图可以看出,走形了,已经不是想让别人看到的那个图。这是为什么呢?其实就是因为客户区的坐标系和习惯用的笛卡尔坐标系不同了。客户区的坐标系是下面这样的:

这样一来,就会导致出现上面“走形”的图像。矛盾就出现在这儿,我们习惯于笛卡尔坐标系来描述一幅图,而windows用的是这种坐标系,接下来会考虑怎样把windows的这种坐标系转个方向变成笛卡尔坐标系,适应自己的习惯!

这里就出现了映射模式,在默认情况下,客户区的坐标参考系为上图所示,名称为MM_TEXT,X轴向右为正,Y轴向下为正,除此之外,还有其它几种映射模式。

现在,将客户区的坐标系改成笛卡尔坐标系,SetMapMode(hdc,MM_LOMETRIC),单位为0.1mm。得到下面这种样式:

这样就得到了自己习惯的坐标系,接下来,会出现这样一个问题,假如说,我想画一个(类似)正弦的弧线,下图所示,我想从X的负坐标开始(这些都是习惯),可是上图所示的坐标系的原点是在客户区的左上角,还是不太习惯,能不能把这个坐标原点移到中间,把上面和左边都留出一定的空隙,这样看起来更适应习惯。

于是,又出现一个函数,可以将这个原点进行移动。

如上图所示,把坐标原点从客户区左上角移到客户区的(100,150)的位置,经过截图软件测距,观察得到的确实是x=100,y=150,这两个值是设备单位,也就是像素。

代码:

SetMapMode(hdc,MM_LOMETRIC);SetViewportOrgEx(hdc,100,150,NULL);

这里采用的函数是:SetViewportOrgEx(),经过上面两步,首先将坐标系改变,接着再将坐标原点移动,目的只有一个:得到一个适应自己习惯的坐标系。

有了这个坐标系之后,就可以在上面进行操作,但是这个坐标系的逻辑单位是0.1mm,假如画一条线,代码写100,其实是1厘米。

//这一段尤其重要,当转换映射模式后,在绘图函数中,它默认的是当前映射模式下的单位,比如,当前是MM_LOMETRIC,则画线函数中采用的400,是以0.1mm为单位,而不是以像素为单位

打算从坐标原点开始画两条直线,已经具有了笛卡尔坐标系,这样【想像】的是什么,画出来的就是原样的外观。

MoveToEx(hdc,0,0,NULL);LineTo(hdc,300,0);MoveToEx(hdc,0,0,NULL); LineTo(hdc,0,400);//y轴400

一个完整的示例:

代码(注意:用win32应用程序创建一个典型的"hello world"模板。)

 

// 正弦.cpp : Defines the entry point for the application.//#include "stdafx.h"#include "resource.h"#include "math.h"#define PI 3.1415#define MAX_LOADSTRING 100// Global Variables:HINSTANCE hInst;                                // current instanceTCHAR szTitle[MAX_LOADSTRING];                                // The title bar textTCHAR szWindowClass[MAX_LOADSTRING];                                // The title bar text// Foward declarations of functions included in this code module:ATOM                MyRegisterClass(HINSTANCE hInstance);BOOL                InitInstance(HINSTANCE, int);LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);LRESULT CALLBACK    About(HWND, UINT, WPARAM, LPARAM);int APIENTRY WinMain(HINSTANCE hInstance,                     HINSTANCE hPrevInstance,                     LPSTR     lpCmdLine,                     int       nCmdShow){     // TODO: Place code here.    MSG msg;    HACCEL hAccelTable;    // Initialize global strings    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);    LoadString(hInstance, IDC_MY, szWindowClass, MAX_LOADSTRING);    MyRegisterClass(hInstance);    // Perform application initialization:    if (!InitInstance (hInstance, nCmdShow))     {        return FALSE;    }    hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MY);    // Main message loop:    while (GetMessage(&msg, NULL, 0, 0))     {        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))         {            TranslateMessage(&msg);            DispatchMessage(&msg);        }    }    return msg.wParam;}////  FUNCTION: MyRegisterClass()////  PURPOSE: Registers the window class.////  COMMENTS:////    This function and its usage is only necessary if you want this code//    to be compatible with Win32 systems prior to the 'RegisterClassEx'//    function that was added to Windows 95. It is important to call this function//    so that the application will get 'well formed' small icons associated//    with it.//ATOM MyRegisterClass(HINSTANCE hInstance){    WNDCLASSEX wcex;    wcex.cbSize = sizeof(WNDCLASSEX);     wcex.style            = CS_HREDRAW | CS_VREDRAW;    wcex.lpfnWndProc    = (WNDPROC)WndProc;    wcex.cbClsExtra        = 0;    wcex.cbWndExtra        = 0;    wcex.hInstance        = hInstance;    wcex.hIcon            = LoadIcon(hInstance, (LPCTSTR)IDI_MY);    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);    wcex.hbrBackground    = (HBRUSH)(COLOR_WINDOW+1);    wcex.lpszMenuName    = (LPCSTR)IDC_MY;    wcex.lpszClassName    = szWindowClass;    wcex.hIconSm        = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);    return RegisterClassEx(&wcex);}////   FUNCTION: InitInstance(HANDLE, int)////   PURPOSE: Saves instance handle and creates main window////   COMMENTS:////        In this function, we save the instance handle in a global variable and//        create and display the main program window.//BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){   HWND hWnd;   hInst = hInstance; // Store instance handle in our global variable   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);   if (!hWnd)   {      return FALSE;   }   ShowWindow(hWnd, nCmdShow);   UpdateWindow(hWnd);   return TRUE;}////  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)////  PURPOSE:  Processes messages for the main window.////  WM_COMMAND    - process the application menu//  WM_PAINT    - Paint the main window//  WM_DESTROY    - post a quit message and return////LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){    int wmId, wmEvent;    PAINTSTRUCT ps;    HDC hdc;    TCHAR szHello[MAX_LOADSTRING];    LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);    static HPEN hpen;    double y,r;    int x;    switch (message)     {    case WM_CREATE:        hpen=CreatePen(PS_SOLID,6,RGB(0,0,0));        return 0;        case WM_COMMAND:            wmId    = LOWORD(wParam);             wmEvent = HIWORD(wParam);             // Parse the menu selections:            switch (wmId)            {                case IDM_ABOUT:                   DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);                   break;                case IDM_EXIT:                   DestroyWindow(hWnd);                   break;                default:                   return DefWindowProc(hWnd, message, wParam, lParam);            }            break;        case WM_PAINT:            hdc = BeginPaint(hWnd, &ps);SelectObject(hdc,hpen);            SetMapMode(hdc,MM_LOMETRIC);//映射模式改变            SetViewportOrgEx(hdc,100,150,NULL);    //视区原点改变                                for(x=-60;x<600;x++)            {                r=x/((double)60*2)*PI;                y=sin(r)*2*60;                MoveToEx(hdc,(int)x,(int)y,NULL);                LineTo(hdc,(int)x,(int)y);            }            MoveToEx(hdc,-240,0,NULL);//横向线            LineTo(hdc,680,0);            MoveToEx(hdc,0,400,NULL);//纵向线            LineTo(hdc,0,-380);            EndPaint(hWnd, &ps);            break;        case WM_DESTROY:            DeleteObject(hpen);            PostQuitMessage(0);            break;        default:            return DefWindowProc(hWnd, message, wParam, lParam);   }   return 0;}// Mesage handler for about box.LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){    switch (message)    {        case WM_INITDIALOG:                return TRUE;        case WM_COMMAND:            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)             {                EndDialog(hDlg, LOWORD(wParam));                return TRUE;            }            break;    }    return FALSE;}

继续新的问题:

考虑这样一种情况,在MM_LOMETRIC的映射模式下(不改变坐标原点),我想从坐标原点处开始画一条线直接到达窗口的最底端。如图所示(现在研究左边的一条线):

这条线尾的特点是,X不变(固定一个值,比如360),而y也就是客户区的高度可以自由伸缩。

要实现这个功能,首先要获取窗口的高度,采用GetClientRect函数。但问题出在这了,这个函数返回的是客户区的矩形区域,它是设备坐标还是逻辑坐标呢?书上的意思是,它返回的是设备坐标,而设备坐标是像素。我这个画线程序里面的360是逻辑坐标(0.1mm为单位),不匹配,所以要将设备坐标转换为逻辑坐标。
这就算是DPtoLP函数的一个来历。

GetClientRect (hwnd, &rect) ; //获取窗口客户区的尺寸pt.y=rect.bottom;//高度pt.x=rect.right;//宽度DPtoLP(hdc,(LPPOINT)&pt,2);//转换pt为逻辑坐标

这样一来,两者就匹配了。

LineTo(hdc,360,pt.y);

但是,在这里又出现一个问题,MM_LOMETRIC的坐标系和笛卡尔坐标系一样,现在LineTo(hdc,360,pt.y),这个pt.y看起来像是一个正数,x和y都为正数的点怎么会落在用户区中呢?如果一个东西只有一个解释,那就是唯一的解释,那就是这个pt.y只能是个负数才合理。验证输出pt.y发现它确实是个负数。

DPtoLP将用户区的高度进行了两个功能的转换,第一,根据坐标方向,转换正负值,第二,将设备高转换为逻辑高。

整理一下这个过程,假设用户区没有做任何映射转换,就是MM_TEXT,x向右为正,y向下为正。通过GetClientRect函数得到的高度是像素为单位。

接着,转换用户区的映射方式,变成MM_LOMETRIC,通过GetClientRect函数获取高度,则依然是像素,说明,GetClientRect是以设备坐标为单位(像素)返回用户区高度。

既然都为正,那就说明问题出在DPtoLP这个函数头上,它的工作要取决于当前的映射方式,当它发现是MM_LOMETRIC的映射模式时,就将这个高度转换为负值的以0.1mm为单位的逻辑坐标。这样,一切就合理了。根据后文一个粗略的近似计算:16像素约等于5.6mm,我这个程序的窗口约694像素,两者一换算:2429(0.1mm为单位),而程序输出经过DPtoLP转换后的高度为:-2444,几乎相等,这样就证明了。

得到下面两条结论:

一、GetClientRect返回的是用户区的矩形高度(设备坐标,像素为单位),和方向无关,总是用设备单位。
二、DPtoLP函数会根据当前的映射方式将设备坐标转换为逻辑坐标,正如上面所示,不仅改单位,还改方向
代码示例:

 

//--------------------DPtoLP函数的研究例子---------------//#include 
#include "stdio.h"LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;void paint();int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int nCmd){ static TCHAR szAppName[] = TEXT ("HelloWin") ; HWND hwnd ; //窗口句柄 MSG msg ; //消息结构 WNDCLASS wndclass ; //窗口类结构 wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;//加载图标供程序使用 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; //加载鼠标指针供程序使用 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;//获取一个图形对象,在这个例子中,是获取绘制窗口背景的刷子 wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass))//为程序窗口注册窗口类 { return 0 ; } //根据窗口类创建一个窗口 hwnd = CreateWindow (szAppName, TEXT ("一个简单的Win32程序"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, SW_SHOWMAXIMIZED) ; //在屏幕上显示窗口 UpdateWindow (hwnd) ; //指示窗口刷新自身 while (GetMessage (&msg, NULL, 0, 0)) //从消息队列中获取消息 { TranslateMessage (&msg) ; //转换某些键盘消息 DispatchMessage (&msg) ; //将消息发送给窗口过程 } return msg.wParam ;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ HDC hdc ; PAINTSTRUCT ps ; RECT rect ; POINT pt; char a[20],b[20],c[20]; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; //开始窗口绘制 GetClientRect (hwnd, &rect) ;//转换映射模式前,默认为MM_TEXT pt.y=rect.bottom;//返回用户区高度 sprintf(a,"MM_TEXT's height:%d",pt.y); TextOut(hdc,350,0,a,strlen(a)); //转换映射模式为MM_LOMETRIC SetMapMode(hdc,MM_LOMETRIC); GetClientRect (hwnd, &rect) ; //获取窗口客户区的尺寸 pt.y=rect.bottom;//高度 pt.x=rect.right;//宽度 sprintf(b,"MM_LOMETRIC's height:%d",pt.y);//没有被DPtoLP转换前 TextOut(hdc,0,0,b,strlen(b)); DPtoLP(hdc,(LPPOINT)&pt,2); sprintf(c,"MM_LOMETRIC's height:%d",pt.y);//被DPtoLP转换后 TextOut(hdc,0,-250,c,strlen(c)); LineTo(hdc,360,pt.y); MoveToEx(hdc,360,pt.y,NULL); LineTo(hdc,pt.x,-90); EndPaint (hwnd, &ps) ; //结束窗口绘制 return 0 ; case WM_DESTROY: PostQuitMessage (0) ; //在消息队列中插入一条“退出”消息 return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam);//执行默认的消息处理}

题目:以毫米为单位在用户区中输出(上下相邻)两行字符串,如何解?

如果打印两行字符串,需要在行间保持(一定的)距离才不会重叠或者行距太大。

第一个问题,如何知道字体的高度呢?根据文本显示的研究,字体的高度存放在TEXTMETRIC这个结构中,通过创建DC得到这个高度,接着的问题是,如果按照默认映射模式,也即MM_TEXT模式,得到的是映射模式的逻辑单位,换句话说,假如不改变映射模式,得出的高度为16(象素)。

16是象素,但是现在需要以毫米为单位,显然这个16不符合要求,需要把映射模式改为MM_LOMETRIC,于是得到逻辑单位以0.1mm为单位,再去取TEXTMETRIC中的高度,得到56。

计算:

同样的字体,象素为单位=16,0.1mm为单位=56,于是16象素=5.6mm。

通过计算得知,在以MM_LOMETRIC的映射模式下,字体的高度为56(即5.6毫米),问题已经基本得到解决。

在MM_LOMETRIC的映射模式下,x向右,y向上, 假如第一行字串起点为:(100,-100),则第二行字串的起点为(100,-100-56)。

同样,如果转换为MM_TEXT模式,在相同的地方输出,则第一行字串转换为(28,28),可以覆盖,稍有点误差。

代码如下:

View Code

 

转载于:https://www.cnblogs.com/tinaluo/p/5389636.html

你可能感兴趣的文章
Dubbo环境搭建
查看>>
[数据结构]插入排序与希尔排序
查看>>
如何高效利用GitHub
查看>>
Server-sent Event 简单介绍
查看>>
nginx 常用的几个命令
查看>>
解决命令行的乱码以及编码的问题
查看>>
python naming convention
查看>>
Linux 程序获取环境变量
查看>>
Kettle资源库的创建及密码修改
查看>>
NASA顶级程序员编程十大原则
查看>>
Linux Kernel 4.14 LTSI正式推出
查看>>
NodeJS基础
查看>>
Jquery与js原生
查看>>
keepalived
查看>>
EventBus
查看>>
iOS Block
查看>>
PHP错误及异常统一处理
查看>>
vim设置括号自动补全
查看>>
Get system clipboard
查看>>
windows环境eclipse开发C++程序
查看>>