Components

  1. Array of components...
  2. how do i getting the component index at runtime
  3. How do I create a component at run-time?
  4. Create an event during Runtime?
  5. 3D border for label component?
  6. Setting read-only columns in StringGrid
  7. ?? Scrolling a Memo ??
  8. BMPs in a StringGrid
  9. TTreeview-speedup
  10. TBitBtn control class question (change bitmap at runtime)
  11. OwnerDraw in TStatusBar
  12. Duplicating components and their children at runtime
  13. Splitter Bar
  14. How to Add Controls to TTabbedNotebook & TNotebook
  15. Saving and Loading aTstringgrid
  16. How do I synchronize two scroll boxes on different panels?
  17. Delayed scrolling / delayed OnChange?
  18. Array of Edit boxes?
  19. Unselectable Tab

Array of components...

From: Lode Deleu <101612.1454@compuserve.com>

> Is it possible to create an array of components? I'm using a LED component for a status display, and I'd like to be able to access it via:

First of all, you'l need to declare the array:

  LED : array[1..10] of TLed;      (TLed being your led component type)

if you would create the LED components dynamically, you could do this during a loop like this :

  for counter := 1 to 10 do
    begin
       LED[counter]:= TLED.Create;
       LED[counter].top := ...
       LED[counter].Left := ...
       LED[counter].Parent := Mainform;   {or something alike}
    end;

If the components already exist on your form (visually designed), you could simply assign them to the array like this:

  leds := 0;
  for counter := 0 to Form.Componentcount do
    begin
       if (components[counter] is TLED) then
         begin
            inc(leds);
            LED[leds] := TLED(components[counter]);
         end
    end;

This however leaves you with a random array of LED's, I suggest you give each LED a tag in the order they should be in the array, and then fill the array using the tag :

  for counter := 0 to Form.Componentcount do
    begin
       if (components[counter] is TLED) then
         begin
            LED[Component[counter].tag] := TLED(components[counter]);
         end
    end;

if you need a two dimensional array, you'll need to find another trick to store the index, I've used the hint property a number of times to store additional information.

how do i getting the component index at runtime

From: baisa@tor.hookup.net (Brad Aisa)

In article <4uevrf$331@duke.telepac.pt>, delphinidae@mail.telepac.pt (Claudio Tereso) wrote: >i need to find the component index in the parent's order. >i tried to modify prjexp.dll but with success? >does any one have an idea?

Here is a function that does this. It gets the parent control, and then iterates through its children, looking for a match. This has been tested and works.


{ function to return index order of a component in its parent's
    component collection; returns -1 if not found or no parent }
function IndexInParent(vControl: TControl): integer;
var
  ParentControl: TWinControl;
begin
  {we "cousin" cast to get at the protected Parent property in base class }
  ParentControl := TForm(vControl.Parent);
  if (ParentControl <> nil) then
  begin
    for Result := 0 to ParentControl.ControlCount - 1 do
    begin
      if (ParentControl.Controls[Result] = vControl) then Exit;
    end;
  end;
  { if we make it here, then wasn't found, or didn't have parent}
  Result := -1;
end;


How do I create a component at run-time?

From: m.a.vaughan@larc.nasa.gov (Mark Vaughan)


Var
  MyButton  :  TButton;

MyButton := TButton.Create(MyForm);    //  MyForm now "owns" MyButton
with MyButton do
  BEGIN
    Parent := MyForm;    //  here MyForm is also the parent of MyButton
    height := 32;
    width := 128;
    caption := 'Here I Am!';
    left := (MyForm.ClientWidth - width) div 2;
    top := (MyForm.ClientHeight - height) div 2;
  END;


Borland also publishes one of their TechInfo sheets on this subject.

Look for

ti2938.asc Creating Dynamic Components at Runtime

which you can get from Borland's web site or ftp site.

Create an event during Runtime?

From: "Hustin Olivier" <ohu@eortc.be>

Definition of memo's properties


memo.onchange:=memo1Change;

procedure TForm1.Memo1Change(Sender: TObject);
begin
   panel1.caption:='Content has been changed';
end;


3D border for label component?

From: Mark Pritchard <pritchma@ozemail.com.au>

Here is a free one (took around half an hour to put together, it doesn't grab the parent font correctly, but I couldn't be bothered putting any more time into it) -


