Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Customizing Delphi Applications


SP 96: Customizing Delphi Applications

Customizing Delphi Applications

The DragCtrl unit does the work for you

Al Williams

Al is a consultant specializing in software development, training, and documentation. You can reach him at http://ourworld.compuserve.com/homepages/Al_Williams.


The look of the Windows 95 shell is certainly customizable. You can relocate the task bar, change colors, even relabel system icons. All this has raised user expectations. Are you willing to do the work to make your Delphi applications customizable? Surprisingly, many customizations are straightforward to program in Delphi.

In this article, I'll present a Delphi program, WININFO, that displays a simple data form containing fields (edit controls); see Figure 1. WININFO allows you to do the following:

  • Move fields to new locations.
  • Select which fields to display (see Figure 2).
  • Change field labels.
  • Adjust the program's colors.
  • Save the customized layout and colors in an .INI file.
Although this sounds like a lot of work, Delphi provides tools that make this simpler than you'd expect. For example, after fine tuning, drag and drop can do most of the field moving. You'll see how WININFO changes the drag-and-drop behavior, and you'll also learn how to set event handlers at run time, write enumerated types to an .INI file, and do a few other assorted tricks. To follow along, you'll need either a 16- or 32-bit version of Delphi running under any version of Windows.

About Drag and Drop

Delphi components are drag-and-drop aware--sort of. Each component has a BeginDrag and an EndDrag method. When you call BeginDrag with a True argument, the cursor changes (to the DragCursor property's value) and the component begins special processing of mouse events. During dragging, the Dragging method returns True. As the mouse moves over other ("target") components, Delphi calls the target's OnDragOver event handler. The component can specify what action, if any, it will allow with the dragging component.

When you call EndDrag, Delphi calls the target component's OnDragDrop event handler and the original component's OnEndDrag event. The target component does all the required work to accept the dropped object. The OnDragDrop handler receives the dragged object and the drop coordinates. What happens then is up to you.

If you call BeginDrag with a False argument, everything works as just described, except dragging does not commence until the mouse moves slightly (five pixels). In both cases, I've assumed the DragMode property is set to dmManual. If, however, you set it to dmAutomatic, then Delphi assumes that any mouse drag should cause a BeginDrag, and it will call EndDrag when you release the mouse button. This frees you from writing drag code for every component. However, it makes it impossible to use the mouse for normal operations in, for example, a TEdit control.

The normal Delphi drag-and-drop mechanism has a few other limitations. The Delphi designers obviously didn't intend it to work as a positioning method. There is no idea of a drag "hot spot"--the point at which the mouse cursor is located inside the dragging component. Delphi drag and drop works best for operations like dragging a file onto a printer icon or a directory. Suppose you wrote the code in Example 1, hoping it would reposition dropped controls. This works, but the X and Y parameters refer to the mouse cursor's position at the drop. However, the code uses this to set the top-left corner of the control. Users may be surprised when they begin dragging in the center of the control, and the control repositions relative to the top-left corner when they let go of the mouse button.

You could manually store the cursor's offset and use it during repositioning, but this would require changes to the standard components and cooperation between the drop target and the dragging component that isn't currently in place.

Solving the Problems

So far, we have several problems with off-the-rack drag and drop:

  • There is no cursor hot spot.
  • Setting DragMode to dmAutomatic prevents normal mouse operations.
  • Manual dragging requires a lot of mindless code.
The DragMode problem is the easiest to fix. At first, WININFO sets all DragMode properties to dmManual. When the user selects Move from the Customize menu, WININFO walks the main form's component list and sets all TEdit and TLabel components so that their DragMode property is dmAutomatic. It also checks the menu item to indicate that Move mode is active. This code is in TForm1.Move1Click (see
Listing One).

Working with all components at once has several advantages. First, all the controls behave the same way. Second, when you add components, they automatically work properly. The only problem is that the code assumes you are working with TEdit and TLabel controls because there is no single ancestor class that has a visible DragMode property. TEdit, for example, derives from TCustomEdit, which hides all of its properties. This leads to ugly type identifications and casts, as in Example 2.

