Click here to Skip to main content
15,867,568 members
Articles / Mobile Apps

Scrolling Property Page in PocketPC 2002

Rate me:
Please Sign up or sign in to vote.
3.51/5 (17 votes)
22 Dec 2003CPOL5 min read 85.7K   24   19
Add a scrollbar to a property page when the SIP is displayed.

Pre-Introduction

I'm using Embedded VC 3.0, and am writing code for PocketPC 2002 as it is implemented on the Compaq Ipaq. Your mileage may vary.

Introduction

You know how it goes. You're screamin' along, coding like a crazed maniac (okay, so this rarely happens with CE), and all is right with the world. Then it happens. A sales nazi (or worse, an otherwise un-involved management type) sticks its head in the door and says something that will affect not only your day, but the schedule as well. In my case it went something like this:

The Problem

"Hey John, on the such-and-such dialog, when I call up the keyboard, it obscures the field I'm entering data for. Can you do something about it?"

Being the good-natured and even-tempered soul that I am, I happily reply "Why yes, you freakin' moron. I can drop what I'm doing right now and get right on it." So there I sat. Mulling it over. After a week of trial-and-error, I finally came up with something. It's still a bit quirky (quirks described at the end of this article), but it pretty much works the way I want it to, and I thought I'd share the wealth, such as it is.

How To Do It

If you haven't already, create a class derived from CPropertyPage. This is essential if you expect to get anything done in a reasonable amount of time. After that, here's what we'll be doing in a nutshell:

  • Creating the scrollbar on the fly. This prevents us from having to physically place an actual scrollbar on the desired property page template, and it lets us make the scrollbar thinner than the app studio allows.
  • Override OnInitDialog.
  • Override OnVScroll.
  • Detect when the SIP is displayed and dismissed, and react appropriately.

We'll start with modifications to the base class header file. First, we need to define a resource ID for the scrollbar. In the Resource editor, I have the habit of assigning control IDs in the 1000-10000 range, and when I need something manually defined (like now), I place it in the 64000-64100 range. This avoids collisions with other resource IDs.

#define CE_DLGSCROLLBAR 64000

Next, we need to make sure, our class definition includes the following lines:

class CMyBasePropertyPage: public CPropertyPage
{
 ... 
protected:
   // Generated message map functions
   //{{AFX_MSG(CPtcPropertyPage)
   virtual BOOL OnInitDialog();
   afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
   //}}AFX_MSG
   afx_msg LRESULT OnSettingChange(WPARAM wParam, LPARAM lParam);
   DECLARE_MESSAGE_MAP() 
   // manually added for the scrollbar
protected:
   CScrollBar m_ctrlScrollBar;    // our hero
   CRect      m_DlgClientRect;    // the propertypage client rectangle
   CRect      m_ScrollBarRect;    // the scrollbar rectangle
   int        m_nScrollPos;       // the current scrollbar position
   int        m_nCurHeight;       // the height of the scrollbar area
   BOOL       m_bNeedScrollBar;   // Default=FALSE - used by derived classes
                                  // to tell this base class that 
                                  // we'll need a scrollbar
   BOOL       m_bAutoScroll;      // Default=FALSE - set by a derived class
                                  // according to whatever criteria you
                                  // might happen to specify. 
   void initScrollBarSupport();   // Call this function from derived
                                  // class to setup the scrollbar. 
    ...
};

Next, we need to add some code to the base class CPP file. First, we want to set some default values telling the base class that we don't need a scrollbar, and that it won't autoscroll. By the way, if m_bAutoScroll is TRUE, the propertypage will automatically be scrolled to the bottom of the page. The way I use this is described near the end of the article.

CMyBasePropertyPage::CMyBasePropertyPage()
{
   m_bNeedScrollBar = FALSE;
   m_bAutoScroll    = FALSE;
}

Since we're going to be using the Create() function to build the scrollbar, we need to call the scrollbar's DestroyWindow() method in the destructor of the propertypage.

CMyBasePropertyPage::~CMyBasePropertyPage()
{
   if (IsWindow(m_ctrlScrollBar))
   {
      m_ctrlScrollBar.DestroyWindow();
   }
}

Now that we're done with the preparation, here's the meat of the code. You should have the following message handlers implemented. For the WM_SETTINGCHANGE handler, you'll have to manually add that to your code because the Class Wizard doesn't know how to add it for you. Notice also that it lies outside the AFX_MSG_MAP group.

BEGIN_MESSAGE_MAP(CPtcPropertyPage, CPropertyPage)
 //{{AFX_MSG_MAP(CPtcPropertyPage)
 ON_WM_VSCROLL()
 //}}AFX_MSG_MAP
   ON_MESSAGE(WM_SETTINGCHANGE, OnSettingChange)
END_MESSAGE_MAP()

Now, let's add the function that initializes the scrollbar. Here, we simply create the scrollbar, set the scroll ranges and other scrollbar stuff, and then hide it.

//---------------------------------------------------------------------------
// If you want your property page to be able to use the scrollbar, call this 
// function from your derived  class' OnInitDialog() function.
//---------------------------------------------------------------------------
void CMyBasePropertyPage::initScrollBarSupport()
{
   m_bNeedScrollBar = TRUE;
   GetClientRect(&m_DlgClientRect);

   // create the scrollbar - the initial size is up to you,
   // but when everything is said and done, you want it
   // to be as narrow as possible. The "142" is 
   // how big the scrollbar would actually be if
   // you didn't dynamically size it.
   DWORD dwStyle = SBS_VERT | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS;
   m_ctrlScrollBar.Create(dwStype, CRect(0, 0, 10, 142), 
                        this, CE_DLGSCROLLBAR);

   m_ctrlScrollBar.GetWindowRect(&m_ScrollBarRect);
   m_ctrlScrollBar.MoveWindow(m_DlgClientRect.Width()-m_ScrollBarRect.Width(), 
                        0, m_ScrollBarRect.Width(), m_ScrollBarRect.Height());
   m_ctrlScrollBar.BringWindowToTop();
   m_ctrlScrollBar.ShowScrollBar();
   m_ctrlScrollBar.GetWindowRect(&m_ScrollBarRect);

   m_nScrollPos = 0;
   m_nCurHeight = m_ScrollBarRect.Height() + 30;
   SCROLLINFO si;
   si.cbSize = sizeof(SCROLLINFO);
   si.fMask  = SIF_ALL; 
   si.nMin   = 0;
   si.nMax   = m_DlgClientRect.Height();
   si.nPage  = (int)(m_DlgClientRect.Height()* 0.75);
   si.nPos   = 0;
   m_ctrlScrollBar.SetScrollInfo(&si, TRUE);  

   // we don't need it right away, so hide it from the user
   m_ctrlScrollBar.ShowWindow(SW_HIDE);
}

If you want the SIP to go away when they change to a different property page, use Class Wizard to include a handler for OnKillActive, and like so:

BOOL CPtcPropertyPage::OnKillActive() 
{
   SHSipPreference(m_hWnd, SIP_FORCEDOWN); 
   return CPropertyPage::OnKillActive();
}

Next, we have to be able to handle the scrolling itself when the user clicks the scrollbar control. Since we're taking about such a small scroll range, I decided to combine the LINEUP (and DOWN with the PAGEUP (and DOWN). One item of note is that in order to get the NEW scroll position, you must also handle the SB_ENDSCROLL message (this little tidbit is NOT mentioned anywhere on MSDN that I found).

Another interesting item is that despite information to the contrary on MSDN, calling ScrollWindowEx with the SW_SCROLLCHILDREN flag set does appear to work in PocketPC 2002. Admittedly, MSDN does say that it doesn't work in CE, which is, as we all know, not exactly the same as PocketPC 2002.

//-------------------------------------------------------------------------
// Handle the WM_VSCROLL message
//-------------------------------------------------------------------------
void CMyBasePropertyPage::OnVScroll(UINT nSBCode, 
                 UINT nPos, CScrollBar* pScrollBar) 
{
   if (pScrollBar == &m_ctrlScrollBar)
   {
    int nDelta  = 0;
    int nMaxPos = m_DlgClientRect.Height() - m_nCurHeight;

    switch (nSBCode)
    {
         case SB_LINEDOWN:
         case SB_PAGEDOWN:
      if (m_nScrollPos >= nMaxPos)
            {
               return;
            }
      nDelta = min(max(nMaxPos / 2, 30), nMaxPos - m_nScrollPos);
      break;

         case SB_LINEUP:
         case SB_PAGEUP:
      if (m_nScrollPos <= 0)
            {
               return;
            }
      nDelta = -min(max(nMaxPos / 2, 30), m_nScrollPos);
      break;
       
         case SB_THUMBTRACK:
         case SB_THUMBPOSITION:
      nDelta = (int)nPos - m_nScrollPos;
      break;

         case SB_BOTTOM:
            nDelta = 50;
            break;
         case SB_TOP:
            if (m_nScrollPos <= 0)
            {
               return;
            }
            nDelta = -m_nScrollPos;
            break;;
         case SB_ENDSCROLL:
            return;
         default:
        return;
    }
      m_nScrollPos += nDelta;
      pScrollBar->SetScrollPos(m_nScrollPos, TRUE);
      ScrollWindowEx(0, -nDelta, NULL, &m_DlgClientRect, 
              NULL, NULL, SW_SCROLLCHILDREN);
      pScrollBar->MoveWindow(m_DlgClientRect.Width()-m_ScrollBarRect.Width(),
              0, m_ScrollBarRect.Width(), m_ScrollBarRect.Height());
   }
}

Finally, we have to handle the WM_SETTINGCHANGE message. When the SIP state changes, this message is sent to the system. This function merely reacts to the calling up and putting down of the SIP. When the SIP is displayed, the scrollbar is also displayed, and if necessary, the propertypage is scrolled to the bottom. When the SIP is dismissed, the scrollbar is hidden.

//------------------------------------------------------------------------
// We have to set up a custom handler for the
// WM_SETTINGCHANGE message because 
// the CDialog class doesn't handle it by default.
//------------------------------------------------------------------------
LRESULT CMyBasePropertyPage::OnSettingChange(WPARAM wParam, LPARAM lParam)
{
 if (m_bNeedScrollBar)
 {
  SIPINFO si;
  memset(&si, 0, sizeof(si));
  si.cbSize = sizeof(si);
  if (SHSipInfo(SPI_GETSIPINFO, 0, &si, 0)) 
  {
   BOOL bShowScrollBar = ((si.fdwFlags & SIPF_ON) == SIPF_ON);
   if (!bShowScrollBar)
   {
     if (m_nScrollPos > 0)
     {
       OnVScroll(SB_TOP, 0, &m_ctrlScrollBar);
     }
     m_ctrlScrollBar.ShowWindow(SW_HIDE);
   }
   else
   {
   // During testing, I discovered that the
   // standard CE SIP is always the same 
   // height, no matter which SIP mode you're using.
   // This means that if you're
   // truly interested in performance
   // (albeit a negligible gain), you can comment 
   // out this code so that the height of the
   // scrollbar is always 142 pixels (as 
   // originally set when you called Create on the scrollbar).
    CRect sipRect(si.rcSipRect);
    CRect clientRect;
    GetClientRect(&clientRect);
    m_ctrlScrollBar.GetWindowRect(&m_ScrollBarRect);
    m_ScrollBarRect.bottom = 
    m_ScrollBarRect.top + clientRect.Height(); 
    m_ctrlScrollBar.MoveWindow(m_DlgClientRect.Width()-m_ScrollBarRect.Width(),
               0, m_ScrollBarRect.Width(), m_ScrollBarRect.Height());

    // show the scrollbar
    m_ctrlScrollBar.ShowWindow(SW_SHOW);
    if (m_bAutoScroll)
    {
      OnVScroll(SB_BOTTOM, 0, &m_ctrlScrollBar);
    }
   }
  }
 }
 return 1L;
}

Using From a Derived Class

Now that we have our base class, we can make use of the scrollbar in a derived class. The following code is needed to activate the scrollbar handling code in the base class:

CMyDerivedPage::OnInitDialog()
{
 CMyBasePropertyPage::OnInitDialog(); 
 initScrollBarSupport();
}

For my needs, I wanted to have the scrollbar automatically scroll when one of a particular group of controls was focused. To implement this functionality, I needed to be able to detect when ANY control on the page gained focus, so I added the following code to the derived class:

In the header file:

protected:
   // Generated message map functions
   //{{AFX_MSG(CMyDerivedPage)
   ...
   ...
   //}}AFX_MSG
   afx_msg void OnEnSetFocus(UINT nID);

In the CPP file:

BEGIN_MESSAGE_MAP(CMyDerivedPage, CMyBasePropertyPage)
 //{{AFX_MSG_MAP(CMyDerivedPage)
 ...
 ...
 //}}AFX_MSG_MAP
   ON_CONTROL_RANGE(EN_SETFOCUS, 0, 65535, OnEnSetFocus)
END_MESSAGE_MAP()

void CMyDerivedPage::OnEnSetFocus(UINT nID)
{
   switch (nID)
   {
      case IDC_CONTROL1 :
      case IDC_CONTROL2 :
      case IDC_CONTROL3 :
         m_bAutoScroll = TRUE;
         break;
      default : 
         m_bAutoScroll = FALSE;
         break;
   }
}

Quirks

The only weirdness that I haven't figured out yet is when scrolling to the bottom of the page. If I use the tab to scroll, it doesn't quite go all the way to the bottom of the page. If I click on the down-arrow scroll button, it scrolls the page the rest of the way. If you'd like to take a stab at finding out why that happens, have a ball, but let me know. :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
General[Message Deleted] Pin
it.ragester28-Mar-09 5:33
it.ragester28-Mar-09 5:33 
General111 Pin
xiangding12-Nov-07 3:51
xiangding12-Nov-07 3:51 
GeneralI cannot see the scrollbar Pin
Rockone25-Oct-04 23:07
Rockone25-Oct-04 23:07 
QuestionfdwFlags always == 2? Pin
majoob28-Aug-04 10:00
majoob28-Aug-04 10:00 
GeneralThe new base class Pin
Endangered15-Jan-04 19:44
Endangered15-Jan-04 19:44 
GeneralRe: The new base class Pin
#realJSOP15-Jan-04 23:39
mve#realJSOP15-Jan-04 23:39 
GeneralRe: The new base class Pin
Hadi Rezaee20-Aug-04 2:08
Hadi Rezaee20-Aug-04 2:08 
Hi John Smile | :)

I want to thank you for this nice article!
My life was under attack and my boss was going to kill me Poke tongue | ;-P
So I think I'm very lucky that found your article ...

Well, my question was this too ...
I think i got your answer but i just donno how to implement it.
I mean how can i give the Dialog ID to the derived class ?!

Please reply ASAP.

Regards, Rose | [Rose]
Hadi
GeneralRe: The new base class Pin
#realJSOP21-Aug-04 1:17
mve#realJSOP21-Aug-04 1:17 
GeneralRe: The new base class Pin
Hadi Rezaee21-Aug-04 7:02
Hadi Rezaee21-Aug-04 7:02 
AnswerRe: The new base class Pin
windrzej20-Nov-05 3:26
windrzej20-Nov-05 3:26 
GeneralWhere is the sample project !! Pin
Youssif Mohammed30-Dec-03 21:33
Youssif Mohammed30-Dec-03 21:33 
GeneralRe: Where is the sample project !! Pin
#realJSOP30-Dec-03 23:34
mve#realJSOP30-Dec-03 23:34 
GeneralRe: Where is the sample project !! Pin
Youssif Mohammed31-Dec-03 10:56
Youssif Mohammed31-Dec-03 10:56 
GeneralRe: Where is the sample project !! Pin
#realJSOP1-Jan-04 2:40
mve#realJSOP1-Jan-04 2:40 
GeneralRe: Where is the sample project !! Pin
Anonymous1-Jan-04 11:19
Anonymous1-Jan-04 11:19 
GeneralRe: Where is the sample project !! Pin
#realJSOP2-Jan-04 2:17
mve#realJSOP2-Jan-04 2:17 
GeneralRe: Where is the sample project !! Pin
MagicVSMichael27-Nov-06 17:13
MagicVSMichael27-Nov-06 17:13 
GeneralScreenshots Pin
naveed30-Dec-03 5:11
naveed30-Dec-03 5:11 
GeneralRe: Screenshots Pin
#realJSOP30-Dec-03 23:32
mve#realJSOP30-Dec-03 23:32 

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.