Programming with Microsoft Visual C++.NET 6ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming with Microsoft Visual C++.NET 6ed [Electronic resources] - نسخه متنی

George Shepherd, David Kruglinski

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید








Using Windows Mapping Modes


Up to now, your drawing units have been display pixels, also known as device coordinates. The Ex05a drawing units are pixels because the device context has the default mapping mode, MM_TEXT, assigned to it. The following statement draws a square of 200 by 200 pixels, with its top left corner at the top left of the window's client area. (Positive y values increase as you move down the window.)

pDC->Rectangle(CRect(0, 0, 200, 200));

This square will look smaller on a high-resolution display of 1024-by-768 pixels than on a standard VGA display that is 640-by-480 pixels, and it will look tiny if printed on a laser printer with 600-dpi resolution. (Try EX05A's Print Preview feature to see for yourself.)

What if you want the square to be 4-by-4 centimeters (cm), regardless of the display device? Windows provides a number of other mapping modes, or coordinate systems, that you can associate with the device context. Coordinates in the current mapping mode are called logical coordinates. If you assign the MM_HIMETRIC mapping mode, for example, a logical unit is 1/100 millimeter (mm) instead of 1 pixel. In the MM_HIMETRIC mapping mode, the y axis runs in the opposite direction to that in the MM_TEXT mode: y values decrease as you move down. Thus, a 4-by-4-cm square is drawn in logical coordinates this way:

pDC->Rectangle(CRect(0, 0, 4000, -4000));

Looks easy, doesn't it? Well, it isn't, because you can't work only in logical coordinates. Your program is always switching between device coordinates and logical coordinates, and you need to know when to convert between them. This section gives you a few rules that can make your programming life easier. First, you need to know what mapping modes Windows gives you.


The MM_TEXT Mapping Mode


At first glance, MM_TEXT appears to be no mapping mode at all, but rather another name for device coordinates. Almost. In MM_TEXT, coordinates map to pixels, values of x increase as you move right, and values of y increase as you move down, but you're allowed to change the origin through calls to the CDC functions SetViewportOrg and SetWindowOrg.

Here's some code that sets the window origin to (100, 100) in logical coordinate space and then draws a 200-by-200-pixel square offset by (100, 100). (An illustration of the output is shown in Figure 5-1.) The logical point (100, 100) maps to the device point (0, 0). A scrolling window uses this kind of transformation.


Figure 5-1: A square drawn after the origin has been moved to (100, 100).

void CMyView::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_TEXT);
pDC->SetWindowOrg(CPoint(100, 100));
pDC->Rectangle(CRect(100, 100, 300, 300));
}



The Fixed-Scale Mapping Modes


One important group of Windows mapping modes provides fixed scaling. You've already seen that, in the MM_HIMETRIC mapping mode, x values increase as you move right and y values decrease as you move down. All fixed mapping modes follow this convention, and you can't change it. The only difference among the fixed mapping modes is the actual scale factor, as shown in Table 5-1.



























Table 5-1: The Scale Factor for Mapping Modes


Mapping Mode


Logical Unit


MM_LOENGLISH


0.01 inch


MM_HIENGLISH


0.001 inch


MM_LOMETRIC


0.1 mm


MM_HIMETRIC


0.01 mm


MM_TWIPS


1/1440 inch


The last mapping mode, MM_TWIPS, is most often used with printers. One twip is 1/20 point. (A point is a type measurement unit that equals exactly 1/72 inch in Windows.) If the mapping mode is MM_TWIPS and you want, for example, 12-point type, you set the character height to 12 × 20, or 240, twips.



The Variable-Scale Mapping Modes


Windows provides two mapping modes, MM_ISOTROPIC and MM_ANISOTROPIC, that allow you to change the scale factor as well as the origin. With these mapping modes, your drawing can change size as the user changes the size of the window. Also, if you invert the scale of one axis, you can "flip" an image about the other axis and you can define your own arbitrary fixed-scale factors.

With the MM_ISOTROPIC mode, a 1:1 aspect ratio is always preserved. In other words, a circle is always a circle as the scale factor changes. With the MM_ANISOTROPIC mode, the x and y scale factors can change independently. Circles can be squished into ellipses.

Here's an OnDraw function that draws an ellipse that fits exactly in its window:

void CMyView::OnDraw(CDC* pDC)
{
CRect rectClient;
GetClientRect(rectClient);
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));
}

What's going on here? The functions SetWindowExt and SetViewportExt work together to set the scale, based on the window's current client rectangle returned by the GetClientRect function. The resulting window size is exactly 1000-by-1000 logical units. The SetViewportOrg function sets the origin to the center of the window. Thus, a centered ellipse with a radius of 500 logical units fills the window exactly, as illustrated in Figure 5-2.


Figure 5-2: A centered ellipse drawn in the MM_ANISOTROPIC mapping mode.

Here are the formulas for converting logical units to device units:



    x scale factor = x viewport extent / x window extent



    y scale factor = y viewport extent / y window extent



    device x = logical x × x scale factor + x origin offset



    device y = logical y × y scale factor + y origin offset



Suppose the window is 448 pixels wide (rectClient.right). The right edge of the ellipse's client rectangle is 500 logical units from the origin. The x scale factor is 448/1000, and the x origin offset is 448/2 device units. If you use the formulas shown above, the right edge of the ellipse's client rectangle comes out to 448 device units, the right edge of the window. The x scale factor is expressed as a ratio (viewport extent/window extent) because Windows device coordinates are integers, not floating-point values. The extent values are meaningless by themselves.

