Implementing windows Drag and Drop functionanlity

We use drag and drop operation number of times. We often drag a file from the source directory to the destination directory in Windows Explorer. However, Windows Explorer drag and drop can handle only files and directories. OLE drag and drop is more general. It is able to transfer any kind of data.

The drag and drop operation involves a drop source and a drop destination. The drop source creates a data object that encapsulates the data and makes its pointer available. The drop target retrieves the pointer to the data object and uses it to extract the data.

In MFC COleDropSource class provides an implementation of the drop source object and COleDropTarget class provides an implementation of the drop target object. However we do not need to instantiate COleDropSource object because COleDataSource class does it for us.

In this article we will write a program that allows to drag and drop items across the list controls.

Create a dialog-based application ‘dragdroplistctrl’. Add two list controls to the dialog template. From the ‘Properties’ dialog box of both the list controls select the ‘View’ as ‘Report’ and check the ‘Single selection’ check box.

Initialise the OLE libraries in InitInstance( ) function before displaying the dialog box as given below:

BOOL CDragdroplistctrlApp::InitInstance( )

{

        if ( !AfxOleInit( ) )

                return FALSE ;

 

        //App Wizard added code

        return TRUE ;

}

Insert a new class by selecting ‘Insert | New Class’. Enter the class name as DragDropList and select the base class as CListCtrl.

Add two variables m_list1 and m_list2 of type DragDropList to the CDragdroplistctrlDlg class through ClassView tab. Add the following code in CDragdroplistctrl::OnInitDialog( ) function.

BOOL CDragdroplistctrlDlg::OnInitDialog( )

