The Second Cut

Extending TOpenDialog, VCL Solution

After writing the message about the API solution to the FAQ: "How do I add a control to TOpenDialog?", I decided that it might be less work to do a simple dialog extension task with pure VCL code. Boian Mitov's TBMMediaDialog is a splendid, encyclopedic project. Could I develop a demonstration version using his kindly posted source code as a reference? Here's the result.

I added a TEdit and a TButton to the basic TOpenDialog. It turns out that the edit control works pretty well by itself, but the button clicks don't reach their VCL event unless the button has a VCL parent. So both controls are placed on an invisible TPanel.

Note that all of the functions in this example are member functions, which means that they know which instance of TInfoOpenDialog they are part of. In my derivation from TComboBox, elsewhere on this site, and in the API solution, Windows requires the use of a non-member function that can potentially cause trouble (or at least confusion) in knowing which instance is in use. In simpler cases, it may turn out not to matter.

Here's what it looks like:

In the InfoOpen.h file:


// ----------------------------------------------
#ifndef InfoOpenH
#define InfoOpenH

class  PACKAGE TInfoOpen : public TOpenDialog
{
 typedef Dialogs::TOpenDialog inherited;

 private:

//     Typical VCL events, to make sure they work
       void _fastcall TInfoOpen::usergobuttonclick(TObject* Sender);
       void __fastcall TInfoOpen::edtKeyPress(TObject *Sender, char &Key);

//     See constructor initializer list for values:
       const int addwidth; // Add at bottom
       const int addheight; // Add at right

       TEdit * nameedit; // Added to OpenDialog
       TButton * usergo; // Added to OpenDialog
       TPanel * pnl; // Gets button clicks to VCL
       RECT staticrect; // Roughly, basic OpenDlg controls
       RECT clientrect; // Dialog, after enlargment
       HWND parenthandle; // Because the VCL always hooks
//         the Windows dialog, Windows creates a parent.
       int DialogWidth, DialogHeight; // As changed, here
 protected:
       void __fastcall TInfoOpen::DoShow(void);
       void __fastcall TInfoOpen::DoSelectionChange(void);

 public:
        __fastcall  TInfoOpen   (TComponent* Owner);
 __published:
};
// ----------------------------------------------
#endif

In the InfoOpen.cpp file:


// ----------------------------------------------
#include 
#pragma hdrstop
#include "dlgs.h"

#include "InfoOpen.h"
// Written and tested in BCB3 under Win98 SE.

//    The principal reference sources were
//    Boian Mitov's TBMMediaDialog, at
//    http://www.mitov.com/
//    Yoto Yotov, for example
//    http://thunder.prohosting.com/~cbdn/cd002.htm

// API Solution with VCL:
// http://groups.google.com/groups?selm=3b334ba9%242_1%40dnews
// (long link-repaste if word-wrapped)

//    Microsoft's list of ControlID's for OpenFile is titled
//    "Explorer-Style Control Identifiers"
// (in case it moves) but it's now at:
//    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/commdlg_7yib.asp
// (long link-repaste if word-wrapped)

//    Constructor.  Note that the Windows Handle of the Dialog
//    box, and the individual dialog controls are NOT
//    available at this time.
__fastcall  TInfoOpen::TInfoOpen(TComponent* Owner) :
       TOpenDialog(Owner),
       addwidth(40),    // Pixels to right
       addheight(35)    // Pixels to bottom
   {
   Title="Extended Open Dialog";

   nameedit=new TEdit(this);
   usergo=new TButton(this);
   pnl=new TPanel(this);
   parenthandle=NULL;
   }
