I have cells containing several choices. I don’t want to display them in a combobox. Is there a way that the user is able to select items from the choice-list by clicking on spin-buttons?

Yes, you can derive a class from CGXSpinEdit and display the choice-list entry associated with the zero-based index value. You need to override GetControlText and GetValue.

If you implement the following class CSpinListControl:

// header (.h) file
class CSpinListControl: public CGXSpinEdit
{
   DECLARE_CONTROL(CSpinListControl)
public:
   // Constructor & Destructor
   CSpinListControl(CGXGridCore* pGrid, UINT nID);
   // Overrides
   BOOL GetControlText(CString& strResult, ROWCOL nRow, ROWCOL nCol, LPCTSTR pszRawValue, const CGXStyle& style);
   BOOL GetValue(CString& sResult);
   void OnClickedButton(CGXChild* pChild);
   void OnInitChildren(ROWCOL nRow, ROWCOL nCol, const CRect& rect);
   void Draw(CDC* pDC, CRect rect, ROWCOL nRow, ROWCOL nCol, const CGXStyle& style, const CGXStyle* pStandardStyle);
   // Attributes
   BOOL m_bUseIndexValue;      // TRUE if control should store zero-based index as value;
                        // FALSE if control should store choice list entry as value.
   // Generated message map functions
protected:
   //{{AFX_MSG(CSpinListControl)
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};
// implementation (.cpp) file
//////////////////////////////////////////////////////////////////
// CSpinListControl control
IMPLEMENT_CONTROL(CSpinListControl, CGXSpinEdit)
CSpinListControl::CSpinListControl(CGXGridCore* pGrid, UINT nID)
   : CGXSpinEdit(pGrid, nID)
{
   m_bUseIndexValue = FALSE;
      // TRUE if control should store zero-based index as value;
      // FALSE if control should store choice list entry as value.
}
BEGIN_MESSAGE_MAP(CSpinListControl, CGXSpinEdit)
   //{{AFX_MSG_MAP(CSpinListControl)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()
