Home

Windows Controls: The Spin Control

  

Introduction to the Spin Control

 

Description

A spin control is a Windows control equipped with two opposite arrows Spin Button. The user clicks one of the arrows at one time to increase or decrease the current value of the control. The value held by the control is also called its position.

The values of a spin control range from a minimum to a maximum. When the up arrow is clicked the value of the control increases. If the user clicks and holds the mouse on the up pointing arrow. The value of the control keeps increasing until it reaches its maximum and stops. The opposite behavior applies when the user clicks or holds the mouse on the down-pointing arrow.

Because a spin control is only equipped with arrows, it does not inherently show its current value. Therefore, this control is usually accompanied by another, text-based, control, usually an edit box, that indicates its position.

Practical LearningPractical Learning: Introducing a Spin Control

  1. Start Microsoft Visual Studio
  2. To start a new application, on the main menu, click File -> New Project...
  3. In the middle list, click MFC Application.
    Set the name to PledgeDistribution
  4. Click OK
  5. In the first page of the wizard, click Next
  6. In the second page of the wizard, click Dialog-Based and click Next
  7. In the third page, set the Dialog Title to Pledge Distribution
  8. Click Finish
  9. Click TODO and press Delete
  10. Click OK and press Delete
  11. Change the Caption of the Cancel button to Close
  12. In the Class View, expand the name of the project
  13. In the top section (of the Class View), click CPledgeDistributionApp
  14. In the bottom section of the Class View, double-click InitInstance
  15. Create a message box as follows:
    BOOL CPledgeDistributionApp::InitInstance()
    {
    	// InitCommonControlsEx() is required on Windows XP if an application
    	// manifest specifies use of ComCtl32.dll version 6 or later to enable
    	// visual styles.  Otherwise, any window creation will fail.
    	INITCOMMONCONTROLSEX InitCtrls;
    	InitCtrls.dwSize = sizeof(InitCtrls);
    	// Set this to include all the common control classes you want to use
    	// in your application.
    	InitCtrls.dwICC = ICC_WIN95_CLASSES;
    	InitCommonControlsEx(&InitCtrls);
    
    	CWinApp::InitInstance();
    
    
    	AfxEnableControlContainer();
    
    	// Create the shell manager, in case the dialog contains
    	// any shell tree view or shell list view controls.
    	CShellManager *pShellManager = new CShellManager;
    
    	// Standard initialization
    	// If you are not using these features and wish to reduce the size
    	// of your final executable, you should remove from the following
    	// the specific initialization routines you do not need
    	// Change the registry key under which our settings are stored
    	// TODO: You should modify this string to be something appropriate
    	// such as the name of your company or organization
    	SetRegistryKey(_T("Local AppWizard-Generated Applications"));
    
    	CPledgeDistributionDlg dlg;
    	m_pMainWnd = &dlg;
    
        MessageBox(NULL,
    	   _T("This application is used by a fictitious company or agency ")
    	   _T("that raises money for edutional institutions.")
    	   _T("The employees of the company call people and organizations. ")
    	   _T("They ask the called person for a pledge (an amount).")
    	   _T("After setting the amount, they ask the called person how he ")
    	   _T("wants to distribute that money for (how much money) each of ")
    	   _T("the three schools should from that amount."),
    	   _T("Pledge Distribution"), MB_OK);
    
    	INT_PTR nResponse = dlg.DoModal();
    
    	if (nResponse == IDOK)
    	{
    		// TODO: Place code here to handle when the dialog is
    		//  dismissed with OK
    	}
    	else if (nResponse == IDCANCEL)
    	{
    		// TODO: Place code here to handle when the dialog is
    		//  dismissed with Cancel
    	}
    
    	// Delete the shell manager created above.
    	if (pShellManager != NULL)
    	{
    		delete pShellManager;
    	}
    
    	// Since the dialog has been closed, return FALSE so that we exit the
    	//  application, rather than start the application's message pump.
    	return FALSE;
    }

