    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/">
     <channel>
        <title>ACCU  :: Custom Controls</title>
        <link>https://members.accu.org/index.php/articles/1385</link>
        <description>Professionalism in Programming</description>
        <dc:language>en-us</dc:language> 
        <dc:creator>Administrator</dc:creator> 
        <admin:generatorAgent rdf:resource="http://www.xaraya.org" /> 
        <admin:errorReportsTo rdf:resource="mailto:webeditor@accu.org" />
       <sy:updatePeriod>hourly</sy:updatePeriod>
       <sy:updateFrequency>1</sy:updateFrequency>
       <docs>http://backend.userland.com/rss</docs>




<div class="xar-mod-head"><span class="xar-mod-title">Programming Topics + Overload Journal #3 - Aug 1993</span></div>

<table border="0" cellpadding="1" cellspacing="0">
    <tbody>
    <tr>
        <td valign="top">
            Browse in :
       </td>
       <td valign="top">

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c13/">Topics</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c65/">Programming</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c76/">Journals</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c78/">Overload</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c225/">03</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/c65-225/">Any of these categories</a>

                    -                        <a href="https://members.accu.org/index.php/articles/c65+225/">All of these categories</a>
<br />
</td>
   </tr>
   </tbody>
</table>




<div class="xar-error">
   <p>
 <strong>Note:</strong> when you create a new publication type,
the articles module will automatically use the templates
<em>user-display-[publicationtype].xt</em>
and <em>user-summary-[publicationtype].xt</em>.
If those templates do not exist when you try to preview or display a new article,
you'll get this warning :-)  Please place your own templates in themes/<em>yourtheme</em>/modules/articles . The templates will get the extension .xt there. </p>
</div>
<div class="xar-norm xar-standard-box-padding">
   <h1><strong>Title:</strong>&nbsp;Custom Controls</h1>
<p><strong>Author:</strong>&nbsp;</p>
<p>
<strong>Date:</strong> 01 August 1993 11:55:00 +01:00 or Sun, 01 August 1993 11:55:00 +01:00</p>
<p><strong>Summary:</strong>&nbsp;</p>
<p><strong>Body:</strong>&nbsp;<p>The ability to easily use attractive custom controls is, I believe,
one of the factors that has given Microsoft's Visual Basic such a
devoted following (as will Visual C++ soon have). But it is best not to
forget that custom controls are relatively easy to create on
ObjectWindows. Most of the hard work being handled by the base classes
of the OWL framework.</p>
<p>The custom control that I will create will be used to represent a
slide control, of the kind found on old TV sets, Graphic Equalisers or
Fader controls on Audio Mixing decks. I assume that before reading this
article you are at least familiar with the basics of ObjectWindows.</p>
<p>The first stage is to create the bitmaps for our controls. These are
called SLIDBASE.BMP for background of the slider and SLIDBUTT.BMP for
the button. Both bitmaps being created using resource workshop.</p>
<p>The button is intended to traverse the dark area in the centre of
the base. The bottom of the control represents 0%, the top 100%.<br>
</p>
<p style="text-align: center;"><img alt="Slider image components"
 src="/content/images/journals/ol03/Figure%201%20-%20Custom%20Controls.png"><br>