If you substitute MM_ISOTROPIC for MM_ANISOTROPIC in the preceding example, the "ellipse" is always a circle, as shown in Figure 5-3. It expands to fit the smallest dimension of the window rectangle.


Figure 5-3: A centered ellipse drawn in the MM_ISOTROPIC mapping mode.



Coordinate Conversion


Once you set the mapping mode (plus the origin) of a device context, you can use logical coordinate parameters for most CDC member functions. If you get the mouse cursor coordinates from a Windows mouse message (the point parameter in OnLButtonDown), for example, you're dealing with device coordinates. Many other MFC library functions, particularly the member functions of class CRect, work correctly only with device coordinates.





Note

The CRect arithmetic functions use the underlying Win32 RECT arithmetic functions, which assume that right is greater than left and bottom is greater than top. A rectangle (0, 0, 1000, –1000) in MM_HIMETRIC coordinates, for example, has bottom less than top and cannot be processed by functions such as CRect::PtInRect unless your program first calls CRect::NormalizeRect, which changes the rectangle's data members to (0, –1000, 1000, 0).


Furthermore, you're likely to need a third set of coordinates that we'll call physical coordinates. Why do you need another set? Suppose you're using the MM_LOENGLISH mapping mode in which a logical unit is 0.01 inch, but an inch on the screen represents a foot (12 inches) in the real world. Now suppose the user works in inches and decimal fractions. A measurement of 26.75 inches translates to 223 logical units, which must ultimately be translated to device coordinates. You'll want to store the physical coordinates as either floating-point numbers or scaled long integers to avoid rounding-off errors.

For the physical-to-logical translation, you're on your own, but the Windows GDI takes care of the logical-to-device translation for you. The CDC functions LPtoDP and DPtoLP translate between the two systems as long as the device context mapping mode and associated parameters have already been set. Your job is to decide when to use each system. Here are a few rules of thumb:



    Assume that the CDC member functions take logical coordinate parameters.



    Assume that the CWnd member functions take device coordinate parameters.



    Do all hit-test operations in device coordinates. Define regions in device coordinates. Functions such as CRect::PtInRect work best with device coordinates.



    Store long-term values in logical or physical coordinates. If you store a point in device coordinates and the user scrolls through a window, that point is no longer valid.



Suppose you need to know whether the mouse cursor is inside a rectangle when the user clicks the left mouse button. The code is shown here:

// m_rect is CRect data member of the derived view class with MM_LOENGLISH
// logical coordinates
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rect = m_rect; // rect is a temporary copy of m_rect.
CClientDC dc(this); // This is how we get a device context
// for SetMapMode and LPtoDP
// -- more in next chapter
dc.SetMapMode(MM_LOENGLISH);
dc.LPtoDP(rect); // rect is now in device coordinates
if (rect.PtInRect(point)) {
TRACE("Mouse cursor is inside the rectangle.\n");
}
}

Notice the use of the TRACE macro (covered in Chapter 2 ).





Note

As you'll soon see, it's better to set the mapping mode in the virtual CView function OnPrepareDC instead of in the OnDraw function.




The Ex05b Example: Converting to the MM_HIMETRIC Mapping Mode


Ex05b is Ex05a converted to MM_HIMETRIC coordinates. The Ex05b project on the companion CD uses new class names and filenames, but the following instructions take you through modifying the Ex05a code. Like Ex05a, Ex05b performs a hit-test so that the ellipse changes color only when you click inside the bounding rectangle.



    Use the Class View's Properties window to override the virtual OnPrepareDC function. You can override virtual functions for selected MFC library base classes, including CView in the Properties window. The code wizards available from the Properties window generate the correct function prototype in the class's header file and a skeleton function in the CPP file. Select the class name CEx05aView in Class View, right-click on it, and then choose Properties. Click the Overrides button on the Properties window toolbar and select the OnPrepareDC function in the list. Add the function. Visual C++ .NET will load the implementation file so you can edit the function as shown here:

    void CEx05aView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) 
    {
    pDC->SetMapMode(MM_HIMETRIC);
    CView::OnPrepareDC(pDC, pInfo);
    }

    The application framework calls the virtual OnPrepareDC function just before it calls OnDraw.



    Edit the view class constructor. You must change the coordinate values for the ellipse rectangle. That rectangle is now 4-by-4 centimeters instead of 200-by-200 pixels. Note that the y value must be negative; otherwise, the ellipse will be drawn on the "virtual screen" right above your monitor! Change the values as shown here:

    CEx05aView::CEx05aView() : m_rectEllipse(0, 0, 4000, -4000)
    {
    m_nColor = GRAY_BRUSH;
    }



    Edit the OnLButtonDown function. This function must convert the ellipse rectangle to device coordinates in order to do the hit-test. Change the function as shown in the following code:

    void CEx05aView::OnLButtonDown(UINT nFlags, CPoint point) 
    {
    CClientDC dc(this);
    OnPrepareDC(&dc);
    CRect rectDevice = m_rectEllipse;
    dc.LPtoDP(rectDevice);
    if (rectDevice.PtInRect(point)) {
    if (m_nColor == GRAY_BRUSH) {
    m_nColor = WHITE_BRUSH;
    }
    else {
    m_nColor = GRAY_BRUSH;
    }
    InvalidateRect(rectDevice);
    }
    }



    Build and run the Ex05b program. The output should look similar to the output from Ex05a, except that the ellipse size will be different. If you try using Print Preview again, the ellipse should appear much larger than it did in Ex05a.





/ 319