VCL Mini CAQ
by Damon Chandler


This Mini FAQ is provided to supplement the user-Communityed
cppbuilder.vcl.components.using newsgroup.  For convenience, it is
divided into separate sections according to the following categories:
 

Part 1/4:  Application, SDI, and MDI
Part 2/4:  Common Controls
Part 3/4:  Standard Controls
Part 4/4:  VCL Specific Controls
 

This text can be downloaded in its entirety from the following URL:
http://www.bcbjournal.com/bcbcaq/mini_caq.zip (12 KB)

//////////////////////////////////////////////////////////////////////
 

Part 1:  Application, SDI, and MDI
====================================
---

(Q1.1)  How do I create a hidden main form?

(A1.1)  The TApplication class has the ShowMainForm property, which
        can be used to control whether or not the main form is
        initially displayed.  The default value is true.

 WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
 {
     try
     {
          Application->Initialize();
          Application->ShowMainForm = false;
          Application->CreateForm(__classid(TForm1), &Form1);
          Application->Run();
     }
     catch (Exception &exception)
     {
          Application->ShowException(&exception);
     }
     return 0;
 }
 

---

(Q1.2)  How do I hide the application's taskbar button?

(A1.2)  To hide a window's taskbar button, use the ShowWindow()
        API function, specifying the SW_HIDE flag.  Since the
        taskbar button that is visible belongs to the Application
        window, pass its Handle property as the hWnd parameter
        of the function.  The taskbar buttton will appear when the
        Application is minimized via an explicit call to the
        TApplication::Minimize() member function, or the by
        minimizing the main form.  Combat this behavior by providing
        a handler for the TApplication::OnMinimize event.

 // in header...
     void __fastcall AppMinimizeHandler(TObject *Sender);

 // in source...
 __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
 {
     Application->OnMinimize = AppMinimizeHandler;
 }

 void __fastcall TForm1::AppMinimizeHandler(TObject *Sender)
 {
     ShowWindow(Application->Handle, SW_HIDE);
 }

 void __fastcall TForm1::FormShow(TObject *Sender)
 {
     ShowWindow(Application->Handle, SW_HIDE);
 }
 

---

(Q1.3)  How do I create a menu that lists all the MDI child forms?

(A1.3)  The VCL has a wrapper around the standard MDI Window menu,
        the TForm::WindowMenu property.
 

---

(Q1.4)  How do I hide an MDI child form?

(A1.4)  The ShowWindow() API function can be used to hide MDI child
        forms.  Specify the handle of the target child form and the
        SW_HIDE flag.  To show the form again, use the same function
        with the SW_SHOW flag.  For example:

 ShowWindow(ActiveMDIChild->Handle, SW_HIDE);
 

---

(Q1.5)  How do I access the active MDI child form?

(A1.5)  The TForm::ActiveMDIChild property can be used to access the
        active MDI child.  This is the same as MDIChildren[0].  If
        you're trying to access a specific control on this child form,
        be sure to cast the returned TForm pointer appropriately.
 

---

(Q1.6)  How do I access a specific MDI child form?

(A1.6)  You can use the MDIChildren array.  Be aware however, contrary
        to the help files, this array contains the most active MDI
        child in the zeroth position.  The child form active before
        the MDIChildren[0] (ActiveMDIChild) form is held in the first
        position, MDIChildren[1], and so on.  If you're trying to
        access a specific control on this form, be sure to cast the
        returned TForm pointer appropriately.
 

---

(Q1.7)  How do I paint a background image on an MDI form?

(A1.7)  To do this, you'll need to draw directly to the client window.
        More specifically, subclass the client window (access its handle
        via the ClientHandle property) and render the image in response
        to the WM_ERASEBKGND message.

 // in header...
     FARPROC NewClientWP;
     FARPROC OldClientWP;
     void __fastcall MDIClientWndProc(TMessage &Msg);
 

 // in source...
 __fastcall TMainForm::TMainForm(TComponent *Owner)
     : TForm(Owner)
 {
    NewClientWP = reinterpret_cast<FARPROC>
        (MakeObjectInstance(MDIClientWndProc));

    OldClientWP = reinterpret_cast<FARPROC>
        (SetWindowLong(ClientHandle, GWL_WNDPROC,
                     reinterpret_cast<LONG>(NewClientWP)));
}

