July 28, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working with Gadgets

  • February 10, 2004
  • By Alex Gusev
  • Send Email »
  • More Articles »

In a previous article, we discussed how to customize tables. Such a technique works fine unless you are requested to create more complicated controls; for example, a table with column headers, progress bars, and so forth. In this case, we recall Gadget object. If you are familiar with MFC/Windows programming, it's similar to inheriting from CWnd class. Shortly speaking, all controls' reaction on all events totally depend on your implementation. Below, we will consider some sample ProgressBar object to illustrate the whole concept.

Gadget is the only choice when any other Form objects can't provide desired functionality. All it has (and correspondingly Form Manager knows about it) is bounds, object ID, and pointer to associated data. Data is set/get programmatically from your application by FrmSetGadgetData/FrmGetGadgetData calls. Thus, Gadget should handle two main tasks:

  • Drawing
  • Processing all taps

Below, we will see how our sample object proceeds them all.

Drawing Gadget

Similar to other OS, Gadget needs to be redrawn in two cases:

  • when form is getting opened the very first time
  • when form is receiving frmUpdateEvent

In our simple case, the Progress object should draw a frame and fill it in according to the current position. Optionally, it may display text also, with a percentage. So, the sample implementation may look like the following. To make our life easier, let's create a C++ project and declare a CProgressCtrl class. Here is the header file:

class CProgressCtrl
{
public:
  CProgressCtrl();
  ~CProgressCtrl();

  void Create(RectangleType* pBounds,
              UInt16 nID);

  void    SetRange(UInt32 dwLower,
                   UInt32 dwUpper);
  void    GetRange(UInt32 &dwLower,
                   UInt32 &dwUpper);
  UInt32  GetPos();
  UInt32  SetPos(UInt32 dwPos);
  UInt32  OffsetPos(UInt32 dwPos);
  void    SetStep(UInt32 dwStep);
  UInt32  StepIt();
  void    ShowPercent(Boolean bEnable = true);

protected:
  void DrawProgressBar();

protected:
  UInt32         m_dwLower;
  UInt32         m_dwUpper;
  UInt32         m_dwPos;
  UInt32         m_dwStep;
  Boolean        m_bShowPercent;

  RectangleType  m_RectProgCtl;
  UInt32         m_dwCtrlWidthInPixels;
  UInt32         m_dwCurrPosInPixels;

  FormGadgetType *m_pGadget;
  UInt16         m_nID;
};

Now, let's take a look at the code of important member functions. Actually, we're interested in creating and drawing. All the rest are functions to provide a suitable interface for the programmer. In our simple examlpe, we will create a Gadget object dynamically. So, the appropriate code may be written as shown below:

void CProgressCtrl::Create(RectangleType* pBounds,UInt16 nID)
{
  FormPtr frmP = FrmGetActiveForm();
  m_nID = nID;
  m_pGadget = FrmNewGadget(&frmP,m_nID,
                           pBounds->topLeft.x,pBounds->topLeft.y,
                           pBounds->extent.x,pBounds->extent.y);
  m_nIdx = FrmGetObjectIndex(frmP,m_nID);

  RctCopyRectangle(pBounds,&m_RectProgCtl);
  m_dwCtrlWidthInPixels = (UInt32)m_RectProgCtl.extent.x;
}

As you see, here we instantiate a new form object by calling FrmNewGadget. Then, object bounds are stored in its member variables. After our ProgressBar is created, we may draw it:

void CProgressCtrl::DrawProgressBar()
{
  RectangleType BarRect;

  WinEraseRectangle(&m_RectProgCtl,0);

  // draw frame
  WinDrawRectangleFrame(rectangleFrame,
                        &m_RectProgCtl);

  // draw bar
  RctCopyRectangle(&m_RectProgCtl,
                   &BarRect);
  BarRect.extent.x = m_dwCurrPosInPixels;
  WinDrawRectangle(&BarRect,0);

  if (m_bShowPercent)
  {
    Int16  dwPerc;
    Char   sPerc[5];

    dwPerc = (Int16)((double)m_dwCurrPosInPixels /
                     (double)m_dwCtrlWidthInPixels * 100);
    StrPrintF(sPerc,"%d%%",dwPerc);
    Coord ptCenterX,ptCenterY;
    ptCenterX = m_RectProgCtl.topLeft.x + m_RectProgCtl.extent.x/2;
    ptCenterY = m_RectProgCtl.topLeft.y + m_RectProgCtl.extent.y/2;
    WinDrawChars(sPerc,StrLen(sPerc),
                 ptCenterX - FntCharsWidth(sPerc,StrLen(sPerc))/2,
                 ptCenterY - FntCharHeight()/2);
  }
}

Drawing is an open area for your imagination. Here, you may implement all desired "look and feel" as far as the Palm OS API allows it. Our control is not too complicated, so the drawing procedure is relatively simple. In the case of the example custom table, it may be much more sophisticated. All we do here is just draw a rectangular frame and solid bar to show the current control's position. Optionally, a percentage is also displayed, right in the center of progress bar. The last that is worth noting is how to advance the control's current value. The sample below is self-documented:

UInt32 CProgressCtrl::SetPos(UInt32 dwPos)
{
  UInt32  dwPrevPos = m_dwPos;

  if (dwPos < m_dwLower)
    dwPos = m_dwLower;
  else if (dwPos > m_dwUpper)
    dwPos = m_dwUpper;

  m_dwPos = dwPos;

  m_dwCurrPosInPixels = (UInt32)((double)
                                 ((double)m_dwCtrlWidthInPixels /
                                  (double )(m_dwUpper - m_dwLower))
                                 *(double)(dwPos - m_dwLower));
  DrawProgressBar();

  return dwPrevPos;
}

You may download the test project for your own fun.

Handling events

In the preceding example, we don't (and have not needed to) process any events occuring in the system. When a control's behavior should be more complicated, you may handle all required events in the HandleEvent function. The main technique is to check whether the tap point is located inside the object's boundaries, and if so, you can process it as needed.

The word "process" here means a wide variety of responses—support for in-place editing inside tables, and so forth. In such situations, you'll simply pass events to the corresponding controls to get the correct behavior. In the previous article, we saw some examples of how to do it. In the case of this simple Gadget, you should handle the user's taps on it:

// HandleEvent function
...
case penDownEvent:
{
   FormPtr frmP = FrmGetActiveForm();
   UInt16 wCtrlIdx = FrmGetObjectIndex(frmP,ID_GADGET);
   RectangleType rcGadget;
   
   FrmGetObjectBounds(frmP, wCtrlIdx, &rcGadget);
   if ( RctPtInRectangle(event->screenX, event->screenY,&rcGadget) )
   {
      // Handle tap as needed
      handled = true;
   }
   break;
}
...

Pro and contra

Because the Gadget object is totally custom, at the beginning it may be difficult to develop it for complicated functionality. To be honest, you may draw and respond to user input without Gadget at all, which may be usable in game programming. Nevertheless, this object has some advantages:

  • You have a rectangular boundary of control.
  • You have a data pointer associated with the control, so you may store some data and then use it.
  • You may use Gremlins to test your application automatically. This last opportunity is very attractive because it gives you additional ways to verify your program.

So, just do it and good luck!

Downloads

Source code: ProgressBarCtrl.zip - 22 kb

About the Author

Alex Gusev started to play with mainframes at 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.

 

 


Sitemap | Contact Us

Rocket Fuel