Creating a Spin Control

To create a spin control, at design time, on the Toolbox, click the Spin button Spin and click the desired area on the intended host.

A spin control control is based on the CSpinButtonCtrl class. Therefore, to programmatically create a spin control, declare a pointer to CSpinButtonCtrl then call its Create() member function to initialize it. The syntax of the CSpinButtonCtrl::Create() member function is:

BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);

Because a spin control cannot be a parent, the minimum style you must apply is WS_CHILD. If you want the control to be visible upon creating, add the WS_VISIBLE style. Here is an example:

BOOL CSpinDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // TODO: Add extra initialization here
    CSpinButtonCtrl *SpinCtrl = new CSpinButtonCtrl;

    SpinCtrl->Create(WS_CHILD | WS_VISIBLE,
		     CRect(60, 10, 80, 35), this, 0x128);

    return TRUE;
}

Practical LearningPractical Learning: Creating Spin Controls

  • Design the dialog box as follows:
     
    Pledge Distribution
     
    Control ID Caption Align Text
    Static Text Static Text   Amount Pledged:  
    Edit Box Edit Control IDC_AMOUNT_PLEDGED   Right
    Static Text Static Text   Rotherham College:  
    Edit Box Edit Control IDC_RATE_VALUE1   Center
    Spin Spin IDC_SPIN_RATE1    
    Edit Text Edit Control IDC_AMOUNT1   Right
    Static Text Static Text   Leicester University:  
    Edit Box Edit Control IDC_RATE_VALUE2   Center
    Spin Spin IDC_SPIN_RATE2    
    Edit Box Edit Control IDC_AMOUNT2   Right
    Static Text Static Text   Lars Union Academy:  
    Edit Box Edit Control IDC_RATE_VALUE3   Center
    Spin Spin Control IDC_SPIN_RATE3    
    Edit Box Edit Control IDC_AMOUNT3   Right
    Static Text Static Text IDC_MESSAGE    
    Button Button IDCANCEL Close  

Characteristics of a Spin Control

 

The Orientation of a Spin Control

By default, a spin control appears with an up and a down pointing arrows. If you want the arrows to be horizontally directed, change the value of the Orientation property from Vertical (the default) to Horizontal Spin Button. If you are programmatically creating the control, add the UDS_HORZ style to it. Here is an example:

SpinCtrl->Create(WS_CHILD | WS_VISIBLE | UDS_HORZ,
		 CRect(60, 10, 80, 35), this, 0x128);

Using the Arrow Keys

We mentioned already that the value of the spin changes as the user clicks, or holds the mouse on, one arrow of the control. In the same way, if you want the user to be able to increment or decrement the value of the control using the up and down arrow keys of the keyboard, at design time, set the Arrow Keys property to True at design time. If you are programmatically creating the control, add the UDS_ARROWKEYS style.

 
 
 

The Buddy Window of a Spin Control

As stated already, a spin control cannot display its value to the user. If you want to inform the user about the value of the control as it is being incremented or decremented, you can add another, usually text-based, control, such as an edit box. This control is called the buddy window of the spin control. The control should be positioned on one horizontal side of the spin control. After adding that new control, you should let the spin control know on what side the accompanying control is positioned, left or right. To do this visually, select the appropriate value in the Alignment field of the Properties window.

If you are programmatically creating the control, to position the spin control to the left edge of the buddy window, add the UDS_ALIGNLEFT style. On the other hand, if you want the spin control on the right side of the buddy window, apply the UDS_ALIGNRIGHT style instead. Just like at design time you cannot apply both styles, at run time, do not add both the UDS_ALIGNLEFT and the UDS_ALIGNRIGHT styles.

If you want the spin control to use an already existing and previously added control as its buddy window, visually set the Auto Buddy property to True or programmatically add the UDS_AUTOBUDDY style. In this case, the control that was just previously added to the host, before adding the spin control, would be used as its buddy.