// GetControlText
//
// Convert the value which is stored in the style object into
// the text which should be displayed in the cell.
//
BOOL CSpinListControl::GetValue(CString& sResult)
{
   if (!CGXSpinEdit::GetValue(sResult))
      return FALSE;
   if (m_bUseIndexValue && m_pStyle && m_pStyle->GetIncludeChoiceList())
   {
      CString sMatch = sResult;
      // finds exact string or the first string with the same prefix
      int nIndex = FindStringInChoiceList(sMatch, sResult,
         m_pStyle->GetChoiceListRef(), FALSE);
      if (nIndex != -1)
      {
         wsprintf(sResult.GetBuffer(20), _T("%d"), nIndex);
         sResult.ReleaseBuffer();
      }
      else
         sResult.Empty();
   }
   return TRUE;
}
BOOL CSpinListControl::GetControlText(CString& strResult, ROWCOL nRow, ROWCOL nCol, LPCTSTR pszRawValue, const CGXStyle& style)
{
   CString sItem;
   if (m_bUseIndexValue)
   {
      // determine index
      short nIndex = -1;
      if (pszRawValue && _tcslen(pszRawValue) > 0)
         nIndex = (short) _ttoi(pszRawValue);
      else if (style.GetIncludeValue() && _tcslen(style.GetValueRef()) > 0)
         nIndex = style.GetShortValue();
      if (nIndex != -1)
         GetChoiceListItem(sItem, style.GetChoiceListRef(), nIndex);
         // base class version will format the text as specified in mask
      pszRawValue = sItem;
   }
   // Now, we can format this entry.
   return CGXSpinEdit::GetControlText(strResult, nRow, nCol, pszRawValue, style);
}
void CSpinListControl::OnClickedButton(CGXChild* pChild)
{
   BOOL bActive = IsActive();
   if (!bActive && !OnStartEditing())
      return;
   SetActive(TRUE);
   NeedStyle();
   CString strText;
   GetValue(strText);
   // empty cell, when user pressed alpahnumeric key
   if (!IsReadOnly() || !m_pStyle->GetIncludeChoiceList())
   {
      LONG lValue ;
      // style
      TCHAR sz[20];
      if ( m_bStartValue  &&  strText.IsEmpty() )
         lValue = m_nStartValue ;
      else
      {
         // Non-Null cell
         if (m_bUseIndexValue)
            lValue = _ttol(strText);
         else
         {
            CString sMatch = strText;
            // finds exact string or the first string with the same prefix
            lValue = FindStringInChoiceList(sMatch, strText,
               m_pStyle->GetChoiceListRef(), FALSE);
         }
         if (pChild == m_pUpArrow)
         {
            if (!m_bMaxBound || lValue < m_nMaxBound)
               lValue++;
            else if (m_bWrap && m_bMinBound)
               lValue = m_nMinBound;
         }
         else
         {
            if (!m_bMinBound || lValue > m_nMinBound)
               lValue--;
            else if (m_bWrap && m_bMaxBound)
               lValue = m_nMaxBound;
         }
      }
      if (m_bMinBound)
         lValue = max(m_nMinBound, lValue);
      if (m_bMaxBound)
         lValue = min(m_nMaxBound, lValue);
      if (m_bUseIndexValue)
      {
         wsprintf(sz, _T("%ld"), lValue);
         SetValue(sz);
      }
      else
      {
         CString sItem;
         GetChoiceListItem(sItem, m_pStyle->GetChoiceListRef(), lValue);
         SetValue(sItem);
      }
      SetModify(TRUE);
      OnModifyCell();
      SetSel(0, -1);
   }
   // eventually destroys and creates CEdit with appropriate window style
   if (!bActive)
      Refresh();
   else
      UpdateEditStyle();
   CGXControl::OnClickedButton(pChild);
}
void CSpinListControl::OnInitChildren(ROWCOL nRow, ROWCOL nCol, const CRect& rect)
{
   nRow, nCol;
   const int nEditBtnWidth = 13;
   // CRect rect = CGXControl::GetCellRect(nRow, nCol, (LPRECT) &r, m_pStyle);
   // init arrow buttons
   CRect rectBtn;
   rectBtn.IntersectRect(rect,
         CRect(rect.right-2-nEditBtnWidth,
            rect.top, rect.right-1, rect.bottom-1)
      );
   m_pUpArrow->SetRect(
         CRect(rectBtn.left, rectBtn.top,
            rectBtn.right, rectBtn.top+rectBtn.Height()/2)
      );
   m_pDownArrow->SetRect(
         CRect(rectBtn.left, rectBtn.top+rectBtn.Height()/2,
            rectBtn.right, rectBtn.bottom)
      );
}
// override the Draw method only if you want that the spin-buttons
// are also visible for inactive cells.
void CSpinListControl::Draw(CDC* pDC, CRect rect, ROWCOL nRow, ROWCOL nCol, const CGXStyle& style, const CGXStyle* pStandardStyle)
{
   pStandardStyle, style;
   CGXSpinEdit::Draw(pDC, rect, nRow, nCol, style, pStandardStyle);
   // Normal, spin-buttons are not drawn for inactive cells.
   // The following code forces that they will be drawn.
   if (!Grid()->IsCurrentCell(nRow, nCol))
   {
      for (int i = 0; i < GetCount(); i++)
      {
         CGXChild* pChild;
         if ((pChild = GetChild(i)) != NULL)
            pChild->Draw(pDC, !Grid()->IsPrinting());
      }
   }
}   

You can register the control and user attribute in the OnInitialUpdate()  routine of your grid class

   // Register all controls and user attributes for the view
   // IDS_CTRL_SPINLIST is a string resource
   // you have to add to your application.
   RegisterControl(IDS_CTRL_SPINLIST, new CSpinListControl(this, IDS_CTRL_SPINLIST));

and later apply it to cells with

SetStyleRange(CGXRange(4,4,10,4), 
   CGXStyle()
      .SetControl(IDS_CTRL_SPINLIST)
      .SetUserAttribute(GX_IDS_UA_SPINBOUND_MIN, "0")   // index of first choice
      .SetUserAttribute(GX_IDS_UA_SPINBOUND_MAX, "4")   // index of last choice
      .SetUserAttribute(GX_IDS_UA_SPINBOUND_WRAP, "1")   // allow wrap value
      .SetUserAttribute(GX_IDS_UA_SPINSTART, "0")   // value when clicked in empty cell
      .SetChoiceList("one\ntwo\nthree\nfour\nfive")
   );

You can determine the index of the selected choice in the cell with

   int nIndex = _ttoi(GetValueRowCol(nRow, nCol));