unit IDSLabel;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
  ExtCtrls;

type
  TIDSLabel = class(TBevel)
  private
    { Private declarations }
    FAlignment : TAlignment;
    FCaption : String;
    FFont : TFont;
    FOffset : Byte;

    FOnChange : TNotifyEvent;

    procedure SetAlignment( taIn : TAlignment );
    procedure SetCaption( const strIn : String);
    procedure SetFont( fntNew : TFont );
    procedure SetOffset( bOffNew : Byte );
  protected
    { Protected declarations }
    constructor Create( compOwn : TComponent ); override;
    destructor Destroy; override;
    procedure Paint; override;
  public
    { Public declarations }
  published
    { Published declarations }
    property Alignment : TAlignment read FAlignment write SetAlignment default taLeftJustify;
    property Caption : String read FCaption write SetCaption;
    property Font : TFont read FFont write SetFont;
    property Offset : Byte read FOffset write SetOffset;

    property OnChange : TNotifyEvent read FOnChange write FOnChange;
  end;

implementation

constructor TIDSLabel.Create;
begin
   inherited Create(compOwn);

   FFont := TFont.Create;
   with compOwn as TForm do
       FFont.Assign(Font);

   Offset := 4;
   Height := 15;
end;

destructor TIDSLabel.Destroy;
begin
   FFont.Free;

   inherited Destroy;
end;

procedure TIDSLabel.Paint;
var
   wXPos, wYPos : Word;
begin

   {Draw the bevel}
   inherited Paint;

   {Retreive the font}
   Canvas.Font.Assign(Font);

   {Calculate the y position}
   wYPos := (Height - Canvas.TextHeight(Caption)) div 2;

   {Calculate the x position}
   wXPos := Offset;
   case Alignment of
       taRightJustify: wXPos := Width - Canvas.TextWidth(Caption) - Offset;
       taCenter:       wXPos := (Width - Canvas.TextWidth(Caption)) div 2;
   end;
   Canvas.Brush := Parent.Brush;
   Canvas.TextOut(wXPos,wYPos,Caption);

end;

procedure TIDSLabel.SetAlignment;
begin
   FAlignment := taIn;
   Invalidate;
end;

procedure TIDSLabel.SetCaption;
begin
   FCaption := strIn;

   if Assigned(FOnChange) then
       FOnChange(Self);

   Invalidate;
end;

procedure TIDSLabel.SetFont;
begin
   FFont.Assign(fntNew);
   Invalidate;
end;

procedure TIDSLabel.SetOffset;
begin
   FOffset := bOffNew;
   Invalidate;
end;

end.


Setting read-only columns in StringGrid

From: Mark Pritchard <pritchma@ozemail.com.au>

In the OnSelectCell event, this works fine (every even column is editable) -


   if Col mod 2 = 0 then
       grd.Options := grd.Options + [goEditing]
   else
       grd.Options := grd.Options - [goEditing];


?? Scrolling a Memo ??

From: Eddie Shipman <eshipman@slip.net>

Found the answer on my own on Delphi WWW Forum.


Var
 ScrollMessage:TWMVScroll;

     ScrollMessage.Msg:=WM_VScroll;
     for i := Memo1.Lines.Count DownTo 0 do
     begin
        ScrollMessage.ScrollCode:=sb_LineUp;
        ScrollMessage.Pos:=0;
        Memo1.Dispatch(ScrollMessage);
     end;


BMPs in a StringGrid

From: "James D. Rofkar" <jim_rofkar%lotusnotes1@instinet.com>

Darren Clements wrote:
> How can I put a Bitmap in a StringGrid cell?

In your StringGrid's OnDrawCell event handler, place some code that resembles:


     with StringGrid1.Canvas do
        begin
        {...}
        Draw(Rect.Left, Rect.Top, Image1.Picture.Graphic);
        {...}
        end;


Using the Draw() or StretchDraw() method of TCanvas should do the trick. BTW, Image1 above is a TImage with a bitmap already loaded into it.

TTreeview-speedup

Haakon Eines <haakon.eines@finale.no>

Here's a little TreeView-component that might be a little bit faster than the default TTreeView from Borland. You also have the ability to set the items text to bold (It's implemented as methods of the treeview, but should have been a TTreeNode's property. Since I can't release VCL source code - methods it is).