After specifying what control would be the buddy window of the spin control, when the user clicks the arrows of the button, the buddy would display its current value. If you want to make sure that the buddy window displays only integral values, whether decimal or hexadecimal, change the Set Buddy Integer property to True. If you are programmatically creating the control, you can add the UDS_SETBUDDYINT style:

SpinCtrl->Create(WS_CHILD | WS_VISIBLE | UDS_SETBUDDYNT,
		 CRect(60, 10, 80, 35), this, 0x128);

After creating the control, to indicate what control would display the value of the spin control, we saw that you can use the Alignment or the Auto Buddy properties. If you did not do this at design time and if you want to explicitly specify the name of the control that would act as the buddy window, you can call the CSpinButtonCtrl::SetBuddy() member function. Its syntax is:

CWnd* SetBuddy(CWnd* pWndBuddy);

The pWndBuddy argument is the new control that would serve as buddy. Here is an example that sets an existing label on the dialog box (the label was created as a Static Text control and identified as IDC_SPIN_BUDDY) as the spin control's buddy window:

BOOL CSpinDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    
    // TODO: Add extra initialization here
    CSpinButtonCtrl *SpinCtrl = new CSpinButtonCtrl;
    CStatic *SpinBuddy;
    
    SpinCtrl->Create(WS_CHILD | WS_VISIBLE | UDS_SETBUDDYINT,
		     CRect(60, 10, 80, 35), this, 0x128);
    SpinBuddy = reinterpret_cast<CStatic *>(GetDlgItem(IDC_SPIN_BUDDY));
    SpinCtrl->SetBuddy(SpinBuddy);
	
    return TRUE; // return TRUE unless you set the focus to a control
}

If the buddy window has already been set and you want to find what control performs that role, you can call the CSpinButtonCtrl::GetBuddy() member function. Its syntax is:

CWnd* GetBuddy() const;

Practical LearningPractical Learning: Creating Spin Controls

  1. Cleck each spin control and, in the Properties window, set its characteristics as follows:
     
    Control ID Auto Buddy Set Buddy Integer
    Spin Spin IDC_SPIN_RATE1 True True
    Spin Spin IDC_SPIN_RATE2 True True
    Spin Spin Button IDC_SPIN_RATE3 True True
  2. On the main menu, click Project -> Class Wizard...
  3. Click Member Variables
  4. Add a Member Variable for the following controls:
     
    ID
    Category Type Name
    IDC_AMOUNT_PLEDGED Value double m_AmountPledged
    IDC_AMOUNT1 Value double m_Amount1
    IDC_AMOUNT2 Value double m_Amount2
    IDC_AMOUNT3 Value double m_Amount3
    IDC_MESSAGE Value CString m_Message
    IDC_RATE_VALUE1 Value double m_RateValue1
    IDC_RATE_VALUE2 Value double m_RateValue2
    IDC_RATE_VALUE3 Value double m_RateValue3
    IDC_SPIN_RATE1 Control CSpinButtonCtrl m_SpinRate1
    IDC_SPIN_RATE2 Control CSpinButtonCtrl m_SpinRate2
    IDC_SPIN_RATE3 Control CSpinButtonCtrl m_SpinRate3

    MFC Class Wizard

The Value of a Spin Control

By default, a spin control is used to show some natural numbers from one range to another. Normally, the values of a spin control are decimal integers. Alternatively, if you prefer the values to be given in hexadecimal format, set the range accordingly and call the CSpinButtonCtrl::SetBase() member function. Its syntax is:

int SetBase(int nBase);

Using this member function, if you want the value to be decimal, pass the nBase argument as 10. If you want hexadecimal values, pass the argument as 16. Here is an example:

