建立一個合適的坐標系可以為我們的繪圖帶來很大的方便 。下面介紹一下如何在VC中建立我們想要的坐標系。

一 裝置座標和邏輯座標
  裝置座標(Device Coordinate)又稱為物理座標(Physical Coordinate),是指輸出裝置上的座標。通常將螢幕上的裝置座標稱為螢幕座標。裝置座標用物件距離視窗左上角的水準距離和垂直距離來指定物件的位置,是以圖元為單位來表示的,裝置座標的X軸向右為正,Y軸向下為正,座標原點位於視窗的左上角。
  邏輯座標(Logical Coordinate)是系統用作記錄的座標。在缺省的模式(MM_TEXT)下,邏輯座標的方向和單位與裝置座標的方向和單位相同,也是以圖元為單位來表示的,X軸向右為正,Y軸向下為正,座標原點位於視窗的左上角。邏輯座標和裝置座標即使在缺省模式下其數值也未必一致,除了在以下兩種情況下:
  1. 視窗為非滾動視窗
  2. 視窗為滾動視窗,但垂直捲動條位於滾動邊框的最上端,水準捲軸位於最左端,但如果移動了捲軸這兩種座標就不一致了。
  在VC中滑鼠座標的座標位置用裝置座標表示,但所有GDI繪圖都用邏輯座標表示,所以用滑鼠繪圖時,那麼必須將裝置座標轉換為邏輯座標,可以使用CDC 函數DptoLP()將裝置座標轉化為邏輯座標,同樣可以用LptoDP()將邏輯座標轉化為裝置座標。


  二 坐标模式
  为了在不同的领域使用逻辑坐标,Windows提供了以下8种坐标模式:
  分别为MM_TEXT、MM_HIENGLISH、MM_LOENGLISH、MM_HIMETRIC、MM_LOMETRIC、MM_TWIPS、MM_ANISOTROPIC和MM_ISOTROPIC。


  三 实例解析
  (一) 建立以左上角为原点,X轴和Y轴为1000的坐标,如下图
  我们可以用以下代码:
  void CTtView::OnDraw(CDC* pDC)
   {
    CTtDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    CRect rect;
    GetClientRect(&rect);


    pDC->SetMapMode(MM_ANISOTROPIC);
    pDC->SetViewportOrg(0,0);
    pDC->SetViewportExt(rect.right,rect.bottom);


    pDC->SetWindowOrg(0,0);
    pDC->SetWindowExt(1000,1000);


    pDC->MoveTo(50,50);
    pDC->LineTo(50,950);
    pDC->LineTo(950,950);
    pDC->LineTo(50,50);
   }
  代码分析:
  1. GetClientRect(&rect); 取得客户区矩形区域,将其存放在rect中
  2. 用pDC->SetMapMode(MM_ANISOTROPIC); 设置映射模式
  3. 通过pDC->SetViewportOrg(0,0);设置逻辑坐标的原点。
  4. 通过pDC->SetViewportExt(rect.right,rect.bottom);和
pDC->SetWindowExt(1000,1000);来确定逻辑坐标下和设备坐标下的尺寸对应关系
  5. 在MM_ANISOTROPIC模式下,X轴单位和Y轴单位可以不相同
  6. 坐标方向的确定方法是如果逻辑窗范围和视口范围符号相同,则逻辑坐标的方向和视口的方向相同,即X轴向右为正,Y轴向下为正。
  7. 如果将显示模式改为MM_ISOTROPIC,那么X轴单位和Y轴单位一定相同,感兴趣的读者可以自己使一下。
  (二) 建立以视窗中心为原点的坐标,如下:
  用如下代码:
  void CTtView::OnDraw(CDC* pDC)
   {
    CTtDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    CRect rect;
    GetClientRect(&rect);


    pDC->SetMapMode(MM_ANISOTROPIC);
    pDC->SetViewportOrg(rect.right/2,rect.bottom/2);
    pDC->SetViewportExt(rect.right,rect.bottom);


    pDC->SetWindowOrg(0,0);
    pDC->SetWindowExt(1000,-1000);


    pDC->MoveTo(150,150);
    pDC->LineTo(-150,-200);
    pDC->LineTo(150,-150);
    pDC->LineTo(150,150);
   }
 代码分析:
  1. 用 pDC->SetViewportOrg(rect.right/2,rect.bottom/2); 设置视口的原点。
2. 用pDC->SetViewportExt(rect.right,rect.bottom);和pDC->SetWindowExt(1000,-1000);来确定设备坐标和逻辑坐标的单位对应关系。
  3. 因为逻辑窗范围和视口范围的符号不一致,纵坐标取反,所以Y轴向上为正。


MM_LOENGLISH、MM_HIENGLISH、MM_LOMETRIC、MM_HIMETRIC、MM_TWIPS这一组是Windows提供的重要的固定比例映射模式。


它們都是x值向右方向遞增,y值向下遞減,並且無法改變。它們之間的區別在於比例因數見下:(我想書上P53頁肯定是印錯了,因為通過程式實驗x值向右方向也是遞增的)


