Click here to Skip to main content
15,895,799 members
Articles / Desktop Programming / MFC
Article

This may be a Grid Control but, call me Array

Rate me:
Please Sign up or sign in to vote.
4.85/5 (16 votes)
4 Aug 20046 min read 83K   4K   46   20
A control that implements an array of items

Image 1

Introduction

Frequently, different kind of information must be presented to the user in two dimensional form. Often, to do this, we can use ListView or TreeView controls, but sometimes this is not appropriate, or it is hard to code or cannot be used in their native form. Besides, we wish keep difference between elements and display them in appropriate form (e.g., one element show a color table, another show a text or image, and so on). Therefore, We need to have a flexible mode to operate. Control presented here implements a such array of items (like array or matrix in mathematics) which are freely drawn by user. An array is, for example, an image or a color table or whatever information that can be presented in two dimensions (third dimension can be implemented using TabControl as show in demo program). Control assist user in the items arrangement, in the resize of control, in the scroll and in the notify of events inherent the control itself. All items of the array have same size (i.e. width and height), that the control will use to arranging them into rectangle of its client area. Dimensions (i.e. columns and rows) and item size can be changed by the user in any instant, columns or rows can be fixed or dynamic (control will compute them at any change). Since each item will be drawn by user, any kind of things can be drawn on it. To do this, control pass to the user a Device Context (DC) specific for the item that is drawing. User paint the given DC and the control will render it to its window at the appropriated position (based on current scroll position and items arrangement).

Control creation

Array control is implemented by class CArrayCtrl which was inherit from CWnd class. Constructor method initialize members variables and destroyer method is not used. The control's window class has name "ArrayControlClass" and will be register at the first call of create method. Control is created using method Create which synopsis is:

BOOL CArrayCtrl::Create(DWORD dwStyle, const CRect& rect,
 CWnd* pParent, UINT nID, const CSize& itemsize);
where
 dwStyle  specifies the control style attributes
   WS_CHILD        
//create a child window (this style will be enforced by control)
//(control enforce WS_HSCROLL and WS_VSCROLL too)
   WS_VISIBLE      //window is visible
   WS_BORDER       //window has a border
   ACS_TOOLTIP     //control support item's tooltip
   ACS_TTBALLOON   
//tooltip style is ballon form (default is the standard rectangular form)
   ACS_BALLOONTIP  
//combines two previously defined styles (ACS_TOOLTIP | ACS_TTBALLOON)
   ACS_GRIDLINES   //gridlines are required
   ACS_CLIENTEDGE  
//specifies thar window has 3D look, that is, a border with sunken edge 
//(this style is a redefinition of corresponding Windows style)
   ACS_USERDATA    //user will store data (an LPARAM type) on each item
                   //(by default, control don't store any information) 

 rect     //the size and position of the window, 
          //in client coordinate of pParent
 pParent  //the parent window
 nID      //the id of the child window
 itemsize //the size of the item (cx is the width and cy is the height)

The following code show how to create such control

CArrayCtrl* pArrayCtrl; // CArrayCtrl object pointer declaration
CSize ItemSize(32,64);
CRect rControl(8,8,200,100);

pArrayCtrl = new CArrayCtrl;
pArrayCtrl->Create(WS_CHILD | WS_VISIBLE | WS_BORDER
     | ACS_BALLOONTIP | ACS_GRIDLINES,
     rControl, pParentWnd, ID_ARRAY, ItemSize);
After this, window is displayed on screen but none item is visible. You have two mode to specify the items to the control. You can use method SetItemsCount(int nItems) that force the control to start item-paint process, or method AddItem(int nItem, LPARAM dwData) that insert a new item into the control based to nItem value. It can be an item index (such as 5) and the control will insert new item after that, or the special item ACAI_TOP and ACAI_BOTTOM (default), meaning of which is obvious. Since AddItem method will redraw control's window each time item is added, we must suspend redrawing process until all items are been added. To do this you will use the SetRedraw(BOOL bDraw) method to stop the drawing process (bDraw=FALSE) and then restart it (bDraw=TRUE). This code snippet show it:
pArrayCtrl->SetRedraw(FALSE);
while( ... )
{
    ... prepare data item ...
    pArrayCtrl->AddItem(dwData);
}
pArrayCtrl->SetRedraw(TRUE);
If you previously know the items count, you can use SetItemsCount method in this way:
... control is already been created ...
pArrayCtrl->SetRedraw(FALSE);
pArrayCtrl->SetItemsCount(100);
for(i=0; i<100; i++)
{
    ... prepare data item ...
    pArrayCtrl->SetItemData(i, dwData);
}
pArrayCtrl->SetRedraw(TRUE);
Naturally, if you don't need to have userdata associated to the items, you can call above methods without call SetRedraw.