void __fastcall TMainForm::MDIClientWndProc(TMessage &Msg)
{
    switch (Msg.Msg)
    {
        // draw the image to the device context of the
        // client window
        case WM_ERASEBKGND:
        {
           HDC Hdc = reinterpret_cast<HDC>(Msg.WParam);
           SelectPalette(Hdc, Image1->Picture->Bitmap->Palette, true);
           RealizePalette(Hdc);
           StretchBlt(Hdc, 0, 0,
                      Image1->Width, Image1->Height,
                      Image1->Canvas->Handle,
                      0, 0,
                      Image1->Picture->Bitmap->Width,
                      Image1->Picture->Bitmap->Height,
                      SRCCOPY);

           Msg.Result = 0;
           return;
        }

        // handle the palette changes
        case WM_QUERYNEWPALETTE:
        {
            HDC Hdc = GetDC(ClientHandle);
            SelectPalette(Hdc, Image1->Picture->Bitmap->Palette, true);
            RealizePalette(Hdc);
            InvalidateRect(ClientHandle, NULL, true);
            ReleaseDC(ClientHandle, Hdc);

            Msg.Result = 0;
            return;
        }
        case WM_PALETTECHANGED:
        {
            if (reinterpret_cast<HWND>(Msg.WParam) != ClientHandle)
            {
                HDC Hdc = GetDC(ClientHandle);
                SelectPalette(Hdc,
                              Image1->Picture->Bitmap->Palette,
                              true);
                RealizePalette(Hdc);
                UpdateColors(Hdc);
                ReleaseDC(ClientHandle, Hdc);
            }

            Msg.Result = 0;
            return;
       }

       // refresh the image upon scrolling
       case WM_HSCROLL:
       case WM_VSCROLL:
       {
           InvalidateRect(ClientHandle, NULL, true);
           break;
       }

       // un-subclass the client window
       case WM_DESTROY:
       {
           SetWindowLong(ClientHandle, GWL_WNDPROC,
                         reinterpret_cast<LONG>(OldClientWP));
           FreeObjectInstance(NewClientWP);
       }
   }

   // call the default window procedure
   Msg.Result = CallWindowProc(OldClientWP, ClientHandle, Msg.Msg,
                               Msg.WParam, Msg.LParam);
}
 

---

(Q1.8)  How do I display a dialog, such as a password dialog,
        before the main form becomes visible?

(A1.8)  Create, display, and destroy the dialog before creating
        the main form.  This can be done in the source file for
        the project (Project | View Source).

#include <memory>
#include "PWDialogUnit.h"

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    try
    {
        // give PWDialog local scope
        {
            AnsiString error_msg("Sorry, acess denied.");
            std::auto_ptr<TPWDialog> PWDialog(new TPWDialog(NULL));
            if (PWDialog->ShowModal() == mrOk)
            {
                if (PWDialog->PasswordEdit->Text != "password")
                throw EAbort(error_msg);
            }
            else throw EAbort(error_msg);
        }

        Application->Initialize();
        Application->CreateForm(__classid(TForm1), &Form1);
        Application->CreateForm(__classid(TPWDialog), &PWDialog);
        Application->Run();
    }
    catch (Exception &exception)
    {
        Application->ShowException(&exception);
    }
    return 0;
}
 



 

Part 2:  Common Controls
==========================
---

(Q2.1)  How can I retrieve all selected items in a TListView?

(A2.1)  Use the TListView::GetNextItem() member function.  This will
        return a pointer to a TListItem which can be successively passed
        into the method again.

 TItemStates selected = TItemStates() << isSelected;
 TListItem *Item = ListView1->Selected;

 while (Item)
 {
     // code...
     Item = ListView1->GetNextItem(Item, sdAll, selected);
 }
 

---

(Q2.2)  How do I speed up adding items to my ListView?

(A2.2a) Be sure to suspend screen updates via the BeginUpdate() and
        EndUpdate() methods (or the WM_SETREDRAW message).  Also,
        disable any sorting routines if present, and switch out of
        vsReport ViewStyle before adding any items.

(A2.2b) Abandon the TListItems/TListItem classes, and use the API
        directly via the LVITEM structure in conjunction with the
        LPSTR_TEXTCALLBACK and I_IMAGECALLBACK flags for the pszText
        and iImage members, respectively.

(A2.2c) Use a virtual-mode ListView instead.  This can be done in
        BCB4 via OwnerData or in previous versions by flagging the
        LVS_OWNERDATA style.  Although some of the functionality is
        lacking in a virtual-mode ListView, this is the fastest
        approach.
 

---

(Q2.3)  My TToolBar images are not showing up on some machines, how
        do I resolve this?