void __fastcall TInfoOpen::DoShow(void)
{
static int onceonly=false;

long newtop, newleft; // After enlargement, to center
RECT rect; // The old dialog, before enlargement

if(!onceonly)
      {
    onceonly=true;
    parenthandle=GetParent(Handle); // Frequently used

//   staticrectTRect=GetStaticRect();
//   Line above fails, for at least two reasons:
//   (1)The VCL uses the VCL Handle (FHandle) instead of
//   GetParent(FHandle).  (2)The ControlID for the
//   static rectangle, stc32, does not exist, probably
//   because the Open Dialog has no *custom* template.
//   Instead, we will approximate the static rectangle
//   with the client size of the unmodified OpenDialog,
//   several pixels too big.  This will leave a few pixels
//   between the static controls and the top of our TPanel.
   GetClientRect(parenthandle, &staticrect);// Client coord

// Get current size of entire dialog, resize, and center
   GetWindowRect(parenthandle, &rect);// Screen coord

// Add new space below and to the right of the static
// rectangle.  If you must add controls ABOVE the static
// rectangle, you have to use the API "Template" method.
   DialogWidth = rect.right - rect.left + addwidth;
   DialogHeight = rect.bottom - rect.top + addheight;

   newleft=(Screen->Width-DialogWidth)/2; // Centering
   newtop=(Screen->Height-DialogHeight)/2;

// Increase size of, and center the dialog window
   MoveWindow(parenthandle, newleft, newtop,
       DialogWidth, DialogHeight, true);

// Get dimensions for sizing the TPanel
   GetClientRect(parenthandle, &clientrect);

// Finish specifying the added controls, now that
// the dialog window exists. Note that the TEdit will
// function as expected, even if it is not placed on a
// TPanel.  However, the Button's clicks will NOT reach
// their Event unless the TButton is placed on the TPanel.
   pnl->ParentWindow=parenthandle;
   pnl->SetBounds(0,staticrect.bottom+1,
      clientrect.right-clientrect.left+1,
      clientrect.bottom-staticrect.bottom);
// Next two lines to make the TPanel imperceptible.
   pnl->BevelInner=bvNone;
   pnl->BevelOuter=bvNone;

// The added Edit:
   nameedit->Parent=pnl;
   nameedit->Ctl3D=true;
   nameedit->Width=233;
   nameedit->Top=2;
   nameedit->Left=81;
   nameedit->Text="What's in a name?";
// Delete following line if Event not desired
   nameedit->OnKeyPress=edtKeyPress;

// The added Button:
   usergo->Parent=pnl;
   usergo->Caption="User Go";
   usergo->Top=0;
   usergo->Left=334;
   usergo->OnClick=usergobuttonclick;

// The following line answers an F.A.Q.  It's not needed
// to *add* controls to the OpenDialog.
// Note that IDOK is in the #include-d dlgs.h
   SendMessage(parenthandle, CDM_SETCONTROLTEXT, IDOK,
       (long)"&Open File");
   } // onceonly bracket

inherited::DoShow(); // Allow user's OnShow
Abort(); // Cancel Windows' WM_NOTIFY message to dialog
}
// Example of TEdit used for output display:
// This override fills in the added edit with
// additional information about the selected file.
void __fastcall TInfoOpen::DoSelectionChange(void)
{
int age;

age=FileAge(FileName); // Use Inherited property
if(age!=-1)
   nameedit->Text=FileDateToDateTime(age).
       FormatString("mm/dd/yyyy hh:mm");
else
   nameedit->Text="";

inherited::DoSelectionChange();// End user's Event
}
// Demonstrate use of TEdit for input
// Delete this function and .h declare if Event not wanted.
void __fastcall TInfoOpen::edtKeyPress(TObject *Sender, char &Key)
{
Key=static_cast<char>(toupper(Key));
}
// Button press result code
void _fastcall TInfoOpen::usergobuttonclick(TObject* Sender)
{
nameedit->Text=nameedit->Text+"Clicked ";
// Difference between modifying VCL and API controls' text:
SendMessage(parenthandle, CDM_SETCONTROLTEXT,
    edt1, reinterpret_cast<LONG>("Newfilename.txt"));
}
// ----------------------------------------------
#pragma package(smart_init)

Below is a shot of the actual application for which I needed to derive a component from TOpenDialog. It's freeware called "Peekoboo", available elsewhere on this site. The Open Dialog is the main "screen" of the application, because it provides most of the functionality needed. It's invoked in a few lines of code, by selecting an "Agree" button on the freeware license form. Each time the user selects a file in the listview, the DoSelectionChange code extracts the displayed data from that file. The files are proprietary-format theatrical light board "shows".

I chose the Open Dialog because I wanted the context menu to be the regular one presented by that dialog. The original application only took 8 hours to write, because I put the display edits at the bottom of the screen, on a background form. It took much longer to develop this method of parenting them to the Open Dialog itself.

The "ListAll->File" button does a tremendous amount of processing, using FindFirstFile to iterate through the entire directory displayed in the Open Dialog. But since it doesn't need to close the dialog (which in fact, can be done), the code is straighforward. The combo box you see above is subclassed, so that when it closes, the CBN_CLOSEUP message is intercepted and the focus moved to the "Close" button. This happens to be appropriate. But the real reason is to avoid leaving the blue selection highlight in the Edit of the combo box.


Back to BCB Examples



Copyright © 2001 Timothy H. Buchman
Trademark notices     Privacy statement
Back to Home
 
Published: October 1, 2001
Modified: November 4, 2001