Some timings:
  TTreeView:
    128 sec. to load 1000 items (no sorting)*
    270 sec. to save 1000 items (4.5 minutes!!!)

  THETreeView:
    1.5 sec. to load 1000 items - about 850% faster!!! (2.3 seconds
				with  sorting = stText)*
    0.7 sec. to save 1000 items - about 3850% faster!!!
NOTES: Have fun playing with the component.
unit HETreeView;
{$R-}

// Made by: HÃ¥kon Eines
// EMail:   haakon.eines@finale.no
// Date: 21.01.1997
// Description: A Speedy TreeView?
(*
  TTREEVIEW:
    128 sec. to load 1000 items (no sorting)*
    270 sec. to save 1000 items (4.5 minutes!!!)

  THETREEVIEW:
    1.5 sec. to load 1000 items - about 850% faster!!! (2.3 seconds with sorting = stText)*
    0.7 sec. to save 1000 items - about 3850% faster!!!

  NOTES:
  - All timings performed on a slow 486SX 33 MhZ, 20 Mb RAM.

  - * If the treeview is empty, loading takes 1.5 seconds,
    else add 1.5 seconds to clear 1000 items (a total loading time of 3 seconds).
    This is also the case for the TTreeView component (a total of 129.5 seconds).
    The process of clearing the items, is a call to
    SendMessage(hwnd, TVM_DELETEITEM, 0, Longint(TVI_ROOT)).
*)

interface

uses
  SysUtils, Windows, Messages, Classes, Graphics,
  Controls, Forms, Dialogs, ComCtrls, CommCtrl, tree2vw;

type
  THETreeView = class(TTreeView)
  private
    FSortType: TSortType;
    procedure SetSortType(Value: TSortType);
  protected
    function GetItemText(ANode: TTreeNode): string;
  public
    constructor Create(AOwner: TComponent); override;
    function AlphaSort: Boolean;
    function CustomSort(SortProc: TTVCompare; Data: Longint): Boolean;
    procedure LoadFromFile(const AFileName: string);
    procedure SaveToFile(const AFileName: string);
    procedure GetItemList(AList: TStrings);
    procedure SetItemList(AList: TStrings);
    //'Bold' should have been a property of TTreeNode, but...
    function IsItemBold(ANode: TTreeNode): Boolean;
    procedure SetItemBold(ANode: TTreeNode; Value: Boolean);
  published
    property SortType: TSortType read FSortType write SetSortType default stNone;
  end;

  procedure Register;

implementation

function DefaultTreeViewSort(Node1, Node2: TTreeNode; lParam: Integer): Integer; stdcall;
begin
  {with Node1 do
    if Assigned(TreeView.OnCompare) then
      TreeView.OnCompare(Node1.TreeView, Node1, Node2, lParam, Result)
    else}
  Result := lstrcmp(PChar(Node1.Text), PChar(Node2.Text));
end;

constructor THETreeView.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FSortType := stNone;
end;

procedure THETreeView.SetItemBold(ANode: TTreeNode; Value: Boolean);
var
  Item: TTVItem;
  Template: Integer;
begin
  if ANode = nil then Exit;

  if Value then Template := -1
  else Template := 0;
  with Item do
  begin
    mask := TVIF_STATE;
    hItem := ANode.ItemId;
    stateMask := TVIS_BOLD;
    state := stateMask and Template;
  end;
  TreeView_SetItem(Handle, Item);
end;

function THETreeView.IsItemBold(ANode: TTreeNode): Boolean;
var
  Item: TTVItem;
begin
  Result := False;
  if ANode = nil then Exit;

  with Item do
  begin
    mask := TVIF_STATE;
    hItem := ANode.ItemId;
    if TreeView_GetItem(Handle, Item) then
      Result := (state and TVIS_BOLD) <> 0;
  end;
end;

procedure THETreeView.SetSortType(Value: TSortType);
begin
  if SortType <> Value then
  begin
    FSortType := Value;
    if ((SortType in [stData, stBoth]) and Assigned(OnCompare)) or
      (SortType in [stText, stBoth]) then
      AlphaSort;
  end;
end;

procedure THETreeView.LoadFromFile(const AFileName: string);
var
  AList: TStringList;