(A2.3)  This is likely due to an outdated comctl32.dll version.  See
        Jeff Overcash's various posts on the subject and the following
        MSDN Knowledge Base article:

http://www.deja.com/threadmsg_ct.xp?AN=519822766

http://www.deja.com/threadmsg_ct.xp?AN=556738732

http://Community.microsoft.com/Community/kb/articles/Q186/1/76.asp
 

---

(Q2.4)  How do I change the font/color of an individual item in
        a TTreeView or TListView?

(A2.4)  Use the Custom Draw (IE3+) service.  This can be done in BCB4
        via the OnCustomDrawItem event or in earlier versions via a
        WM_NOTIFY and NM_CUSTOMDRAW message handler.  See the following
        reference for the latter implementation:

http://bcbcaq.freeservers.com/CustomDraw_TV.html
 

---

(Q2.5)  How do I place a TProgressBar inside a TStatusBar?

(A2.5a) Recall, the TControl::Parent property determines which surface
        a particular control is displayed on.  To have a ProgressBar
        inside a StatusBar, assign the latter as the Parent of the
        former.

 ProgressBar1->Parent = StatusBar1;

        Note: The Top and Left properties of the ProgressBar are now
        relative to the client area of the StatusBar.

(Q2.5b) Create a new TStatusBar descendant that includes
        csAcceptsControls in its ControlStyle property.  Doing this
        allows controls to be parented to the StatusBar at design-time.

 __fastcall TMyStatusBar::TMyStatusBar(TComponent *AOwner)
     : TStatusBar(AOwner)
 {
     ControlStyle = ControlStyle << csAcceptsControls;
 }
 

---

(Q2.6)  How do I enable multiple selection of nodes in my TTreeView?

(A2.6a) The underlying WC_TREEVIEW common control does not Community
        such functionality.  Consequently, neither does the TTreeView
        class.  The easiest approach is to search for a third-party
        component that implements this (http://www.torry.ru).

(A2.6b) The following URL links to a primitive implementation of adding
        multi-select functionality to the TTreeView class:

http://www.deja.com/threadmsg_ct.xp?AN=555168670.1
 



 

Part 3: Standard Controls
===========================
---

(Q3.1)  How do I supress the default system sound when the Enter key is
        pressed in a TEdit?

(A3.1)  You can manipulate the Key parameter in the edit control's
        OnKeyPress event handler.  This is event is a wrapper around
        the WM_CHAR message.  Specifically, set the Key parameter to
        zero.

 void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key)
 {
   if (Key == VK_RETURN) Key = 0;
 }

         For a more in-depth discussion, examine the following post:

http://www.deja.com/threadmsg_ct.xp?AN=498742760
 

---

(Q3.2)  I've changed the Color property of my Button's Font, but it
        doesn't seem to work.  What's wrong?

(A3.2)  The TButton class is a wrapper for the Windows BUTTON standard
        control.  Since the standard control itself does not Community
        any other font color except clBtnText, changing the
        TButton::Font::Color property will have no effect.  Use a
        TBitBtn instead (TBitBtn is owner-drawn implementation of the
        BUTTON control).
 

---

(Q3.3)  How do I change a TButton's Color?  I've tried setting the
        Brush::Color property, but this does not work.