{

        // AppWizard added code

        m_list1.SubclassDlgItem ( IDC_LIST1, this ) ;

        m_list2.SubclassDlgItem ( IDC_LIST2, this ) ;

       

        m_list1.InsertColumn ( 0, "Item1", LVCFMT_LEFT, 150 ) ;

        m_list2.InsertColumn ( 0, "Item1", LVCFMT_LEFT, 150 ) ;

        CString str = "" ;

        for ( int i = 0 ; i <>

        {

                str.Format ( "Item #%d", i ) ;

                m_list1.InsertItem ( i, str ) ;

        }

        for ( i = 0 ; i <>

        {

                str.Format ( "Item #%d", i ) ;

                m_list2.InsertItem ( i, str ) ;

        }

        m_list1.initialize( ) ;

        m_list2.initialize( ) ;

        return TRUE ;

}

Here, we have subclassed the list controls, inserted columns and added items in them. Next, we have called DragDropList::initialize( ) function. initialize( ) is the user-defined function that looks as shown below:

void DragDropList::initialize( )
{

       
BOOL success = m_droptarget.Register ( this ) ;
       
if ( !success )
               
AfxMessageBox ( "Ole Register Drop Target Failed" ) ;
}

To register a window as a valid drop target we must call COleDropTarget::Register( ) function. However, generally we derive a class from COleDropTarget to provide our own functionality and call Register( ) function of the derived class. We have derived a class ListDropTarget from COleDropTarget. Add a variable m_droptarget of type ListDropTarget through ClassView tab. Using this object we have registered the list controls as the drop targets. Insert the class ListDropTarget by selecting ‘Insert | New Class’. Select the ‘Class type’ as ‘Generic Class’. Enter the class name as ListDropTarget. In the ‘Derived From’ list box enter the base class name as COleDropTarget. Click the ‘OK’ button. In ’ListDropTarget.h’ file #include ‘afxole.h’ file for the COleDropTarget class.

The Drop Source

Whenever the user drags a list control item the list control gets a notification called LVN_BEGINDRAG. We have handled this notification. The OnBegindrag( ) handler is given below:

void DragDropList::OnBegindrag ( NMHDR* pNMHDR, LRESULT* pResult )

{

        NM_LISTVIEW* pNMListView = ( NM_LISTVIEW* ) pNMHDR ;

        int item = ( ( NM_LISTVIEW* ) pNMHDR ) -> iItem ;

        m_source = TRUE ;

        HANDLE hdata = ::GlobalAlloc ( GPTR, sizeof ( LV_ITEM ) ) ;

        LV_ITEM *lvi = ( LV_ITEM* ) ::GlobalLock ( hdata ) ;

        ::GlobalUnlock ( hdata ) ;

        char label[256] ;

        lvi -> mask  = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;

        lvi -> stateMask = LVIS_DROPHILITED | LVIS_FOCUSED | LVIS_SELECTED;

        lvi -> pszText = label ;

        lvi -> iItem = item;

        lvi -> cchTextMax = 255 ;

        GetItem ( lvi ) ;

        COleDataSource m_coledatasource ;

        m_coledatasource.CacheGlobalData ( CF_TEXT, hdata ) ;

 

        DROPEFFECT dropeffect = m_coledatasource.DoDragDrop (DROPEFFECT_COPY | DROPEFFECT_MOVE ) ;

         if ( ( dropeffect & DROPEFFECT_MOVE ) == DROPEFFECT_MOVE )

                DeleteItem ( item ) ;

 

        m_source = FALSE ;

        *pResult = 0;

}

Here, firstly the index of the item being dragged is stored in an integer variable item. We have initialised a BOOL variable m_source to TRUE. m_source is a data member of DragDropList class which is being initialised to FALSE in the constructor. We will see the use of this variable later on. We have used ::GlobalAlloc( ) API function to create a global memory block as a storage medium. The GlobalAlloc( ) function returns a handle to the memory. ::GlobalLock( ) function takes this handle and returns a pointer to the memory block. We have stored this pointer in LV_ITEM*. ::GlobalUnlock( ) function unlocks the memory. In the pointer lvi we have retrieved the list control item specified by item. The data is then handed over to the COleDataSource object by calling COleDataSource::CacheGlobalData( ) function. The actual process of drag and drop starts when COleDataSource::DoDragDrop( ) function is called. The first parameter passed to DoDragDrop( ) specifies the action to be taken when the item is dragged and dropped. We have specified the ‘Copy’ or ‘Move’ operation. So, when user drags the item from one list control to other it will get moved and if Ctrl key is kept depressed while dragging the item the item will get copied to the other control. The DoDragDrop( ) function returns the drop effect, i.e., whether the item has been moved or copied.

The Drop Target

The COleDropTarget class provides few overridables that are listed below:

Function

Description

DragEnter( )

Called when cursor enters the drop target window

DragOver( )

Called as the cursor moves over the drop target window

DragLeave( )

Called when cursor leaves the drop target window

Drop( )

Called when a drop occurs

The COleDropTarget derived class (ListDropTarget in our case) must override all or the combination of these functions. We have overridden all the functions except DragLeave( ). Add the declarations of the functions in ‘ListDropTarget.h’ file as given below:

DROPEFFECT OnDragEnter ( CWnd* pWnd, COleDataObject* pDataObject,DWORD dwKeyState, CPoint point ) ;

DROPEFFECT OnDragOver ( CWnd* pWnd, COleDataObject* pDataObject,DWORD dwKeyState, CPoint point ) ;

BOOL OnDrop ( CWnd* pWnd, COleDataObject* pDataObject,DROPEFFECT dropEffect, CPoint point ) ;

Add the definitions to ‘ListDropTarget.cpp’ file as given below:

DROPEFFECT ListDropTarget::OnDragEnter ( CWnd* pWnd,COleDataObject* pDataObject, DWORD                                                dwKeyState, CPoint point )
{

}

DROPEFFECT ListDropTarget::OnDragOver ( CWnd* pWnd,COleDataObject* pDataObject, DWORD                                                dwKeyState, CPoint point )
{

}

BOOL ListDropTarget::OnDrop ( CWnd* pWnd,COleDataObject*  pDataObject,DROPEFFECT dropEffect, CPoint point )
{

}

Note that since ListDropTarget is a generic class we need to add these functions manually.
Add the code shown below in OnDragEnter( ) function.

DROPEFFECT ListDropTarget::OnDragEnter ( CWnd* pWnd,COleDataObject* pDataObject, DWORD                                                dwKeyState, CPoint point )
{

        if ( ( dwKeyState & MK_CONTROL ) == MK_CONTROL )

                return DROPEFFECT_COPY ;

        else

                return DROPEFFECT_MOVE ;
}

Add the same code as given in OnDragEnter( ), in the OnDragOver( ) function.

The parameters passed to these functions are same. The first parameter is the pointer to the window the cursor is entering. The second parameter is the pointer to the data object containing the data that can be dropped. (This pointer actually points to the global memory block that is allocated in the DragDropList::OnBegindrag( ) function). The third parameter contains the state of the modifier keys like Ctrl, Shift etc. The last parameter is the current location of the cursor in client coordinates. The OnDragEnter( ) and OnDragOver( ) functions return the drop effect flag. If the Ctrl key is pressed we have returned DROPEFFECT_COPY flag otherwise we have returned the DROPEFFECT_MOVE flag.

The actual drop operation is performed in OnDrop( ) function. The OnDrop( ) function is given below:

BOOL ListDropTarget::OnDrop ( CWnd* pWnd, COleDataObject*pDataObject, DROPEFFECT dropEffect, CPoint point )

{

        DragDropList* plist = ( DragDropList* ) pWnd ;

        int item ;

        if ( plist -> GetItemCount( ) == 0 )

                item = 0 ;

        else

        {

                UINT flags ;

                point.y += 5 ;

                item = plist -> HitTest ( point, &flags ) ;

                if ( item == -1 )

                        item = plist -> GetItemCount( ) ;

                else

                        item++ ; // drop dragged item after the selected item

        }

        HANDLE hglobal = pDataObject -> GetGlobalData ( CF_TEXT ) ;

        LV_ITEM *litem = ( LV_ITEM* ) GlobalLock ( hglobal ) ; 

        litem -> iItem = item ;

        if ( plist -> m_source )

        {

                AfxMessageBox ( "An item can't be dropped onto itself" ) ;

                return FALSE ;

        }

        else

                plist -> InsertItem ( litem ) ;

        GlobalUnlock ( hGlobal ) ;

        return TRUE ;

}

To the OnDrop( ) function too, the pointer to the target window and the data object get passed as first two parameters. The third parameter is the effect user chose as the drop operation and last parameter is the location of cursor. Here, firstly we have ascertained the position or index where the item is to be dropped. CListCtrl::HitTest( ) function returns the index of the item specified by the first parameter, –1 otherwise. If HitTest( ) function returns –1 we will drop the item at the end of the list. Next we have retrieved the data by calling CDataObject::GetGlobalData( ) function. The handle returned by GetGlobalData( ) is then passed to GlobalLock( ) function to obtain pointer to the data. We have stored the pointer in LV_ITEM*. We have stored the index where the item is to be dropped in the iItem element of LV_ITEM structure. Here we have made use of the m_source variable to check whether the source and target are the same. In DragDropList::OnBegindrag( ) function we had initialised m_source of the source window to TRUE. Since pointer of the target window is passed to OnDrop( ) we can check whether the m_source of target window is TRUE or not. If it is then source and target are the same. If the source and target windows are same we have cancelled the drop operation by returning FALSE. Otherwise we have inserted the item in the target window by calling InsertItem( ) member function of CListCtrl class. Finally we have unlocked the memory.

If you run the program you will see two list controls having few items. You can transfer the items across them by the drag and drop operation

Download

A Memory Game

This is a simple game which displays 25 push buttons in the client area. On top of each button a bitmap of a different animal is loaded. When you play the game the computer on its own depresses a sequence of buttons at random. As each button is depressed, the color of the animal changes to gray and the button goes in. Both these things are done to give the user a feeling that a button has indeed been depressed. As the computer depresses a sequence of buttons the user is supposed to memorize the sequence. Once the computer has done its job the user is then supposed to depress the buttons by clicking the left mouse button in the same sequence as done by computer. If the user's sequence matches the computer's sequence then a message box appears telling the user of his success and then the computer proceeds to depress another sequence of buttons. This time it depresses one more button than what it did last time around. The user is again supposed to repeat the sequence by clicking the push buttons with the mouse. This goes on till the time the users' sequence matches the computer's sequence. In case of a mismatch the game ends. The following figure shows the game window.


As seen from the figure, there are three items in the menu. Selecting the first menu item results into starting a fresh game. Using the 'Sound' menu item it can be decided whether we want a sound to accompany the depressing of a button. The third menu item is the usual 'About' menu item. The only additional feature in this program's 'About' menu item is that it displays the application icon in the 'About' dialog box. This article explains how to create the memory game. This project has an application class, myapp, and a frame window class, myframe. During execution the real action begins when the control reaches the myframe constructor to create the window. Since we want a user-defined icon and a stock brush of light gray color, we have called the AfxRegisterWndClass( ) function to register the window class with these properties. Once registered, we have created a window based on this class by calling CFrameWnd::Create( ). Once the window is created a WM_CREATE message gets posted into the message queue. In response to this message the function myframe::OnCreate( ) gets called. In this function we have created 25 buttons and loaded 25 different bitmaps on them. b[ ] is an array of CBitmapButton objects. The class CBitmapButton encapsulates owner-drawn push buttons that display pictures in place of text. In the OnCreate( ) handler we have called CBitmapButton::Create( ) to create each button. The first parameter passed to Create( ) is NULL since we don't want any text to be displayed on the button. The window style WS_CHILD indicates that each button is a child of the frame window. The style WS_VISIBLE ensures that each button is shown automatically when its parent (the frame window) gets displayed. The style BS_OWNERDRAW must be included to indicate that the button is owner-drawn. The CRect passed to Create( ) indicates the size of the button, whereas, the this pointer passed to it is the pointer to the parent window object. The last parameter passed to Create( ) stands for the id given to each button. Once the 25 buttons are created, a pair of bitmaps is loaded for each button. These bitmaps must be created using the Resource Editor. When drawing the bitmaps for a CBitmapButton-style button you should follow a few simple rules to ensure that the button's appearance is consistent with that of normal push buttons. Each bitmap is of size 50 * 50 pixels. It is important that both the bitmaps are of same size. For best results, the button's face and edges should be drawn with the colors. Start with the up bitmap, and then create the down bitmap by reversing the border and moving the image on the face of the button right and down by one pixel. This gives the illusion of the button going down when it is clicked. Note that it is not necessary that you should strictly style your buttons as per these guidelines and you are free to decide your own style. However, the users are likely to judge your application more favourably if the appearance and behaviour of their controls are similar to that of the controls in other Windows applications. In fact a CBitmapButton can use as many as four bitmapped images: one depicting the button in its normal, undepressed, state; a second depicting the button when it is depressed; a third depicting the button when it is undepressed and has the input focus; and a fourth depicting the button when it is disabled. At a minimum, you should supply "up" and "down" bitmaps showing the button in its normal and depressed states. When the button is clicked, the framework redraws the button using the down bitmap so that the button appears to recede into the screen. When the button is released it is redrawn with the up bitmap. The "focus" and "disable" bitmaps are optional, hence we have not used them in our program. The parameters passed to CBitmapButton::LoadBitmaps( ) specify, in order, the up bitmap, the down bitmap, the focus bitmap and the disabled bitmap. Since we are using only the up and down bitmaps we have passed 0 (NULL) for the focus and the disabled bitmap as shown below:
b[i].LoadBitmaps ( IDB_BITMAP1 + i, IDB_BITMAP26 + i, 0, 0 ) ;
Once the frame window with 25 buttons and 25 bitmaps atop them appears we can start the game by clicking on the 'New game' menu item. On doing so, the newgame( ), menu handler gets called. This function calls function myframe::computersmove( ) with a value 1. The 1 here indicates that first time the computer should depress only one button. The computersmove( ) is a general function which can depress as many buttons as you ask it to and record the id of each button depressed in an array m_computerplayed[ ]. To show the depressing and releasing of the button the CBitmapButton::SetState( ) function is called by computersmove( ). The SetState( ) function is passed a value 1 or 0 for showing the depressed and the released state respectively. The User's Move The user can imitate the computer's sequence of moves by attempting to click the buttons with the left mouse button in the same sequence. On clicking any button the control reaches usersmove( ). This has been ensured by the following entry in the message map:
ON_CONTROL_RANGE ( BN_CLICKED, 1, 25, usersmove )
This entry maps the group of control with ids 1 to 25 to a single handler usersmove( ). Note that when usersmove( ) gets called the id of the button which prompted the call would be passed to this function. In this function first it is checked whether the user is trying to make a move before the computer makes its move. That is cheating and a message box is immediately popped up to indicate this and the control is returned from the function. If the move is found to be legal then the id of the depressed button is stored in the array m_userplayed[ ]. It is then verified whether this move made by the user matches with the computer's move or not. If it doesn't match then it is promptly reported so and the user is asked to restart the game. If all the moves made by the user match the ones made by the computer then it is concluded that the user has imitated the computer's moves correctly. In such a case the computersmove( ) function is called again to let the computer make its next move. However, the value passed to computersmove( ) this time is one more than what was passed to it last time around . Thus the degree of difficulty of the game goes on increasing as the game proceeds.
As the computer or the user makes a move (depresses a button) we can create a sound by calling ::MessageBeep( ) API function. However, the choice of creating or not creating such a sound has been left to the user. He can exercise his choice by selecting the item 'Yes' or 'No' from the 'Sound' menu. To keep track of what he has selected a variable m_state has been used. To ensure that if he selects 'Yes' then he should not be allowed to select 'Yes' again, the program disables the 'Yes' menu item once it is selected. A similar procedure is adopted for the 'No' menu item. To implement this functionality we have defined the function enable_disable( ) which gets called whenever the sound menu is being displayed. Note that this function is called twice, once when the 'Yes' menu item is being displayed and next when the 'No' menu item is being displayed. A call to this function is arranged through the message map entry,
ON_UPDATE_COMMAND_UI_RANGE ( 201, 202, enable_disable )
On getting called, this function appropriately disables the menu item using the value in the m_state variable. The actual enabling or disabling is done by the function CCmdUI::Enable( ). The CCmdUI MFC class contains lot of other functions to update user interface items. Icon in The 'About' Dialog If we want to show the icon in the 'About' dialog box we should make a provision for it while creating the dialog box in the Resource Editor. The steps involved in doing this are as follows:
Create an icon and save it.
Create a dialog box.
Select 'Picture' control from the 'Controls' toolbar.
Place this control in the dialog box.
Double click on this control in the dialog to get the 'Picture Properties' dialog box.
Select 'General' property page, select 'Icon' from the 'Type' combobox.
Select id of the icon which you wish to include from the 'Image' combobox.
Save the dialog.

    Attaching menu items to the system menu

    Creating menus and adding menu items to them is a common phenomenon. But adding menu items to a menu provided by framework like system menu is rather uncommon. here is the article which shows how to add items to the system menu. The way an application calls CWnd::GetMenu( ) to obtain a CMenu pointer to its top-level menu, likewise, it can call CWnd::GetSystemMenu( ) to obtain a pointer to its system menu. This is what has been done in the OnCreate( ) handler. The FALSE parameter passed to the GetSystemMenu( ) indicates that you want a pointer to a copy of the system menu that you can modify. Passing TRUE resets the system menu to its default state. Once the pointer to the system menu is obtained, the AppendMenu( ) function is called once to add a separator and then to add the 'About' menu item. Note that the ID for this menu item has been given as 112. This is because it is necessary that the IDs of system menu items are multiples of 16 and the 'About' menu item happens to be the seventh menu item. When the user clicks on an item from the system menu, the window receives a WM_SYSCOMMAND message. We have tackled this message using the handler OnSysCommand( ). The first parameter passed to it contains the ID of the menu item selected in the upper 12 bits. The lower 4 bits are used by Windows internally. Hence, the id has been ANDed with 0xFFF0 to strip off any bits that Windows may have added to it. If the 'About' menu item is selected then an appropriate message is displayed using the MessageBox( ) function. It is necessary to call the base class's OnSysCommand( ) handler so that the selection of other system menu items gets properly processed.
     
    Download

    How Windows uses colors?

    Though XGA is gaining ground fast, VGA and SVGA still remain the most commonly used video adapters today. Of these, the VGA adapter uses 4 bits to represent a pixel, whereas, the SVGA and XGA use 8 and 24 bits respectively. Let us understand how these adapters manage colors.

    In VGA the display memory is organized in four planes—Red, Green, Blue and Intensity. One bit from each of these planes contributes towards one pixel value. The VGA adapter has several registers which together makes up its programming interface. Of these, the ones that are of our interest here are the palette registers, color select registers, and the DAC registers. The 4-bit pixel value from display memory is used as the address of one of the 16 palette registers. For example, a pixel value of 0 selects palette register 0, a pixel value of 1 selects palette register 1 and so on. Each palette register is 8 bits long of which 6 bits are used. Once the palette register has been chosen, the 6-bit value in it is combined with a 2-bit value from a color select register, resulting into an 8- bit value. The 8-bit value is used as the address of one of the 256 DAC (Digital to Analog Converter) registers. Each DAC register contains an 18-bit value which represents the color. The 18-bit value is organized in 6-bit red, green and blue color components. This value is sent to the analog conversion circuitry, which converts it into three proportional analog signals and sends them to the monitor. Since each DAC register is 18-bit long, a pixel can have any of the 2,62,144 values ( 218 ).

    VGA supports several graphics modes. The two popular ones are: 640 x 480, 16-color mode and a 320 x 200, 256-color mode. In the former, the color select register bits always have a value 0. Hence, in this mode only the first 64 DAC registers get used. As a result, only 64 out of the possible 2,62,144 color values can be used. And since there are only 16 palette registers, only 16 out of the 64 DAC registers can get selected. Thus in this graphics mode we can use any 16 out of the 2,62,144 colors.

    In the 256-color mode, the 2-bits from the color select register can take values like 00, 01, 10 and 11. This combined with 64 possible values from the palette registers permit us to select one of the 256 DAC registers. As a result we can use 256 out of possible 2,62,144 color values.

    As said earlier, SVGA uses 8 bits to represent a pixel. In SVGA there are 256 palette registers. Thus, using the 8-bit pixel value it can access 256 palette registers. Each value in the palette register can further access 256 DAC registers. Each DAC register is 24 bits long. Hence, in SVGA we can choose 256 colors simultaneously out of the possible 16,77, 716 (224) colors.

    Let us now see how Windows utilizes the color capabilities of these adapters. When you pass a color to the windows GDI, you pass a COLORREF value, containing 8 bits each for red, green and blue. The RGB macro combines individual red, green and blue values into a single COLORREF value. For example, the statement

    COLORREF c = RGB ( 128, 128, 0 ) ;

    creates a COLORREF value named c. We can use this COLORREF value while building pens and brushes. When we pass this COLORREF value on XGA adapter (which uses 24 bits per pixel) the 24-bit value is translated directly into colors on the screen. However, as we saw earlier, The VGA and the SVGA support a wide range of colors but can display only a limited amount of colors at one time. For example, a VGA running at a resolution of 640 x 480 pixels can display only 16 colors at a time out of the possible 2,62,144 colors. Similarly, a SVGA we can display only 256 colors at a time out of the possible 16,777,16 colors. Note that 16 or the 256 colors that can be displayed on these adapters are determined by the contents of the DAC Registers. The pixel value is merely an index into the palette registers and the palette register value is an index into the DAC registers.

    Windows handles VGA and SVGA devices by preprogramming a standard selection of colors into the adapter’s DAC registers. The SVGA adapter is preprogrammed with 20 colors shown in the following table. These colors are known as static colors.

    Color

    R

    G

    B

    Black

    0

    0

    0

    Dark black

    128

    0

    0

    Dark green

    0

    128

    0

    Dark yellow

    128

    128

    0

    Dark blue

    0

    0

    128

    Dark magenta

    128

    0

    128

    Dark cyan

    0

    128

    128

    Light gray

    192

    192

    192

    Money Green

    192

    220

    192

    Sky blue

    166

    202

    240

    Cream

    251

    251

    240

    Intermediate gray

    160

    160

    164

    Medium gray

    128

    128

    128

    Red

    255

    0

    0

    Green

    0

    255

    0

    Yellow

    255

    0

    255

    Magenta

    255

    0

    255

    Blue

    0

    0

    255

    Cyan

    0

    255

    255

    White

    255

    255

    255

    Table 1

    When we draw on a SVGA the GDI maps each COLORREF value to the nearest static color using a color matching algorithm. For many applications this form of color mapping is acceptable. But in an application where a more accurate color output is required 20 colors are too little to choose from. In such cases we can use a logical palette to add 236 more colors to the DAC registers. We, of course, cannot program the DAC registers directly because the same VDU is being shared amongst several applications running in memory. Hence, to get our jobs done we have to use a GDI object called logical palette. A logical palette is nothing but a table of RGB color values. The process of adding the colors in the logical palette to the DAC registers (often called Hardware Palette) is known as realizing the palette.

    In this article we will see a program that shows how to create a logical palette and realize it.

    The program displays a window containing 24 rectangles drawn with a solid pen of thickness 8 pixels. The first 12 rectangles are drawn using different shades of red color. As mentioned earlier, these colors are mapped to the nearest out of the 20 static colors. As a result, all 12 shades mapped to one of the two shades of red colors present in the list of static colors (Refer Table 1). The next 12 rectangles have been drawn after realizing the palette. Hence you can notice 12 distinct shades of red.

    In MFC, to create logical palette we have to use the CPalette class’s member functions. Once a logical palette is created it can be selected into a device context and realized with CDC member functions.

    In our program once the window is created the myframe::OnCreate( ) function gets called. Here we have first ascertained whether using a logical palette will improve color output, by calling CDC::GetDeviceCaps( ) as shown below.

    if ( ( d.GetDeviceCaps ( RASTERCAPS ) & RC_PALETTE ) == 0 )

    {

    MessageBox ( "Palette is not supported by this device \nChange Settings", "Sorry" ) ;

    return -1 ;

    }

    It is a good idea to perform this check. If you are running the application on XGA then you don’t need a logical palette because in a XGA, perfect color output is available free. If you are running the application on a VGA, palettes are meaningless since the system palette is initialized with 16 static colors that leave no room for colors in logic palette. It is when the application is run on SVGA, we can get more color accuracy by using a logical palette. Hence the check. Next we have created 12 pens for drawing the first 12 rectangles. Then we have created a logical palette through the statement,

    m_palette.CreatePalette ( pLP ) ;

    Before we call CPalette::CreatePalette( ) we have to fill in a LOGPALETTE structure with information describing the palette colors. The LOGPALETTE structure has been defined as follows.

    typedef struct tagLOGPALETTE

    {

    WORD palVersion ;

    WORD palNumEntries ;

    PALETTEENTRY palPlaEntry[1] ;

    } LOGPALETTE ;

    palVersion specifies the LOGPALETTE version number; in all current releases of Windows, it should be set to 0x300. palNumEntires specifies the number of colors in logical palette. palPalEntry is an array of PALETTEENTRY structures defining the colors themselves. The number of elements in the array should equal the value of palNumEntries. PALETTEENTRY is defined as follows:

    typedef struct tagPALETTEENTRY

    {

    BYTE peRed ;

    BYTE peGreen ;

    BYTE peBlue ;

    } PALETTEENTRY ;

    peRed, peGreen and peBlue specify a color’s 8-bit RGB components. peFlags contains zero or more bit flags describing the type of palette entry.

    The PLAETTENETRY array in the LOGPALETTE structure is declared with just one array element because Windows has no way of anticipating how many colors a logical palette will contain. As a result, you cannot just declare an instance of LOGPALETTE on the stack and fill it in; instead, we have to allocate memory for it based on numbers of PALETTEENTRY structures it contains.

    Once the palette has been created it needs to be selected into the device context and then realized. Since this must be done before any drawing takes place we have done these two tasks in the OnPaint( ) handler. Note that palette is selected with CDC::SelectPalette( ) instead of CDC::SelectObject( ). CDC::RealizePalette( ) realizes the palette that is currently selected into the device context by asking the palette manager to map colors from logical palette to the system palette. Once the palette has been realized we are ready to start drawing. If we use CDC::BitBlt( ) to display a bitmap, the realized colors are used automatically. However, if we are using a pen or a brush and are using the RGB macro, the GDI maps the COLORREF value only to the static colors. Since we want the GDI to use all the palette colors we must use the PALETTERGB( ) macro instead of RGB( ).

    Download

    Printing a Text file

    This article shows how one can read text files from the disk and print them page by page. While printing, on each page we would print a header consisting of filename and a footer containing page number. Figure 1 displays one page of the document in print preview mode.

    Figure 1. A page in print preview mode.

    In this article we have managed drawing on the screen using OnDraw( ) and printing on the printer using OnPrint( ). This makes the code more straightforward. It also emphasizes the fact that MFC doesn’t make it compulsory for programmers to do printing and painting in OnDraw( ) itself.

    To be able to print a file we must first load it from the disk into memory. This is achieved using ‘File | Open’ option. On selecting this option a menu handler mydoc::openfile( ) gets called. In this function we have popped up a dialog shown in Figure 2.

    Figure 2.

    There are two ways in which we can specify the file to be opened:

    1. We can supply the name of the file in the edit box of the dialog.

    2. We can select the ‘Browse’ button to popup a ‘File’ dialog. The user can now navigate through the various directories and then select the file that he wishes to open. The selected filename is displayed in the edit box of dialog shown in Figure 2.

      On clicking OK, the dialog is dismissed and the file is opened using CStdioFile constructor. Once the file is opened we must read it line by line. This has been achieved using the mydoc::openfile( ) function as shown below:

      void mydoc::openfile( )
      {

      1. mydialog d ( IDD_DIALOG1 ) ;
        if ( d.DoModal( ) == IDOK )
        {

          CStdioFile fr ( d.filepath_str, CFile::modeRead ) ;
          filelines_str.RemoveAll( ) ;
          CString str ;

          while ( fr.ReadString ( str ) )
          filelines_str.AddTail ( str ) ;

          SetTitle ( d.filename_str ) ;
          UpdateAllViews ( NULL ) ;

        }

      POSITION pos = GetFirstViewPosition( ) ;
      myview *p = ( myview * ) GetNextView ( pos ) ;
      p -> setview( ) ;

      }

      Here, each line from the file is read into a CString object. This object is then added to a list of CString objects maintained using a CStringList object. To carry out the addition we have used the function CStringList::AddTail( ). Once the file is loaded into memory we must now make a provision to display it on the screen and to print it on the printer. To this effect we have overridden several CView virtual functions. Table 1 displays a list of the functions, the purpose of each and whether it aids in printing or painting.

      Function

      Purpose

      Aids in

      OnInitialUpdate( )

      Called when view first gets attached to a document. Creates font, obtains text metrics and sets scroll sizes.

      Painting

      OnDraw( )

      Compulsory to override. Contains logic to display file contents in the view.

      Painting

      OnPreparePrinting( )

      Calls DoPreparePrinting( ) which pops up the ‘Print’ dialog.

      Printing

      OnBeginPrinting( )

      Called just before printing begins. Creates fonts, obtains text metrics, sets maximum page limit and prepares a list of strings to be printed on the printer.

      Printing

      OnPrint( )

      Called before each page is printed. Prints header, page body and footer.

      Printing

      OnEndPriting( )

      Called when printing is finished. Deletes font and deallocates list of strings.

      Printing

      Table 1. Overridden CView virtual functions.

      As in any Doc/View application, here too, painting has been done through myview::OnDraw( ) function. While displaying the file contents in myview::OnDraw( ) there are two things that we should take care of:

    3. The view window should be big enough to accommodate all the lines in the file. This has a direct bearing on determining the scroll sizes to be set.

    4. Deciding the y-coordinate of every line being displayed in the view window. The y-coordinate and the scroll sizes would vary according to the font and the actual contents of the file. For example, the scroll sizes for a file containing 100 lines, a maximum line length of 150 characters being displayed in 10-point, ‘Arial’ font would be different if any of these three parameters change. For the sake of simplicity we have kept the font fixed—10-point, ‘Arial’ font. No assumptions can however be made about the number of lines in the file as well as the maximum line length.

      We have done the creation of font in myview::OnInitialUpdate( ) function. To create a font we have used the CFont::CreatePoint-Font( ) function. Note that this function is more direct and compact as compared to the CFont::CreateFont( ) function. Had we used CreateFont( ) we would have been first required to calculate the height of the characters corresponding to the 10-point, ‘Arial’ font. This height would then have been passed to CreateFont( ). As against this, we can pass point size multiplied by 10 to CreatePointFont( ) to create a font of 10 points.

      Once the font is created it is selected into the device context. Next, the height and width of the characters is determined in logical units using CClientDC::GetTextMetrics( ). Since we have set the mapping mode to MM_LOENGLISH the height and width obtained using GetTextMetrics( ) would be in logical units, where, one logical units is equal to 0.01 inches. Lastly, we have called myview::setview( ).

      In setview( ) we have determined the maximum line length by iterating through list of lines maintained by the CStringList object called filelines_str (this object is a data member of the mydoc class and is initialized when the file is opened.) Once the maximum line length is determined we have set the scroll sizes by calling SetScrollSizes( ).

      Why have we written a separate setview( ) function? Could we not have determined the maximum line length and set the scroll sizes in myview::OnIntialUpdate( )? This would not be feasible because every time we open a new document the scroll sizes would change, but the OnInitialUpdate( ) function would be called only once when the view is attached to the document. Hence, whenever a new document is opened, setview( ) is called again such that the scroll sizes are set appropriately as per the contents of the file being opened.

      Note that whenever we derive our view class from CScrollView it is necessary to override OnInitialUpdate( ) and set the scroll sizes in it (or in a function called from it).

      When OnDraw( ) is called for displaying the lines the scroll sizes already stand set. Hence in OnDraw( ) all that we have done is select the font in the device context, get the count of lines in the document being displayed, and display the lines in the view. For displaying the lines we have used the CDC::TabbedTextOut( ) function, rather than the normal TextOut( ) function. This ensures that a tab character, if present in the document, is not displayed as a character. Instead the output is properly tabbed.

      This program does not generate a WYSIWYG output. This is because there are virtually no limits to the screen width and height once we have a scrollable view in place. However, on the printer we have to print the output keeping in mind the physical dimensions of the page. This is the reason why we have done painting through OnDraw( ) and printing through OnPrint( ).

      If the document contains a line whose length is more than the width of the page then in one line we should print as much as can be accommodated in it, and the balance in the next line. To achieve this we have to do a lot of arrangements in myview::OnBeginPrinting( ). These are as under:

    5. We have created a 10-point ‘Arial’ font and selected it into a device context. It is necessary to do the font creation for printer separately because the printer metrics are different than the screen metrics.

    6. We have determined the number of lines that can be accommodated per page and the number of characters that can be accommodated per line. These have been determined keeping in view the following things:

    • The width and the height of the paper selected.

    • The width and the height of the character of the selected font.

    1. We have copied the list of lines maintained by the CStringList object called filelines_str into another CStringList object called prn_str. This copying is done since we wanted that the two string lists—one going to the screen and another going to the printer—to be maintained separately. This is necessary, because, on the screen we want to scroll the line if its length is more than the width of the client area, whereas, on the printer we want to split the line appropriately.

    2. We have updated the list of strings going to the printer by

    • Splitting the line if its length is more than the page width.

    • Replacing every tab with four spaces.

    While copying or updating the list of strings we have to iterate through the list. This is achieved through the following simple code:

    CStringList list ;

    POSITION pos,

    CString str ;

    pos = list.GetHeadPosition( ) ;

    int count = list.GetCount( ) ;

    for ( int i = 0, i <>

    str = list.GetNext( ) ;

    To begin with, we have to retrieve the POSITION of the first item in the list using CStringList::GetHeadPosition( ). Then to iterate through the list we have to use the GetNext( ) function. This function accepts a POSITION value identifying the current position in the list and returns the string at that position. It also updates the POSITION value to reference the next item in the list.

    Given a POSITION value pos identifying a particular item, we can retrieve or remove an item using the CStringList member functions GetAt( ) and RemoveAt( ). We can also insert items into the list using the member function InsertAfter( ).

    1. Once the list of strings going to printer is updated we have determined maximum number of pages that the document would need for printing. We have then set the maximum page value into the CPrintInfo structure using CPrintInto::SetMax-Page( )

    We are now all set to do the actual printing. This has been done in myview::OnPrint( ). This function contains three logical parts:

    • Printing header

    • Printing page contents

    • Printing footer

    In the header we want to display the name of the file. Hence we have retrieved it using CDocument::GetTitle( ). Once retrieved, depending upon its length we have calculated the x-coordinate where it should be printed. Lastly, we have printed the header using CDC::TextOut( ).

    While printing the page body, on each page we must print the appropriate lines from the string list. To pick up the relevant lines, we have found the index of the first string to be printed on the current page. The current page number is available in CPrintInfo::m_nCurPage and the index can be obtained using CStringList::FindIndex( ). Once the index is found out, through a loop, as many lines as can be accommodated on a page are printed using CDC::TextOut( ). Lastly, the footer is printed with the page number properly aligned in the center.

    Once the printing is over the font as well as the list of strings is deleted by calling the functions CFont::DeleteObject( ) and CStringList::RemoveAll( ), respectively.

    In the print preview mode we can see how the printed output will look like. Once we have incorporated the ability to print in a Doc/View application, adding print previewing facility is a simple matter. We just need to add the ‘Print Preview’ item to the file menu (with id ID_FILE_ PRINT_PREVIEW) and connect it to the function CView::OnFilePrintPreview( ) through a message map entry.

    MFC has packed a lot of code in the OnFilePrintPreview( ) function. This function creates a view from a CScrollView-derived class called CPreviewView. It also adds a toolbar containing buttons to go to next or previous page, to switch between one-page and two-page views, to zoom in or zoom out etc. The CPreviewView::OnDraw( ) function does three jobs:

    1. It draws a white rectangle representing a printed page. If a two-page view is selected then two white rectangles are drawn.

    2. It sets up the scaling parameters such that the printable area of the white rectangle matches the printable area of the real page.

    3. Lastly, it calls the OnPrint( ) function to do the drawing in the rectangle.

    The application may believe that the output is being sent to the printer, whereas, in actuality it is being sent to the print preview window.