Once users are allowed to move objects around, they have a problem keeping track of the items on the screen when the fields are separated from their labels. To help combat this, each field and label is described in the Hint property. It doesn't ordinarily appear, since the ShowHint property is set to False. However, when the main program sets the drag mode to automatic, it also sets ShowHint to True. Now, when the cursor floats over a field or label, the description appears in a balloon. WININFO also uses the description when selecting which fields are visible--you'll see that a little later.

The only unresolved issue is the cursor offset. Any solution that tracks the cursor offset requires you to create new edit-control and label components and use them instead of TEdit and TLabel. Instead of going to that trouble, I used a slightly different user interface. When the user first drags a field, the cursor jumps to the top-left corner of the field automatically. Then the user intuitively knows that the top corner is the drag hot spot. This works well and is easy to implement.

The trick is to install an OnDragOver handler in each field. This handler recognizes when the control is dragged over itself (Sender=Source). The first time the control enters itself, the program converts the top-left-corner coordinates to screen coordinates and sets the mouse cursor (see CtrlDragOver in Listing Two). Of course, if you drag over the control again, the cursor position will reset, but this isn't a problem in practice.

To avoid installing any handlers in the fields themselves, I wrote a small routine to automatically set the OnDragOver property for all TLabel and TEdit controls. Don't forget, what appear to be events in the Component Browser are actually just special properties. You can set and read them like any other property.

Fields cannot be dropped onto themselves--as you drag over a field, the cursor turns into a "No" sign. This is not a problem unless you want to move a field slightly. Then, you need to move the control away from the area, drop it, and drag it back to the new position. This wouldn't be difficult to fix, but you'd need special processing in each field's OnDragOver and OnDragDrop events.

Putting it Together

Since many of the drag-and-drop routines belong in the form, it is tempting to write them there. However, this doesn't lend itself to code reuse. Therefore, I wrote a stand-alone unit (see DRAGCTRL.PAS, in Listing Two) containing several procedures (see Table 1). To use these in your program, just add DragCtrl to your uses clause and delegate certain key procedures to those in the DragCtrl unit. The FormDragOver and FormDragDrop routines should handle the corresponding events for forms or other window parents (panels, for example). The CtrlDragOver procedure handles the drag-over logic for controls. The example program uses a panel to illustrate the use of these routines with panels and forms.

Since user-interface preferences vary, I made no attempt to manage the user interface in DragCtrl. Instead, the main form is responsible for setting the drag mode (or manually initiating the drag sequence), setting the hints on or off, and handling all other user-interface issues. This allows maximum flexibility in the look and feel of your program. To display the hints while moving, modify the control's ShowHint property. To exclude certain controls, don't set their event properties or enable their autodragging. Using SetCallbacks will alter every TEdit and TLabel on the form, but you also can manage the callbacks at design time or handle them at run time with your own code.

As the DragCtrl unit stands, SetCallbacks only handles TEdit and TLabel components (or components derived from these objects). To handle other components, simply add the appropriate tests and handle them separately. This isn't very clean, but there is no common ancestor class that exposes the OnDragOver property, so you have little choice. The other routines only rely on properties visible for all TControls, so you don't need to worry there. Much of the code in the MainWin unit (Listing One) also needs to distinguish between TEdits and TLabels, so expect to make similar changes in your main form code if you are working with other component types.

The DragCtrl unit handles only control drag and drop. All the other customization features are much simpler to implement and are built into the form.

Changing Edit Labels

Changing edit-control labels is simple compared to the drag-and-drop feature. The Customize menu has a selection for changing labels. This mode is mutually exclusive of the Move command; only one is active at a time. The chglbl flag is True when this mode is active. Each label has its OnDblClick handler set to TForm1.LabelDblClick. The program could automatically set the handler in the same fashion as the OnDragOver property. However, in this case, I decided to set this property at design time.

When the LabelDblClick routine executes, it examines the chglbl flag. If this is False, the routine exits. If the flag is True, the code executes the LblEditor dialog (for more on dialogs, see the accompanying text box entitled "Delphi Dialogs"). If the dialog box's modal result is mbOK, the program assigns the user's string to the label's Caption property. Only one routine handles all labels, since the Sender parameter allows the code to work with the correct component.

