zoukankan      html  css  js  c++  java
  • 研究:窗口映射

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

    *

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

    *

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

    对于我们来说,都学过笛卡尔坐标系,无论画什么图都首先考虑用笛卡尔坐标系来描述,比如说,我想在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 instance
    TCHAR szTitle[MAX_LOADSTRING];                                // The title bar text
    TCHAR 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 <windows.h>
    #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

     

  • 相关阅读:
    利用Session和HashTable制作购物车实例
    在windows 7上安装Maven2.2.1
    tail & cut 命令
    软件开发常用名词中英文对照
    字符,字节和编码
    grep 简介
    HSQLDB: java程序使用hsqldb 入门教程 java启动hsqldb
    eclipse 安装 maven 插件
    JNI 返回结构体参数
    HSQLDB 安装与使用
  • 原文地址:https://www.cnblogs.com/tinaluo/p/5389636.html
Copyright © 2011-2022 走看看