begin
  AList := TStringList.Create;
  Items.BeginUpdate;
  try
    AList.LoadFromFile(AFileName);
    SetItemList(AList);
  finally
    Items.EndUpdate;
    AList.Free;
  end;
end;

procedure THETreeView.SaveToFile(const AFileName: string);
var
  AList: TStringList;
begin
  AList := TStringList.Create;
  try
    GetItemList(AList);
    AList.SaveToFile(AFileName);
  finally
    AList.Free;
  end;
end;

procedure THETreeView.SetItemList(AList: TStrings);
var
  ALevel, AOldLevel, i, Cnt: Integer;
  S: string;
  ANewStr: string;
  AParentNode: TTreeNode;
  TmpSort: TSortType;

  function GetBufStart(Buffer: PChar; var ALevel: Integer): PChar;
  begin
    ALevel := 0;
    while Buffer^ in [' ', #9] do
    begin
      Inc(Buffer);
      Inc(ALevel);
    end;
    Result := Buffer;
  end;

begin
  //Delete all items - could have used Items.Clear (almost as fast)
  SendMessage(handle, TVM_DELETEITEM, 0, Longint(TVI_ROOT));
  AOldLevel := 0;
  AParentNode := nil;

  //Switch sorting off
  TmpSort := SortType;
  SortType := stNone;
  try
    for Cnt := 0 to AList.Count-1 do
    begin
      S := AList[Cnt];
      if (Length(S) = 1) and (S[1] = Chr($1A)) then Break;

      ANewStr := GetBufStart(PChar(S), ALevel);
      if (ALevel > AOldLevel) or (AParentNode = nil) then
      begin
        if ALevel - AOldLevel > 1 then raise Exception.Create('Invalid TreeNode Level');
      end
      else begin
        for i := AOldLevel downto ALevel do
        begin
          AParentNode := AParentNode.Parent;
          if (AParentNode = nil) and (i - ALevel > 0) then
            raise Exception.Create('Invalid TreeNode Level');
        end;
      end;
      AParentNode := Items.AddChild(AParentNode, ANewStr);
      AOldLevel := ALevel;
    end;
  finally
    //Switch sorting back to whatever it was...
    SortType := TmpSort;
  end;
end;

procedure THETreeView.GetItemList(AList: TStrings);
var
  i, Cnt: integer;
  ANode: TTreeNode;
begin
  AList.Clear;
  Cnt := Items.Count -1;
  ANode := Items.GetFirstNode;
  for i := 0 to Cnt do
  begin
    AList.Add(GetItemText(ANode));
    ANode := ANode.GetNext;
  end;
end;

function THETreeView.GetItemText(ANode: TTreeNode): string;
begin
  Result := StringOfChar(' ', ANode.Level) + ANode.Text;
end;

function THETreeView.AlphaSort: Boolean;
var
  I: Integer;
begin
  if HandleAllocated then
  begin
    Result := CustomSort(nil, 0);
  end
  else Result := False;
end;

function THETreeView.CustomSort(SortProc: TTVCompare; Data: Longint): Boolean;
var
  SortCB: TTVSortCB;
  I: Integer;
  Node: TTreeNode;
begin
  Result := False;
  if HandleAllocated then
  begin
    with SortCB do
    begin
      if not Assigned(SortProc) then lpfnCompare := @DefaultTreeViewSort
      else lpfnCompare := SortProc;
      hParent := TVI_ROOT;
      lParam := Data;
      Result := TreeView_SortChildrenCB(Handle, SortCB, 0);
    end;

    if Items.Count > 0 then
    begin
      Node := Items.GetFirstNode;
      while Node <> nil do
      begin
        if Node.HasChildren then Node.CustomSort(SortProc, Data);
        Node := Node.GetNext;
      end;
    end;
  end;
end;

//Component Registration
procedure Register;
begin
  RegisterComponents('Win95', [THETreeView]);
end;


end.


TBitBtn control class question (change bitmap at runtime)

"David Zajac" <dzajac@HiWAAY.net>

Keep in mind that when a property is an object, it has memory associated with it.When you are changing the value of a bitmap property, somehow the memory associated with the old value has to be freed, and new memory allocated. By convention in Delphi, that's what an "Assign" method does. The code below works.


implementation

{$R *.DFM}

var n: integer;  // It'll be zero when the program starts

procedure TForm1.Button1Click(Sender: TObject);
var Image: TBitmap;
begin // Changes the bitmap in BitBtn1
  Image:= TBitmap.Create;
  if n < ImageList1.Count then
    ImageList1.GetBitmap(n, Image);
  {end if}

  BitBtn1.Glyph.Assign(Image)   // NOTE: Assign is used to change an object
property

  inc(n,2); // Button Bitmaps hold two images!
  if n > ImageList1.Count then
    n:= 0;
  {end if}
  Image.Free;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin // adds a new button bitmap to ImageList1
  if OpenDialog1.Execute then
    ImageList1.FileLoad(rtBitMap,OpenDialog1.FileName,clBtnFace);
  label1.Caption:= 'ImageCount = ' + IntToStr(ImageList1.Count);
end;

OwnerDraw in TStatusBar

From: Chris Jobson <chrisj@rcp.co.uk>

Just write an OnDrawPanel handler for the StatusBar something like


procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar;
   Panel: TStatusPanel; const Rect: TRect);