Chameleon Application

Colors are certainly an area where personal preferences differ wildly. Luckily, this is the easiest area to customize because of the built-in TColorDialog component. Example 3 changes the form's background color. The first line of code sets the current color as the default choice in the color-selection dialog. (This may not work if the color isn't standard.) Then, if Execute returns True, the program simply assigns the dialog box's color property to the form's color property. That's it!

Changing the field color is similar, but you have to walk the component list again and alter the color property for TEdit controls. This code is in TForm1.SetFieldColor. This is a separate procedure, because both the form's menu and the load procedure call it.

Customizing the View

The most visually interesting customization allows users to pick which fields and labels are visible (see Figure 2). This dual-listbox dialog looks complex, but it is easy to create with Delphi's form template. Simply choose New Form from Delphi's file menu and select Dual List Box from the Template tab. The result is in the DispList unit (available electronically; see "Availability," page 3). Delphi generated all the code in that unit.

By using the dual-listbox dialog, it is very simple to achieve the customizable-field selection. You can find the code in TForm1.Show1Click (see Listing One). The dialog (DisplayList) contains two listboxes: SrcList and DstList. The code first clears them just to make certain they are empty. Then, the program walks the component list (again). For each TControl object it finds, it checks the Visible property. If Visible is True, the program adds the control's hint string to the DstList listbox. It also places the TControl object (which is actually a pointer) in the listbox's Objects array. If Visible is False, the string and the object go to the SrcList box.

Once the listboxes are ready, a simple call to SetButtons and ShowModal gets things started. The SetButtons call causes the dialog box to enable and disable the correct buttons when it starts. The ShowModal call, of course, starts the dialog box.

If the dialog box's ModalResult property is mrOK, WININFO reverses the process. It scans the SrcList and DstList listboxes. Each item in the SrcList gets its Visible property set to False. The DstList entries have Visible set to True. This is simple, since the listboxes contain the TControl object in their Objects array. The hint text is merely a convenient way for the user to select items. The TControl object does all the work.

Saving Customizations

These customizations wouldn't be very useful if you couldn't save them. Delphi's TIniFile class simplifies using .INI files. Since .INI files are a common place to store configuration information, I decided to save the application's state using TIniFile.

The important procedures are Load and Save (both in Listing One). The .INI file uses three sections: COLORS stores the form and field background colors; TEDIT stores information about each edit control; and TLABEL stores data pertaining to labels. The program calls Load during the main form's Create procedure and Save during the Close processing.

Storing the colors isn't difficult. The only problem is that .INI files want to store strings and integers, not enumerated types. In particular, TColor is the type WININFO needs to save. There is a ColorToRGB function that will convert a TColor value to a number, but there isn't a readily available way to reverse the process. Once again, WININFO must resort to type casting to get the job done. Although you could cast the color to a LongInt type, this isn't a good idea. A TColor type can have a value less than 0. In this case, it isn't an RGB value, it is a system-color index made negative. ColorToRGB handles this translation. When you read the RGB value back in, you can simply cast it to a TColor since you never store system-color indexes in the .INI file; see Example 4.

Filling in the TEDIT and TLABEL sections takes a bit more work. Each entry in these sections uses the component name as a key. The string that follows has the x- and y-coordinates (separated by commas), and a flag to indicate if the component is visible (T is visible, F is not visible). Labels also have another comma in the string, followed by their caption.

To generate this string, Save has to walk the component list, build the string (using Format), and write it to the .INI file. Load reverses the process by reading the string, parsing it by brute force using Pos and Copy, and filling in the component's properties. Load only reads strings for components on the form--it ignores any spurious entries in the .INI file.

For Windows 95, you might consider writing this data to the registry. That wouldn't be any more difficult; in fact, since the registry can store binary data, it might even be easier. However, even under Windows 95 or Windows NT, the .INI file method will work.

Summary