(A3.3)  The TButton class is a wrapper for the Windows BUTTON standard
        control.  The only way to change the color of this control is
        to make the button owner-drawn via the BS_OWNERDRAW style.
        Doing this, however, will require an implementation for drawing
        every aspect of the button.  Since this is not a trivial task,
        it is recommended to search for a third-party component
        (http://www.torry.ru).  If one is interested in implementing
        this functionality, either start with a TBitBtn or create a
        custom "button" control (TCustomControl is a good place to
        start).  The following posts describe this task:

http://www.deja.com/threadmsg_ct.xp?AN=483185496.1

http://www.deja.com/threadmsg_ct.xp?AN=498626460

http://www.deja.com/threadmsg_ct.xp?AN=513500673
 

--

(Q3.4)  When I load a large text file in a TRichEdit, I am no longer
        able to input any additional text.  What's wrong?

(A3.4)  Send the RichEdit the EM_EXLIMITTEXT message before loading the
        file, to set a new upper limit (in characters).  See Robert
        Dunn's FAQ section for more information:

http://home.att.net/~robertdunn/Yacs.html
 

---

(Q3.5)  How do I select an item in a TComboBox or TListBox via code?

(A3.5)  Set the ItemIndex property to the zero-based index of the item
        that you want selected.  For multiple-selection ListBoxes, use
        the Selected property.
 

---

(Q3.6)  I have used the Selected property to set the selected item in
        my TListBox, but it raises an EListError exception.  What's the
        problem?

(A3.6)  The Selected property only applies to ListBoxes with the
        MultiSelect property set to true.  For single selection
        ListBoxes, use the ItemIndex property as described in (A3.5).
 

---

(Q3.7)  How can I determine which items are selected in a
        multi-select TListBox?

(A3.7)  Use the TListBox::Selected property.  This an array of booleans
        indicating the selected state of each item.  In an iterative
        implementation, this is best used in conjunction with
        the SelCount property.  Note: the Selected property only applies
        to multiple-selection ListBoxes (see also A3.6).
 

---

(Q3.8)  How do I determine the position of a TRichEdit's caret in a
        (row, col) format?

(A3.8)  Use the EM_EXLINEFROMCHAR and EM_LINEINDEX messages to retrieve
        this information.

 int col = RichEdit1->SelStart;
 int row = SNDMSG(RichEdit1->Handle, EM_EXLINEFROMCHAR,
                  0, static_cast<LPARAM>(col));
 col = col - SNDMSG(RichEdit1->Handle, EM_LINEINDEX, row, 0);
 

---

(Q3.9)  I have provided a handler for the OnClick event of my CheckBox,
        however, I've noticed that this handler is fired when I
        manipulate the TCheckBox::Checked property.  Is this a bug?

(A3.9)  This is by design (see the TCustomCheckBox::SetState method
        in the stdcltrs.pas file).  There are, however, a few ways
        around this behavior as described in the following posts:

http://www.deja.com/threadmsg_ct.xp?AN=569313536

http://www.deja.com/threadmsg_ct.xp?AN=569335452
 
 



 

Part 4:  VCL Specifics
========================

---

(Q4.1)  I've created a control dynamically using the following code,
        but it fails to appear.  What's wrong?

 TShape *Shape = new TShape(Panel1);
 Shape->Top = 20;
 Shape->Left = 30;
 Shape->Visible = true;

(A4.1)  The above code is missing the Parent property assignment.
        Among other things, the Parent property is used to determine
        which windowed control's surface to draw a particular child
        control on.  Note: The Top and Left properties of the child
        control are relative to its Parent.

 Shape->Parent = Panel1;
 

---

(Q4.2)  How do I assign an event handler at run-time?

(A4.2)  Examine the signature of the target event, create a member
        function of the same signature, then assign this handler to the
        desired event in the same way a property value is specified.

 // in header...
     TSpeedButton *SpeedButton1;
     void __fastcall SpeedButtonClick(TObject *Sender);

 // in source...
 __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
 {
     SpeedButton1 = new TSpeedButton(this);
     SpeedButton1->Parent = this;
     SpeedButton1->OnClick = SpeedButtonClick;
 }

 void __fastcall TForm1::SpeedButtonClick(TObject *Sender)
 {
     ShowMessage("you clicked it!");
 }
 

---

(Q4.3)  How do I change the font/background color of a particular cell
        in a TStringGrid?

(A4.3)  Set the DefaultDrawing property to false, then implement a
        handler for the OnDrawCell event.
 

 void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender,
     int Col, int Row, TRect &Rect, TGridDrawState State)
 {
      // if it's a fixed row (headers)
      if (State.Contains(gdFixed))
      {
          StringGrid1->Canvas->Brush->Color = clBtnFace;
          StringGrid1->Canvas->Font->Color = clWindowText;
          StringGrid1->Canvas->FillRect(Rect);
          Frame3D(StringGrid1->Canvas, Rect,
                  clBtnHighlight, clBtnShadow, 1);
      }

      // if the cell is selected
      else if (State.Contains(gdSelected))
      {
          StringGrid1->Canvas->Brush->Color = clHighlight;
          StringGrid1->Canvas->Font->Color = clHighlightText;
          StringGrid1->Canvas->FillRect(Rect);
      }

      // color cell (2, 2) only
      else if (Col == 2 && Row == 2)
      {
          StringGrid1->Canvas->Brush->Color = clBlue;
          StringGrid1->Canvas->Font->Color = clRed;
          StringGrid1->Canvas->FillRect(Rect);
      }

      // if normal
      else
      {
          StringGrid1->Canvas->Brush->Color = StringGrid1->Color;
          StringGrid1->Canvas->Font->Color = StringGrid1->Font->Color;
          StringGrid1->Canvas->FillRect(Rect);
      }

      AnsiString text = StringGrid1->Cells[Col][Row];
      StringGrid1->Canvas->TextRect(Rect, Rect.Left, Rect.Top, text);
 }
 