begin
  with statusbar1.Canvas do begin
    Brush.Color := clRed;
    FillRect(Rect);
    TextOut(Rect.Left, Rect.Top, 'Panel '+IntToStr(Panel.Index));
  end;
end;

Duplicating components and their children at runtime

Gary McGhee <gmcghee@wt.com.au>

The following code provides a function called DuplicateComponents that duplicates any given component and its child components at run time. It tries to emulate copying and pasting a component at design time. The new component is created with the same parentage and owner as the original and all new component names are similar (but different) to their original. This is provided as is and may have bugs that I haven't found yet. It is provided because it contains techniques that are not commonly known and may be of use to people struggling with similar problems.

This procedure is very useful when you want to design a section of an interface once that will appear n times at run time. You just design it once visually all on a TPanel or other component as a parent, and then do "newpanel := DuplicateComponents(designedpanel)".


uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, IniFiles, TypInfo, Debug;

type
        TUniqueReader = Class(TReader)
                LastRead: TComponent;
                procedure ComponentRead(Component: TComponent);
                procedure       SetNameUnique(
                        Reader: TReader;
                        Component: TComponent;
                        var Name: string
                );
        end;

implementation

procedure TUniqueReader.ComponentRead(
        Component: TComponent
);
begin
        LastRead := Component;
end;

procedure TUniqueReader.SetNameUnique(  // sets the name of the read
component to something like "Panel2" if "Panel1" already exists
        Reader: TReader;
        Component: TComponent;          // component being read
        var Name: string                // Name to use and modify
);
var
        i: Integer;
        tempname: string;
begin
        i := 0;
        tempname := Name;
        while Component.Owner.FindComponent(Name) <> nil do begin
                Inc(i);
                Name := Format('%s%d', [tempname, i]);
        end;
end;


function DuplicateComponents(
        AComponent: TComponent  // original component
): TComponent;                  // returns created new component
        procedure RegisterComponentClasses(
                AComponent: TComponent
        );
        var
                i : integer;
        begin
                RegisterClass(TPersistentClass(AComponent.ClassType));
                if AComponent is TWinControl then
                        if TWinControl(AComponent).ControlCount > 0 then
                                for i := 0 to
(TWinControl(AComponent).ControlCount-1) do

RegisterComponentClasses(TWinControl(AComponent).Controls[i]);
        end;

var
        Stream: TMemoryStream;
        UniqueReader: TUniqueReader;
        Writer: TWriter;
begin
        result := nil;
        UniqueReader := nil;
        Writer := nil;

        try
                Stream := TMemoryStream.Create;
                RegisterComponentClasses(AComponent);

                try
                        Writer := TWriter.Create(Stream, 4096);
                        Writer.Root := AComponent.Owner;
                        Writer.WriteSignature;
                        Writer.WriteComponent(AComponent);
                        Writer.WriteListEnd;
                finally
                        Writer.Free;
                end;

                Stream.Position := 0;
                try
                        UniqueReader := TUniqueReader.Create(Stream, 4096);     // create reader
                        // should probably move these routines into theconstructor
                        UniqueReader.OnSetName := UniqueReader.SetNameUnique;
                        UniqueReader.LastRead := nil;

                        if AComponent is TWinControl then

