December 22, 2014
Hot Topics:

Creating Custom Table Items Under Palm OS

  • December 31, 2003
  • By Alex Gusev
  • Send Email »
  • More Articles »

Tables (or grids) are very useful controls when you need to display and edit multiple data columns. Especially if column's types are different like checkboxes, text fields or popup triggers. In addition to standard cell types (described in Table.h) Table API provides an opportunity to create custom cells. This article highlights some aspects of custom table development.

Getting started

Let's cover shortly the basics of tables. Usual working flow is as follows:

  • set type of column in table's row
  • assign a value to integer or pointer data of the cell if needed
  • set desired columns usable
  • set custom procedures for loading, saving and drawing if needed
  • set ID and Data values for row (each one is of two bytes length) if needed

Note that by default columns are unusable, so you should explicitly set them into visible state. The following illustrates all of these concepts:

FormPtr frmP = FrmGetActiveForm();
TablePtr tableP = FrmGetObjectPtr( frmP,
            FrmGetObjectIndex(frmP,TestTableTable));
UInt nNumRows = TblGetNumberOfRows(tableP);
UInt i = 0;

for (i = 0; i < nNumRows; i++)
{
   TblSetItemStyle(tableP, i, 0, numericTableItem);
   TblSetItemInt(tableP, i, 0, i);
   
   TblSetItemStyle(tableP, i, 1, textTableItem);

   TblSetItemStyle(tableP, i, 2, checkboxTableItem);
   TblSetItemInt(tableP, i, 2, i % 2);

   TblSetItemStyle(tableP, i, 3, customTableItem);
   TblSetItemInt(tableP, i, 2, i % 2 + 1);
}

for (i = 0; i < 4; i++)
{
   TblSetColumnUsable(tableP,i,true);
}

TblSetLoadDataProcedure(tableP,1,LoadDataProc);
TblSetCustomDrawprocedure(tableP,3,CustomDrawProc);

The LoadDataProc function has following signature:

Err TableLoadDataFuncType (void *tableP, 
                           Int16 row, 
                           Int16 column, 
                           Boolean editable,
                           MemHandle *dataH, 
                           Int16 *dataOffset,
                           Int16 *dataSize, 
                           FieldPtr fld);

You return the same values for dataH, dataOffset, and dataSize that you would pass to FldSetText. That is, you can use this function to point the table cell's text field to a string in a database record so that you can edit that string directly using text field routines. To do so, return the handle to a database record in dataH, the offset from the start of the record to the start of the string in dataOffset, and the allocated size of the string in dataSize.

Finally, custom cell has its own drawing procedure CustomDrawProc defined as:

void TableDrawItemFuncType (void *tableP, 
                            Int16 row, 
                            Int16 column, 
                            RectangleType *bounds);

Thus you may supply any possible look and feel to your custom cell. We will take a look at some sample implementation in the very next section.

Custom drawing procedure

Now it's time to take a look at custom drawing. Our procedure will draw a bitmap depending on integer value stored for particular cell. It may be implemented as follows:

void CustomDrawProc (void *tableP, 
                     Int16 row, 
                     Int16 column, 
                     RectangleType *bounds)
{
   Int16 nValue = TblGetItemInt(tableP,row,column);
   MemHandle	  bitmapH;
   BitmapPtr	  bitmapP;

   if ( nValue == 1 )
      bitmapH = DmGetResource('Tbmp', resBitmapIDEven);
   else
      bitmapH = DmGetResource('Tbmp', resBitmapIDOdd);

  if ( !bitmapH )
     return;

   bitmapP = (BitmapPtr) MemHandleLock (bitmapH);
   WinDrawBitmap (bitmapP, bounds->topLeft.x, bounds->topLeft.y);
   MemHandleUnlock (bitmapH);
}

All this function does is load the appropriate bitmap resource according to stored value and then draw it inside the cell bounds. You may freely apply your imagination here and implement almost any behavior. Just avoid time consuming operations when the cell is being drawn, otherwise users will not understand you!

Responding to events

The Table API provides more opportunities to display data than to edit data. One more point where you may customize your control is in handling some events. Applying to tables, it will be tblEnterEvent and tblSelectEvent. You may process them in HandleEvent function. As an example we'll implement in-place edit. The main idea is simple. We need to create field in cell's bounds and then redirect events to this control. For example, in case of in-place edit you may write something like following code sample. This is just a very simple and schematical implementation of what was presented above:

/*********************************************************************
 * Global variables
 *********************************************************************/
FieldPtr g_InPlaceField;
UInt16 g_wCurrRow, g_wCurrCol;
MemHandle g_hText;
Boolean g_bSelected;
static void SaveValue(TablePtr table);
static void HandleTapInCell(EventType* eventP, 
                            TablePtr tableP, 
                            UInt16 wCol, 
                            UInt16 wRow, 
                            Boolean& bHandled);
