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
|