BOOL CSpinDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // TODO: Add extra initialization here
    CSpinButtonCtrl *SpinCtrl = new CSpinButtonCtrl;
    CStatic *SpinBuddy;

    SpinCtrl->Create(WS_CHILD | WS_VISIBLE | UDS_SETBUDDYINT,
		     CRect(60, 10, 80, 35), this, 0x128);
   
    SpinCtrl->SetBase(16);

    SpinBuddy = reinterpret_cast<CStatic *>(GetDlgItem(IDC_SPIN_BUDDY));
    SpinCtrl->SetBuddy(SpinBuddy);

    return TRUE;
}

To find out the current base, decimal or hexadecimal, that a spin control is using for its values, call the CSpinButtonCtrl::GetBase() member function. Its syntax:

UINT GetBase() const;

The value held by a spin control is referred to as its position. If you want to specify the value of a spin buttoan, call the CSpinButtonCtrl::SetPos() member function. Its syntax:

int SetPos(int nPos);

This member function takes as argument the new value of the spin control. Here is an example:

BOOL CSpinDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // TODO: Add extra initialization here
    CSpinButtonCtrl *SpinCtrl = new CSpinButtonCtrl;
    CStatic *SpinBuddy;

    SpinCtrl->Create(WS_CHILD | WS_VISIBLE | UDS_SETBUDDYINT,
		     CRect(60, 10, 80, 35), this, 0x128);

    SpinCtrl->SetPos(36);

    SpinBuddy = reinterpret_cast<CStatic *>(GetDlgItem(IDC_SPIN_BUDDY));
    SpinCtrl->SetBuddy(SpinBuddy);
    SpinBuddy->SetWindowText("4");

    return TRUE;
}

To explore the spin control, the user click one of its arrow buttons or presses the arrow keys to increase or decrease the control's value. The only actual thing the spin control provides is the value it holds. It is up to you to decide what to do with such a value. This means that, on a regular basis, you will need to retrieve the current value of the control and do whatever you want with it.. To get the position of a spin control, call the CSpinButtonCtrl::GetPos() member function. Its syntax is:

int GetPos() const;

This member function returns the value of the spin control at the time the member function is called.

The Range of Values

One of the most important actions you should perform after creating a spin control is to specify its lowest and its highest values. The default range is 100 (lowest) to 0 (highest). This causes the spin control to count in decrement. If you do not want this (bizarre) behavior, you must explicitly set the lower and higher values.

To set the minimum and maximum values of a spin control, call either the CSpinButtonCtrl::SetRange() or the CSpinButtonCtrl::SetRange32() member functions. Their syntaxes are:

void SetRange(int nLower, int nUpper);
void SetRange32(int nLower, int nUpper);

In both cases the nLower argument holds the minimum value and the nUpper argument specifies the maximum value. Here is an example:

BOOL CSpinDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    
    // TODO: Add extra initialization here
    CSpinButtonCtrl *SpinCtrl = new CSpinButtonCtrl;
    
    SpinCtrl->Create(WS_CHILD | WS_VISIBLE | UDS_SETBUDDYINT,
    		     CRect(60, 10, 80, 35), this, 0x128);
    SpinCtrl->SetRange(-12, 1244);
    
    return TRUE;
}

If the control exists already and you want to get its minimum and maximum values, call either the CSpinButtonCtrl::GetRange() or the CSpinButtonCtrl::GetRange32() member functions. The possible syntaxes used are:

DWORD GetRange() const;
void GetRange(int &lower, int& upper) const;
void GetRange32(int &lower, int &upper) const;