UniqueReader.ReadComponents(
// read in components and sub-components
                                        TWinControl(AComponent).Owner,
                                        TWinControl(AComponent).Parent,
                                        UniqueReader.ComponentRead
                                )
                        else

UniqueReader.ReadComponents(
// read in components
                                        AComponent.Owner,
                                        nil,
                                        UniqueReader.ComponentRead
                                );
                        result := UniqueReader.LastRead;
                finally
                        UniqueReader.Free;
                end;
        finally
                Stream.Free;
        end;
end;

Splitter Bar

From: adam@adamr.ftech.co.uk (Adam Redgewell)

Bart Mertens <b_mertens@roam.agfa.be> wrote:
Hi,
I've got a form with a treeview and a memo component on it. They are
both aligned to take up the entire client area. I'd like to put a
splitter bar between them so I can make one wider and the other smaller
or vice versa. Which control can do this or how can I do this?
Assuming your treeview is meant to be on the left and the memo on the right, you need to do the following:

Panel2 is the divider strip: you now need to add the procedures shown below. Your code will look something like the following:


type
  TForm1 = class(TForm)
    TreeView1: TTreeview;
    Panel1: TPanel;
    Panel2: TPanel;
    Memo1: TMemo;
    procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer);
    procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer);
    procedure Panel1MouseMove(Sender: TObject; Shift:TShiftState;
        X, Y: Integer);
  private
    Resizing: Boolean;
  public
    ...
end;

procedure TForm1.Panel2MouseDown(Sender: TObject; Button:
        TMouseButton;  Shift: TShiftState; X, Y: Integer);
begin
   Resizing:=true;
end;

procedure TForm1.Panel2MouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer);
begin
   Resizing:=false;
end;

procedure TForm1.Panel2MouseMove(Sender: TObject; Shift: TShiftState;
        X, Y: Integer);
begin
    if Resizing then begin
      TreeView1.Width:=TreeView1.Width+X;
      // Prevent occasional strange repaint errors on resize:
      Panel1.Invalidate;
   end;
end;

This code can be adapted to split the screen horizontally - you get the idea...

How to Add Controls to TTabbedNotebook & TNotebook

From: keeper@mindspring.com (Mark R. Johnson)

I have seen the question "how do you add controls to TTabbedNotebook or TNotebook at run-time?" several times here and elsewhere. Well, after finally getting a few spare minutes to check into it, I have stumbled across the solution:

TTabbedNotebook

Adding controls to a TTabbedNotebook during design time is a pretty simple task. All you need to do is set the PageIndex or ActivePage property to the page you want to add controls to, and begin dropping the controls onto the TTabbedNotebook.

Adding controls to a TTabbedNotebook during run-time is also very simple. However, there is no mention what-so-ever in the Delphi documentation on how to do this. To make matters worse, the TTabbedNotebook source code is not included when you purchase the Delphi VCL source. Thus, we are left with a mystery. Fortunately, I have stumbled across the solution.

The first step to solving this mystery was to take a look at \DELPHI\DOC\TABNOTBK.INT, the interface section of the TABNOTBK.PAS unit where TTabbedNotebook is defined. A quick examination will reveal the TTabPage class, which is described as holding the controls for a given page of the TTabbedNotebook.

The second clue to solving this case comes from observation that the Pages property of TTabbedNotebook has a type of TStrings. It just so happens that Delphi's TStrings and TStringList classes provide both Strings and Objects property pairs. In other words, for every string in TStrings, there is a corresponding Objects pointer. In many cases, this extra pointer is ignored, but if you're like me, you're thinking "Ah-hah!"

After a quick little test in code, sure enough, the Objects property points to a TTabPage instance -- the one that corresponds to the page name in the Strings property. Bingo! Just what we were looking for. Now see what we can do:


{ This procedure adds places a button at a random location on the }
{ current page of the given TTabbedNotebook.                      }

procedure AddButton(tabNotebook : TTabbedNotebook);
var
  tabpage : TTabPage;
  button  : TButton;
begin
  with tabNotebook do
    tabpage := TTabPage(Pages.Objects[PageIndex]);
  button := TButton.Create(tabpage);
  try
    with button do begin
      Parent := tabpage;
      Left   := Random(tabpage.ClientWidth - Width);
      Top    := Random(tabpage.ClientHeight - Height);
    end;
  except
    button.Free;
  end;