MM_LOENGLISH 0.01英寸
MM_HIENGLISH 0.001英寸
MM_LOMETRIC 0.1mm
MM_HIMETRIC 0.01mm
MM_TWIPS 1/1440英寸 //應用於印表機,一個twip相當於1/20磅,一磅又相當於1/72英寸。


在VC裡, 準確的說是在Windows程式設計中,映射模式有:MM_TEXT,(裝置座標)
MM_HIMETRIC,(邏輯座標)
MM_ISOTROPIC,MM_ANISOTROPIC,.....在的開發環境下,(Windows 預設的模式是MM_TEXT),我們可以通過SetMapMode()來改變當是的映射模式~
MM_TEXT :X向右方向遞增加,Y向下方向遞增加,我們可以通過SetViewportOrg()和SetWindowOrg()來改變座標原點的位置.
void CInSide_VCView::OnDraw(CDC* pDC)
{
pDC->TextOut (0,0,"TEST");
pDC->SelectStockObject (GRAY_BRUSH);
pDC->SetMapMode (MM_TEXT);
//pDC->SetWindowOrg (100,100); 交替使用這兩句看有什麼現象
//pDC->SetViewportOrg(CPoint(100,100));
pDC->Ellipse (CRect(0,0,300,300));
pDC->TextOut (0,0,"22");


}
固定比例模式:X向右方向遞減(我認為是增加),Y向下方向遞減,MM_LOENGLISH(0.01英寸) MM_HIENGLISH(0.001英寸) MM_LOMETRIC(0.1mm) MM_HIMETRIC(0.01mm) MM_TWIPS(1/1400英寸,一般用於印表機)
可變比例模式:M_ISOTROPIC(1:1),MM_ANISOTROPIC(可為任意比例),用SetWindowsExt() 和SetViewportExt()來設定比例,
void CInSide_VCView::OnDraw(CDC* pDC)
{
CRect rectClient;
GetClientRect(rectClient);
pDC->SelectStockObject (m_nColor);
pDC->SetMapMode (MM_ANISOTROPIC);
pDC->SetWindowExt (1000,1000);
pDC->SetViewportExt (rectClient.right,rectClient.bottom );
pDC->SetViewportOrg (rectClient.right/2,rectClient.bottom/2);
pDC->Ellipse (CRect(-500,-500,500,500));
}
物理座標,即我們現實中的尺寸,螢幕中一英寸是現實中的12英寸,如果我們使用MM_LOENGLISH(0.01英寸)的映射模式,那麼26.75英寸將是電腦的26.75/12=2.23(英寸),在電腦裡1個邏輯單位是0.01英寸,所以,26.75英寸轉換為邏輯單位是223單位,但是這過程中有省略,為了防止這樣的情況,我們可以使用物理座標保存。


轉換函數;DPtoLP() 裝置座標到邏輯座標; LPtoDP()邏輯座標到裝置座標;物理座標到邏輯座標的轉換全是由我們自己計算;


MFC 中一般情況下有下面的情況:
CDC類中的所有成員函數用的是邏輯座標做參數
CWND類中的所有成員函數用的是裝置座標做參數
所有的選中-測試(HIT-TEST)操作都是用的裝置座標,有些函數只能使用裝置座標如:CRect::PtInRect()
所有要長期保存的值一般用邏輯座標,用裝置座標則使用者對視窗滾動就會改變,該座標就會失效了;


下一例子,測試滑鼠左鍵點的區域是不是在指定的地方
void CMyView::OnLButtonDown(UINT uFlags,CPoint point)
{
CRect rect=m_rect;
CClientDC dc(this);
dc.SetMapMode(MM_TEXT);
dc.LPtoDP(rect);
if(rect.PtInRect(point))
{
MessageBox("YES");
}


}


一般設置映射模式在OnPrepareDC() 中比較合適~,OnPrepareDC在OnDraw前調用!



MFC 對140 種Windows 消息提供了直接的消息控制函數,特別注意下面5個:WM_CREATE,WM_CLOSE,WM_DESTROY,WM_NCDESTROY,WM_QUERYENDSESSION.


WM_CREATES 是WINDOWS 發送給視圖的第一個消息,所以在OnCreate()中不能調用那寫依賴視窗完全處於啟動的WINDOWS函數!,一般來說可以在 OnInitialUpdate()中調用,如:設置映射模式~,但是必須注意,OnInitialUpdate()在視圖生存之間可能被調用多次
MSDN :
Call SetScrollSizes when the view is about to be updated.
void SetScrollSizes(
int nMapMode,
SIZE sizeTotal,
const SIZE& sizePage = sizeDefault,
const SIZE& sizeLine = sizeDefault
);
Parameters
nMapMode
The mapping mode to set for this view. Possible values include:
Mapping Mode Logical Unit Positive y-axis Extends...
MM_TEXT 1 pixel Downward
MM_HIMETRIC 0.01 mm Upward
MM_TWIPS 1/1440 in Upward
MM_HIENGLISH 0.001 in Upward
MM_LOMETRIC 0.1 mm Upward
MM_LOENGLISH 0.01 in Upward