---

(Q4.4)  How do I insert/remove a row or column in a TStringGrid?

(A4.4)  Implement this functionality manually by manipulating the
        RowCount or ColCount property, then shifting the contents of
        the surrounding cells.  The following function can be used to
        insert a row.  Inserting a column, or deleting a row or column
        can be accomplished via a similar technique.

 void __fastcall InsertRow(TStringGrid *StringGrid, long AfterIndex)
 {
     SNDMSG(StringGrid->Handle, WM_SETREDRAW, false, 0);
     try
     {
         int row_count = StringGrid->RowCount;
         StringGrid->RowCount = row_count + 1;

         for (int row = row_count; row > AfterIndex + 1; row--)
             StringGrid->Rows[row] = StringGrid->Rows[row - 1];

         StringGrid->Rows[AfterIndex + 1]->Clear();
     }
     catch (...)
     {
         SNDMSG(StringGrid->Handle, WM_SETREDRAW, true, 0);
         throw;

     }
     SNDMSG(StringGrid->Handle, WM_SETREDRAW, true, 0);

     RECT R = StringGrid->CellRect(0, AfterIndex);
     InflateRect(&R, StringGrid->Width, StringGrid->Height);
     InvalidateRect(StringGrid->Handle, &R, false);
 }
 

---

(Q4.5)  I've assigned an OnExit event handler for a particular control,
        however, it does not fire when I click a TSpeedButton.  What's
        the problem?

(A4.5)  TSpeedButton is a TGraphicControl descendant.  Since
        TGraphicControls can never truly receive keyboard focus, and
        the OnExit event of a windowed control is fired when that
        control loses keyboard focus, clicking the TSpeedButton will
        not fire the OnExit event.  This also helps explain why
        TGraphicControl descendants do not exhibit OnEnter and OnExit
        events.  The easiest solution in this case is to use a TBitBtn
        instead of a TSpeedButton.
 

---

(Q4.6)  I've changed the Color property of a TPaintBox, but it seems
        to have no effect.  What could be wrong?

(A4.6)  The TPaintBox component is little more than a fancy visual
        TControlCanvas.  The TControlCanvas in this case simply
        encaptulates the process of grabbing the device context of the
        PaintBox's Parent, then selecting the appropriate clipping
        region, defined by the bounds of the PaintBox, into this
        device context.  As the name suggests, anything that is to be
        displayed in the PaintBox must be "painted".  To fill a
        PaintBox, use the FillRect() method of the PaintBox's Canvas
        property.

 PaintBox1->Color = clBlue;
 PaintBox1->Canvas->FillRect(PaintBox1->Canvas->ClipRect);
 

---

(Q4.7)  How can I detect when the mouse cursor enters or leaves a
        control?

(A4.7)  Handle the CM_MOUSEENTER and CM_MOUSELEAVE VCL messages to
        determine when the mouse cursor enters or leaves the bounds
        of a VCL control.  This can be done via a subclass procedure
        or by creating a descendant of the target control and
        augmenting the WndProc() method (or via a message map).

 // in header...
     Controls::TWndMethod OldLabelWP;
     void __fastcall NewLabelWP(TMessage &Msg);

 // in source...
 __fastcall TForm1::TForm1(TComponent *Owner)
     : TForm(Owner)
 {
     OldLabelWP = Label1->WindowProc;
     Label1->WindowProc = NewLabelWP;
 }

 void __fastcall TForm1::NewLabelWP(TMessage &Msg)
 {
     switch (Msg.Msg)
     {
         case CM_MOUSEENTER:
         {
            // mouse cursor has entered control
            break;
         }
         case CM_MOUSELEAVE:
         {
            // mouse cursor has left control
            break;
         }
         case WM_DESTROY:
         {
            Label1->WindowProc = OldLabelWP;
            break;
         }
     }
     OldLabelWP(Msg);
 }

---

(Q4.8)  When I switch my StringGrid into EditorMode, the caret does not
        appear in the cell, although I can still input text.  What's
        wrong here?

(A4.8)  [From Jonathan Arnold]: If the font height is too tall for the
        cell, the input caret will not appear.  Adjust either the size
        of the font, or the StringGrid's DefaultRowHeight property.
 
 


///////////////////////////////////////////////////////////////////

[EOF:All--Last Modified: 06/01/2004]