Methods

There are several methods grouped by category and it will be listed with a brief description. For more specific information on code utilization you should look source code implementation and demo.

General

  UINT   GetCtrlId   ();     //return the child window id
  void   SetCtrlData (LPARAM dwData);     
         // set a global user data to the control
  LPARAM GetCtrlData ();     
        //return the global user data associated to the control
  void   SetToolTip  (DWORD ttstyle);      
       //enable items tooltip (ttstyle is a TOOLTIP style)
  void   SetCursor   (HCURSOR hCursor);    
        //set the window cursor with a specific one 
  void   SetRedraw   (BOOL bResize);       
        //disable or enable items drawing process
  void   Activate    (BOOL bFlag);   
       //hide (bFlag=FALSE) or show (bFlag=TRUE) the control's window
Items

  void   SetItemCount   (int nItems);      
  //set the number of items in a control
  int    GetItemCount   ();                
  //retrieve the current items count
  DWORD  GetItemState   (int nItem);       
    //retrieve the item state
               //ACIS_NORMAL   none of the follow (item is normal)
               //ACIS_SELECTED item is been selected
               //ACIS_VISIBLE  item is visible 
                   //(totally or partially) in the current view 
               //ACIS_HIDDEN   item is hidden (out of current view)
  int    GetRowIndex    (int nItem);       
    //return the item row index (in the current arrangement)
  int    GetRowIndex    ();                
    //same as above but for selected item
  int    GetColumnIndex (int nItem);       
    //return the item column index (in the current arrangement) 
  int    GetColumnIndex ();                
    //same as above but for selected item
  void   AddItem        (int nItem, LPARAM dwData); 
    //insert an item into control
  void   AddItem        (LPARAM dwData)    
    //add an item to the bottom
  void   DrawItem       (int nItem);       
    //force an item redraw
  void   RemoveItem     (int nItem);      
     //delete an item from the control (items will be rearranged)
  void   RemoveAllItems ();               
    //delete all items from the control
  LPARAM SetItemData    (int nItem, LPARAM dwData);
     // associate user data to an item
  LPARAM GetItemData    (int nItem);       
    //retrieve the user data from an item
Sizing and dimensioning

  void SetItemSize   (int width, int height); //set the item size
  void SetItemSize   (CSize* pSize);       //set the item size
  void SetDimension  (int nCount, int obj);   
     // set a dimension to be fixed, obj is the dimension object
     //ACDT_COLUMNS  columns must be fixed to given number
     //ACDT_ROWS     rows must be fixed to given number
  void SetDimension  (int nCount);    //set the current dimension to be fixed
  int  GetDimension  (int* dimtype);  //get the dimension count and type
  int  GetWidth      ();     //get the control's window width
  int  GetHeight     ();    // get the control's window height
  void GetItemSize   (CSize* pSize);       //get the item size
  int  GetItemWidth  ();     //get the item width
  int  GetItemHeight ();     //get the item height
  void SetColumns    (int nCol);           
   //set the columns to be fixed to given number
  void SetRows       (int nRow);           
   //set the rows to be fixed to given number
  int  GetColumns    ();     //get the number of columns
  int  GetRows       ();     //get the number of rows 
Colouring

  void     SetBkColor   (COLORREF crBack);    
   //set the background color of the control (and the item)
  void     SetGridColor (COLORREF crGrid);    //set the gridlines color
  void     SetColors    (COLORREF crBack, COLORREF crGrid); 
     //set the color of background and the gridlines (together)
  COLORREF GetBkColor   ();    // get the background color
  COLORREF GetGridColor ();     //get the gridlines color
  void     SetTipColor  (COLORREF crBkg, COLORREF crTxt); 
     //set the tooltip background and foreground colors
Selection and Scrolling

  void SelectItem      (int nItem);             
    //set the item state to selected
  void ActivateItem    (int nItem);             
    //activate an item (send LBUTTONDBLCLK message to control)
  void ActivateItem    ();        //same as above but for selected item
  int  GetSelectedItem ();        //get the item that was been selected 
  int  GetHitItem      ();        //get the item under the mouse pointer
  void SetScrollSize   (int nSBVers, int nSBObj, int nSize); 
    //set the scrolling amount value
                       //nSBVers  SB_VERT
                  //SB_HORZ
                       //nSBObj   ACSO_LINE
                  //ACSO_PAGE 

  int  GetScrollSize   (int nSBVers, int nSBObj); 
    //get the amount of the scroll component
  void Scroll          (int nSBVers, UINT nSBCode); 
    //send a scroll event to control (amount is default)
                       //nSBCode  SB_TOP