</p>
<p>Before I start on a description of the stages needed to animate
these simple bitmaps and create a reusable custom control, I need to
define some constants as follows:</p>
<pre class="programlisting">const int MAXPOINT = 19;<br><br>const int SLIDERHEIGHT = 112; <br>const int SLIDERWIDTH = 40;<br><br>const int BUTTONWIDTH = 20; <br>const int BUTTONHEIGHT = 10;<br><br>const int XOFFSET&nbsp; = SLIDERWIDTH/2 - BUTTONWIDTH/2;<br><br>const int ARROWWIDTH = 16; <br>const int ARROWHEIGHT = 16;<br><br>const int DOWNX = 3; <br>const int DOWNY = 94;<br><br>const int UPX = 22; <br>const int UPY = 94;<br><br>const int TEXTPOSX = 4; <br>const int TEXTPOSY = 4;</pre>
<p>We need a number of include files, OWL.H for the ObjectWindows
system, CONTROL.H to supply the base class from which our custom
control is derived, BWINDOW.H, to supply a Borland Window type
(TBWindow) into which the position of the control as a percentage of
maximum is displayed. STRSTREAM.H and IOMANIP.H are required to format
the numbers displayed in the percentage window.</p>
<p>The name of the new custom control is to be TMyScroll. For those not
yet totally au fait with ObjectWindows nomenclature, the leading letter
'T' is used to denote that this is a Type of MyScroll. At this point we
also use a macro (_CLASSDEF). This is used to typedef references and
pointers to the class. In this case the statement:</p>
<pre class="programlisting">_CLASSDEF(TMyScroll)</pre>
<p>generates the following statements: <br>
</p>
<pre class="programlisting">class _CLASSTYPE TMyScroll;<br>typedef TMyScroll&nbsp; FAR * PTMyScroll; <br>typedef TMyScroll _FAR &amp; RTMyScroll; <br>typedef TMyScroll _FAR * _FAR &amp; RPTMyScroll; <br>typedef const TMyScroll _FAR * PCTMyScroll; <br>typedef const TMyScroll _FAR &amp; RCTMyScroll;</pre>
<p>This is a convenient way of generating all the typedefs you may
require for the class.</p>
<p>Our TMyScroll class is publicly derived from TControl, and responds
to the mouse left button being pressed and released, and the mouse
being moved while over the custom control. We therefore need to capture
these events and process them. This is done using the DDVT (Dynamic
Dispatch Virtual Tables) of Object Windows. The DDVT uses a Borland
specific extension to the C++ language in order to semi-automatically
allow us to tap into the message processing loop. This is achieved with
the declaration:</p>
<pre class="programlisting">virtual void MyOispatchHandler(RTMessage) =<br>[DispatchValue];</pre>
<p>The only windows message, in addition to the mouse ones already
mentioned, that we are required to handle is the paint message. Each
time windows wants the control to be drawn, it will send a WMPAINT
message, this is trapped by using the Dispatch value of
WM_FIRST+WM_PAINT</p>
<p>All the messages not specifically catered for are handled by the
base class (TControl) so we need not worry about them.</p>
<p>We also need a number of values retained by the class, these include
the number of units by which the control moves for one click of the
arrow buttons (Delta), the values represented by the top and bottom of
the slidebar range, (I've conveniently fixed them to 0% and 100%, but
they could take other ranges). A series of Boolean values used to
indicate if buttons have been pressed (and therefore require shading ),
the physical position of the thumbnail and the value represented by the
thumbnail position (in the range of BottomOfRange to TopOfRange).</p>
<pre class="programlisting">// class for the new scroll bar <br>class TMyScroll : public TControl <br>{ <br>protected:<br>    int Delta;&nbsp;&nbsp; &nbsp;// The amount by which the thumbnail moves<br>                  // for each click of the arrow buttons <br>    int BottomOfRange,<br>        TopOfRange;<br>    BOOL DownButton,&nbsp;&nbsp;&nbsp; // logicals used to decide which <br>        UpButton,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // buttons need to be inverted<br>        SliderButton;<br>    int CurrentValue,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // The current value of the control<br>                             // in the range BottomOfRange to TopOfRange<br>        ThumbnailPosition; // Physical position of thumbnail in window<br><br>    virtual LPSTR GetClassName() { return &quot;TMyScroll&quot;;} <br>    virtual void SetupWindow(); <br>public:<br>    TMyScroll(PTWindowsObject AParent, int AnID,<br>              int X, int Y, PTModule AModule = NULL);<br><br>    virtual void SetRange(int LoVal, int HiVal); <br>    virtual void GetRange(int&amp; LoVal, int&amp; HiVal);<br><br>    virtual int GetPos();<br>    virtual void SetPos(int ThumbnailPosition);<br><br>    virtual WORD Transfer(Pvoid DataPtr, WORD TransferFlag);<br><br>    // DDVT response functions<br>    virtual void WMLButtonDown(RTMessage)<br>                    = [WM_FIRST + WM_LBUTTONDOWN]; <br>    virtual void WMLButtonUp(RTMessage)<br>                    = [WM_FIRST + WM_LBUTTONUP]; <br>    virtual void WMMouseMove(RTMessage)<br>                    = [WM_FIRST + WM_MOUSEMOVE]; <br>    virtual void WMPaint(RTMessage)<br>                    = [WM_FIRST + WM_PAINT];<br>};</pre>
<p>The constructor simply sets the start position of the control.</p>
<pre class="programlisting">TMyScroll::TMyScroll(PTWindowsObject AParent, int AnID, <br>                     int X, int Y, PTModule AModule)<br>        : TControl(AParent, AnID, NULL, X, Y, 40, 112, AModule) <br>{<br>    Delta =1;&nbsp;&nbsp; &nbsp;// Amount to add when the user presses an arrow<br>    ThumbnailPosition = ZEROPOINT;&nbsp;&nbsp;&nbsp;&nbsp; // start position for thumbnail<br>    CurrentValue = 0; // initial value of control<br>    DownButton = FALSE;&nbsp;&nbsp; &nbsp;// set all buttons off<br>    UpButton = FALSE;<br>    SliderButton = FALSE; <br>}<br></pre>
<p>SetupWindow is used to perform any post-constructor initialisation
tasks. In this case, sets the range of values the control will report.
In this case between zero and 100. The call to the base class's
SetupWindow must not be forgotten, in this case the order in which it
is called is not significant.</p>
<pre class="programlisting">// sets up the range 0..100 <br>void TMyScroll::SetupWindow()<br>{<br>    SetRange(0,100); <br>    TControl::SetupWindow(); <br>}</pre>
<p>The SetRange member function is not too exciting either, it is used
to represent the minimum and maximum values that can be expressed by
the control.</p>
<pre class="programlisting">void TMyScroll::SetRange(int LoVal, int HiVal)<br>{<br>    BottomOfRange = LoVal;<br>    TopOfRange&nbsp;&nbsp;&nbsp; = HiVal; <br>}</pre>
<p>The GetRange member function returns (as reference) the values set
up with SetRange.</p>
<pre class="programlisting">void TMyScroll::GetRange(int&amp; LoVal, int&amp; HiVal)<br>{<br>   &nbsp;LoVal = BottomOfRange; <br>    HiVal&nbsp; = TopOfRange;<br>}</pre>
<p>The GetPos member function returns the current setting of the
thumbnail in the range set by SetRange (not in percent).<br>
</p>
<pre class="programlisting">int TMyScroll::GetPos() <br>{<br>    return CurrentValue;<br>}</pre>
<p>The SetPos member function (like GetPos) sets the current setting in
the range (and units) used in SetRange. If the position set is
different from the current position, the physical position of the
slider is recalculated and the control is invalidated, thus redrawing
the complete control. <br>
</p>
<pre class="programlisting">void TMyScroll::SetPos(int NewPos) <br>{<br>    int LoVal, HiVal;<br>    // get the current control range<br>    GetRange(LoVal, HiVal);<br>    // see if the new value is in range<br>    if (NewPos &gt; HiVal)<br>        NewPos = HiVal; <br>    else if (NewPos &lt; LoVal)<br>        NewPos = LoVal;<br><br>    if (CurrentValue != NewPos) // if the position has changed <br>    {<br>        CurrentValue = NewPos; <br>        ThumbnailPosition = ZEROPOINT -<br>            ((CurrentValue - BottomOfRange)<br>            * (ZEROPOINT -MAXPOINT)<br>            / (TopOfRange - BottomOfRange));<br>        // Redraw the control <br>        InvalidateRect(HWindow, NULL, FALSE);<br>    }<br>}<br></pre>
<p>The Transfer member function is not used by this control, but is
provided in case you wish to use the control in conjunction with a
Transfer buffer.</p>
<pre class="programlisting">WORD TMyScroll::Transfer(Pvoid DataPtr, WORD TransferFlag) <br>{<br>    // transfer the thumb position<br>    int* NewPtr = (int*) DataPtr;<br>    if (TransferFlag == TF_GETDATA) <br>        *NewPtr = CurrentValue;<br>    else<br>        SetPos(*NewPtr);<br>    return sizeof(int); <br>}</pre>
<p>The WMMoveMouse member function is used to detect the new position
of the mouse, and to use that information to decide on the position of
the slide button, or if the mouse has moved outside the immediate area
of the button.</p>
<pre class="programlisting">void TMyScroll::WMMouseMove(RTMessage Msg) <br>{<br>    if (SliderButton &amp;&amp;<br>        (Msg.LP.Lo&lt;XOFFSET ||<br>          Msg.LP.Lo&gt;XOFFSET+BUTTONWIDTH)) <br>        SliderButton = FALSE;<br><br>    if (SliderButton &amp;&amp; Msg.LP.Hi&gt;=MAXPOINT &amp;&amp;<br>        Msg.LP.Hi&lt;=ZEROPOINT) <br>    {<br>        int LoVal, HiVal;<br>        // get the current control range<br>        GetRange(LoVal, HiVal);<br><br>        // Redraw the control <br>        SetPos((ZEROPOINT-Msg.LP.Hi)*<br>            (TppOfRange-BottomOfRange)/<br>            (ZEROPOINT-MAXPOINT));<br>    }<br>}</pre>
<p>The WMLButtonDown member function determines if the mouse button was
pressed in either of the arrow buttons or on the slider button. If this
is the case, the appropriate flag is set. <br>
</p>
<pre class="programlisting">void TMyScroll::WMLButtonDown(RTMessage Msg)<br>{<br>    RECT rRect; <br>    RECT lRect; <br>    RECT sRect;<br><br>    // Set the area of the right button <br>    SetRect(&amp;rRect, UPX, UPY, UPX + ARROWWIDTH, UPY +<br>                    ARROWHEIGHT);<br>    // Set the area of the left button <br>    SetRect(&amp;lRect, DOWNX, DOWNY, DOWNX + ARROWWIDTH,<br>                    DOWNY + ARROWHEIGHT); <br>    // Set the area of the slider button <br>    SetRect(&amp;sRect, XOFFSET, ThumbnailPosition, XOFFSET +<br>                    BUTTONWIDTH, ThumbnailPosition + BUTTONHEIGHT);<br>    // Check for up arrow<br>    if (PtInRect(&amp;rRect,MAKEPOINT(Msg.LP)))<br>    {<br>        UpButton = TRUE;<br>        SetPos(GetPos() + Delta); <br>    }<br>    // check for down arrow<br>    else if (PtInRect(&amp;lRect,MAKEPOINT(Msg.LP))) <br>    {<br>        DownButton = TRUE;<br>        SetPos(GetPos() - Delta); <br>    }<br>    else if (PtInRect(&amp;sRect,MAKEPOINT(Msg.LP))) <br>    {<br>        SliderButton = TRUE; <br>    } <br>}</pre>
<p>The WMLButton member function sets all the buttons to inactive and
invalidates the control to redraw. <br>
</p>
<pre class="programlisting">void TMyScroll::WMLButtonUp(RTMessage Msg) <br>{<br>    DownButton = FALSE; <br>    UpButton = FALSE; <br>    SliderButton = FALSE; <br>    InvalidateRect(HWindow, NULL, FALSE);<br>}</pre>
<p>This is the big one! The WMPaint member function is responsible for
drawing the custom control.&nbsp; Firstly the backdrop is redrawn and
the slider button deposited in the appropriate position. The percentage
value is written to the little window on the top of the control and
then, depending on the flags set, one or other of the arrow buttons is
inverted to show that it is being pressed. <br>
</p>
<pre class="programlisting">void TMyScroll::WMPaint(RTMessage) <br>{<br>    HDC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   hDC, MemDC;<br>    HBITMAP&nbsp;&nbsp;&nbsp;  OldBitmap;<br>    HBRUSH&nbsp;&nbsp;&nbsp;   OldBrush;<br>    RECT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   Rect;<br>    PAINTSTRUCT ps;<br>    int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   BMHeight;<br><br>    memset(&amp;ps,0x00,sizeof(PAINTSTRUCT)); <br>    hDC = BeginPaint(HWindow, &amp;ps); <br>    // draw the up arrow <br>    GetClientRect(HWindow,&amp;Rect); <br>    MemDC = CreateCompatibleDC(hDC); <br>    OldBitmap = (HBITMAP)SelectObject(MemDC,<br>                    LoadBitmap( GetApplication()-&gt;hlnstance,<br>                                &quot;SLIDBASE&quot;));<br>    StretchBlt(hDC, 0, 0, Rect.right, Rect.bottom, <br>               MemDC,0,0,39,111,<br>               SRCCOPY); <br>    DeleteObject( SelectObject( MemDC, LoadBitmap(<br>                  GetApplication()-&gt;hInstance, &quot;SLIDBUTT&quot;)));<br>    BitBlt(hDC, XOFFSET, ThumbnailPosition, 20, 10,<br>           MemDC,0,0,SRCCOPY);<br>    DeleteObject(SelectObject(MemDC, OldBitmap)); DeleteDC(MemDC);<br><br>    // display the number<br>    char buffer[6];<br>    ostrstream num(buffer,sizeof(buffer));<br>    num &lt;&lt; setw(3) &lt;&lt; GetPos() &lt;&lt; &quot;%&quot; &lt;&lt; ends;<br>    HFONT hFont = CreateFont(14,7,0,0,400,0,0,0,<br>                    OEM__CHARSET, OUT_DEFAULT_PRECIS,<br>                    CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,<br>                    DEFAULT_PITCH|FF_DONTCARE, &quot;system&quot;);<br>    SelectObject(hDC,hFont);<br><br>    DWORD BkGrnd = SetBkColor(hDC,<br>                    GetPixel(hDC,TEXTPOSX,TEXTPOSY)); <br>    TextOut(hDC,TEXTPOSX,TEXTPOSY,buffer,4); <br>    DeleteObject(hFont); <br>    SetBkColor(hDC,BkGrnd);<br><br>    RECT rRect; <br>    RECT lRect; <br>    SetRect(&amp;rRect, UPX, UPY, UPX + ARROWWIDTH, UPY +<br>            ARROWHEIGHT); <br>    SetRect(&amp;lRect, DOWNX, DOWNY, DOWNX + ARROWWIDTH,<br>            DOWNY + ARROWHEIGHT); <br>    // Check for up arrow <br>    if (UpButton) <br>    {<br>        InvertRect(hDC,&amp;rRect); <br>    }<br>    // check for down arrow <br>    else if (DownButton)<br>    {<br>        InvertRect(hDC,&amp;lRect);<br>    }<br><br>    EndPaint(HWindow, &amp;ps);<br>}</pre>
<p>All that is now left is to create the main window (in this case a
BWCC gray window) and to instantiate several Sliders to demonstrate
their use, and to create the necessary Application class and the
WinMain function.</p>
<pre class="programlisting">class MyWindow : public TBWindow<br>{<br>public:<br>    TMyScroll* TheScroller[4];<br>    MyWindow(PTWindowsObject AParent, LPSTR ATitle);<br>};<br><br>MyWindow::MyWindow(PTWindowsObject AParent, LPSTR ATitle) <br>        : TBWindow (AParent, ATitle)<br>{<br>    for (int i = 0; i &lt; 4; i++)<br>    {<br>        TheScroller[i] = new TMyScroll(this, <br>                        200 + i, 10 + 100 * (i % 2),<br>                        10 + 100 * (i % 2));<br>    } <br>}<br><br>class MyApp : public TApplication<br>{<br>public:<br>    MyApp(LPSTR ApName, HANDLE Inst, HANDLE Prevlnst,<br>          LPSTR CmdLine, int CmdShow) <br>       : TApplication( ApName, Inst, PrevInst, CmdLine, CmdShow) {}<br>    virtual void InitMainWindow();<br>};<br><br>void MyApp::InitMainWindow() <br>{<br>    MainWindow = new MyWindow(NULL, &quot;Custom Control&quot;);<br>}<br><br>int PASCAL WinMain (HANDLE Inst, HANDLE PrevInst,<br>                    LPSTR CmdLine, int CmdShow ) <br>{<br>    MyApp X(&quot;Custom Control App&quot;,<br>            Inst,PrevInst,CmdLine,CmdShow);<br>    X.Run();<br>    return X.Status; <br>}</pre>
</p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