Practical LearningPractical Learning: Setting the Ranges of Spin Controls

  1. In the MFC Class Wizard, click Virtual Functions
  2. Click OnInitDialog and click Edit Code
  3. Initialize the spin controls as follows:
    BOOL CPledgeDistributionDlg::OnInitDialog()
    {
    	CDialogEx::OnInitDialog();
    
    	// Add "About..." menu item to system menu.
    
    	// IDM_ABOUTBOX must be in the system command range.
    	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    	ASSERT(IDM_ABOUTBOX < 0xF000);
    
    	CMenu* pSysMenu = GetSystemMenu(FALSE);
    	if (pSysMenu != NULL)
    	{
    		BOOL bNameValid;
    		CString strAboutMenu;
    		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
    		ASSERT(bNameValid);
    		if (!strAboutMenu.IsEmpty())
    		{
    			pSysMenu->AppendMenu(MF_SEPARATOR);
    			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
    		}
    	}
    
    	// Set the icon for this dialog.  The framework does this automatically
    	//  when the application's main window is not a dialog
    	SetIcon(m_hIcon, TRUE);			// Set big icon
    	SetIcon(m_hIcon, FALSE);		// Set small icon
    
    	// TODO: Add extra initialization here
    	m_SpinRate1.SetRange(0, 100);
    	m_SpinRate1.SetPos(50);
    	m_SpinRate2.SetRange(0, 100);
    	m_SpinRate2.SetPos(25);
    	m_SpinRate3.SetRange(0, 100);
    	m_SpinRate3.SetPos(25);
    	
    	return TRUE;  // return TRUE  unless you set the focus to a control
    }
  4. Execute the application. Test the spin controls and make sure they are working fine
  5. Close the dialog box and return to your programming environment

The Accelleration of Values

Based on the range of values that a spin control can handle, the user can increment and decrement the control's value. By default, the values are are incremented by adding 1 and decrement by adding -1 to the current value. If you want to increment and decrement by a different value, you have two main options. You can write a routine to take care of this, or call the CSpinButtonCtrl::SetAccel() member function. Its syntax is:

BOOL SetAccel(int nAccel, UDACCEL* pAccel);

The SetAccel() member function takes a UDACCEL value and its size as arguments. The UDACCEL class is defined as follows:

typedef struct
{
    UINT nSec;
    UINT nInc;
}UDACCEL, FAR *LPUDACCEL;

The nSec member variable is a semi-timer that ticks at a specified rate of seconds.

The nInc member variable of this structure defines the incremental value to apply when the nSec value has passed.

The nAccel argument of the SetAccel() member function is the size of the UDACCEL class.

In the following example, a spin control was added to a dialog box and a control variable named m_Spin was added for it. When the user clicks the arrow buttons or presses the arrow keys, the value of the spin control is incremented by 5:

BOOL CDlgSpin::OnInitDialog()
{
    CDialog::OnInitDialog();

    // TODO: Add extra initialization here
    UDACCEL udAccel;

    int SizeOfAccel = sizeof(UDACCEL);
    udAccel.nSec = 100;
    udAccel.nInc = 5;

    m_Spin.SetAccel(SizeOfAccel, &udAccel);
    m_Spin.SetRange(12, 168);

    return TRUE;
}

If a spin control has been created already, to find its incremental value, you can call the CSpinButtonCtrl::GetAccel() member function. Its syntax is:

UINT GetAccel(int nAccel, UDACCEL* pAccel) const;

Once a spin control is ready to hold values, its default value is the lowest specified with the SetRange() or the SetRange32() member function. For example, if you create a spin control that can hold values from 15 to 62, when the control displays at startup, it would assume a value of 15.

Using a Spin Control

When the user clicks one of the arrow buttons of a spin control or presses an up or a down arrow key when the control has focus, the operating system sends a UDN_DELTAPOS message to the parent window of the spin control, notifying this parent that the position (the value) of the control is about to be changed. The syntax of the event fired by the UDN_DELTAPOS message is:

OnDeltaPosSpin(NMHDR* pNMHDR, LRESULT* pResult);

Because this event is fired before the value of the spin control is changed, you can use it to check, validate, allow or deny the change. The first argument, pNMHDR, is an NMHDR value. The NMHDR structure is sometimes used to carry information about a message. It is defined as follows:

typedef struct tagNMHDR
{
    HWND hwndFrom;
    UINT idFrom;
    UINT code; 

} NMHDR;