All of these modes are defined by Windows. Two standard mapping modes, MM_ISOTROPIC and MM_ANISOTROPIC, are not used for CScrollView. The class library provides the SetScaleToFitSize member function for scaling the view to window size. Column three in the table above describes the coordinate orientation.
sizeTotal //SCrollbar的顯示範圍,含不在視區顯示部分的大小
The total size of the scroll view. The cx member contains the horizontal extent. The cy member contains the vertical extent. Sizes are in logical units. Both cx and cy must be greater than or equal to 0.
sizePage //當點擊Scrollbar空白條時,視區移動得大小
The horizontal and vertical amounts to scroll in each direction in response to a mouse click in a scroll-bar shaft. The cx member contains the horizontal amount. The cy member contains the vertical amount.
sizeLine //點擊SCrollbar的上下左右按鈕時,視區移動的大小
The horizontal and vertical amounts to scroll in each direction in response to a mouse click in a scroll arrow. The cx member contains the horizontal amount. The cy member contains the vertical amount.
如下實例:
void CEx04cView::OnInitialUpdate()
...{
CScrollView::OnInitialUpdate();
CSize sizeTotal(20000, 30000); // 20 by 30 cm
CSize sizePage(sizeTotal.cx / 4, sizeTotal.cy / 4); //當點擊Scrollbar空白條時,視區移動得大小
CSize sizeLine(sizeTotal.cx / 20, sizeTotal.cy / 20); //點擊SCrollbar的上下左右按鈕
SetScrollSizes(MM_TEXT, sizeTotal, sizePage, sizeLine);
}
再來看下這裡的CSize,MSDN :
CSize(
int initCX,
int initCY
)
Parameters
initCX
Sets the cx member for the CSize.
initCY
Sets the cy member for the CSize.
VC技術內幕:CSize物件可以看成是兩個CPoint物件的差
CDocument類提供了兩個函數用於視圖類的定位:GetFirstViewPosition()和GetNextView(),具體語法如下:
virtual POSITION GetFirstViewPosition() const;
virtual CView* GetNextView(POSITION& rPosition) const;
注意:GetNextView()括弧中的參數用的是引用方式,因此執行後值可能改變。
GetFirstViewPosition()用於返回第一個視圖位置(返回的並非視圖類指標,而是一個POSITION類型值),GetNextView()有兩個功能:返回下一個視圖類的指標以及用引用調動的方式來改變傳入的POSITION類型參數的值。很明顯,在Test程式中,只有一個視圖類,因此只需將這兩個函式呼叫一次即可得到CTestView的指標如下(需定義一個POSITION結構變數來輔助操作):
CTestView* pTestView;
POSITION pos=GetFirstViewPosition();
pTestView=GetNextView(pos);
這樣,便可到了CTestView類的指標pTestView.執行完成幾句後,變數pos=Null,因為沒有下一個視圖類,自然也沒有下一個視圖類的POSITION.

但是之幾條語句太簡單,不具有太強的通用性和安全特徵;當象前面說的那樣,當要在多個視圖為中返回某個指定類的指標時,我們需要遍歷所有視圖類,直到找到指定類為止。判斷一個類指標指向的是否某個類的實例時,可用IsKindOf()成員函數時行檢查,如:
pView->IsKindOf(RUNTIME_CLASS(CTestView));
即可檢查pView所指是否是CTestView類。
有了以上基礎,我們已經可以從文檔類取得任何類的指標。為了方便,我們將其作為一個文檔類的成員函數,它有一個參數,表示要獲得哪個類的指標。實現如下:
CView* CTestDoc::GetVieww(CRuntimeClass* pClass)
{ CView* pView;
POSITION pos=GetFirstViewPosition();
while(pos!=Null)
{
pView=GetNextView(pos);
if(pView->IsKindOf(pClass))
break;
}
if(!pView->IsKindOf(pClass))
return Null;
return pView;}
其中用了兩次視圖類的成員函數IsKindOf()來判斷,是因為退出while迴圈有三種可能:
1.pos為Null,即已經不存在下一個視圖類供操作;
2.pView已符合要求。
3.1和2同是滿足。這是因為GetNextView()的功能是將當前視圖指標改變成一個視圖的位置同時返回當前視圖指標,因此pos是pView的下一個視圖類的POSITION,完全有可能既是pos==Null又是pView符合需要。當所需的視圖是最後一個視圖是最後一個視圖類時就如引。因此需採用兩次判斷。
使用該函數應遵循如下格式(以取得CTestView指標為例):
CTestView* pTestView=(CTestView*)GetView(RUNTIME_CLASS(CTestView));
RUNTIME_CLASS是一個宏,可以簡單地理解它的作用:將類的名字轉化為CRuntimeClass為指標。
至於強制類型轉換也是為了安全特性考慮的,因為從同一個基類之間的指標類型是互相相容的。這種強制類型轉換也許並不必要,但能避免一些可能出現的麻煩。


本文來自CSDN博客,轉載請標明出處:HTTP://blog.csdn.net/bluewind23/archive/2009/04/10/4061278.aspx
 
創作者介紹
創作者 shadow 的頭像
shadow

資訊園

shadow 發表在 痞客邦 留言(0) 人氣()