All the files you need to build WININFO are available electronically. For a 16-bit version of Delphi, use WININF16.DPR as the project; for 32-bit versions, use WININFO.DPR. The source code is exactly the same, but you need different project files.

Using these techniques, you shouldn't have any problems writing your own customizable applications. You can use the DragCtrl unit as-is to do most of the work. It is not difficult to work with other component types (TMemo, for example) or exclude certain fields.

Delphi Dialogs and Windows 95

If your experience is in more traditional Windows programming, you may find Delphi dialogs confusing: Where's the callback routine? Where's the resource template? In Delphi, a dialog is really just a special case of an ordinary form. You create a new form by selecting New Form from the File menu and filling it in with components as you would with any other form. The New Form dialog has several choices you can use for your initial layout (like the dual-listbox layout WININFO uses).

When your program starts, the dialog box does not appear. To show the form as a modeless dialog, call the Show method. For a modal dialog, call ShowModal, after which the form will run and will not allow the user to interact with other forms in your program. To close the dialog, set the ModalResult property to a nonzero value. Buttons also have a ModalResult property; when you press a button, it automatically sets its parent's ModalResult property to the value you set. Your program can examine the dialog's ModalResult property to see what action dismissed the dialog.

--A.W.

Figure 1: WININFO.

Figure 2: Customizing WININFO.

Table 1: DragCtrl procedures.

 
Procedure     Description

FormDragOver  Handles OnDragOver event for form, panel, and so on.
FormDragDrop  Handles OnDragDrop event for form, panel, and so on.
CtrlDragOver  Handles OnDragOver event for controls.
SetCallbacks  Sets OnDragOver event  for all controls in a form.

Example 1: Drag-and-drop code.

