Introduction
The purpose of this article is to show how a user control could be
implemented. Also, its sample is useful for programmers who would like to use a
rotating knob slider control, for which the example is provided.
Background
For any developer who wants to understand this sample and /or to implement a
new one, it is required to know how to derive a new MFC control class from a
standard one.
Using the code
The code could be used by inserting the KbutsliderCtrl
class in
your own project, and also by adding a new slider control in the resources. The
class can be used directly by creating instances of it, or creating a new class
derived from this one. The derivation is useful when the programmer wants to
capture the OnLButtonUp
and OnLButtonDown
events.
Functions SetKnobNum
and SetKnobPosi
are used to
set the number and position of each knob which is gliding on the circular
channel. These functions also will update the control's window with the new
parameters which are protected against overflow errors by using the
%
(modulo) operator. Of course in order to get the current position
of each knob, function GetKnobPosi
was implemented.
The KbutsliderCtrl
class has some private functions which are
used for some mathematical and graphical operations, as follows:
- Function
GetCurentAngle
calculates the angle composed by the
vertical axis, the central point, and the specified point (normally mouse's
coordinates).
- Function
DetectNearestKnob
scans the knobs in order to identify
the knob with the nearest angle value with the specified value. In most of the
cases, the last two functions are used together.
- The function
CalculateKnobPosi
is used to calculate the
position of a knob which has to stay on the channel and to describe a specified
angle.
- The functions
DrawCircle
, DrawBitmap
, and
DrawText
are used to show parts of the graphical control.
- The function
DrawLine
is used to substitute the function
SetPixelV
, which is optional.
The OnPaint
routine is presented:
void KbutsliderCtrl::OnPaint() {
int j;
CPaintDC dc(this);
CMemDC memDC(&dc);
CMemDC *pDC = &memDC;
int nRadius = m_nRadius;
CRect rc;
GetClientRect(rc);
pDC->SelectStockObject(NULL_BRUSH);
pDC->FillSolidRect(rc, ::GetSysColor(COLOR_BTNFACE));
DrawCircle(pDC, m_ptCenter, nRadius--, ::GetSysColor(COLOR_3DDKSHADOW),
::GetSysColor(COLOR_3DHIGHLIGHT));
DrawCircle(pDC, m_ptCenter, nRadius, ::GetSysColor(COLOR_3DSHADOW),
::GetSysColor(COLOR_3DLIGHT));
nRadius-=2;
DrawCircle(pDC, m_ptCenter, nRadius--, ::GetSysColor(COLOR_3DHIGHLIGHT),
::GetSysColor(COLOR_3DDKSHADOW));
DrawCircle(pDC, m_ptCenter, nRadius--, ::GetSysColor(COLOR_3DLIGHT),
::GetSysColor(COLOR_3DSHADOW));
DrawBitmap(pDC,s_ButStateNew);
for ( j=0; j<m_nKnobNum; j++ ) {
const CPoint ptKnobCenter = CalculateKnobPosi(m_nKnobPosi[j]);
int nKnobRadius = m_nKnobRadius;
const CRect rcKnob(ptKnobCenter.x - nKnobRadius,
ptKnobCenter.y - nKnobRadius, ptKnobCenter.x + nKnobRadius,
ptKnobCenter.y + nKnobRadius);
CRgn rgnKnob;
rgnKnob.CreateEllipticRgnIndirect(rcKnob);
CBrush brKnob(::GetSysColor(COLOR_BTNFACE));
pDC->FillRgn(&rgnKnob, &brKnob);
rgnKnob.DeleteObject();
if(m_bDragging) {
DrawCircle(pDC, ptKnobCenter, --nKnobRadius,
::GetSysColor(COLOR_3DDKSHADOW),
::GetSysColor(COLOR_3DHIGHLIGHT));
DrawCircle(pDC, ptKnobCenter, --nKnobRadius,
::GetSysColor(COLOR_3DSHADOW),
::GetSysColor(COLOR_3DLIGHT));
} else {
DrawCircle(pDC, ptKnobCenter, --nKnobRadius,
::GetSysColor(COLOR_3DHIGHLIGHT),
::GetSysColor(COLOR_3DDKSHADOW));
DrawCircle(pDC, ptKnobCenter, --nKnobRadius,
::GetSysColor(COLOR_3DLIGHT),
::GetSysColor(COLOR_3DSHADOW));
}
if(GetFocus() == this) {
DrawCircle(pDC, ptKnobCenter, nKnobRadius-2, RGB(0, 0, 0), TRUE);
if ( j == m_nKnobCrt ) {
DrawCircle(pDC, ptKnobCenter, nKnobRadius-4, RGB(0, 0, 0),
TRUE);
}
}
}
DrawText(pDC);
}
Points of interest
The thing which I learned from this project is the fact that some internal
state variables were necessary to be used together with window events in order
to define the desired behavior of the control.
History
This is the first version of this kind of mixed control developed, which
looks like a knob slider.
Acknowledgements
I would like to appreciate the help of code from Kyle Rule which I used for
flicker free drawing context object. Also thanks to other developers who shared
on this site, interesting source codes.