end;

TNotebook

The process of adding controls to a TNotebook is almost exactly the same as that for TTabbedNotebook -- only the page class type is TPage instead of TTabPage. However, if you look in DELPHI\DOC\EXTCTRLS.INT for the type declaration for TPage, you won't find it. For some reason, Borland did not include the TPage definition in the DOC files that shipped with Delphi. The TPage declaration *IS* in the EXTCTRLS.PAS unit that you get when you order the VCL source, right where it should be in the interface section of the unit. Here's the TPage information they left out:


  TPage = class(TCustomControl)
  private
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
  protected
    procedure ReadState(Reader: TReader); override;
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property Caption;
    property Height stored False;
    property TabOrder stored False;
    property Visible stored False;
    property Width stored False;
  end;

Now, to make the above procedure work for adding a button to a TNotebook, all we have to do is replace "TTabbedNotebook" with "TNotebook" and "TTabPage" with "TPage", as follows:


{ This procedure adds places a button at a random location on the }
{ current page of the given TNotebook.                            }

procedure AddButton(Notebook1 : TNotebook);
var
  page    : TPage;
  button  : TButton;
begin
  with Notebook1 do
    page := TPage(Pages.Objects[PageIndex]);
  button := TButton.Create(page);
  try
    with button do begin
      Parent := page;
      Left   := Random(page.ClientWidth - Width);
      Top    := Random(page.ClientHeight - Height);
    end;
  except
    button.Free;
  end;
end;

That's all there is to it!

Saving and Loading aTstringgrid

From: "Eric Lawrence" <deltagrp@keynetcorp.net>

>>How can I save an entire stringgrid with all cells to a file?

Procedure SaveGrid;
var f:textfile;
x,y:integer;
begin
assignfile (f,'Filename');
rewrite (f);
writeln (f,stringgrid.colcount);
writeln (f,stringgrid.rowcount);
For X:=0 to stringgrid.colcount-1 do
        For y:=0 to stringgrid.rowcount-1 do
writeln (F, stringgrid.cells[x,y]);
closefile (f);
end;

Procedure LoadGrid;
var f:textfile;
temp,x,y:integer;
tempstr:string;
begin
assignfile (f,'Filename');
reset (f);
readln (f,temp);
stringgrid.colcount:=temp;
readln (f,temp);
stringgrid.rowcount:=temp;
For X:=0 to stringgrid.colcount-1 do
        For y:=0 to stringgrid.rowcount-1 do begin
        readln (F, tempstr);
        stringgrid.cells[x,y]:=tempstr;
        end;
closefile (f);


How do I synchronize two scroll boxes on different panels?

By handle OnScroll event ,follow code can synchronize two scroll boxes on different panels: (You have two scrollboxes in TMainForm:ScrollBar1 and ScrollBar2)