procedure TForm1.FormDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  ctl:TControl;
begin
  ctl:=Source as TControl;  { type cast }
  if (X < 0) or (Y < 0) then exit; { don't drop in menu bar, etc. }
  ctl.Left:=X;
  ctl.Top:=Y;
end;

Example 2: Ugly type identifications and casts in bold

procedure TForm1.SetMoving(flag : Boolean);
var
eobj : TEdit;
lobj : TLabel;
i : Integer;
begin
for i:=0 to ComponentCount-1 do
  begin
  eobj:=nil;
  lobj:=nil;
  <B>if (Components[i] is TEdit) or</B>
     <B>(Components[i] is TLabel) then</B>
    begin
    if Components[i] is TEdit then
      <B>eobj:=Components[i] as TEdit</B>
    else
      <B>lobj:=Components[i] as TLabel;</B>
 . . .

Example 3: Changing a form's background color.

procedure TForm1.BackgroundColor1Click(Sender: TObject);
begin
ColorDialog.Color:=Color;
if ColorDialog.Execute then
  Color:=ColorDialog.Color;
end;
<h4><a name="0295_00ec"><B>Example 4:</B> Casting an RGB value to a Tcolor.<a name="0295_00ec"></h4>


inifile.WriteInteger('Colors','Form',ColorToRGB(Color));
inifile.WriteInteger('Colors','Field',ColorToRGB(fieldColor));
and:
tmpint:=inifile.ReadInteger('Colors','Form',-1);
if tmpint <> -1 then
    Color:=TColor(tmpint);
tmpint:=inifile.ReadInteger('Colors','Field',-1);
if tmpint <> -1 then
    SetFieldColor(TColor(tmpint));

Listing One

{ WININFO Main Unit }
unit Mainwin;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Menus, StdCtrls, IniFiles,
  DragCtrl,EditDlg,DispList,About, ExtCtrls;
type
  TForm1 = class(TForm)
    CXScreen: TEdit;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Customize1: TMenuItem;
    Move1: TMenuItem;
    Show1: TMenuItem;
    BackgroundColor1: TMenuItem;
    About1: TMenuItem;
    N1: TMenuItem;
    Exit1: TMenuItem;
    CYScreen: TEdit;
    ChangeLabels: TMenuItem;
    N2: TMenuItem;
    ColorDialog: TColorDialog;
    SMMouse: TEdit;
    FieldColor1: TMenuItem;
    Panel1: TPanel;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    procedure Exit1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Move1Click(Sender: TObject);
    procedure FormDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure FormDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure CtrlDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure LabelDblClick(Sender: TObject);
    procedure ChangeLabelsClick(Sender: TObject);
    procedure BackgroundColor1Click(Sender: TObject);
    procedure Show1Click(Sender: TObject);
    procedure FieldColor1Click(Sender: TObject);
    procedure About1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    private
    { Private declarations }
    Moving, chglbl : Boolean; { Change label text? }
    inifile : TIniFile;       { Our INI file }
    fieldColor : TColor;      { Field color (-1=default)}
    procedure SetMoving(flag : Boolean); { Moving?}
    procedure Load;
    procedure Save;
    procedure SetFieldColor(aColor : TColor);
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
s : String;
begin
{ Reset modes and set defaults }
   Moving:=False;
   chglbl:=False;
   fieldColor:=-1;
{ Set INI file }
   inifile:=TIniFile.Create('WININFO.INI');
{ Set up info fields }
   Str(GetSystemMetrics(SM_CXSCREEN),s);
   CXScreen.Text:=s;
   Str(GetSystemMetrics(SM_CYSCREEN),s);
   CYScreen.Text:=s;
   if GetSystemMetrics(SM_MOUSEPRESENT)=0 then
     SMMouse.Text:='No'
   else
     SMMouse.Text:='Yes';
{ Set up callbacks }
   DragCtrl.SetCallbacks(self,CtrlDragOver);
   Load;  { Load custom config }
end;
{ Helper to set moving status on }
procedure TForm1.SetMoving(flag : Boolean);
var
eobj : TEdit;
lobj : TLabel;
i : Integer;
begin
{ Walk component list }
for i:=0 to ComponentCount-1 do
  begin
  eobj:=nil;
  lobj:=nil;
  if (Components[i] is TEdit) or
     (Components[i] is TLabel) then
    begin
    if Components[i] is TEdit then
      eobj:=Components[i] as TEdit
    else
      lobj:=Components[i] as TLabel;
{ Set drag mode and hint flag }
    if not flag then
      if eobj <> nil then
        begin
        eobj.DragMode:=dmManual;
        eobj.ShowHint:=False;
        end
      else
        begin
        lobj.DragMode:=dmManual;
        lobj.ShowHint:=False;
        end
    else
      if eobj <> nil then
        begin
        eobj.DragMode:=dmAutomatic;
        eobj.ShowHint:=True;
        end
      else
        begin
        lobj.DragMode:=dmAutomatic;
        lobj.ShowHint:=True;
        end;
    end;
  end;
end;
{Move menu }
procedure TForm1.Move1Click(Sender: TObject);
var
menu : TMenuItem;
begin
menu := Sender as TMenuItem;
 if menu.Checked then
   menu.Checked:=False
 else
   menu.Checked:=True;
 SetMoving(menu.Checked);
 Moving:=menu.Checked;
 if (Moving) then {uncheck label change flag }
   begin
   chglbl:=False;
   MainMenu1.Items[0][0][1].Checked:=False;
   end;
end;
{ Pass DragOver to CtrlDrag }
procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
 DragCtrl.FormDragOver(Sender,Source,X,Y,State,Accept);
end;
{ Pass DragDrop to CtrlDrag }
procedure TForm1.FormDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  DragCtrl.FormDragDrop(Sender,Source,X,Y);
end;
{ Pass DragOver to CtrlDrag }
procedure TForm1.CtrlDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  DragCtrl.CtrlDragOver(Sender,Source,X,Y,State,Accept);
end;
{Double click label to edit }
procedure TForm1.LabelDblClick(Sender: TObject);
var
  lbl : TLabel;
begin
  if not chglbl then exit;   { ignore if wrong mode }
  lbl:=Sender as TLabel;
  LblEditor.text:=lbl.Caption;
  LblEditor.ShowModal;
  if LblEditor.ModalResult=mrOK then
    lbl.Caption:=LblEditor.text;
end;
{ Toggle label change mode }
procedure TForm1.ChangeLabelsClick(Sender: TObject);
begin
chglbl:=not MainMenu1.Items[0][0][1].Checked;
MainMenu1.Items[0][0][1].Checked:=chglbl;
{need to turn off moving }
Moving:=False;
SetMoving(False);
MainMenu1.Items[0][0][0].Checked:=False;
end;
{ Change form's background color }
procedure TForm1.BackgroundColor1Click(Sender: TObject);
begin
ColorDialog.Color:=Color;
if ColorDialog.Execute then
  Color:=ColorDialog.Color;
end;
{ Show which fields? }
procedure TForm1.Show1Click(Sender: TObject);
var
 i,n: integer;
 obj : TControl;
begin
 DisplayList.SrcList.Clear;
 DisplayList.DstList.Clear;
{ Put all hidden fields in SrcList and
  all visible fields in DstList }
 for i:=0 to ComponentCount-1 do
  begin
  if Components[i] is TControl then
    obj := Components[i] as TControl
  else
    continue;
  if obj.Visible then
    begin
    n:=DisplayList.DstList.Items.Add(obj.hint);
{ Store object too }
    DisplayList.DstList.Items.Objects[n]:=obj;
    end
  else
    begin
    n:=DisplayList.SrcList.Items.Add(obj.hint);
{ Store object too }
    DisplayList.SrcList.Items.Objects[n]:=obj;
    end;
  end;
{GO!}
DisplayList.SetButtons;
DisplayList.ShowModal;
if DisplayList.ModalResult=mrOk then
  begin
{ Show/hide based on which list control is in }
  for i:=0 to DisplayList.SrcList.Items.Count-1 do
    (DisplayList.SrcList.Items.Objects[i] as TControl).Visible:=False;
  for i:=0 to DisplayList.DstList.Items.Count-1 do
    (DisplayList.DstList.Items.Objects[i] as TControl).Visible:=True;
  end;
end;
{ Change field colors }
procedure TForm1.FieldColor1Click(Sender: TObject);
begin
ColorDialog.Color:=fieldColor;
if ColorDialog.Execute then
  SetFieldColor(ColorDialog.Color);
end;
{ Set field color }
procedure TForm1.SetFieldColor(aColor : TColor);
var
  i : Integer;
begin
  fieldColor:=aColor;
  for i:=0 to ComponentCount-1 do
    if Components[i] is TEdit then
      (Components[i] as TEdit).Color:=aColor;
end;
procedure TForm1.About1Click(Sender: TObject);
begin
AboutBox.ShowModal;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Save;  { Write custom config to INI file }
end;
procedure TForm1.Save;
var
 tfstring : String;
 i : Integer;
 eobj : TEdit;
 lobj : TLabel;
begin
{ Save colors }
  inifile.WriteInteger('Colors','Form',ColorToRGB(Color));
  if fieldColor <> -1 then
    inifile.WriteInteger('Colors','Field',ColorToRGB(fieldColor));
{ Save components }
  for i:=0 to ComponentCount-1 do
  begin
  if Components[i] is TEdit then
      begin
      eobj:=Components[i] as TEdit;
      if eobj.Visible then
        tfstring:='T'
      else
        tfstring:='F';
      inifile.WriteString('TEdit',eobj.Name,Format('%d,%d,%s',
       [eobj.Left,eobj.Top,tfstring]));
      end;
  if Components[i] is TLabel then
      begin
      lobj:=Components[i] as TLabel;
      if lobj.Visible then
        tfstring:='T'
      else
        tfstring:='F';
      inifile.WriteString('TLabel',lobj.Name,Format('%d,%d,%s,%s',
       [lobj.Left,lobj.Top,tfstring,lobj.Caption]));
      end;
  end;
end;
procedure TForm1.Load;
var
  tmpint : LongInt;
  str : String;
  i,n : Integer;
  eobj : TEdit;
  lobj : TLabel;
begin
{ Read colors }
  tmpint:=inifile.ReadInteger('Colors','Form',-1);
  if tmpint <> -1 then
    Color:=TColor(tmpint);
  tmpint:=inifile.ReadInteger('Colors','Field',-1);
  if tmpint <> -1 then
    SetFieldColor(TColor(tmpint));
{ Read config for each Edit control/label }
 for i:=0 to ComponentCount-1 do
  begin
  if Components[i] is TEdit then
      begin
      eobj:=Components[i] as TEdit;
      str:=inifile.ReadString('TEdit',eobj.Name,'');
      n:=pos(',',str);
      if n<>0 then
        eobj.Left:=StrToInt(Copy(str,1,n-1));
      str:=Copy(str,n+1,255);
      n:=pos(',',str);
      if n<>0 then
        eobj.Top:=StrToInt(Copy(str,1,n-1));
      n:=pos(',',str);
      if (n<>0) and (str[n+1]='F') then
        eobj.Visible:=False
      else
        eobj.Visible:=True;
      end;
  if Components[i] is TLabel then
      begin
      lobj:=Components[i] as TLabel;
      str:=inifile.ReadString('TLabel',lobj.Name,'');
      n:=pos(',',str);
      if n<>0 then
        lobj.Left:=StrToInt(Copy(str,1,n-1));
      str:=Copy(str,n+1,255);
      n:=pos(',',str);
      if n<>0 then
        lobj.Top:=StrToInt(Copy(str,1,n-1));
      n:=pos(',',str);
      if (n<>0) and (str[n+1]='F') then
        lobj.Visible:=False
      else
        lobj.Visible:=True;
      if n <> 0 then
        lobj.Caption:=Copy(str,n+3,255);
      end;
  end;
end;
end.

Listing Two

{ Drag and Drop Control Unit -- Williams }
unit Dragctrl;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Menus, StdCtrls;
{ Stand in for Form (or parent) OnDragOver }
procedure FormDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
{ Stand in for Parent's OnDragDrop }
procedure FormDragDrop(Sender, Source: TObject; X, Y: Integer);
{ Stand in for Control's OnDragOver }
procedure CtrlDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
{ Set Edit and label OnDragOver events }
procedure SetCallbacks(aForm : TForm; cb : TDragOverEvent);
implementation
procedure FormDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
{ Don't accept over non-client area }
Accept:= (X>=0) and (Y>=0);
end;
procedure FormDragDrop(Sender, Source: TObject; X, Y: Integer);
var
ctl, own:TControl;
begin
{ don't drop in title bar, etc.}
if (X < 0) or (Y < 0) then exit;
ctl:=Source as TControl;
own:=Sender as TControl;
{ If not over parent, then reparent control }
  if ctl.Parent <> own then
    ctl.Parent:=own as TWinControl;
  ctl.Left:=X;
  ctl.Top:=Y;
end;
procedure CtrlDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var
  obj : TControl;
  pt : TPoint;
  aForm : TWinControl;
begin
if (Sender = Source) and (State = dsDragEnter) then
  begin
{ Drag into self, so adjust hot spot }
  obj:=Source as TControl;
  aForm:=obj.Parent;
  pt.x:=obj.Left;
  pt.y:=obj.Top;
  pt:=aForm.ClientToScreen(pt);
  SetCursorPos(pt.x,pt.y);
  end;
{ Don't allow control to accept another control }
Accept:=False;
end;
procedure SetCallbacks(aForm : TForm; cb : TDragOverEvent);
var
i : integer;
eobj : TEdit;
lobj : TLabel;
begin
{ Walk component list }
for i:=0 to aForm.ComponentCount-1 do
  begin
  eobj:=nil;
  lobj:=nil;
{ Set eobj for edits and lobj for labels }
  if (aForm.Components[i] is TEdit) then
    eobj:=aForm.Components[i] as TEdit;
  if (aForm.Components[i] is TLabel) then
    lobj:=aForm.Components[i] as TLabel;
{ Set event as appropriate }
  if (eobj <> nil) then
     eobj.OnDragOver:=cb;
  if (lobj <> nil) then
     lobj.OnDragOver:=cb;
  end;
end;
end.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.