The hwndFrom member variable is a handle to the window that is sending the message. The idFrom is the identifier of the control that is sending the message. The code member is the actual notification code.

When implementing the event of the UDN_DELTAPOS message, instead of using the value of the NMHDR argument, Visual C++ takes the liberty of casting the pNMHDR pointer into a pointer to NM_UPDOWN. Therefore, the event provided to you appears as follows:

void CDlgSpin::OnDeltaPosSpinNew(NMHDR* pNMHDR, LRESULT* pResult)
{
    NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
    
    // TODO: Add your control notification handler code here
    
    *pResult = 0;
}

The NM_UPDOWN structure is defined as follows:

typedef struct _NM_UPDOWN
{
    NMHDR hdr;
    int iPos;
    int iDelta;
} NMUPDOWN, FAR *LPNMUPDOWN;

This structure was specifically created to carry notification information for a spin control. The first member variable of the NM_UPDOWN structure, hdr, is an NMHDR. The hdr member itself carries additional information about the message being sent, as mentioned above. The iPos member variable is the value of the current position of the spin control. The iDelta member is the intended change that would be performed on the spin control.

Practical LearningPractical Learning: Using the Spin Control Events

  1. On the main menu, click Project -> Class Wizard...
  2.  In the MFC Class Wizard, click Commands.
    In the list of Commands, click IDC_SPIN_RATE1
  3. In the Messages list, click UDN_DELTAPOS
  4. Click Add Handler...
  5. Set the name to OnDeltaPosSpinRate1 and click OK
  6. In the Commands list, click IDC_SPIN_RATE2
  7. In the Messages list, double-click UDN_DELTAPOS
  8. Set the name to OnDeltaPosSpinRate2 and click OK
  9. In the Commands list, click IDC_SPIN_RATE3
  10. In the Messages list, double-click UDN_DELTAPOS
  11. Set the name to OnDeltaPosSpinRate3 and click OK
     
    MFC Class Wizard
  12. Click Edit Code
  13. Implement the events as follows:
    void CPledgeDistributionDlg::OnDeltaPosSpinRate1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
    	// TODO: Add your control notification handler code here
    	UpdateData();
    
    	double AmountPledged,
    		   Amount1, Amount2, Amount3,
    		   Rest;
    	int Value1, Value2, Value3;
    	int Rate1, Rate2, Rate3;
    
    	// Get the current value of each spin control
    	Value1 = m_SpinRate1.GetPos();
    	Value2 = m_SpinRate2.GetPos();
    	Value3 = m_SpinRate3.GetPos();
    	
    	// Calculate the percentage of each value based on the amount pledged
    	m_Amount1 = m_AmountPledged * Value1 / 100;
    	m_Amount2 = m_AmountPledged * Value2 / 100;
    	m_Amount3 = m_AmountPledged * Value3 / 100;
    
    	// Calculate the amount left
    	Rest = m_AmountPledged - m_Amount1 - m_Amount2 - m_Amount3;
        
    	// If the whole amount has not yet been distributed, display a message
    	if( Rest > 0 )
    		m_Message.Format(_T("$%.2f still to be used"), Rest);
    	else // If the whole amount has been distributed, don't display a message
    		m_Message = _T("");
    
    	UpdateData(FALSE);
    	*pResult = 0;
    }
    
    
    void CPledgeDistributionDlg::OnDeltaPosSpinRate2(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
    	// TODO: Add your control notification handler code here
    	OnDeltaPosSpinRate1(pNMHDR, pResult);
    	*pResult = 0;
    }
    
    
    void CPledgeDistributionDlg::OnDeltaPosSpinRate3(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
    	// TODO: Add your control notification handler code here
    	OnDeltaPosSpinRate1(pNMHDR, pResult);
    	*pResult = 0;
    }
  14. To test the application, press F5
     
    Pledge Distribution
     
    Pledge Distribution
  15. Close it and return to your programming environment
 
 
   
 

Home Copyright © 2010-2011 FunctionX, Inc.