procedure TMainForm.ScrollBar1Scroll(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
     ScrollBar2.Position:=ScrollPos;
end;

procedure TMainForm.ScrollBar2Scroll(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
     ScrollBar1.Position := ScrollPos;
end;

Delayed scrolling / delayed OnChange?

Erik Sperling Johansen >erik@info-pro.no> Stefan Hoffmeister wrote:
If the user keeps either key pressed and the change of the item
(ComboBox.OnChange) takes a long(ish) time an annoying delay will be
noticed.

As a "work around" I would like to react to the change of the
ItemIndex only after a short period of time, e.g. 100 ms.
Here's an example. Written in D2, but technique should work OK in D1 too. Just a simple form with a combo and a label. You probably should consider using Yield in addition to the call to Application.ProcessMessages, to avoid slowing down the PC when the forms message queue is empty.


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
  StdCtrls;


const
  // Just some message constant
  PM_COMBOCHANGE = WM_USER + 8001;

  // 500 ms
  CWantedDelay = 500;

type
  TForm1 = class(TForm)
    ComboBox1: TComboBox;
    Label1: TLabel;
    procedure ComboBox1Change(Sender: TObject);
  private
    procedure PMComboChange(var message : TMessage); message PM_COMBOCHANGE;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ComboBox1Change(Sender: TObject);
begin
  PostMessage(Handle, PM_COMBOCHANGE, 0, 0);
end;

procedure TForm1.PMComboChange(var message : TMessage);
const
  InProc    : BOOLEAN = FALSE;
  StartTick : LONGINT = 0;
begin
  if InProc then begin
    // Update the starting time for the delay
    StartTick := GetTickCount;

  end else begin
    // We're in the loop
    InProc := TRUE;

    // Initial starting time
    StartTick := GetTickCount;

    // Wait until wanted time has elapsed.
    // If proc gets called again, starting time will change
    while GetTickCount - StartTick < CWantedDelay do Application.ProcessMessages;

    // Increment a counter, just for illustration of when to do the actual OnChange work
    Label1.Caption := IntToStr ( StrToIntDef ( Label1.Caption, 0 ) + 1);

    // We're finished with the loop
    InProc := FALSE;
  end;
end;

end.

Array of Edit boxes?

From: "Volker Schneider" <vschneid@arco.met.fu-berlin.de<


Procedure DoSomethingWithEditControls;
Var K: Integer;
      EditArray: Array[0..99] of Tedit;
begin
Try
  For K:= 0 to 99 do 
  begin
    EditArray[K]:= TEdit.Create(Self);
    EditArray[K].Parent:= Self;
    SetSomeOtherPropertiesOfTEdit;
     Left:= 100; Top:= K*10; 
     OnMouseMove:= WhatToDoWhenMouseIsMoved;
    end;
  DoWhateverYouWantToDoWithTheseEdits;
Finally
For K:= 0 to 99 do EditArray[K].Free;
end;

{Notes: To find out what additional properties to set, have a look into ObjectInspector and/or view the Form in TextMode (RightClick Form, choose TextMode)

Unselectable Tab

Is there any way at all of making a tab on a tabbed notebook
unselectable?  i.e not allowing the user to click and see its
contents?
[Mike O'Hanlon, TMike@zoo.co.uk]

Yes, this is possible. The simplest way to do it is to remove the relevant page of the TabbedNotebook with something like:


      with TabbedNotebook do
         Pages.Delete(PageIndex);

and retrieve the deleted page (if necessary) by reloading the Form. Disabling (rather than deleting) is a bit trickier because you have to set up a loop in the Form's Create procedure to assign names to the tabs of the TabbedNotebook. Something like:
    J := 0;
    with TabbedNotebook do
      for I := 0 to ComponentCount - 1 do
        if Components[I].ClassName = 'TTabButton' then
        begin
          Components[I].Name := ValidIdentifier(TTabbedNotebook(
                          Components[I].Owner).Pages[J]) + 'Tab';
          Inc(J);
        end;

where ValidIdentifier is a function which returns a valid Pascal identifier derived from the Tab string:
  function ValidIdentifier (theString: str63): str63;
 {----------------------------------------------------------}
 { Turns the supplied string into a valid Pascal identifier }
 { by removing all invalid characters, and prefixing with   }
 { an underscore if the first character is numeric.         }
  {----------------------------------------------------------}
  var
    I, Len: Integer;
  begin
    Len := Length(theString);
    for I := Len downto 1 do
      if not (theString[I] in LettersUnderscoreAndDigits) then
        Delete(theString, I, 1);
    if not (theString[1] in LettersAndUnderscore) then
      theString := '_' + theString;
    ValidIdentifier := theString;
  end; {ValidIdentifier}

A Tab of the TabbedNotebook may then be disabled with
    with TabbedNotebook do
    begin
      TabIdent := ValidIdentifier(Pages[PageIndex]) + 'Tab';
      TControl(FindComponent(TabIdent)).Enabled := False;
    { Switch to the first enabled Tab: }
      for I := 0 to Pages.Count - 1 do
      begin
        TabIdent := ValidIdentifier(Pages[I]) + 'Tab';
        if TControl(FindComponent(TabIdent)).Enabled then
        begin
          PageIndex := I;
          Exit;
        end;
      end; {for}
    end; {with TabbedNotebook}

and you could re-enable all tabs with:
    with TabbedNotebook do
      for I := 0 to Pages.Count - 1 do
      begin
        TabIdent := ValidIdentifier(Pages[I]) + 'Tab';
        if not TControl(FindComponent(TabIdent)).Enabled then
               TControl(FindComponent(TabIdent)).Enabled := True;
      end; {for}


Please email me and tell me if you liked this page.