//               SB_BOTTOM
//               SB_LEFT
//               SB_RIGHT
//               SB_PAGEUP 
//               SB_PAGEDOWN 
//               SB_LINEUP 
//               SB_LINEDOWN 
  void Scroll          (int nSBVers, UINT nSBCode, UINT nPos); 
    //send a scroll event to control with amount specified
  void VertScroll      (UINT nSBCode);          
    //send a vertical scroll event to control (amount is default)
  void VertScroll      (UINT nSBCode, UINT nPos); 
    //send a vertical scroll event to control with amount specified
  void HorzScroll      (UINT nSBCode);          
    //send an horizontal scroll event to control (amount is default)
  void HorzScroll      (UINT nSBCode, UINT nPos); 
    //send an horizontal scroll event to control with amount specified
  void EnsureVisible   (int nItem);             
    //ensure that item is visible and scroll control to make this
  BOOL IsVisible       (int nItem);             
    //verify if item is on current view
Grid Lines

  void SetGridLines     (BOOL bActive);       
    //activate or deactivate gridlines
  BOOL GetGridLines     ();     
    //get gridlines activation status
  void SetGridThickness (int nThickness);     
    //set the thickness of the gridlines
  int  GetGridThickness ();     
    //get the thickness of the gridlines

Notification process

Control send to parent window some notification messages using the WM_NOTIFY form. It is sent by control to its parent window when an event has occurred or the control requires some information. Events which notification is required are listed below:

  • mouse action over control
  • item draw
  • item deletion
  • tooltip event
You can mapping the message by
      ON_NOTIFY(wNotifyCode, CtrlId, memberFunction)
or
      ON_NOTIFY_RANGE(wNotifyCode, CtrlIdFirst, CtrlIdLast, memberFunction)
Your member functions must be declared with the following prototypes:
   afx_msg void memberFunction(NMHDR* pNotifyStruct, 
     LRESULT* result);
or
   afx_msg void memberFunction(UINT CtrlId, NMHDR* pNotifyStruct, 
     LRESULT* result);

where the parameters are:

CtrlId        the child identifier of the control that sent the notification.
pNotifyStruct a pointer to the notification structure, as described above.
result        a pointer to the result code you’ll set before you return.
WM_NOTIFY messages contain the ID of the control sending the message in wParam and a pointer to an NMHDR structure in lParam. This structure is followed by a related event structure that has an NMHDR structure as its first member. Note that since the NMHDR member is first, a pointer to this structure can be used as either a pointer to an NMHDR or as a pointer to the structure depending on what type of event is, and you must cast it to proper structure.

For mouse activation events control send an ACITEMACTIVATE which have this declaration:

typedef struct tagACITEMACTIVATE
{
  NMHDR       hdr;        // standard header
  CArrayCtrl* pControl;   // control object pointer
  int         nItem;      // item activate
  DWORD       dwState;    // item state
  int         nRow;       // row (relative to current arrangement)
  int         nColumn;    // column (relative to current arrangement)
  CPoint      ptAction;   // mouse pointer location at event
  UINT        nKeyFlags;  // modifier key that were pressed
  LPARAM      dwParam;    // user data
}
  ACITEMACTIVATE, *LPACITEMACTIVATE;
The field code of the NMHDR structure will report the event id:
ACN_CLICK        // User clicked left mouse button in the control
ACN_RCLICK       // User clicked right mouse button in the control
ACN_DBCLICK      // User double-clicked left mouse button in the control
For item draw and deletion events control send an ACITEMINFO which have this declaration:
typedef struct tagACITEMINFO
{
  NMHDR       hdr;       // standard header
  CArrayCtrl* pControl;  // control pointer
  int         nItem;     // item to be draw
  DWORD       dwState;   // item state
  int         nRow;      // row (relative to current arrangement)
  int         nColumn;   // column (relative to current arrangement)
  CDC*        pDC;       // DC to be draw the item
  int         nWidth;    // width of the given DC
  int         nHeight;   // height of the given DC
  LPARAM      dwParam;   // user data
}
  ACITEMINFO, *LPACITEMINFO;
The field code of the NMHDR structure will report the event id:
ACN_DRAWITEM     //  Item must be drawn
ACN_DELETEITEM  //   Item is been deleted

On item deletion only pControl, nItem and dwParam fields are filled with meaningful information. For tooltip events control send an ACTOOLTIPINFO which have this declaration:

typedef struct tagACTOOLTIPINFO
{
  NMHDR       hdr;          // standard header
  CArrayCtrl* pControl;     // control pointer
  int         nItem;        // item for which tooltip text is required
  DWORD       dwState;      // item state
  int         nRow;         // row (relative to current arrangement)
  int         nColumn;      // column (relative to current arrangement)
  LPTSTR      lpszTipText;
        // address of need text buffer (max size is MAX_PATH)
  LPARAM      dwParam;      // user data
}
  ACTOOLTIPINFO, *LPACTOOLTIPINFO;