..................................
static void MainFormInit(FormType *frmP)
{
.......................
	g_wCurrRow = g_wCurrCol = -1;
	g_bSelected = false;
	g_InPlaceField = 
 (FieldPtr)FrmGetObjectPtr(frmP,FrmGetObjectIndex(frmP,MainInPlaceEdit));
.......................
}

static Boolean MainFormHandleEvent(EventType * eventP)
{
   Boolean handled = false;
   FormType * frmP;

   switch (eventP->eType)
   {
      // other events
      ......................
      case tblEnterEvent:
      {
         frmP = FrmGetActiveForm();
         TablePtr tableP = eventP->data.tblEnter.pTable;
         UInt16 wRow = eventP->data.tblEnter.row;
         UInt16 wCol = eventP->data.tblEnter.column;
         HandleTapInCell(eventP, tableP, wCol, wRow, handled);
         g_wCurrRow = wRow;
         g_wCurrCol = wCol;
      }
      break;
   }
   return handled;
}

/*********************************************************************
 * Internal Functions
 *********************************************************************/
static void SaveValue(TablePtr table)
{
   Boolean  dirty;
	
   if (!g_InPlaceField)
      return;

   dirty = FldDirty(g_InPlaceField);

   if (dirty)
   {
      CharPtr textP = FldGetTextPtr(g_InPlaceField);
      Int nInt;
			
      if (textP)
         nInt = StrAToI(textP);
      else
         nInt = 0;
      TblSetItemInt(table,g_wCurrRow,0,nInt);
   }

   FldReleaseFocus(g_InPlaceField);
   FldSetSelection(g_InPlaceField, 0, 0);
}

static void HandleTapInCell( EventType* eventP, 
                             TablePtr tableP, 
                             UInt16 wCol, 
                             UInt16 wRow, 
                             Boolean& bHandled)
{
   FormPtr frmP = FrmGetActiveForm();
	
   // if user tapped on new row then deselect current one
   if ( g_bSelected &&
       (wCol != g_wCurrCol || wRow != g_wCurrRow) )
   {
	SaveValue(tableP);
	FrmHideObject(frmP,FrmGetObjectIndex(frmP,MainInPlaceEdit));
	TblMarkRowInvalid(tableP,g_wCurrRow);
	g_bSelected = false;
   }
   TblRedrawTable(tableP);

   if ( wCol > 0 )
   {
      g_bSelected = false;
      bHandled = false;
      return;
   }
	
    // show field at new position if needed
    RectangleType r;
    TblGetItemBounds(tableP,wRow,wCol,&r);

    if ( (wCol == 0 && wCol != g_wCurrCol) || (wRow != g_wCurrRow) )
    {
       TblGetItemBounds(tableP,wRow,wCol,&r);
       Int16 nInt = TblGetItemInt(tableP,wRow,wCol);

       FrmSetObjectBounds(frmP,FrmGetObjectIndex(frmP,MainInPlaceEdit),&r);
       FrmShowObject(frmP,FrmGetObjectIndex(frmP,MainInPlaceEdit));
			
       char szBuffer[10];
       StrPrintF(szBuffer,"%d",nInt);
		
       char* pText = (char*)MemHandleLock(g_hText);
       StrCopy(pText,szBuffer);
       MemHandleUnlock(g_hText);
       FldSetTextHandle(g_InPlaceField,g_hText);
       FrmSetFocus(frmP,FrmGetObjectIndex(frmP,MainTestTable));
       {
          // Convert tblEnter event to fldEnter event
          EventType newEvent;
          EvtCopyEvent(eventP,&newEvent);
          newEvent.eType = fldEnterEvent;
          newEvent.data.fldEnter.fieldID = 1971;
          newEvent.data.fldEnter.pField = g_InPlaceField;
		
          FldHandleEvent(g_InPlaceField, &newEvent);
       }
       FldDrawField(g_InPlaceField);
       g_bSelected = true;
       bHandled = true;
   }
   FrmDrawForm(frmP);
}

Such solution is really simple yet effective enough. You create field control once; then you just need to move it to correct position of edited cell or hide field control if no cells are required to be edited. An important point in this sample is that you should convert and redirect tblEnter event to FldHandleEvent function in order to support natural field functionality. Once you are done with it, saving/assigning values of cells are just trivial things! The only additional step ought to be noted here is that usually you need to save data in frmCloseEvent handler.

Where to go

As we have seen, Table API manager gives us several opportunities to customize our tables. For most of relatively simple cases it's more than enough. But if you're requested to implement more complex behavior, next possible area of thinking is Gadget control. We will discuss it in next article.

About the Author

Alex Gusev started to play with mainframes in the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

# # #






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel