Visual C++


Banner horitzontal per rotació de caràcters

Aquest article ha estat possible gràcies a la col·laboració de Pere Llaveria.

Si ens fixem en algunes aplicacions com WinAmp veurem que tenen un control en el qual apareix el títol de la cançó, l'autor, la durada i alguna altra informació que es va desplaçant de dreta a esquerra per poder mostrar-se en la seva totalitat.

En realitat el que fa WinAmp és moure el primer caràcter de la cadena mostrada al final de la mateixa, és a dir, fa una rotació de caràcters. Amb un control estàtic podem fer una cosa similar de forma molt senzilla.

Derivem una classe anomenada CHStaticRotativo de CStatic d'aquesta manera podrem utilitzar-la de forma genèrica en qualsevol aplicació simplement afegint aquesta classe al nostre projecte.


class CHStaticRotativo : public CStatic
{
....
protected:
   // Generated message map functions
   //{{AFX_MSG(CHStaticRotativo)
   afx_msg LRESULT OnSetText(WPARAM wParam, LPARAM lParam);
   afx_msg void OnPaint();
   afx_msg void OnTimer(UINT nIDEvent);
   afx_msg void OnDestroy();
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()

private:
   bool      m_flagTimer;   // indica si el temporitzador ha estat establert
   RECT      m_rect;        // rectangle del control
   CString   m_sTexto;      // text que es mostra actualment
};

Per establir el text d'un control estàtic des d'una classe de diàleg utilitzem normalment la funció SetWindowText. No s'ha d'oblidar que hem d'assignar un identificador al control estàtic en temps de disseny del nostre diàleg, ja que per defecte s'assigna a -1. La funció SetWindowText envia el missatge WM_SETTEXT, així doncs hem de gestionar aquest missatge per assignar el text a la variable membre m_sTexto de la nostra classe CHStaticRotativo. Això ho fa la funció OnSetText.


LRESULT CHStaticRotativo::OnSetText(WPARAM wParam, LPARAM lParam)
{
   SIZE        size;
   CWnd        *pWndParent = NULL;
   CClientDC   dc(this);

   // obtenim la mida del control
   GetClientRect(&m_rect);

   // assignem el text enviat per SetWindowText() des del diàleg
   m_sTexto = _T((LPSTR)lParam);

   // seleccionem la font per defecte del diàleg
   pWndParent = GetParent();
   if (pWndParent != NULL)
   {
      // seleccionem la font per defecte
      dc.SelectObject(pWndParent->GetFont());
   }

   // obtenim la mida del text segons el context de dispositiu
   GetTextExtentPoint32(dc.GetSafeHdc(), m_sTexto, m_sTexto.GetLength(), &size);

   // si el text no hi cap en el control, inicialitzar temporitzador per a rotar-lo
   // altrament no fa falta rotar-lo
   if (size.cx > m_rect.right)
   {
      // establim el temporitzador a 200 mil·lisegons
      UINT ok = SetTimer(ID_TIMER, 200, NULL);
      ASSERT(ok != NULL);
      m_flagTimer = true;
   }

   return 0L;
}

La gestió del missatge WM_SETTEXT no es pot afegir des de ClassWizard, per tant cal fer-ho a mà.


BEGIN_MESSAGE_MAP(CHStaticRotativo, CStatic)
   //{{AFX_MSG_MAP(CHStaticRotativo)
   ON_MESSAGE(WM_SETTEXT, OnSetText)
   ON_WM_TIMER()
   ON_WM_PAINT()
   ON_WM_DESTROY()
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Cal afegir el gestor OnTimer per al missatge WM_TIMER. Aquí serà on actualitzem el text i obligarem que es repinti de nou.


void CHStaticRotativo::OnTimer(UINT nIDEvent) 
{
   if (nIDEvent == ID_TIMER)
   {
      // afegim el primer caràcter al final de la cadena...
      m_sTexto += m_sTexto.GetAt(0);

      // ... i l'eliminem del principi, és com si el text sencer
      // es mogués una posició a l'esquerra
      m_sTexto.Delete(0);

      // obliguem que es repinti de nou
      Invalidate();
   }
   
   CStatic::OnTimer(nIDEvent);
}

Quan sortim del diàleg hem d'eliminar el temporitzador utilitzant la funció KillTimer. Per a fer això el millor és utilitzar la funció OnDestroy la qual gestiona el missatge WM_DESTROY:


BOOL CHScrollingBannerStc::OnDestroy()
{
   return CStatic::OnDestroy();

   if (m_flagTimer)
      KillTimer(ID_TIMER);
}

Finalment només ens queda la funció OnPaint per a la gestió del missatge WM_PAINT.


void CHStaticRotativo::OnPaint() 
{
   CWnd     *pWndParent = NULL;
   CPaintDC dc(this);

   pWndParent = GetParent();
   if (pWndParent != NULL)
   {
      // seleccionem la font per defecte
      dc.SelectObject(pWndParent->GetFont());
   }

   // seleccionem el color de fons per defecte del control estàtic
   CBrush bBrush(::GetSysColor(COLOR_3DFACE));
   CBrush* pOldBrush;
   pOldBrush = dc.SelectObject(&bBrush);

   // omplim el control amb el color seleccionat
   dc.FillRect(&m_rect, &bBrush);

   // escrivim el text
   dc.SetBkMode(TRANSPARENT);
   dc.DrawText(m_sTexto, &m_rect, DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE);

   dc.SelectObject(&pOldBrush);
   bBrush.DeleteObject();
}

Ja tenim acabada la gestió del nostre control banner per rotació. Fixem-nos que si el text ja hi cap en el control no s'estableix el temporitzador, per la qual cosa no hi haurà rotació dels caràcters de la cadena, el seu aspecte serà com el d'un control estàtic normal amb un marc.

A la classe del diàleg hem de declarar una variable membre m_ctrlBanner de tipus CHStaticRotativo, després, en OnInitDialog cridar a la funció SetWindowText amb el text que desitgem.


BOOL CHBannerRotativoDlg::OnInitDialog() 
{
   CDialog::OnInitDialog();
   
   m_ctrlBanner.SetWindowText(
      "Aquest text és un exemple per al control de Banner horitzontal amb rotació de caràcters. - ");
   
   return TRUE;
}

Fonts ... h_banner_rotativo_sources.zip 19 Kb
Vegi's també ... Banner horitzontal per desplaçament del text
Banner vertical per desplaçament del text