For the time being, the field code of the NMHDR structure will only report the event id:
ACN_NEEDTIPTEXT    Tooltip require a text

The demo program

Demo program creates four controls to show any kind of operation performed with Array control. zbr> First and second control are CArrayCtrl which show images, icons and text loaded by selecting a folder. Images will be shown as thumbnail, while icons and text will be shown as they are. A CxImage class, by Davide Pizzolato (see his article), is used to load images. I have used the related LIB built on my computer and I have included the original header file. The right button of the mouse show two menus: one if pointer is over an item and another if pointer is over a control but not over an item (know as item-menu and control-menu) that enable user to choose various operation.

Third control show an image selected from above controls and splashed on it. Selected icon will be shown using IconEdit program (.ico file extension must be associated to this via desktop) and text using Notepad. Other stuff can be chosen. There isn't menu associated to this control. Fourth control is a TabCtrl used for contrasting color test. It has ten items that specify the color Brightness value, and each item has an array control which items specify the Saturation and Hue values. This is a 3-dimensional control. By select an item, a new window will be displayed. In this window you can see two rectangle coloured with contrasting color of the background color: left rectangle is computed by alucardx algorithm (see article on general section) and right rectangle is computed by my own. At the side of each there are related color information. Two small icons rating the goodness of algorithms.

Enhancements and improvements

This is a list of open issues about CArrayCtrl control:

  • Make internal use of TabControl to implement 3-D array (this isn't a real limitation)
  • Use Vector template to manage user data
  • Sending WM_NOTIFY for mouse movements
  • Make item management more complex (with more to use defaults)
  • Implement items hot-tracking
  • Make each item a mini-window with a own life
  • Link each item to a thread to perform asynchronous operation

History

  • This is the version 1 modification 0. I don't believe that all bugs were discovered.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Systems / Hardware Administrator
Italy Italy
I'm working in a big Mobile Telecommunication company. I am a System Programmer. My main task is the integration between Host application and distributed Open-systems and workstations, picture processing and graphic.

Comments and Discussions

 
GeneralFixing compile errors Pin
andyj11531-Mar-07 10:40
andyj11531-Mar-07 10:40 
GeneralCompile error Pin
jeffyaowei11-Jun-05 18:10
jeffyaowei11-Jun-05 18:10 
Generala little DrawItem bug, Pin
EPulse14-May-05 20:31
EPulse14-May-05 20:31 
GeneralLink Error... Pin
EPulse29-Jan-05 0:53
EPulse29-Jan-05 0:53 
GeneralCompile Error Pin
xinxi5-Aug-04 21:03
xinxi5-Aug-04 21:03 
GeneralRe: Compile Error Pin
asnaghi5-Aug-04 23:19
asnaghi5-Aug-04 23:19 
GeneralRe: Compile Error Pin
xinxi6-Aug-04 2:06
xinxi6-Aug-04 2:06 
GeneralRe: Compile Error Pin
asnaghi6-Aug-04 3:09
asnaghi6-Aug-04 3:09 
Generala memory leak. Pin
Luo735-Aug-04 3:23
Luo735-Aug-04 3:23 
GeneralRe: a memory leak. Pin
asnaghi9-Aug-04 0:04
asnaghi9-Aug-04 0:04 
GeneralRe: Please can you include an EXE Pin
asnaghi5-Aug-04 3:09
asnaghi5-Aug-04 3:09 
GeneralRe: Please can you include an EXE Pin
Luo735-Aug-04 3:30
Luo735-Aug-04 3:30 
GeneralFatal Error Pin
\Kevin15-Jul-04 22:08
\Kevin15-Jul-04 22:08 
GeneralRe: Fatal Error Pin
asnaghi16-Jul-04 0:38
asnaghi16-Jul-04 0:38 
GeneralRe: Fatal Error Pin
WREY15-Nov-04 9:21
WREY15-Nov-04 9:21 
GeneralCompile error Pin
Kochise15-Jul-04 21:23
Kochise15-Jul-04 21:23 
GeneralRe: Compile error Pin
asnaghi16-Jul-04 0:14
asnaghi16-Jul-04 0:14 
GeneralHmmm... Pin
Kochise16-Jul-04 4:11
Kochise16-Jul-04 4:11 
GeneralRe: Hmmm... Pin
asnaghi16-Jul-04 10:07
asnaghi16-Jul-04 10:07 
GeneralRe: Compile error Pin
David Pritchard24-Jul-04 6:35
David Pritchard24-Jul-04 6:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.